package services import ( "context" "fmt" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" "go.uber.org/zap" "gorm.io/gorm" "veza-backend-api/internal/models" "veza-backend-api/internal/repositories" ) type MockRoomRepository struct { rooms map[uuid.UUID]*models.Room members map[uuid.UUID][]*models.RoomMember } func NewMockRoomRepository() *MockRoomRepository { return &MockRoomRepository{ rooms: make(map[uuid.UUID]*models.Room), members: make(map[uuid.UUID][]*models.RoomMember), } } func (m *MockRoomRepository) Create(ctx context.Context, room *models.Room) error { room.ID = uuid.New() // Generate new UUID room.CreatedAt = time.Now() room.UpdatedAt = time.Now() m.rooms[room.ID] = room return nil } func (m *MockRoomRepository) GetByID(ctx context.Context, id uuid.UUID) (*models.Room, error) { room, ok := m.rooms[id] if !ok { return nil, gorm.ErrRecordNotFound } return room, nil } func (m *MockRoomRepository) GetByUserID(ctx context.Context, userID uuid.UUID) ([]*models.Room, error) { var userRooms []*models.Room for _, room := range m.rooms { // In a real scenario, this would query room_members. // For mock, we'll assume a direct match for now. // This mock is simplified and doesn't fully simulate the join logic of a real repo. // We'll rely on the AddMember mock below to add members correctly. if _, ok := m.members[room.ID]; ok { for _, member := range m.members[room.ID] { if member.UserID == userID { userRooms = append(userRooms, room) break } } } } return userRooms, nil } func (m *MockRoomRepository) AddMember(ctx context.Context, member *models.RoomMember) error { // If the member ID is not set, generate it if member.ID == uuid.Nil { // This is a mock internal ID, actual GORM might auto-increment member.ID = int64(len(m.members[member.RoomID]) + 1) } m.members[member.RoomID] = append(m.members[member.RoomID], member) return nil } func (m *MockRoomRepository) GetMembersByRoomID(ctx context.Context, roomID uuid.UUID) ([]*models.RoomMember, error) { return m.members[roomID], nil } func (m *MockRoomRepository) Update(ctx context.Context, room *models.Room) error { panic("not implemented") } func (m *MockRoomRepository) Delete(ctx context.Context, id uuid.UUID) error { panic("not implemented") } func (m *MockRoomRepository) RemoveMember(ctx context.Context, roomID uuid.UUID, userID uuid.UUID) error { panic("not implemented") } type MockChatMessageRepository struct { messages []models.ChatMessage } func NewMockChatMessageRepository() *MockChatMessageRepository { return &MockChatMessageRepository{ messages: make([]models.ChatMessage, 0), } } func (m *MockChatMessageRepository) GetConversationMessages(ctx context.Context, conversationID uuid.UUID, limit, offset int) ([]models.ChatMessage, error) { var filtered []models.ChatMessage for _, msg := range m.messages { if msg.ConversationID == conversationID { filtered = append(filtered, msg) } } // Simple reverse order and limit/offset for mock // Order by CreatedAt DESC if len(filtered) > 1 { for i := 0; i < len(filtered)/2; i++ { filtered[i], filtered[len(filtered)-1-i] = filtered[len(filtered)-1-i], filtered[i] } } start := offset end := offset + limit if start > len(filtered) { start = len(filtered) } if end > len(filtered) { end = len(filtered) } return filtered[start:end], nil } func TestRoomService_CreateRoom(t *testing.T) { logger := zap.NewNop() roomRepo := NewMockRoomRepository() messageRepo := NewMockChatMessageRepository() // Not used in this test service := NewRoomService(roomRepo, messageRepo, logger) userID := int64(1) req := CreateRoomRequest{ Name: "Test Room", Type: "public", IsPrivate: false, } room, err := service.CreateRoom(context.Background(), userID, req) assert.NoError(t, err) assert.NotNil(t, room) assert.Equal(t, req.Name, room.Name) assert.Contains(t, room.Participants, userID) // Verify room created in repo createdRoom, _ := roomRepo.GetByID(context.Background(), room.ID) assert.NotNil(t, createdRoom) assert.Equal(t, room.ID, createdRoom.ID) // Check UUID match } func TestRoomService_GetUserRooms(t *testing.T) { logger := zap.NewNop() roomRepo := NewMockRoomRepository() messageRepo := NewMockChatMessageRepository() service := NewRoomService(roomRepo, messageRepo, logger) userID := int64(1) userID2 := int64(2) roomReq1 := CreateRoomRequest{Name: "Room 1", Type: "public", IsPrivate: false} roomReq2 := CreateRoomRequest{Name: "Room 2", Type: "private", IsPrivate: true} room1, _ := service.CreateRoom(context.Background(), userID, roomReq1) room2, _ := service.CreateRoom(context.Background(), userID2, roomReq2) // User 1 joins room 2 err := service.AddMember(context.Background(), room2.ID, userID) assert.NoError(t, err) rooms, err := service.GetUserRooms(context.Background(), userID) assert.NoError(t, err) assert.Len(t, rooms, 2) // Should contain Room 1 and Room 2 // Check content var foundRoom1, foundRoom2 bool for _, r := range rooms { if r.ID == room1.ID { foundRoom1 = true } if r.ID == room2.ID { foundRoom2 = true } } assert.True(t, foundRoom1) assert.True(t, foundRoom2) } func TestRoomService_GetRoomHistory(t *testing.T) { logger := zap.NewNop() roomRepo := NewMockRoomRepository() mockMessageRepo := NewMockChatMessageRepository() service := NewRoomService(roomRepo, mockMessageRepo, logger) // Create a dummy conversation ID convID := uuid.New() // Create a room first to simulate existence roomReq := CreateRoomRequest{Name: "History Room", Type: "public", IsPrivate: false} _, _ = service.CreateRoom(context.Background(), int64(1), roomReq) // Add mock messages mockMessageRepo.messages = []models.ChatMessage{ {ID: uuid.New(), ConversationID: convID, SenderID: uuid.New(), Content: "Hello 1", CreatedAt: time.Now().Add(-2 * time.Minute)}, {ID: uuid.New(), ConversationID: convID, SenderID: uuid.New(), Content: "Hello 2", CreatedAt: time.Now().Add(-1 * time.Minute)}, {ID: uuid.New(), ConversationID: convID, SenderID: uuid.New(), Content: "Hello 3", CreatedAt: time.Now()}, } history, err := service.GetRoomHistory(context.Background(), convID, 10, 0) assert.NoError(t, err) assert.Len(t, history, 3) assert.Equal(t, "Hello 3", history[0].Content) // Should be ordered by created_at DESC history, err = service.GetRoomHistory(context.Background(), convID, 1, 1) // limit 1, offset 1 assert.NoError(t, err) assert.Len(t, history, 1) assert.Equal(t, "Hello 2", history[0].Content) } func TestRoomService_GetRoom_Success(t *testing.T) { logger := zap.NewNop() roomRepo := NewMockRoomRepository() messageRepo := NewMockChatMessageRepository() service := NewRoomService(roomRepo, messageRepo, logger) userID := int64(1) req := CreateRoomRequest{Name: "Single Room", Type: "public", IsPrivate: false} createdRoom, _ := service.CreateRoom(context.Background(), userID, req) retrievedRoom, err := service.GetRoom(context.Background(), createdRoom.ID) assert.NoError(t, err) assert.NotNil(t, retrievedRoom) assert.Equal(t, createdRoom.ID, retrievedRoom.ID) assert.Equal(t, "Single Room", retrievedRoom.Name) } func TestRoomService_GetRoom_NotFound(t *testing.T) { logger := zap.NewNop() roomRepo := NewMockRoomRepository() messageRepo := NewMockChatMessageRepository() service := NewRoomService(roomRepo, messageRepo, logger) _, err := service.GetRoom(context.Background(), uuid.New()) assert.Error(t, err) assert.Equal(t, "playlist not found", err.Error()) // Gorm returns playlist not found here } func TestRoomService_AddMember_Success(t *testing.T) { logger := zap.NewNop() roomRepo := NewMockRoomRepository() messageRepo := NewMockChatMessageRepository() service := NewRoomService(roomRepo, messageRepo, logger) userID := int64(1) roomReq := CreateRoomRequest{Name: "Member Room", Type: "public", IsPrivate: false} room, _ := service.CreateRoom(context.Background(), userID, roomReq) newMemberID := int64(2) err := service.AddMember(context.Background(), room.ID, newMemberID) assert.NoError(t, err) members, _ := roomRepo.GetMembersByRoomID(context.Background(), room.ID) assert.Len(t, members, 2) // Original creator + new member assert.Equal(t, newMemberID, members[1].UserID) }