package repository import ( "backend/pkg/chat/domain/chat" "backend/pkg/chat/domain/entity" "backend/pkg/chat/domain/repository" "backend/pkg/library/cassandra" "context" "strconv" "testing" "time" "github.com/gocql/gocql" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var ( // 全局變量:所有測試共享的資源(與 message_test.go 共享) roomTestRepo repository.RoomRepository ) // ensureRoomRepository 確保 Room Repository 已初始化 func ensureRoomRepository(t *testing.T) { if roomTestRepo == nil { if testDB == nil { t.Fatal("testDB is not initialized. Make sure TestMain in message_test.go runs first.") } var err error roomTestRepo, err = NewRoomRepository(testDB, "test_keyspace") if err != nil { t.Fatalf("Failed to create room repository: %v", err) } } } // clearRoomData 清空所有 room 相關表的數據 func clearRoomData(t *testing.T) { ctx := context.Background() tables := []string{"user_room", "room_member", "room"} for _, table := range tables { truncateStmt := "TRUNCATE test_keyspace." + table if err := testDB.GetSession().Query(truncateStmt, nil).WithContext(ctx).Exec(); err != nil { t.Fatalf("Failed to truncate %s table: %v", table, err) } } // 等待數據清空 time.Sleep(50 * time.Millisecond) } // ==================== Room Interface 測試 ==================== func TestNewRoomRepository(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) assert.NotNil(t, roomTestRepo) } func TestRoomRepository_Create(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() tests := []struct { name string room *entity.Room wantErr bool }{ { name: "successful create", room: &entity.Room{ Name: "Test Room", Status: "active", }, wantErr: false, }, { name: "create with auto timestamp", room: &entity.Room{ Name: "Auto Timestamp Room", Status: "active", }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := roomTestRepo.Create(ctx, tt.room) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) // 驗證 RoomID 和時間戳是否被自動設置 assert.NotZero(t, tt.room.RoomID) assert.NotZero(t, tt.room.CreatedAt) assert.NotZero(t, tt.room.UpdatedAt) } }) } } func TestRoomRepository_RoomGet(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "Get Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) roomIDStr := room.RoomID.String() // 等待數據寫入 time.Sleep(100 * time.Millisecond) tests := []struct { name string roomID string wantErr bool }{ { name: "get existing room", roomID: roomIDStr, wantErr: false, }, { name: "get non-existent room", roomID: gocql.TimeUUID().String(), wantErr: true, }, { name: "get with invalid UUID", roomID: "invalid-uuid", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := roomTestRepo.RoomGet(ctx, tt.roomID) if tt.wantErr { assert.Error(t, err) assert.Nil(t, result) } else { assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, room.Name, result.Name) assert.Equal(t, room.Status, result.Status) } }) } } func TestRoomRepository_RoomUpdate(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "Original Name", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) originalCreatedAt := room.CreatedAt // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 更新房間 room.Name = "Updated Name" room.Status = "archived" err := roomTestRepo.RoomUpdate(ctx, room) require.NoError(t, err) // 驗證更新 updated, err := roomTestRepo.RoomGet(ctx, room.RoomID.String()) require.NoError(t, err) assert.Equal(t, "Updated Name", updated.Name) assert.Equal(t, "archived", updated.Status) assert.Equal(t, originalCreatedAt, updated.CreatedAt) // CreatedAt 不應該改變 assert.Greater(t, updated.UpdatedAt, originalCreatedAt) // UpdatedAt 應該更新 } func TestRoomRepository_RoomDelete(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "Delete Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) roomIDStr := room.RoomID.String() // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 刪除房間 err := roomTestRepo.RoomDelete(ctx, roomIDStr) require.NoError(t, err) // 驗證房間已被刪除 _, err = roomTestRepo.RoomGet(ctx, roomIDStr) assert.Error(t, err) assert.True(t, cassandra.IsNotFound(err)) } func TestRoomRepository_RoomList(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建多個測試房間 rooms := make([]*entity.Room, 5) for i := 0; i < 5; i++ { room := &entity.Room{ Name: "Room " + strconv.Itoa(i), Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) rooms[i] = room time.Sleep(10 * time.Millisecond) // 確保時間戳不同 } // 等待數據寫入 time.Sleep(100 * time.Millisecond) tests := []struct { name string param repository.ListRoomsReq wantLen int wantErr bool }{ { name: "list all rooms", param: repository.ListRoomsReq{ PageSize: 10, }, wantLen: 5, wantErr: false, }, { name: "list with status filter", param: repository.ListRoomsReq{ Status: "active", PageSize: 10, }, wantLen: 5, wantErr: false, }, { name: "list with page size limit", param: repository.ListRoomsReq{ PageSize: 2, }, wantLen: 2, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := roomTestRepo.RoomList(ctx, tt.param) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Len(t, result, tt.wantLen) } }) } } func TestRoomRepository_RoomCount(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 for i := 0; i < 3; i++ { room := &entity.Room{ Name: "Count Room " + strconv.Itoa(i), Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) } // 創建一個 archived 房間 archivedRoom := &entity.Room{ Name: "Archived Room", Status: "archived", } require.NoError(t, roomTestRepo.Create(ctx, archivedRoom)) // 等待數據寫入 time.Sleep(100 * time.Millisecond) tests := []struct { name string param repository.CountRoomsReq want int64 wantErr bool }{ { name: "count all rooms", param: repository.CountRoomsReq{}, want: 4, }, { name: "count active rooms", param: repository.CountRoomsReq{ Status: "active", }, want: 3, }, { name: "count archived rooms", param: repository.CountRoomsReq{ Status: "archived", }, want: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { count, err := roomTestRepo.RoomCount(ctx, tt.param) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, tt.want, count) } }) } } func TestRoomRepository_RoomExists(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "Exists Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) roomIDStr := room.RoomID.String() nonExistentID := gocql.TimeUUID().String() // 等待數據寫入 time.Sleep(100 * time.Millisecond) tests := []struct { name string roomID string want bool wantErr bool }{ { name: "existing room", roomID: roomIDStr, want: true, wantErr: false, }, { name: "non-existent room", roomID: nonExistentID, want: false, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { exists, err := roomTestRepo.RoomExists(ctx, tt.roomID) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, tt.want, exists) } }) } } func TestRoomRepository_RoomGetByID(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建多個測試房間 rooms := make([]*entity.Room, 3) roomIDs := make([]string, 3) for i := 0; i < 3; i++ { room := &entity.Room{ Name: "Room " + strconv.Itoa(i), Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) rooms[i] = room roomIDs[i] = room.RoomID.String() } // 等待數據寫入 time.Sleep(100 * time.Millisecond) tests := []struct { name string roomIDs []string wantLen int wantErr bool }{ { name: "get multiple rooms", roomIDs: roomIDs, wantLen: 3, wantErr: false, }, { name: "get empty list", roomIDs: []string{}, wantLen: 0, wantErr: false, }, { name: "get with non-existent ID", roomIDs: []string{roomIDs[0], gocql.TimeUUID().String()}, wantLen: 1, // 只返回存在的房間 wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := roomTestRepo.RoomGetByID(ctx, tt.roomIDs) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Len(t, result, tt.wantLen) } }) } } // ==================== Member Interface 測試 ==================== func TestRoomRepository_Insert(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "Member Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) tests := []struct { name string member *entity.RoomMember wantErr bool }{ { name: "successful insert", member: &entity.RoomMember{ RoomID: room.RoomID, UID: "user-1", Role: chat.RoomRoleMember.String(), }, wantErr: false, }, { name: "insert with auto role", member: &entity.RoomMember{ RoomID: room.RoomID, UID: "user-2", }, wantErr: false, }, { name: "insert with admin role", member: &entity.RoomMember{ RoomID: room.RoomID, UID: "user-3", Role: chat.RoomRoleAdmin.String(), }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := roomTestRepo.Insert(ctx, tt.member) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) // 驗證 JoinedAt 是否被自動設置 assert.NotZero(t, tt.member.JoinedAt) // 驗證 Role 是否被自動設置(如果為空) if tt.member.Role == "" { assert.Equal(t, chat.RoomRoleMember.String(), tt.member.Role) } // 驗證 user_room 表也被更新 userRooms, err := roomTestRepo.GetUserRooms(ctx, tt.member.UID) require.NoError(t, err) assert.Greater(t, len(userRooms), 0) } }) } } func TestRoomRepository_Get(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間和成員 room := &entity.Room{ Name: "Get Member Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) member := &entity.RoomMember{ RoomID: room.RoomID, UID: "user-1", Role: chat.RoomRoleAdmin.String(), } require.NoError(t, roomTestRepo.Insert(ctx, member)) // 等待數據寫入 time.Sleep(100 * time.Millisecond) tests := []struct { name string roomID string uid string wantErr bool }{ { name: "get existing member", roomID: room.RoomID.String(), uid: "user-1", wantErr: false, }, { name: "get non-existent member", roomID: room.RoomID.String(), uid: "non-existent-user", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := roomTestRepo.Get(ctx, tt.roomID, tt.uid) if tt.wantErr { assert.Error(t, err) assert.Nil(t, result) } else { assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, member.UID, result.UID) assert.Equal(t, member.Role, result.Role) } }) } } func TestRoomRepository_AllMembers(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "All Members Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) // 插入多個成員 members := []*entity.RoomMember{ {RoomID: room.RoomID, UID: "user-1", Role: chat.RoomRoleMember.String()}, {RoomID: room.RoomID, UID: "user-2", Role: chat.RoomRoleAdmin.String()}, {RoomID: room.RoomID, UID: "user-3", Role: chat.RoomRoleMember.String()}, } for _, member := range members { require.NoError(t, roomTestRepo.Insert(ctx, member)) } // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 查詢所有成員 result, err := roomTestRepo.AllMembers(ctx, room.RoomID.String()) require.NoError(t, err) assert.Len(t, result, 3) } func TestRoomRepository_UpdateRole(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間和成員 room := &entity.Room{ Name: "Update Role Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) member := &entity.RoomMember{ RoomID: room.RoomID, UID: "user-1", Role: chat.RoomRoleMember.String(), } require.NoError(t, roomTestRepo.Insert(ctx, member)) // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 更新角色 member.Role = chat.RoomRoleAdmin.String() err := roomTestRepo.UpdateRole(ctx, member) require.NoError(t, err) // 驗證更新 updated, err := roomTestRepo.Get(ctx, room.RoomID.String(), "user-1") require.NoError(t, err) assert.Equal(t, chat.RoomRoleAdmin.String(), updated.Role) } func TestRoomRepository_DeleteMember(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間和成員 room := &entity.Room{ Name: "Delete Member Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) member := &entity.RoomMember{ RoomID: room.RoomID, UID: "user-1", Role: chat.RoomRoleMember.String(), } require.NoError(t, roomTestRepo.Insert(ctx, member)) // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 刪除成員 err := roomTestRepo.DeleteMember(ctx, room.RoomID.String(), "user-1") require.NoError(t, err) // 驗證成員已被刪除 _, err = roomTestRepo.Get(ctx, room.RoomID.String(), "user-1") assert.Error(t, err) assert.True(t, cassandra.IsNotFound(err)) // 驗證 user_room 表也被刪除 userRooms, err := roomTestRepo.GetUserRooms(ctx, "user-1") require.NoError(t, err) assert.Len(t, userRooms, 0) } func TestRoomRepository_DeleteRoom(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "Delete Room Test", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) // 插入多個成員 members := []*entity.RoomMember{ {RoomID: room.RoomID, UID: "user-1", Role: chat.RoomRoleMember.String()}, {RoomID: room.RoomID, UID: "user-2", Role: chat.RoomRoleAdmin.String()}, } for _, member := range members { require.NoError(t, roomTestRepo.Insert(ctx, member)) } // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 刪除整個房間 err := roomTestRepo.DeleteRoom(ctx, room.RoomID.String()) require.NoError(t, err) // 驗證所有成員已被刪除 allMembers, err := roomTestRepo.AllMembers(ctx, room.RoomID.String()) require.NoError(t, err) assert.Len(t, allMembers, 0) // 驗證 user_room 表中的關聯也被刪除 for _, member := range members { userRooms, err := roomTestRepo.GetUserRooms(ctx, member.UID) require.NoError(t, err) // 驗證該用戶的 user_room 記錄中不包含這個房間 found := false for _, ur := range userRooms { if ur.RoomID == room.RoomID { found = true break } } assert.False(t, found, "user_room should not contain deleted room") } } func TestRoomRepository_Count(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "Count Members Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) // 插入多個成員 for i := 0; i < 5; i++ { member := &entity.RoomMember{ RoomID: room.RoomID, UID: "user-" + strconv.Itoa(i), Role: chat.RoomRoleMember.String(), } require.NoError(t, roomTestRepo.Insert(ctx, member)) } // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 計算成員數 count, err := roomTestRepo.Count(ctx, room.RoomID.String()) require.NoError(t, err) assert.Equal(t, int64(5), count) } // ==================== User Interface 測試 ==================== func TestRoomRepository_GetUserRooms(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建多個測試房間 rooms := make([]*entity.Room, 3) for i := 0; i < 3; i++ { room := &entity.Room{ Name: "User Room " + strconv.Itoa(i), Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) rooms[i] = room } // 將用戶加入多個房間 uid := "user-1" for _, room := range rooms { member := &entity.RoomMember{ RoomID: room.RoomID, UID: uid, Role: chat.RoomRoleMember.String(), } require.NoError(t, roomTestRepo.Insert(ctx, member)) } // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 查詢用戶所在的所有房間 userRooms, err := roomTestRepo.GetUserRooms(ctx, uid) require.NoError(t, err) assert.Len(t, userRooms, 3) } func TestRoomRepository_CountUserRooms(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 rooms := make([]*entity.Room, 5) for i := 0; i < 5; i++ { room := &entity.Room{ Name: "Count User Room " + strconv.Itoa(i), Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) rooms[i] = room } // 將用戶加入多個房間 uid := "user-1" for _, room := range rooms { member := &entity.RoomMember{ RoomID: room.RoomID, UID: uid, Role: chat.RoomRoleMember.String(), } require.NoError(t, roomTestRepo.Insert(ctx, member)) } // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 計算用戶所在的房間數 count, err := roomTestRepo.CountUserRooms(ctx, uid) require.NoError(t, err) assert.Equal(t, int64(5), count) } func TestRoomRepository_IsUserInRoom(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建測試房間 room := &entity.Room{ Name: "Is User In Room Test", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) // 插入成員 member := &entity.RoomMember{ RoomID: room.RoomID, UID: "user-1", Role: chat.RoomRoleMember.String(), } require.NoError(t, roomTestRepo.Insert(ctx, member)) // 等待數據寫入 time.Sleep(100 * time.Millisecond) tests := []struct { name string uid string roomID string want bool wantErr bool }{ { name: "user in room", uid: "user-1", roomID: room.RoomID.String(), want: true, wantErr: false, }, { name: "user not in room", uid: "user-2", roomID: room.RoomID.String(), want: false, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := roomTestRepo.IsUserInRoom(ctx, tt.uid, tt.roomID) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) assert.Equal(t, tt.want, result) } }) } } // ==================== 整合測試 ==================== func TestRoomRepository_Integration(t *testing.T) { ensureRoomRepository(t) clearRoomData(t) ctx := context.Background() // 創建房間 room := &entity.Room{ Name: "Integration Test Room", Status: "active", } require.NoError(t, roomTestRepo.Create(ctx, room)) // 添加多個成員 members := []*entity.RoomMember{ {RoomID: room.RoomID, UID: "user-1", Role: chat.RoomRoleOwner.String()}, {RoomID: room.RoomID, UID: "user-2", Role: chat.RoomRoleAdmin.String()}, {RoomID: room.RoomID, UID: "user-3", Role: chat.RoomRoleMember.String()}, } for _, member := range members { require.NoError(t, roomTestRepo.Insert(ctx, member)) } // 等待數據寫入 time.Sleep(100 * time.Millisecond) // 驗證房間存在 exists, err := roomTestRepo.RoomExists(ctx, room.RoomID.String()) require.NoError(t, err) assert.True(t, exists) // 驗證成員數量 count, err := roomTestRepo.Count(ctx, room.RoomID.String()) require.NoError(t, err) assert.Equal(t, int64(3), count) // 驗證所有成員 allMembers, err := roomTestRepo.AllMembers(ctx, room.RoomID.String()) require.NoError(t, err) assert.Len(t, allMembers, 3) // 驗證用戶所在的房間 for _, member := range members { userRooms, err := roomTestRepo.GetUserRooms(ctx, member.UID) require.NoError(t, err) assert.Greater(t, len(userRooms), 0) inRoom, err := roomTestRepo.IsUserInRoom(ctx, member.UID, room.RoomID.String()) require.NoError(t, err) assert.True(t, inRoom) } }