Skip to content

Commit fddc2ea

Browse files
Moved golang to version 1.23.0 and improved core features
1 parent 3915040 commit fddc2ea

File tree

720 files changed

+36901
-189749
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

720 files changed

+36901
-189749
lines changed

.github/workflows/go.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Upload Go test results
2+
3+
on: [push]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ubuntu-latest
9+
strategy:
10+
matrix:
11+
go-version: [ '1.23.0']
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
- name: Setup Go
16+
uses: actions/setup-go@v5
17+
with:
18+
go-version: ${{ matrix.go-version }}
19+
- name: Install dependencies
20+
run: go get .
21+
- name: Test with Go
22+
run: go test -json > TestResults-${{ matrix.go-version }}.json
23+
- name: Upload Go test results
24+
uses: actions/upload-artifact@v4
25+
with:
26+
name: Go-results-${{ matrix.go-version }}
27+
path: TestResults-${{ matrix.go-version }}.json

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ A web application that serves as a load-balanced backend for a Chat Application,
1111

1212
- One - to - One chat among friends that spans multiple chat server instances.
1313

14+
- Simple Round-Robin Load Balancing with Nginx
15+
1416
- Saving and paginated retrieval of messages within the database.
1517

1618
- Logging through Filebeat as a Sidecar, Elasticsearch and Kibana.

api/http/apiintegrator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
type APIIntegrator struct {
1313
ChatService chatadapter.Adapter
1414
AccountService accountadapter.Adapter
15+
Logger loggeradapter.Adapter
1516
}
1617

1718
/*
@@ -23,6 +24,7 @@ func NewAPIIntegrator(req NewAPIIntegratorReq) *APIIntegrator {
2324
return &APIIntegrator{
2425
ChatService: req.ChatService,
2526
AccountService: req.AccountService,
27+
Logger: req.Logger,
2628
}
2729
}
2830

@@ -37,6 +39,7 @@ func API(req APIReq) {
3739
integrator := NewAPIIntegrator(NewAPIIntegratorReq{
3840
ChatService: req.ChatService,
3941
AccountService: req.AccountService,
42+
Logger: req.Logger,
4043
})
4144

4245
chat := req.E.Group("")

api/http/healthcheck.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package http
22

33
import (
4+
"fmt"
45
"net/http"
6+
"time"
57

68
"github.com/labstack/echo"
79
)
810

11+
// HealthCheck is a special API that will be called periodically by docker for their health checking functionality.
912
func (integrator APIIntegrator) HealthCheck(c echo.Context) error {
13+
integrator.Logger.NewInfo(fmt.Sprintf("[Integrator][HealthCheck] Health Check called at time %s", time.Now().String()))
1014
return c.JSON(http.StatusOK, nil)
1115
}

api/http/healthcheck_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package http
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
"websocket_client/internal/pkg/core/adapter/loggeradapter"
8+
9+
"github.com/golang/mock/gomock"
10+
"github.com/labstack/echo"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestHealthCheck(t *testing.T) {
15+
// Define mocks
16+
mockCtrl := gomock.NewController(t)
17+
mockLogger := loggeradapter.NewMockAdapter(mockCtrl)
18+
19+
// Define test data
20+
type HealthCheckTestData struct {
21+
name string
22+
mock func()
23+
assertions func(res error, rec *httptest.ResponseRecorder)
24+
}
25+
26+
// Populate test data
27+
tests := []HealthCheckTestData{
28+
{
29+
name: "Test if HealthCheck returns correct response",
30+
mock: func() {
31+
// No mocks needed for this simple function
32+
},
33+
assertions: func(res error, rec *httptest.ResponseRecorder) {
34+
if assert.NoError(t, res) {
35+
assert.Equal(t, http.StatusOK, rec.Code)
36+
assert.Equal(t, "null\n", rec.Body.String())
37+
}
38+
},
39+
},
40+
}
41+
42+
for _, test := range tests {
43+
t.Run(test.name, func(t *testing.T) {
44+
// Create a new HTTP request
45+
req := httptest.NewRequest(http.MethodGet, "/health", nil)
46+
47+
// Create a new HTTP response recorder
48+
rec := httptest.NewRecorder()
49+
50+
// Create a new context
51+
c := echo.New().NewContext(req, rec)
52+
53+
// Initialize tested struct
54+
mockIntegrator := NewAPIIntegrator(
55+
NewAPIIntegratorReq{
56+
Logger: mockLogger,
57+
},
58+
)
59+
60+
// Execute mocks
61+
test.mock()
62+
63+
// Run the tested function
64+
res := mockIntegrator.HealthCheck(c)
65+
66+
// Check the assertions
67+
test.assertions(res, rec)
68+
})
69+
}
70+
}

api/http/receivemessage.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package http
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"net/http"
67
"websocket_client/api/http/structs"
78
"websocket_client/internal/pkg/core/adapter/chatadapter"
@@ -14,23 +15,29 @@ func (integrator *APIIntegrator) ReceiveMessage(c echo.Context) error {
1415
req := ReceiveMessageReq{}
1516
err := json.NewDecoder(c.Request().Body).Decode(&req)
1617
if err != nil {
18+
integrator.Logger.NewError(fmt.Sprintf("[Integrator][ReceiveMesssage] Error when decoding request, err: %s", err.Error()))
1719
return c.JSON(http.StatusInternalServerError, structs.ErrorRet{
1820
Error: err.Error(),
1921
})
2022
}
21-
err = integrator.ChatService.ReceiveMessage(chatadapter.ReceiveMessageReq{
23+
isOnline, err := integrator.ChatService.ReceiveMessage(chatadapter.ReceiveMessageReq{
2224
Message: req.Message,
2325
FromUserID: req.FromUserID,
2426
Type: req.Type,
2527
ToUserID: req.ToUserID,
2628
Timestamp: req.Timestamp,
2729
})
28-
if err != nil {
30+
if isOnline && err != nil {
31+
integrator.Logger.NewError(fmt.Sprintf("[Integrator][ReceiveMesssage] Error when sending message to online user, err: %s", err.Error()))
2932
return c.JSON(http.StatusInternalServerError, structs.ErrorRet{
3033
Error: err.Error(),
3134
})
3235
}
33-
return c.JSON(http.StatusOK, nil)
36+
37+
integrator.Logger.NewInfo(fmt.Sprintf("[Integrator][ReceiveMesssage] Received message from user with ID %s to ID %s, user online = %v", req.FromUserID, req.ToUserID, isOnline))
38+
return c.JSON(http.StatusOK, ReceiveMessageRes{
39+
IsOnline: isOnline,
40+
})
3441
}
3542

3643
type ReceiveMessageReq struct {
@@ -40,3 +47,7 @@ type ReceiveMessageReq struct {
4047
ToUserID string `json:"to_user_id"`
4148
Timestamp string `json:"timestamp"`
4249
}
50+
51+
type ReceiveMessageRes struct {
52+
IsOnline bool `json:"is_online"`
53+
}

api/http/receivemessage_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package http
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"net/http"
8+
"net/http/httptest"
9+
"testing"
10+
"websocket_client/api/http/structs"
11+
"websocket_client/internal/pkg/core/adapter/chatadapter"
12+
"websocket_client/internal/pkg/core/adapter/loggeradapter"
13+
14+
"github.com/golang/mock/gomock"
15+
"github.com/labstack/echo"
16+
"github.com/stretchr/testify/assert"
17+
)
18+
19+
func TestReceiveMessage(t *testing.T) {
20+
// Define mocks
21+
mockCtrl := gomock.NewController(t)
22+
mockChatService := chatadapter.NewMockAdapter(mockCtrl)
23+
mockLogger := loggeradapter.NewMockAdapter(mockCtrl)
24+
25+
// Define test data
26+
type ReceiveMessageTestData struct {
27+
name string
28+
wrongBody bool
29+
reqBody ReceiveMessageReq
30+
mock func()
31+
assertions func(res error, rec *httptest.ResponseRecorder)
32+
}
33+
34+
// Populate test data
35+
tests := []ReceiveMessageTestData{
36+
{
37+
name: "Test if correct body returns correct response provided the user is online",
38+
reqBody: ReceiveMessageReq{
39+
Message: "Hello",
40+
FromUserID: "user1",
41+
Type: 1,
42+
ToUserID: "user2",
43+
Timestamp: "2025-04-02T06:53:00Z",
44+
},
45+
mock: func() {
46+
mockChatService.EXPECT().ReceiveMessage(chatadapter.ReceiveMessageReq{
47+
Message: "Hello",
48+
FromUserID: "user1",
49+
Type: 1,
50+
ToUserID: "user2",
51+
Timestamp: "2025-04-02T06:53:00Z",
52+
}).Return(true, nil).Times(1)
53+
},
54+
assertions: func(res error, rec *httptest.ResponseRecorder) {
55+
if assert.NoError(t, res) {
56+
assert.Equal(t, http.StatusOK, rec.Code)
57+
expected, _ := json.Marshal(ReceiveMessageRes{
58+
IsOnline: true,
59+
})
60+
assert.Equal(t, expected, rec.Body.String())
61+
}
62+
},
63+
},
64+
{
65+
name: "Test if correct body returns correct response provided the user is not online",
66+
reqBody: ReceiveMessageReq{
67+
Message: "Hello",
68+
FromUserID: "user1",
69+
Type: 1,
70+
ToUserID: "user2",
71+
Timestamp: "2025-04-02T06:53:00Z",
72+
},
73+
mock: func() {
74+
mockChatService.EXPECT().ReceiveMessage(chatadapter.ReceiveMessageReq{
75+
Message: "Hello",
76+
FromUserID: "user1",
77+
Type: 1,
78+
ToUserID: "user2",
79+
Timestamp: "2025-04-02T06:53:00Z",
80+
}).Return(false, nil).Times(1)
81+
},
82+
assertions: func(res error, rec *httptest.ResponseRecorder) {
83+
if assert.NoError(t, res) {
84+
assert.Equal(t, http.StatusOK, rec.Code)
85+
expected, _ := json.Marshal(ReceiveMessageRes{
86+
IsOnline: false,
87+
})
88+
assert.Equal(t, expected, rec.Body.String())
89+
}
90+
},
91+
},
92+
{
93+
name: "Test if correct body returns error response provided the chat service returns an error",
94+
reqBody: ReceiveMessageReq{
95+
Message: "Hello",
96+
FromUserID: "user1",
97+
Type: 1,
98+
ToUserID: "user2",
99+
Timestamp: "2025-04-02T06:53:00Z",
100+
},
101+
mock: func() {
102+
mockChatService.EXPECT().ReceiveMessage(chatadapter.ReceiveMessageReq{
103+
Message: "Hello",
104+
FromUserID: "user1",
105+
Type: 1,
106+
ToUserID: "user2",
107+
Timestamp: "2025-04-02T06:53:00Z",
108+
}).Return(errors.New("foo")).Times(1)
109+
},
110+
assertions: func(res error, rec *httptest.ResponseRecorder) {
111+
if assert.NoError(t, res) {
112+
assert.Equal(t, http.StatusInternalServerError, rec.Code)
113+
expected, _ := json.Marshal(structs.ErrorRet{
114+
Error: "foo",
115+
})
116+
assert.Equal(t, string(expected)+"\n", rec.Body.String())
117+
}
118+
},
119+
},
120+
{
121+
name: "Test if wrong body returns error",
122+
wrongBody: true,
123+
mock: func() {},
124+
assertions: func(res error, rec *httptest.ResponseRecorder) {
125+
if assert.NoError(t, res) {
126+
assert.Equal(t, http.StatusInternalServerError, rec.Code)
127+
// We don't handle the error string for this one, we just assume there will be an error.
128+
assert.True(t, bytes.Contains(rec.Body.Bytes(), []byte("\"error\"")))
129+
}
130+
},
131+
},
132+
}
133+
134+
for _, test := range tests {
135+
t.Run(test.name, func(t *testing.T) {
136+
// Set up the body. Below, we handle one unique case first which is the invalid json before going to different requests controlled by the wrongBody boolean.
137+
var req *http.Request = httptest.NewRequest(http.MethodPost, "/receive_message", bytes.NewReader([]byte("{invalid_json}")))
138+
if !test.wrongBody {
139+
jsonData, err := json.Marshal(test.reqBody)
140+
assert.NoError(t, err)
141+
req = httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(jsonData))
142+
}
143+
144+
// Create a new HTTP request
145+
146+
rec := httptest.NewRecorder()
147+
148+
// Create a new context
149+
c := echo.New().NewContext(req, rec)
150+
151+
// Initialize tested struct
152+
mockIntegrator := NewAPIIntegrator(
153+
NewAPIIntegratorReq{
154+
ChatService: mockChatService,
155+
Logger: mockLogger,
156+
},
157+
)
158+
159+
// Execute mocks
160+
test.mock()
161+
162+
// Run the tested function
163+
res := mockIntegrator.ReceiveMessage(c)
164+
165+
// Check the assertions
166+
test.assertions(res, rec)
167+
})
168+
}
169+
}

0 commit comments

Comments
 (0)