package repository import ( "context" "testing" "github.com/user-management-system/internal/domain" ) func containsInt64(values []int64, target int64) bool { for _, value := range values { if value == target { return true } } return false } func TestRoleRepositoryLifecycleAndQueries(t *testing.T) { db := openTestDB(t) repo := NewRoleRepository(db) ctx := context.Background() admin := &domain.Role{ Name: "Admin Test", Code: "admin-test", Description: "root role", Level: 1, IsSystem: true, Status: domain.RoleStatusEnabled, } if err := repo.Create(ctx, admin); err != nil { t.Fatalf("Create(admin) failed: %v", err) } parentID := admin.ID auditor := &domain.Role{ Name: "Auditor Test", Code: "auditor-test", Description: "audit role", ParentID: &parentID, Level: 2, IsDefault: true, Status: domain.RoleStatusDisabled, } viewer := &domain.Role{ Name: "Viewer Test", Code: "viewer-test", Description: "view role", Level: 1, Status: domain.RoleStatusEnabled, } for _, role := range []*domain.Role{auditor, viewer} { if err := repo.Create(ctx, role); err != nil { t.Fatalf("Create(%s) failed: %v", role.Code, err) } } loadedByID, err := repo.GetByID(ctx, admin.ID) if err != nil { t.Fatalf("GetByID failed: %v", err) } if loadedByID.Code != "admin-test" { t.Fatalf("expected admin-test, got %q", loadedByID.Code) } loadedByCode, err := repo.GetByCode(ctx, "auditor-test") if err != nil { t.Fatalf("GetByCode failed: %v", err) } if loadedByCode.ID != auditor.ID { t.Fatalf("expected auditor id %d, got %d", auditor.ID, loadedByCode.ID) } allRoles, total, err := repo.List(ctx, 0, 10) if err != nil { t.Fatalf("List failed: %v", err) } if total != 3 || len(allRoles) != 3 { t.Fatalf("expected 3 roles, got total=%d len=%d", total, len(allRoles)) } enabledRoles, total, err := repo.ListByStatus(ctx, domain.RoleStatusEnabled, 0, 10) if err != nil { t.Fatalf("ListByStatus failed: %v", err) } if total != 2 || len(enabledRoles) != 2 { t.Fatalf("expected 2 enabled roles, got total=%d len=%d", total, len(enabledRoles)) } defaultRoles, err := repo.GetDefaultRoles(ctx) if err != nil { t.Fatalf("GetDefaultRoles failed: %v", err) } if len(defaultRoles) != 1 || defaultRoles[0].ID != auditor.ID { t.Fatalf("expected auditor as default role, got %+v", defaultRoles) } exists, err := repo.ExistsByCode(ctx, "viewer-test") if err != nil { t.Fatalf("ExistsByCode(viewer-test) failed: %v", err) } if !exists { t.Fatal("expected viewer-test to exist") } missing, err := repo.ExistsByCode(ctx, "missing-role") if err != nil { t.Fatalf("ExistsByCode(missing-role) failed: %v", err) } if missing { t.Fatal("expected missing-role to be absent") } auditor.Description = "audit role updated" if err := repo.Update(ctx, auditor); err != nil { t.Fatalf("Update failed: %v", err) } if err := repo.UpdateStatus(ctx, auditor.ID, domain.RoleStatusEnabled); err != nil { t.Fatalf("UpdateStatus failed: %v", err) } searchResults, total, err := repo.Search(ctx, "audit", 0, 10) if err != nil { t.Fatalf("Search failed: %v", err) } if total != 1 || len(searchResults) != 1 || searchResults[0].ID != auditor.ID { t.Fatalf("expected auditor search hit, got total=%d len=%d", total, len(searchResults)) } childRoles, err := repo.ListByParentID(ctx, admin.ID) if err != nil { t.Fatalf("ListByParentID failed: %v", err) } if len(childRoles) != 1 || childRoles[0].ID != auditor.ID { t.Fatalf("expected auditor child role, got %+v", childRoles) } roleSubset, err := repo.GetByIDs(ctx, []int64{admin.ID, auditor.ID}) if err != nil { t.Fatalf("GetByIDs failed: %v", err) } if len(roleSubset) != 2 { t.Fatalf("expected 2 roles from GetByIDs, got %d", len(roleSubset)) } emptySubset, err := repo.GetByIDs(ctx, []int64{}) if err != nil { t.Fatalf("GetByIDs(empty) failed: %v", err) } if len(emptySubset) != 0 { t.Fatalf("expected empty slice for GetByIDs(empty), got %d", len(emptySubset)) } if err := repo.Delete(ctx, viewer.ID); err != nil { t.Fatalf("Delete failed: %v", err) } if _, err := repo.GetByID(ctx, viewer.ID); err == nil { t.Fatal("expected deleted role lookup to fail") } } func TestPermissionRepositoryLifecycleAndQueries(t *testing.T) { db := openTestDB(t) repo := NewPermissionRepository(db) ctx := context.Background() parent := &domain.Permission{ Name: "Dashboard", Code: "dashboard:view", Type: domain.PermissionTypeMenu, Description: "dashboard menu", Path: "/dashboard", Sort: 1, Status: domain.PermissionStatusEnabled, } if err := repo.Create(ctx, parent); err != nil { t.Fatalf("Create(parent) failed: %v", err) } parentID := parent.ID apiPermission := &domain.Permission{ Name: "Audit API", Code: "audit:read", Type: domain.PermissionTypeAPI, Description: "audit api", ParentID: &parentID, Path: "/api/audit", Method: "GET", Sort: 2, Status: domain.PermissionStatusDisabled, } buttonPermission := &domain.Permission{ Name: "Audit Button", Code: "audit:button", Type: domain.PermissionTypeButton, Description: "audit action", Sort: 3, Status: domain.PermissionStatusEnabled, } for _, permission := range []*domain.Permission{apiPermission, buttonPermission} { if err := repo.Create(ctx, permission); err != nil { t.Fatalf("Create(%s) failed: %v", permission.Code, err) } } role := &domain.Role{ Name: "Permission Role", Code: "permission-role", Description: "role for permission join queries", Status: domain.RoleStatusEnabled, } if err := db.WithContext(ctx).Create(role).Error; err != nil { t.Fatalf("create role for permission joins failed: %v", err) } for _, rolePermission := range []*domain.RolePermission{ {RoleID: role.ID, PermissionID: parent.ID}, {RoleID: role.ID, PermissionID: apiPermission.ID}, } { if err := db.WithContext(ctx).Create(rolePermission).Error; err != nil { t.Fatalf("create role_permission failed: %v", err) } } loadedByID, err := repo.GetByID(ctx, parent.ID) if err != nil { t.Fatalf("GetByID failed: %v", err) } if loadedByID.Code != "dashboard:view" { t.Fatalf("expected dashboard:view, got %q", loadedByID.Code) } loadedByCode, err := repo.GetByCode(ctx, "audit:read") if err != nil { t.Fatalf("GetByCode failed: %v", err) } if loadedByCode.ID != apiPermission.ID { t.Fatalf("expected audit:read id %d, got %d", apiPermission.ID, loadedByCode.ID) } allPermissions, total, err := repo.List(ctx, 0, 10) if err != nil { t.Fatalf("List failed: %v", err) } if total != 3 || len(allPermissions) != 3 { t.Fatalf("expected 3 permissions, got total=%d len=%d", total, len(allPermissions)) } apiPermissions, total, err := repo.ListByType(ctx, domain.PermissionTypeAPI, 0, 10) if err != nil { t.Fatalf("ListByType failed: %v", err) } if total != 1 || len(apiPermissions) != 1 || apiPermissions[0].ID != apiPermission.ID { t.Fatalf("expected audit api permission, got total=%d len=%d", total, len(apiPermissions)) } enabledPermissions, total, err := repo.ListByStatus(ctx, domain.PermissionStatusEnabled, 0, 10) if err != nil { t.Fatalf("ListByStatus failed: %v", err) } if total != 2 || len(enabledPermissions) != 2 { t.Fatalf("expected 2 enabled permissions, got total=%d len=%d", total, len(enabledPermissions)) } rolePermissions, err := repo.GetByRoleIDs(ctx, []int64{role.ID}) if err != nil { t.Fatalf("GetByRoleIDs failed: %v", err) } if len(rolePermissions) != 1 || rolePermissions[0].ID != parent.ID { t.Fatalf("expected only enabled parent permission in join query, got %+v", rolePermissions) } exists, err := repo.ExistsByCode(ctx, "audit:button") if err != nil { t.Fatalf("ExistsByCode(audit:button) failed: %v", err) } if !exists { t.Fatal("expected audit:button to exist") } missing, err := repo.ExistsByCode(ctx, "permission:missing") if err != nil { t.Fatalf("ExistsByCode(missing) failed: %v", err) } if missing { t.Fatal("expected permission:missing to be absent") } apiPermission.Description = "audit api updated" if err := repo.Update(ctx, apiPermission); err != nil { t.Fatalf("Update failed: %v", err) } if err := repo.UpdateStatus(ctx, apiPermission.ID, domain.PermissionStatusEnabled); err != nil { t.Fatalf("UpdateStatus failed: %v", err) } searchResults, total, err := repo.Search(ctx, "audit", 0, 10) if err != nil { t.Fatalf("Search failed: %v", err) } if total != 2 || len(searchResults) != 2 { t.Fatalf("expected 2 audit-related permissions, got total=%d len=%d", total, len(searchResults)) } childPermissions, err := repo.ListByParentID(ctx, parent.ID) if err != nil { t.Fatalf("ListByParentID failed: %v", err) } if len(childPermissions) != 1 || childPermissions[0].ID != apiPermission.ID { t.Fatalf("expected api permission child, got %+v", childPermissions) } permissionSubset, err := repo.GetByIDs(ctx, []int64{parent.ID, apiPermission.ID}) if err != nil { t.Fatalf("GetByIDs failed: %v", err) } if len(permissionSubset) != 2 { t.Fatalf("expected 2 permissions from GetByIDs, got %d", len(permissionSubset)) } emptySubset, err := repo.GetByIDs(ctx, []int64{}) if err != nil { t.Fatalf("GetByIDs(empty) failed: %v", err) } if len(emptySubset) != 0 { t.Fatalf("expected empty slice for GetByIDs(empty), got %d", len(emptySubset)) } if err := repo.Delete(ctx, buttonPermission.ID); err != nil { t.Fatalf("Delete failed: %v", err) } if _, err := repo.GetByID(ctx, buttonPermission.ID); err == nil { t.Fatal("expected deleted permission lookup to fail") } } func TestUserRoleAndRolePermissionRepositoriesLifecycle(t *testing.T) { db := openTestDB(t) userRoleRepo := NewUserRoleRepository(db) rolePermissionRepo := NewRolePermissionRepository(db) ctx := context.Background() users := []*domain.User{ {Username: "repo-user-1", Password: "hash", Status: domain.UserStatusActive}, {Username: "repo-user-2", Password: "hash", Status: domain.UserStatusActive}, } for _, user := range users { if err := db.WithContext(ctx).Create(user).Error; err != nil { t.Fatalf("create user failed: %v", err) } } roles := []*domain.Role{ {Name: "Repo Role 1", Code: "repo-role-1", Status: domain.RoleStatusEnabled}, {Name: "Repo Role 2", Code: "repo-role-2", Status: domain.RoleStatusEnabled}, } for _, role := range roles { if err := db.WithContext(ctx).Create(role).Error; err != nil { t.Fatalf("create role failed: %v", err) } } permissions := []*domain.Permission{ {Name: "Repo Permission 1", Code: "repo:permission:1", Type: domain.PermissionTypeAPI, Status: domain.PermissionStatusEnabled}, {Name: "Repo Permission 2", Code: "repo:permission:2", Type: domain.PermissionTypeAPI, Status: domain.PermissionStatusEnabled}, } for _, permission := range permissions { if err := db.WithContext(ctx).Create(permission).Error; err != nil { t.Fatalf("create permission failed: %v", err) } } userRolePrimary := &domain.UserRole{UserID: users[0].ID, RoleID: roles[0].ID} if err := userRoleRepo.Create(ctx, userRolePrimary); err != nil { t.Fatalf("UserRole Create failed: %v", err) } if err := userRoleRepo.BatchCreate(ctx, []*domain.UserRole{}); err != nil { t.Fatalf("UserRole BatchCreate(empty) failed: %v", err) } userRoleBatch := []*domain.UserRole{ {UserID: users[0].ID, RoleID: roles[1].ID}, {UserID: users[1].ID, RoleID: roles[0].ID}, } if err := userRoleRepo.BatchCreate(ctx, userRoleBatch); err != nil { t.Fatalf("UserRole BatchCreate failed: %v", err) } exists, err := userRoleRepo.Exists(ctx, users[0].ID, roles[0].ID) if err != nil { t.Fatalf("UserRole Exists failed: %v", err) } if !exists { t.Fatal("expected primary user-role relation to exist") } missing, err := userRoleRepo.Exists(ctx, users[1].ID, roles[1].ID) if err != nil { t.Fatalf("UserRole Exists(missing) failed: %v", err) } if missing { t.Fatal("expected missing user-role relation to be absent") } rolesForUserOne, err := userRoleRepo.GetByUserID(ctx, users[0].ID) if err != nil { t.Fatalf("GetByUserID failed: %v", err) } if len(rolesForUserOne) != 2 { t.Fatalf("expected 2 roles for user one, got %d", len(rolesForUserOne)) } usersForRoleOne, err := userRoleRepo.GetByRoleID(ctx, roles[0].ID) if err != nil { t.Fatalf("GetByRoleID failed: %v", err) } if len(usersForRoleOne) != 2 { t.Fatalf("expected 2 users for role one, got %d", len(usersForRoleOne)) } roleIDs, err := userRoleRepo.GetRoleIDsByUserID(ctx, users[0].ID) if err != nil { t.Fatalf("GetRoleIDsByUserID failed: %v", err) } if len(roleIDs) != 2 || !containsInt64(roleIDs, roles[0].ID) || !containsInt64(roleIDs, roles[1].ID) { t.Fatalf("unexpected role IDs for user one: %+v", roleIDs) } userIDs, err := userRoleRepo.GetUserIDByRoleID(ctx, roles[0].ID) if err != nil { t.Fatalf("GetUserIDByRoleID failed: %v", err) } if len(userIDs) != 2 || !containsInt64(userIDs, users[0].ID) || !containsInt64(userIDs, users[1].ID) { t.Fatalf("unexpected user IDs for role one: %+v", userIDs) } if err := userRoleRepo.BatchDelete(ctx, []*domain.UserRole{}); err != nil { t.Fatalf("UserRole BatchDelete(empty) failed: %v", err) } if err := userRoleRepo.BatchDelete(ctx, []*domain.UserRole{userRoleBatch[0]}); err != nil { t.Fatalf("UserRole BatchDelete failed: %v", err) } if err := userRoleRepo.Delete(ctx, userRolePrimary.ID); err != nil { t.Fatalf("UserRole Delete failed: %v", err) } existsAfterDelete, err := userRoleRepo.Exists(ctx, users[0].ID, roles[0].ID) if err != nil { t.Fatalf("UserRole Exists after Delete failed: %v", err) } if existsAfterDelete { t.Fatal("expected primary user-role relation to be removed") } if err := userRoleRepo.DeleteByUserID(ctx, users[1].ID); err != nil { t.Fatalf("DeleteByUserID failed: %v", err) } if err := userRoleRepo.Create(ctx, &domain.UserRole{UserID: users[0].ID, RoleID: roles[1].ID}); err != nil { t.Fatalf("recreate user-role failed: %v", err) } if err := userRoleRepo.DeleteByRoleID(ctx, roles[1].ID); err != nil { t.Fatalf("DeleteByRoleID failed: %v", err) } remainingUserRoles, err := userRoleRepo.GetByRoleID(ctx, roles[1].ID) if err != nil { t.Fatalf("GetByRoleID after DeleteByRoleID failed: %v", err) } if len(remainingUserRoles) != 0 { t.Fatalf("expected no user-role relations for role two, got %d", len(remainingUserRoles)) } rolePermissionPrimary := &domain.RolePermission{RoleID: roles[0].ID, PermissionID: permissions[0].ID} if err := rolePermissionRepo.Create(ctx, rolePermissionPrimary); err != nil { t.Fatalf("RolePermission Create failed: %v", err) } if err := rolePermissionRepo.BatchCreate(ctx, []*domain.RolePermission{}); err != nil { t.Fatalf("RolePermission BatchCreate(empty) failed: %v", err) } rolePermissionBatch := []*domain.RolePermission{ {RoleID: roles[0].ID, PermissionID: permissions[1].ID}, {RoleID: roles[1].ID, PermissionID: permissions[0].ID}, } if err := rolePermissionRepo.BatchCreate(ctx, rolePermissionBatch); err != nil { t.Fatalf("RolePermission BatchCreate failed: %v", err) } rpExists, err := rolePermissionRepo.Exists(ctx, roles[0].ID, permissions[0].ID) if err != nil { t.Fatalf("RolePermission Exists failed: %v", err) } if !rpExists { t.Fatal("expected primary role-permission relation to exist") } rpMissing, err := rolePermissionRepo.Exists(ctx, roles[1].ID, permissions[1].ID) if err != nil { t.Fatalf("RolePermission Exists(missing) failed: %v", err) } if rpMissing { t.Fatal("expected missing role-permission relation to be absent") } permissionsForRoleOne, err := rolePermissionRepo.GetByRoleID(ctx, roles[0].ID) if err != nil { t.Fatalf("GetByRoleID failed: %v", err) } if len(permissionsForRoleOne) != 2 { t.Fatalf("expected 2 permissions for role one, got %d", len(permissionsForRoleOne)) } rolesForPermissionOne, err := rolePermissionRepo.GetByPermissionID(ctx, permissions[0].ID) if err != nil { t.Fatalf("GetByPermissionID failed: %v", err) } if len(rolesForPermissionOne) != 2 { t.Fatalf("expected 2 roles for permission one, got %d", len(rolesForPermissionOne)) } permissionIDs, err := rolePermissionRepo.GetPermissionIDsByRoleID(ctx, roles[0].ID) if err != nil { t.Fatalf("GetPermissionIDsByRoleID failed: %v", err) } if len(permissionIDs) != 2 || !containsInt64(permissionIDs, permissions[0].ID) || !containsInt64(permissionIDs, permissions[1].ID) { t.Fatalf("unexpected permission IDs for role one: %+v", permissionIDs) } roleIDsByPermission, err := rolePermissionRepo.GetRoleIDByPermissionID(ctx, permissions[0].ID) if err != nil { t.Fatalf("GetRoleIDByPermissionID failed: %v", err) } if len(roleIDsByPermission) != 2 || !containsInt64(roleIDsByPermission, roles[0].ID) || !containsInt64(roleIDsByPermission, roles[1].ID) { t.Fatalf("unexpected role IDs for permission one: %+v", roleIDsByPermission) } loadedPermission, err := rolePermissionRepo.GetPermissionByID(ctx, permissions[0].ID) if err != nil { t.Fatalf("GetPermissionByID failed: %v", err) } if loadedPermission.Code != "repo:permission:1" { t.Fatalf("expected repo:permission:1, got %q", loadedPermission.Code) } permissionIDsByRoleIDs, err := rolePermissionRepo.GetPermissionIDsByRoleIDs(ctx, []int64{roles[0].ID, roles[1].ID}) if err != nil { t.Fatalf("GetPermissionIDsByRoleIDs failed: %v", err) } if len(permissionIDsByRoleIDs) != 3 { t.Fatalf("expected 3 permission IDs from combined roles, got %d", len(permissionIDsByRoleIDs)) } emptyPermissionIDs, err := rolePermissionRepo.GetPermissionIDsByRoleIDs(ctx, []int64{}) if err != nil { t.Fatalf("GetPermissionIDsByRoleIDs(empty) failed: %v", err) } if len(emptyPermissionIDs) != 0 { t.Fatalf("expected empty slice for GetPermissionIDsByRoleIDs(empty), got %d", len(emptyPermissionIDs)) } if err := rolePermissionRepo.BatchDelete(ctx, []*domain.RolePermission{}); err != nil { t.Fatalf("RolePermission BatchDelete(empty) failed: %v", err) } if err := rolePermissionRepo.BatchDelete(ctx, []*domain.RolePermission{rolePermissionBatch[0]}); err != nil { t.Fatalf("RolePermission BatchDelete failed: %v", err) } if err := rolePermissionRepo.Delete(ctx, rolePermissionPrimary.ID); err != nil { t.Fatalf("RolePermission Delete failed: %v", err) } if err := rolePermissionRepo.DeleteByPermissionID(ctx, permissions[0].ID); err != nil { t.Fatalf("DeleteByPermissionID failed: %v", err) } if err := rolePermissionRepo.Create(ctx, &domain.RolePermission{RoleID: roles[0].ID, PermissionID: permissions[1].ID}); err != nil { t.Fatalf("recreate role-permission failed: %v", err) } if err := rolePermissionRepo.DeleteByRoleID(ctx, roles[0].ID); err != nil { t.Fatalf("DeleteByRoleID failed: %v", err) } remainingRolePermissions, err := rolePermissionRepo.GetByRoleID(ctx, roles[0].ID) if err != nil { t.Fatalf("GetByRoleID after DeleteByRoleID failed: %v", err) } if len(remainingRolePermissions) != 0 { t.Fatalf("expected no role-permission relations for role one, got %d", len(remainingRolePermissions)) } }