feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers

This commit is contained in:
2026-04-02 11:19:50 +08:00
parent e59a77bc49
commit dcc1f186f8
298 changed files with 62603 additions and 0 deletions

View File

@@ -0,0 +1,603 @@
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))
}
}