package repository import ( "backend/pkg/permission/domain" "backend/pkg/permission/domain/entity" domainRepo "backend/pkg/permission/domain/repository" "context" "fmt" "github.com/alicebob/miniredis/v2" "github.com/zeromicro/go-zero/core/stores/redis" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zeromicro/go-zero/core/stores/cache" "go.mongodb.org/mongo-driver/v2/bson" mgo "backend/pkg/library/mongo" ) func setupRoleRepo(db string) (domainRepo.RoleRepository, func(), error) { h, p, tearDown, err := startMongoContainer() if err != nil { return nil, nil, err } s, _ := miniredis.Run() conf := &mgo.Conf{ Schema: mongoSchema, Host: fmt.Sprintf("%s:%s", h, p), Database: db, MaxStaleness: 300, MaxPoolSize: 100, MinPoolSize: 100, MaxConnIdleTime: 300, Compressors: []string{}, EnableStandardReadWriteSplitMode: false, ConnectTimeoutMs: 3000, } cacheConf := cache.CacheConf{ cache.NodeConf{ RedisConf: redis.RedisConf{ Host: s.Addr(), Type: redis.NodeType, }, Weight: 100, }, } cacheOpts := []cache.Option{ cache.WithExpiry(1000 * time.Microsecond), cache.WithNotFoundExpiry(1000 * time.Microsecond), } param := RoleRepositoryParam{ Conf: conf, CacheConf: cacheConf, CacheOpts: cacheOpts, } repo := NewRoleRepository(param) _, _ = repo.Index20251009002UP(context.Background()) return repo, tearDown, nil } func TestRoleRepository_CreateAndGet(t *testing.T) { repo, tearDown, err := setupRoleRepo("testDB") defer tearDown() assert.NoError(t, err) ctx := context.Background() tests := []struct { name string role *entity.Role wantErr bool }{ { name: "成功創建角色", role: &entity.Role{ ID: bson.NewObjectID(), UID: "ROLE0000000001", ClientID: 1, Name: "管理員", Status: domain.RecordActive, }, wantErr: false, }, { name: "創建另一個角色", role: &entity.Role{ ID: bson.NewObjectID(), UID: "ROLE0000000002", ClientID: 1, Name: "一般使用者", Status: domain.RecordActive, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := repo.Create(ctx, tt.role) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) // 驗證可以找到創建的角色 retrieved, err := repo.GetByUID(ctx, tt.role.UID) assert.NoError(t, err) assert.Equal(t, tt.role.UID, retrieved.UID) assert.Equal(t, tt.role.Name, retrieved.Name) assert.Equal(t, tt.role.ClientID, retrieved.ClientID) } }) } } func TestRoleRepository_GetByUID(t *testing.T) { repo, tearDown, err := setupRoleRepo("testDB") defer tearDown() assert.NoError(t, err) ctx := context.Background() // 準備測試數據 testRole := &entity.Role{ ID: bson.NewObjectID(), UID: "ROLE0000000001", ClientID: 1, Name: "測試角色", Status: domain.RecordActive, } err = repo.Create(ctx, testRole) require.NoError(t, err) tests := []struct { name string uid string wantErr error check func(*testing.T, *entity.Role) }{ { name: "找到存在的角色", uid: "ROLE0000000001", wantErr: nil, check: func(t *testing.T, role *entity.Role) { assert.Equal(t, "測試角色", role.Name) assert.Equal(t, 1, role.ClientID) }, }, { name: "不存在的角色", uid: "ROLE9999999999", wantErr: ErrNotFound, check: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { role, err := repo.GetByUID(ctx, tt.uid) if tt.wantErr != nil { assert.ErrorIs(t, err, tt.wantErr) assert.Nil(t, role) } else { assert.NoError(t, err) assert.NotNil(t, role) if tt.check != nil { tt.check(t, role) } } }) } } func TestRoleRepository_Update(t *testing.T) { repo, tearDown, err := setupRoleRepo("testDB") defer tearDown() assert.NoError(t, err) ctx := context.Background() // 準備測試數據 testRole := &entity.Role{ ID: bson.NewObjectID(), UID: "ROLE0000000001", ClientID: 1, Name: "原始名稱", Status: domain.RecordActive, } err = repo.Create(ctx, testRole) require.NoError(t, err) tests := []struct { name string update func(*entity.Role) wantErr bool check func(*testing.T, *entity.Role) }{ { name: "更新角色名稱", update: func(role *entity.Role) { role.Name = "新的名稱" }, wantErr: false, check: func(t *testing.T, role *entity.Role) { assert.Equal(t, "新的名稱", role.Name) }, }, { name: "更新角色狀態", update: func(role *entity.Role) { role.Status = domain.RecordInactive }, wantErr: false, check: func(t *testing.T, role *entity.Role) { assert.Equal(t, domain.RecordInactive, role.Status) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // 獲取當前角色 role, err := repo.GetByUID(ctx, testRole.UID) require.NoError(t, err) // 應用更新 tt.update(role) // 執行更新 err = repo.Update(ctx, role) if tt.wantErr { assert.Error(t, err) } else { assert.NoError(t, err) // 驗證更新 updated, err := repo.GetByUID(ctx, testRole.UID) assert.NoError(t, err) if tt.check != nil { tt.check(t, updated) } } }) } } func TestRoleRepository_Delete(t *testing.T) { repo, tearDown, err := setupRoleRepo("testDB") defer tearDown() assert.NoError(t, err) ctx := context.Background() // 準備測試數據 testRole := &entity.Role{ ID: bson.NewObjectID(), UID: "ROLE0000000001", ClientID: 1, Name: "要刪除的角色", Status: domain.RecordActive, } err = repo.Create(ctx, testRole) require.NoError(t, err) t.Run("軟刪除角色", func(t *testing.T) { err := repo.Delete(ctx, testRole.UID) assert.NoError(t, err) // 驗證角色狀態變為已刪除 role, err := repo.GetByUID(ctx, testRole.UID) assert.NoError(t, err) assert.Equal(t, domain.RecordDeleted, role.Status) }) } func TestRoleRepository_List(t *testing.T) { repo, tearDown, err := setupRoleRepo("testDB") defer tearDown() assert.NoError(t, err) ctx := context.Background() // 準備測試數據 testRoles := []*entity.Role{ { ID: bson.NewObjectID(), UID: "ROLE0000000001", ClientID: 1, Name: "管理員", Status: domain.RecordActive, }, { ID: bson.NewObjectID(), UID: "ROLE0000000002", ClientID: 1, Name: "編輯者", Status: domain.RecordActive, }, { ID: bson.NewObjectID(), UID: "ROLE0000000003", ClientID: 2, Name: "訪客", Status: domain.RecordInactive, }, } for _, role := range testRoles { err := repo.Create(ctx, role) require.NoError(t, err) } tests := []struct { name string filter domainRepo.RoleFilter wantCount int }{ { name: "列出所有角色", filter: domainRepo.RoleFilter{}, wantCount: 3, }, { name: "按 ClientID 過濾", filter: domainRepo.RoleFilter{ ClientID: 1, }, wantCount: 2, }, { name: "按名稱模糊搜尋", filter: domainRepo.RoleFilter{ Name: "管理", }, wantCount: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { roles, err := repo.List(ctx, tt.filter) assert.NoError(t, err) assert.Len(t, roles, tt.wantCount) }) } } func TestRoleRepository_Page(t *testing.T) { repo, tearDown, err := setupRoleRepo("testDB") defer tearDown() assert.NoError(t, err) ctx := context.Background() // 準備測試數據 - 創建 15 個角色 for i := 1; i <= 15; i++ { role := &entity.Role{ ID: bson.NewObjectID(), UID: bson.NewObjectID().Hex(), ClientID: 1, Name: bson.NewObjectID().Hex(), Status: domain.RecordActive, } err := repo.Create(ctx, role) require.NoError(t, err) } tests := []struct { name string page int size int wantCount int wantTotal int64 }{ { name: "第一頁,每頁 10 筆", page: 1, size: 10, wantCount: 10, wantTotal: 15, }, { name: "第二頁,每頁 10 筆", page: 2, size: 10, wantCount: 5, wantTotal: 15, }, { name: "第一頁,每頁 5 筆", page: 1, size: 5, wantCount: 5, wantTotal: 15, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { roles, total, err := repo.Page(ctx, domainRepo.RoleFilter{}, tt.page, tt.size) assert.NoError(t, err) assert.Len(t, roles, tt.wantCount) assert.Equal(t, tt.wantTotal, total) }) } } func TestRoleRepository_Exists(t *testing.T) { repo, tearDown, err := setupRoleRepo("testDB") defer tearDown() assert.NoError(t, err) ctx := context.Background() // 準備測試數據 testRole := &entity.Role{ ID: bson.NewObjectID(), UID: "ROLE0000000001", ClientID: 1, Name: "測試角色", Status: domain.RecordActive, } err = repo.Create(ctx, testRole) require.NoError(t, err) tests := []struct { name string uid string exists bool }{ { name: "存在的角色", uid: "ROLE0000000001", exists: true, }, { name: "不存在的角色", uid: "ROLE9999999999", exists: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { exists, err := repo.Exists(ctx, tt.uid) assert.NoError(t, err) assert.Equal(t, tt.exists, exists) }) } }