backend/pkg/chat/repository/room_test.go

953 lines
21 KiB
Go
Raw Normal View History

2026-01-06 07:15:18 +00:00
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)
2026-01-07 02:47:37 +00:00
assert.Equal(t, originalCreatedAt, updated.CreatedAt) // CreatedAt 不應該改變
2026-01-06 07:15:18 +00:00
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
}{
{
2026-01-07 02:47:37 +00:00
name: "count all rooms",
2026-01-06 07:15:18 +00:00
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)
}
}