Skip to content

Commit d060bf2

Browse files
Refactored code, added get user and tests for 80% coverage minimum
1 parent db26fb8 commit d060bf2

File tree

242 files changed

+29235
-15540
lines changed

Some content is hidden

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

242 files changed

+29235
-15540
lines changed

api/http/apiintegrator.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
package http
22

33
import (
4+
"websocket_client/internal/conf"
45
"websocket_client/internal/pkg/core/adapter/accountadapter"
56
"websocket_client/internal/pkg/core/adapter/chatadapter"
67
"websocket_client/internal/pkg/core/adapter/loggeradapter"
8+
"websocket_client/internal/pkg/core/adapter/wsprocessoradapter"
9+
"websocket_client/internal/pkg/core/adapter/wsupgraderadapter"
710

8-
"github.com/labstack/echo"
11+
"github.com/labstack/echo/v4"
12+
"github.com/labstack/echo/v4/middleware"
13+
"golang.org/x/time/rate"
914
)
1015

1116
// APIIntegrator is the struct for all API handler methods
1217
type APIIntegrator struct {
1318
ChatService chatadapter.Adapter
1419
AccountService accountadapter.Adapter
1520
Logger loggeradapter.Adapter
21+
Upgrader wsupgraderadapter.Adapter
22+
WsProcessor wsprocessoradapter.Adapter
1623
}
1724

1825
/*
@@ -25,13 +32,17 @@ func NewAPIIntegrator(req NewAPIIntegratorReq) *APIIntegrator {
2532
ChatService: req.ChatService,
2633
AccountService: req.AccountService,
2734
Logger: req.Logger,
35+
Upgrader: req.Upgrader,
36+
WsProcessor: req.WsProcessor,
2837
}
2938
}
3039

3140
type NewAPIIntegratorReq struct {
3241
ChatService chatadapter.Adapter
3342
AccountService accountadapter.Adapter
3443
Logger loggeradapter.Adapter
44+
Upgrader wsupgraderadapter.Adapter
45+
WsProcessor wsprocessoradapter.Adapter
3546
}
3647

3748
// API is a method that initializes the integrator and sets up all the APIs for the application
@@ -40,19 +51,27 @@ func API(req APIReq) {
4051
ChatService: req.ChatService,
4152
AccountService: req.AccountService,
4253
Logger: req.Logger,
54+
Upgrader: req.Upgrader,
55+
WsProcessor: req.WsProcessor,
4356
})
4457

4558
chat := req.E.Group("")
59+
chat.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(rate.Limit(conf.GetConfig().Server.RateLimit))))
4660

47-
chat.GET("/ws", integrator.Websocket)
4861
chat.POST("/receive", integrator.ReceiveMessage)
4962
chat.POST("/register", integrator.RegisterAccount)
5063
chat.GET("/health_check", integrator.HealthCheck)
64+
65+
chatWs := req.E.Group("")
66+
chatWs.Use(integrator.Auth, integrator.AddContextID)
67+
chatWs.GET("/ws", integrator.HandleWebsocket)
5168
}
5269

5370
type APIReq struct {
5471
E *echo.Echo
5572
ChatService chatadapter.Adapter
5673
AccountService accountadapter.Adapter
5774
Logger loggeradapter.Adapter
75+
Upgrader wsupgraderadapter.Adapter
76+
WsProcessor wsprocessoradapter.Adapter
5877
}

api/http/constants.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package http
2+
3+
//This group of constants define the possible incoming message types
4+
const (
5+
incomingMessageTypeMessage = "MESSAGE"
6+
incomingMessageTypeAddFriend = "ADDFRIEND"
7+
incomingMessageTypeGetChatHistory = "GETCHATHISTORY"
8+
incomingMessageTypeRemoveFriend = "REMOVEFRIEND"
9+
incomingMessageTypeSearchUser = "SEARCHFRIEND"
10+
11+
returnErrorPasswordLength = "Password should be shorter than 72 characters"
12+
)

api/http/healthcheck.go

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

33
import (
4-
"fmt"
54
"net/http"
65
"time"
76

8-
"github.com/labstack/echo"
7+
"github.com/labstack/echo/v4"
98
)
109

1110
// HealthCheck is a special API that will be called periodically by docker for their health checking functionality.
1211
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()))
12+
integrator.Logger.NewInfo("Health Check called at time %s", time.Now().String())
1413
return c.JSON(http.StatusOK, nil)
1514
}

api/http/healthcheck_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"websocket_client/internal/pkg/core/adapter/loggeradapter"
88

99
"github.com/golang/mock/gomock"
10-
"github.com/labstack/echo"
10+
"github.com/labstack/echo/v4"
1111
"github.com/stretchr/testify/assert"
1212
)
1313

@@ -28,7 +28,7 @@ func TestHealthCheck(t *testing.T) {
2828
{
2929
name: "Test if HealthCheck returns correct response",
3030
mock: func() {
31-
mockLogger.EXPECT().NewInfo(gomock.Any()).Times(1)
31+
mockLogger.EXPECT().NewInfo(gomock.Any(), gomock.Any()).Times(1)
3232
},
3333
assertions: func(res error, rec *httptest.ResponseRecorder) {
3434
if assert.NoError(t, res) {

api/http/middleware.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package http
2+
3+
import (
4+
"websocket_client/internal/common"
5+
"websocket_client/internal/pkg/core/adapter/accountadapter"
6+
7+
"github.com/labstack/echo/v4"
8+
)
9+
10+
// AddContextID returns a function which generates FlowID and puts it within Context
11+
func (integrator *APIIntegrator) AddContextID(next echo.HandlerFunc) echo.HandlerFunc {
12+
return func(c echo.Context) error {
13+
/*
14+
Generate FlowID for easy tracing
15+
The FlowID is a UUID. Do take note of the RPS to find out when we should reset or recycle the UUIDs.
16+
*/
17+
flowID := common.GenerateUUID()
18+
c.Set("flow_id", flowID)
19+
return next(c)
20+
}
21+
}
22+
23+
// Auth is a rudimentary authorization function that checks whether the user exists in the database and the password is correct
24+
func (integrator *APIIntegrator) Auth(next echo.HandlerFunc) echo.HandlerFunc {
25+
return func(c echo.Context) error {
26+
var err error
27+
defer func() {
28+
if err != nil {
29+
integrator.Logger.NewError(err.Error())
30+
}
31+
}()
32+
userId, password, err := common.SplitUserIDAndPasswordFromAuth(c.Request().Header.Get("Authorization"))
33+
if err != nil {
34+
c.Error(err)
35+
return err
36+
}
37+
38+
err = integrator.AccountService.VerifyAuth(accountadapter.VerifyAuthReq{
39+
UserID: userId,
40+
Password: password,
41+
})
42+
43+
if err != nil {
44+
c.Error(err)
45+
return err
46+
}
47+
return next(c)
48+
}
49+
}

api/http/middleware_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package http
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
"websocket_client/internal/pkg/core/adapter/accountadapter"
9+
"websocket_client/internal/pkg/core/adapter/loggeradapter"
10+
11+
"github.com/golang/mock/gomock"
12+
"github.com/google/uuid"
13+
"github.com/labstack/echo/v4"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func TestSetFlowID(t *testing.T) {
18+
e := echo.New()
19+
req := httptest.NewRequest(http.MethodGet, "/", nil)
20+
rec := httptest.NewRecorder()
21+
c := e.NewContext(req, rec)
22+
23+
// Call the function under test
24+
dummyNextFunc := func(c echo.Context) error {
25+
return nil
26+
}
27+
28+
integrator := NewAPIIntegrator(NewAPIIntegratorReq{})
29+
30+
handlerFunc := integrator.AddContextID(dummyNextFunc)
31+
32+
err := handlerFunc(c)
33+
assert.NoError(t, err)
34+
35+
val := c.Get("flow_id")
36+
assert.NotNil(t, val)
37+
38+
strVal, ok := val.(string)
39+
assert.True(t, ok)
40+
41+
// Optionally validate if strVal is a valid UUID (using github.com/google/uuid or similar)
42+
if _, err = uuid.Parse(strVal); err != nil {
43+
t.Errorf("Expected valid UUID for flow_id but got: %v", strVal)
44+
}
45+
}
46+
47+
func TestAuthenticationHandler(t *testing.T) {
48+
ctrl := gomock.NewController(t)
49+
defer ctrl.Finish()
50+
mockAccountService := accountadapter.NewMockAdapter(ctrl)
51+
mockLogger := loggeradapter.NewMockAdapter(ctrl)
52+
53+
tests := []struct {
54+
name string
55+
authHeader string
56+
mock func()
57+
expectError bool
58+
expectedStatusCode int
59+
expectLogCall bool
60+
}{
61+
{
62+
name: "Successful Authentication",
63+
authHeader: "Basic dXNlcjE6cGFzc3dvcmQx", // base64 user1:password1,
64+
mock: func() {
65+
mockAccountService.EXPECT().VerifyAuth(gomock.Any()).Return(nil).Times(1)
66+
},
67+
expectError: false,
68+
expectedStatusCode: http.StatusOK,
69+
expectLogCall: false,
70+
},
71+
{
72+
name: "Invalid Authorization Header",
73+
authHeader: "InvalidHeader",
74+
mock: func() {
75+
mockLogger.EXPECT().NewError(gomock.Any()).Times(1)
76+
},
77+
expectError: true,
78+
expectedStatusCode: http.StatusInternalServerError,
79+
},
80+
{
81+
name: "Failed Verification",
82+
authHeader: "Basic dXNlcjE6cGFzc3dvcmQx",
83+
mock: func() {
84+
mockAccountService.EXPECT().VerifyAuth(gomock.Any()).Return(errors.New("foo")).Times(1)
85+
mockLogger.EXPECT().NewError(gomock.Any()).Times(1)
86+
},
87+
expectError: true,
88+
expectedStatusCode: http.StatusInternalServerError,
89+
}}
90+
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
// Setup mocks based on test case:
94+
integrator := NewAPIIntegrator(
95+
NewAPIIntegratorReq{
96+
AccountService: mockAccountService,
97+
Logger: mockLogger,
98+
},
99+
)
100+
101+
e := echo.New()
102+
req := httptest.NewRequest(http.MethodGet, "/", nil)
103+
req.Header.Set("Authorization", tt.authHeader)
104+
105+
rec := httptest.NewRecorder()
106+
107+
tt.mock()
108+
109+
c := e.NewContext(req, rec)
110+
111+
dummyNextFunc := func(c echo.Context) error {
112+
return nil
113+
}
114+
115+
handlerFunc := integrator.Auth(dummyNextFunc)
116+
err := handlerFunc(c)
117+
118+
if tt.expectError {
119+
assert.Error(t, err)
120+
assert.Equal(t, tt.expectedStatusCode, rec.Code)
121+
} else {
122+
assert.NoError(t, err)
123+
assert.Equal(t, tt.expectedStatusCode, rec.Code)
124+
}
125+
})
126+
127+
}
128+
129+
}

api/http/receivemessage.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"websocket_client/api/http/structs"
88
"websocket_client/internal/pkg/core/adapter/chatadapter"
99

10-
"github.com/labstack/echo"
10+
"github.com/labstack/echo/v4"
1111
)
1212

1313
// ReceiveMessage is the handler method for the /receive api endpoint.
@@ -20,10 +20,9 @@ func (integrator *APIIntegrator) ReceiveMessage(c echo.Context) error {
2020
Error: err.Error(),
2121
})
2222
}
23-
isOnline, err := integrator.ChatService.ReceiveMessage(chatadapter.ReceiveMessageReq{
23+
isOnline, err := integrator.ChatService.ReceiveMessage(req.ToUserID, chatadapter.Message{
2424
Message: req.Message,
2525
FromUserID: req.FromUserID,
26-
Type: req.Type,
2726
ToUserID: req.ToUserID,
2827
Timestamp: req.Timestamp,
2928
})

api/http/receivemessage_test.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
"websocket_client/internal/pkg/core/adapter/loggeradapter"
1313

1414
"github.com/golang/mock/gomock"
15-
"github.com/labstack/echo"
15+
"github.com/labstack/echo/v4"
1616
"github.com/stretchr/testify/assert"
1717
)
1818

@@ -43,10 +43,9 @@ func TestReceiveMessage(t *testing.T) {
4343
Timestamp: "2025-04-02T06:53:00Z",
4444
},
4545
mock: func() {
46-
mockChatService.EXPECT().ReceiveMessage(chatadapter.ReceiveMessageReq{
46+
mockChatService.EXPECT().ReceiveMessage("user2", chatadapter.Message{
4747
Message: "Hello",
4848
FromUserID: "user1",
49-
Type: 1,
5049
ToUserID: "user2",
5150
Timestamp: "2025-04-02T06:53:00Z",
5251
}).Return(true, nil).Times(1)
@@ -72,10 +71,9 @@ func TestReceiveMessage(t *testing.T) {
7271
Timestamp: "2025-04-02T06:53:00Z",
7372
},
7473
mock: func() {
75-
mockChatService.EXPECT().ReceiveMessage(chatadapter.ReceiveMessageReq{
74+
mockChatService.EXPECT().ReceiveMessage("user2", chatadapter.Message{
7675
Message: "Hello",
7776
FromUserID: "user1",
78-
Type: 1,
7977
ToUserID: "user2",
8078
Timestamp: "2025-04-02T06:53:00Z",
8179
}).Return(false, nil).Times(1)
@@ -101,10 +99,9 @@ func TestReceiveMessage(t *testing.T) {
10199
Timestamp: "2025-04-02T06:53:00Z",
102100
},
103101
mock: func() {
104-
mockChatService.EXPECT().ReceiveMessage(chatadapter.ReceiveMessageReq{
102+
mockChatService.EXPECT().ReceiveMessage("user2", chatadapter.Message{
105103
Message: "Hello",
106104
FromUserID: "user1",
107-
Type: 1,
108105
ToUserID: "user2",
109106
Timestamp: "2025-04-02T06:53:00Z",
110107
}).Return(false, errors.New("foo")).Times(1)
@@ -139,7 +136,7 @@ func TestReceiveMessage(t *testing.T) {
139136
for _, test := range tests {
140137
t.Run(test.name, func(t *testing.T) {
141138
// 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.
142-
var req *http.Request = httptest.NewRequest(http.MethodPost, "/receive_message", bytes.NewReader([]byte("{invalid_json}")))
139+
req := httptest.NewRequest(http.MethodPost, "/receive_message", bytes.NewReader([]byte("{invalid_json}")))
143140
if !test.wrongBody {
144141
jsonData, err := json.Marshal(test.reqBody)
145142
assert.NoError(t, err)

api/http/registeraccount.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"websocket_client/api/http/structs"
88
"websocket_client/internal/pkg/core/adapter/accountadapter"
99

10-
"github.com/labstack/echo"
10+
"github.com/labstack/echo/v4"
1111
)
1212

1313
// GetMessages is the handler method for the /get_messages api endpoint
@@ -22,9 +22,9 @@ func (integrator APIIntegrator) RegisterAccount(c echo.Context) error {
2222
}
2323
//Validate password length. Since we're using BCrypt it must be 72 characters or less, starting from the 0 index.
2424
if len(req.Password) > 72 {
25-
integrator.Logger.NewInfo(fmt.Sprintf("[Integrator][RegisterAccount] Password is not shorter than 72 bytes, ID: %s", req.UserID))
25+
integrator.Logger.NewInfo(fmt.Sprintf("[Integrator][RegisterAccount] Password is not shorter than 72 characters, ID: %s", req.UserID))
2626
return c.JSON(http.StatusBadRequest, structs.ErrorRet{
27-
Error: "",
27+
Error: returnErrorPasswordLength,
2828
})
2929
}
3030

0 commit comments

Comments
 (0)