feat: /v1/role list, get, create, update and delete. Reassign members on delete. Write-time validation (ValidateGrants, duplicates, typed keys against real content types). Name uniqueness (role_name_taken), role_locked, role_has_members, role_conflict on version mismatch. No-escalation: nobody grants a permission they don't hold, and roles.edit_owner_only guards the Owner role. Activity events for role changes. Typed-key exposure in /auth/me. Cleanup of typed rows when a content type is deleted. | Backend only. Will carry the deferred ListGrants decision.
This commit is contained in:
1 parent
a043f7f41a
commit
094df63a7f
51 files changed
+4822
-115
No files matched your search
@@ -0,0 +1,95 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/role"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func rolesAPIRoleUseCaseFiles() []string {
|
||||
return []string{
|
||||
"../internal/infrastructure/useCases/roleSupport.go",
|
||||
"../internal/infrastructure/useCases/roleView.go",
|
||||
"../internal/infrastructure/useCases/ListRoles.go",
|
||||
"../internal/infrastructure/useCases/GetRole.go",
|
||||
"../internal/infrastructure/useCases/CreateRole.go",
|
||||
"../internal/infrastructure/useCases/UpdateRole.go",
|
||||
"../internal/infrastructure/useCases/DeleteRole.go",
|
||||
"../internal/infrastructure/useCases/ReassignRoleMembers.go",
|
||||
}
|
||||
}
|
||||
|
||||
func TestRolesAPIHygiene(t *testing.T) {
|
||||
t.Run("The comparison logic lives once, in the domain", func(t *testing.T) {
|
||||
files := append(rolesAPIRoleUseCaseFiles(), nonTestGoFiles(t, "../api/handlers/role", "../api/middleware")...)
|
||||
for _, path := range files {
|
||||
ast.Inspect(parsedGoFile(t, path), func(node ast.Node) bool {
|
||||
selector, ok := node.(*ast.SelectorExpr)
|
||||
if ok && (selector.Sel.Name == "ScopeAll" || selector.Sel.Name == "ScopeOwn" || selector.Sel.Name == "ScopeNone") {
|
||||
t.Errorf("%s reads the scope %s", path, selector.Sel.Name)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("api/router.go and the role handler hold no catalogue key literal", func(t *testing.T) {
|
||||
keys := map[string]bool{}
|
||||
for _, permission := range role.Catalogue() {
|
||||
keys[permission.Key] = true
|
||||
}
|
||||
files := append(nonTestGoFiles(t, "../api/handlers/role"), "../api/router.go")
|
||||
for _, path := range files {
|
||||
ast.Inspect(parsedGoFile(t, path), func(node ast.Node) bool {
|
||||
literal, ok := node.(*ast.BasicLit)
|
||||
if !ok || literal.Kind != token.STRING {
|
||||
return true
|
||||
}
|
||||
value, err := strconv.Unquote(literal.Value)
|
||||
if err == nil && keys[value] {
|
||||
t.Errorf("%s holds the literal %q", path, value)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no handler under api/handlers/role imports persistence, mailer, models or gorm", func(t *testing.T) {
|
||||
files, _ := filepath.Glob("../api/handlers/role/*.go")
|
||||
if len(files) == 0 {
|
||||
t.Fatal("no role handler")
|
||||
}
|
||||
for _, file := range files {
|
||||
for _, imported := range importsOf(t, strings.TrimPrefix(filepath.ToSlash(file), "../")) {
|
||||
for _, forbidden := range []string{"persistence", "mailer", "models", "gorm"} {
|
||||
if strings.Contains(imported, forbidden) {
|
||||
t.Errorf("%s imports %s", file, imported)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("the role use cases import only domain packages", func(t *testing.T) {
|
||||
for _, path := range rolesAPIRoleUseCaseFiles() {
|
||||
for _, imported := range importsOf(t, strings.TrimPrefix(filepath.ToSlash(path), "../")) {
|
||||
if strings.HasPrefix(imported, "RenewCMS/") && !strings.HasPrefix(imported, "RenewCMS/internal/domain/") {
|
||||
t.Errorf("%s imports %s", path, imported)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("the router mounts every role route behind the keys", func(t *testing.T) {
|
||||
source := readTestFile(t, "../api/router.go")
|
||||
for _, wanted := range []string{"role.KeyRolesView", "role.KeyRolesManage", "NewRoleRouter"} {
|
||||
if !strings.Contains(source, wanted) {
|
||||
t.Errorf("api/router.go lacks %s", wanted)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user