153 lines
5.5 KiB
Go
153 lines
5.5 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/builtintype"
|
|
"RenewCMS/internal/domain/contenttype"
|
|
"RenewCMS/internal/domain/invite"
|
|
"RenewCMS/internal/domain/role"
|
|
"RenewCMS/internal/domain/user"
|
|
"errors"
|
|
"go/ast"
|
|
"go/token"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestUsersAPIDomain(t *testing.T) {
|
|
t.Run("user.User has no role string and invite.Invite carries RoleID", func(t *testing.T) {
|
|
if _, present := reflect.TypeOf(user.User{}).FieldByName("Role"); present {
|
|
t.Errorf("user.User.Role must not exist")
|
|
}
|
|
if field, present := reflect.TypeOf(invite.Invite{}).FieldByName("RoleID"); !present || field.Type.Kind() != reflect.Uint32 {
|
|
t.Errorf("invite.Invite.RoleID is missing")
|
|
}
|
|
})
|
|
|
|
t.Run("the legacy role vocabulary is gone from the source tree", func(t *testing.T) {
|
|
for _, path := range nonTestGoFiles(t, "../internal", "../api", "../cmd") {
|
|
ast.Inspect(parsedGoFile(t, path), func(node ast.Node) bool {
|
|
switch found := node.(type) {
|
|
case *ast.SelectorExpr:
|
|
if owner, ok := found.X.(*ast.Ident); ok && (owner.Name == "user" || owner.Name == "domainUser") && (found.Sel.Name == "Role" || found.Sel.Name == "RoleOwner" || found.Sel.Name == "RoleAdmin") {
|
|
t.Errorf("%s reads %s.%s", path, owner.Name, found.Sel.Name)
|
|
}
|
|
case *ast.Ident:
|
|
if found.Name == "legacyBuiltInNames" || found.Name == "legacyRoleNames" || found.Name == "BackfillUserRoleIDs" {
|
|
t.Errorf("%s names %s", path, found.Name)
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("the new sentinels map with errors.Is", func(t *testing.T) {
|
|
wrapped := errors.Join(errors.New("context"), user.ErrRoleChanged, user.ErrInvalidTransfer, user.ErrConfirmationMismatch, role.ErrUnknownRole)
|
|
for _, sentinel := range []error{user.ErrRoleChanged, user.ErrInvalidTransfer, user.ErrConfirmationMismatch, role.ErrUnknownRole} {
|
|
if !errors.Is(wrapped, sentinel) {
|
|
t.Errorf("%v is not matched", sentinel)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("user and invite import the standard library and domain packages only", func(t *testing.T) {
|
|
for _, pattern := range []string{"../internal/domain/user/*.go", "../internal/domain/invite/*.go"} {
|
|
files, _ := filepath.Glob(pattern)
|
|
if len(files) == 0 {
|
|
t.Fatalf("no files for %s", pattern)
|
|
}
|
|
for _, file := range files {
|
|
for _, imported := range importsOf(t, strings.TrimPrefix(filepath.ToSlash(file), "../")) {
|
|
if !isStandardLibrary(imported) && !strings.HasPrefix(imported, "RenewCMS/internal/domain/") {
|
|
t.Errorf("%s imports %s", file, imported)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("GrantSet.Covers rows used by the slice", func(t *testing.T) {
|
|
manager := unlockedGrantSet("content.read=all", "content.create=all", "content.update=own", "content.publish=own", "schema.view=all")
|
|
cases := []struct {
|
|
name string
|
|
grants []string
|
|
key string
|
|
}{
|
|
{"Writer", []string{"content.read=all", "content.create=all", "content.update=all"}, "content.update"},
|
|
{"Content lead", []string{"content.read=all", "content.update=all", "content.publish=all"}, "content.update"},
|
|
{"Intern", []string{"content.read=own"}, ""},
|
|
{"Contributor", []string{"content.read=all", "content.create=all", "content.update=own"}, ""},
|
|
}
|
|
for _, row := range cases {
|
|
err := manager.Covers(unlockedGrantSet(row.grants...), nil)
|
|
if row.key == "" && err != nil {
|
|
t.Errorf("%s: unexpected %v", row.name, err)
|
|
}
|
|
var escalation role.EscalationError
|
|
if row.key != "" && (!errors.As(err, &escalation) || escalation.Key != row.key) {
|
|
t.Errorf("%s: expected an escalation on %s, got %v", row.name, row.key, err)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("the builtin user type keeps an optional plain text role field", func(t *testing.T) {
|
|
found, err := builtintype.Find(builtintype.KeyUser)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, field := range found.Fields {
|
|
if field.Key != "role" {
|
|
continue
|
|
}
|
|
if field.Type != contenttype.TypeText || field.Required || len(field.Options) != 0 {
|
|
t.Errorf("unexpected role field %+v", field)
|
|
}
|
|
return
|
|
}
|
|
t.Errorf("the user type has no role field")
|
|
})
|
|
|
|
t.Run("api/router.go uses the key constants for the new routes", func(t *testing.T) {
|
|
wanted := map[string]bool{"KeyUsersChangeRole": false, "KeySystemTransferOwnership": false}
|
|
ast.Inspect(parsedGoFile(t, "../api/router.go"), func(node ast.Node) bool {
|
|
if selector, ok := node.(*ast.SelectorExpr); ok {
|
|
if _, tracked := wanted[selector.Sel.Name]; tracked {
|
|
wanted[selector.Sel.Name] = true
|
|
}
|
|
}
|
|
if literal, ok := node.(*ast.BasicLit); ok && literal.Kind == token.STRING {
|
|
if value, err := strconv.Unquote(literal.Value); err == nil && (value == "users.change_role" || value == "system.transfer_ownership") {
|
|
t.Errorf("router holds the literal %q", value)
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
for name, seen := range wanted {
|
|
if !seen {
|
|
t.Errorf("router does not use role.%s", name)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("handlers of the slice import no persistence, mailer, models or gorm", func(t *testing.T) {
|
|
for _, pattern := range []string{"../api/handlers/user/*.go", "../api/handlers/ownership/*.go"} {
|
|
files, _ := filepath.Glob(pattern)
|
|
if len(files) == 0 {
|
|
t.Fatalf("no files for %s", pattern)
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|