package handlers import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "go.uber.org/zap" ) // MockRoomService implements RoomServiceInterface for testing type MockRoomService struct { CreateRoomFunc func(ctx context.Context, userID uuid.UUID, req services.CreateRoomRequest) (*services.RoomResponse, error) GetUserRoomsFunc func(ctx context.Context, userID uuid.UUID) ([]*services.RoomResponse, error) GetRoomFunc func(ctx context.Context, roomID uuid.UUID) (*services.RoomResponse, error) UpdateRoomFunc func(ctx context.Context, roomID uuid.UUID, userID uuid.UUID, req services.UpdateRoomRequest) (*services.RoomResponse, error) AddMemberFunc func(ctx context.Context, roomID, userID uuid.UUID) error RemoveMemberFunc func(ctx context.Context, roomID, userID uuid.UUID) error GetRoomHistoryFunc func(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error) GetRoomHistoryWithCursorFunc func(ctx context.Context, roomID uuid.UUID, limit int, cursor string) (*services.RoomHistoryWithCursorResult, error) DeleteRoomFunc func(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error GetRoomMembersFunc func(ctx context.Context, roomID uuid.UUID, requestUserID uuid.UUID) (*services.RoomMembersResponse, error) CreateInvitationFunc func(ctx context.Context, roomID uuid.UUID, inviterID uuid.UUID) (*services.RoomInvitationResponse, error) JoinByTokenFunc func(ctx context.Context, token uuid.UUID, userID uuid.UUID) (uuid.UUID, error) KickMemberFunc func(ctx context.Context, roomID, targetUserID, requestUserID uuid.UUID) error UpdateMemberRoleFunc func(ctx context.Context, roomID, targetUserID, requestUserID uuid.UUID, newRole string) error IsRoomMemberFunc func(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) (bool, error) } func (m *MockRoomService) CreateRoom(ctx context.Context, userID uuid.UUID, req services.CreateRoomRequest) (*services.RoomResponse, error) { if m.CreateRoomFunc != nil { return m.CreateRoomFunc(ctx, userID, req) } return nil, nil } func (m *MockRoomService) GetUserRooms(ctx context.Context, userID uuid.UUID) ([]*services.RoomResponse, error) { if m.GetUserRoomsFunc != nil { return m.GetUserRoomsFunc(ctx, userID) } return nil, nil } func (m *MockRoomService) GetRoom(ctx context.Context, roomID uuid.UUID) (*services.RoomResponse, error) { if m.GetRoomFunc != nil { return m.GetRoomFunc(ctx, roomID) } return nil, nil } func (m *MockRoomService) AddMember(ctx context.Context, roomID, userID uuid.UUID) error { if m.AddMemberFunc != nil { return m.AddMemberFunc(ctx, roomID, userID) } return nil } func (m *MockRoomService) GetRoomHistory(ctx context.Context, roomID uuid.UUID, limit, offset int) ([]services.ChatMessageResponse, error) { if m.GetRoomHistoryFunc != nil { return m.GetRoomHistoryFunc(ctx, roomID, limit, offset) } return nil, nil } func (m *MockRoomService) GetRoomHistoryWithCursor(ctx context.Context, roomID uuid.UUID, limit int, cursor string) (*services.RoomHistoryWithCursorResult, error) { if m.GetRoomHistoryWithCursorFunc != nil { return m.GetRoomHistoryWithCursorFunc(ctx, roomID, limit, cursor) } return &services.RoomHistoryWithCursorResult{Messages: nil, NextCursor: ""}, nil } func (m *MockRoomService) UpdateRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID, req services.UpdateRoomRequest) (*services.RoomResponse, error) { if m.UpdateRoomFunc != nil { return m.UpdateRoomFunc(ctx, roomID, userID, req) } return nil, nil } func (m *MockRoomService) RemoveMember(ctx context.Context, roomID, userID uuid.UUID) error { if m.RemoveMemberFunc != nil { return m.RemoveMemberFunc(ctx, roomID, userID) } return nil } func (m *MockRoomService) DeleteRoom(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error { if m.DeleteRoomFunc != nil { return m.DeleteRoomFunc(ctx, roomID, userID) } return nil } func (m *MockRoomService) GetRoomMembers(ctx context.Context, roomID uuid.UUID, requestUserID uuid.UUID) (*services.RoomMembersResponse, error) { if m.GetRoomMembersFunc != nil { return m.GetRoomMembersFunc(ctx, roomID, requestUserID) } return nil, nil } func (m *MockRoomService) CreateInvitation(ctx context.Context, roomID uuid.UUID, inviterID uuid.UUID) (*services.RoomInvitationResponse, error) { if m.CreateInvitationFunc != nil { return m.CreateInvitationFunc(ctx, roomID, inviterID) } return nil, nil } func (m *MockRoomService) JoinByToken(ctx context.Context, token uuid.UUID, userID uuid.UUID) (uuid.UUID, error) { if m.JoinByTokenFunc != nil { return m.JoinByTokenFunc(ctx, token, userID) } return uuid.Nil, nil } func (m *MockRoomService) KickMember(ctx context.Context, roomID, targetUserID, requestUserID uuid.UUID) error { if m.KickMemberFunc != nil { return m.KickMemberFunc(ctx, roomID, targetUserID, requestUserID) } return nil } func (m *MockRoomService) UpdateMemberRole(ctx context.Context, roomID, targetUserID, requestUserID uuid.UUID, newRole string) error { if m.UpdateMemberRoleFunc != nil { return m.UpdateMemberRoleFunc(ctx, roomID, targetUserID, requestUserID, newRole) } return nil } func (m *MockRoomService) IsRoomMember(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) (bool, error) { if m.IsRoomMemberFunc != nil { return m.IsRoomMemberFunc(ctx, roomID, userID) } return true, nil } func TestRoomHandler_CreateRoom(t *testing.T) { // Setup gin.SetMode(gin.TestMode) logger := zap.NewNop() userID := uuid.New() tests := []struct { name string setupMock func() *MockRoomService requestBody interface{} setupContext func(*gin.Context) expectedStatus int }{ { name: "Success", setupMock: func() *MockRoomService { return &MockRoomService{ CreateRoomFunc: func(ctx context.Context, uid uuid.UUID, req services.CreateRoomRequest) (*services.RoomResponse, error) { return &services.RoomResponse{ ID: uuid.New(), Name: req.Name, Type: req.Type, }, nil }, } }, requestBody: services.CreateRoomRequest{ Name: "General", Type: "public", }, setupContext: func(c *gin.Context) { c.Set("user_id", userID) }, expectedStatus: http.StatusCreated, }, { name: "Unauthorized", setupMock: func() *MockRoomService { return &MockRoomService{} }, requestBody: services.CreateRoomRequest{Name: "Test"}, setupContext: func(c *gin.Context) { // No user_id set }, expectedStatus: http.StatusUnauthorized, }, { name: "Invalid Payload", setupMock: func() *MockRoomService { return &MockRoomService{} }, requestBody: "invalid-json", // String instead of struct setupContext: func(c *gin.Context) { c.Set("user_id", userID) }, expectedStatus: http.StatusBadRequest, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockService := tt.setupMock() handler := NewRoomHandler(mockService, logger) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) // Setup request c.Request, _ = http.NewRequest(http.MethodPost, "/conversations", nil) if body, ok := tt.requestBody.(string); ok && body == "invalid-json" { c.Request.Body = &closingBuffer{bytes.NewBufferString("invalid-json")} } else { jsonBytes, _ := json.Marshal(tt.requestBody) c.Request.Body = &closingBuffer{bytes.NewBuffer(jsonBytes)} } c.Request.Header.Set("Content-Type", "application/json") // Setup context (auth) tt.setupContext(c) // Execute handler.CreateRoom(c) // Assert if w.Code != tt.expectedStatus { t.Errorf("Expected status %d, got %d. Body: %s", tt.expectedStatus, w.Code, w.Body.String()) } }) } } // closingBuffer helps to mock ReadCloser type closingBuffer struct { *bytes.Buffer } func (cb *closingBuffer) Close() error { return nil }