package services import ( "context" "testing" "time" "veza-backend-api/internal/models" "veza-backend-api/internal/repositories" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func setupTestRoomService(t *testing.T) (*RoomService, *gorm.DB) { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Enable foreign keys db.Exec("PRAGMA foreign_keys = ON") err = db.AutoMigrate(&models.User{}, &models.Room{}, &models.RoomMember{}, &models.Message{}) require.NoError(t, err) logger := zap.NewNop() roomRepo := repositories.NewRoomRepository(db) messageRepo := repositories.NewChatMessageRepository(db) service := NewRoomService(roomRepo, messageRepo, logger) return service, db } func createTestUserForRoom(t *testing.T, db *gorm.DB, username string) *models.User { user := &models.User{ ID: uuid.New(), Username: username, Email: username + "@example.com", PasswordHash: "hash", IsActive: true, } err := db.Create(user).Error require.NoError(t, err) return user } func TestRoomService_CreateRoom(t *testing.T) { service, db := setupTestRoomService(t) user := createTestUserForRoom(t, db, "user1") req := CreateRoomRequest{ Name: "Test Room", Type: "public", IsPrivate: false, } room, err := service.CreateRoom(context.Background(), user.ID, req) assert.NoError(t, err) assert.NotNil(t, room) assert.Equal(t, req.Name, room.Name) assert.Contains(t, room.Participants, user.ID) // Verify room created in DB var createdRoom models.Room err = db.First(&createdRoom, "id = ?", room.ID).Error assert.NoError(t, err) assert.Equal(t, room.ID, createdRoom.ID) } func TestRoomService_GetUserRooms(t *testing.T) { service, db := setupTestRoomService(t) user1 := createTestUserForRoom(t, db, "user1") user2 := createTestUserForRoom(t, db, "user2") roomReq1 := CreateRoomRequest{Name: "Room 1", Type: "public", IsPrivate: false} roomReq2 := CreateRoomRequest{Name: "Room 2", Type: "private", IsPrivate: true} room1, err := service.CreateRoom(context.Background(), user1.ID, roomReq1) require.NoError(t, err) room2, err := service.CreateRoom(context.Background(), user2.ID, roomReq2) require.NoError(t, err) // User 1 joins room 2 err = service.AddMember(context.Background(), room2.ID, user1.ID) assert.NoError(t, err) rooms, err := service.GetUserRooms(context.Background(), user1.ID) 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) { service, db := setupTestRoomService(t) user := createTestUserForRoom(t, db, "user1") // Create a room roomReq := CreateRoomRequest{Name: "History Room", Type: "public", IsPrivate: false} room, err := service.CreateRoom(context.Background(), user.ID, roomReq) require.NoError(t, err) // Add messages to DB msgs := []models.Message{ {ID: uuid.New(), RoomID: room.ID, UserID: user.ID, Content: "Hello 1", CreatedAt: time.Now().Add(-2 * time.Minute)}, {ID: uuid.New(), RoomID: room.ID, UserID: user.ID, Content: "Hello 2", CreatedAt: time.Now().Add(-1 * time.Minute)}, {ID: uuid.New(), RoomID: room.ID, UserID: user.ID, Content: "Hello 3", CreatedAt: time.Now()}, } for _, msg := range msgs { db.Create(&msg) } history, err := service.GetRoomHistory(context.Background(), room.ID, 10, 0) assert.NoError(t, err) assert.Len(t, history, 3) assert.Equal(t, "Hello 3", history[0].Content) // ordered by created_at DESC history, err = service.GetRoomHistory(context.Background(), room.ID, 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) { service, db := setupTestRoomService(t) user := createTestUserForRoom(t, db, "user1") req := CreateRoomRequest{Name: "Single Room", Type: "public", IsPrivate: false} createdRoom, err := service.CreateRoom(context.Background(), user.ID, req) require.NoError(t, err) retrievedRoom, err := service.GetRoom(context.Background(), createdRoom.ID) assert.NoError(t, err) require.NotNil(t, retrievedRoom) assert.Equal(t, createdRoom.ID, retrievedRoom.ID) assert.Equal(t, "Single Room", retrievedRoom.Name) } func TestRoomService_GetRoom_NotFound(t *testing.T) { service, _ := setupTestRoomService(t) _, err := service.GetRoom(context.Background(), uuid.New()) assert.Error(t, err) // GORM RecordNotFound might be wrapped or returned as error // Implementation returns fmt.Errorf("failed to get room: %w", err) // So we assume it errors out. } func TestRoomService_AddMember_Success(t *testing.T) { service, db := setupTestRoomService(t) user1 := createTestUserForRoom(t, db, "user1") user2 := createTestUserForRoom(t, db, "user2") roomReq := CreateRoomRequest{Name: "Member Room", Type: "public", IsPrivate: false} room, err := service.CreateRoom(context.Background(), user1.ID, roomReq) require.NoError(t, err) err = service.AddMember(context.Background(), room.ID, user2.ID) assert.NoError(t, err) // Verify members in DB var members []models.RoomMember db.Where("room_id = ?", room.ID).Find(&members) assert.Len(t, members, 2) // Owner + New Member var foundUser2 bool for _, m := range members { if m.UserID == user2.ID { foundUser2 = true } } assert.True(t, foundUser2) } func TestRoomService_RemoveMember_Success(t *testing.T) { service, db := setupTestRoomService(t) user1 := createTestUserForRoom(t, db, "user1") user2 := createTestUserForRoom(t, db, "user2") roomReq := CreateRoomRequest{Name: "Remove Member Room", Type: "public", IsPrivate: false} room, err := service.CreateRoom(context.Background(), user1.ID, roomReq) require.NoError(t, err) err = service.AddMember(context.Background(), room.ID, user2.ID) require.NoError(t, err) err = service.RemoveMember(context.Background(), room.ID, user2.ID) assert.NoError(t, err) // Verify member removed var count int64 db.Model(&models.RoomMember{}).Where("room_id = ? AND user_id = ?", room.ID, user2.ID).Count(&count) assert.Equal(t, int64(0), count) } func TestRoomService_UpdateRoom_Success(t *testing.T) { service, db := setupTestRoomService(t) user := createTestUserForRoom(t, db, "user1") req := CreateRoomRequest{Name: "Original Name", Type: "public", IsPrivate: false} room, err := service.CreateRoom(context.Background(), user.ID, req) require.NoError(t, err) newName := "Updated Name" newDesc := "Updated Desc" updateReq := UpdateRoomRequest{Name: &newName, Description: &newDesc} updatedRoom, err := service.UpdateRoom(context.Background(), room.ID, user.ID, updateReq) assert.NoError(t, err) assert.Equal(t, newName, updatedRoom.Name) assert.Equal(t, newDesc, updatedRoom.Description) } func TestRoomService_UpdateRoom_Forbidden(t *testing.T) { service, db := setupTestRoomService(t) owner := createTestUserForRoom(t, db, "owner") other := createTestUserForRoom(t, db, "other") req := CreateRoomRequest{Name: "Owner Room", Type: "public", IsPrivate: false} room, err := service.CreateRoom(context.Background(), owner.ID, req) require.NoError(t, err) newName := "Hacked Name" updateReq := UpdateRoomRequest{Name: &newName} _, err = service.UpdateRoom(context.Background(), room.ID, other.ID, updateReq) assert.Error(t, err) assert.Contains(t, err.Error(), "forbidden") } func TestRoomService_DeleteRoom_Success(t *testing.T) { service, db := setupTestRoomService(t) user := createTestUserForRoom(t, db, "user1") req := CreateRoomRequest{Name: "Delete Me", Type: "public", IsPrivate: false} room, err := service.CreateRoom(context.Background(), user.ID, req) require.NoError(t, err) err = service.DeleteRoom(context.Background(), room.ID, user.ID) assert.NoError(t, err) // Verify soft delete var deletedRoom models.Room err = db.First(&deletedRoom, "id = ?", room.ID).Error assert.ErrorIs(t, err, gorm.ErrRecordNotFound) // Should not find with default scope (which excludes deleted) // Verify it still exists in DB but with DeletedAt set (Unscoped) err = db.Unscoped().First(&deletedRoom, "id = ?", room.ID).Error assert.NoError(t, err) assert.NotNil(t, deletedRoom.DeletedAt) } func TestRoomService_DeleteRoom_Forbidden(t *testing.T) { service, db := setupTestRoomService(t) owner := createTestUserForRoom(t, db, "owner") other := createTestUserForRoom(t, db, "other") req := CreateRoomRequest{Name: "Safe Room", Type: "public", IsPrivate: false} room, err := service.CreateRoom(context.Background(), owner.ID, req) require.NoError(t, err) err = service.DeleteRoom(context.Background(), room.ID, other.ID) assert.Error(t, err) assert.Contains(t, err.Error(), "forbidden") }