Files
RenewCMS/test/users_api_domain_test.go
T

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)
}
}
}
}
}
})
}