Files
RenewCMS/test/users_api_world_test.go
T

184 lines
7.8 KiB
Go

package test
import (
"RenewCMS/internal/domain/contenttype"
"RenewCMS/internal/domain/invite"
"RenewCMS/internal/domain/role"
"RenewCMS/internal/domain/user"
"RenewCMS/internal/infrastructure/useCases"
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
)
const (
uaOwner uint32 = 3
uaAdmin uint32 = 5
uaWriter uint32 = 9
uaOtherAdmin uint32 = 10
uaUnverified uint32 = 12
uaNoRole uint32 = 13
uaGhostRole uint32 = 14
uaPeople uint32 = 31
uaViewer uint32 = 32
uaInviter uint32 = 33
uaContributor uint32 = 41
uaContentLead uint32 = 45
uaIntern uint32 = 46
uaUnknownRole uint32 = 999
)
type usersAPIRoleSpec struct {
name string
grants []string
members []uint32
}
var usersAPICustomRoles = []usersAPIRoleSpec{
{"Contributor", []string{"content.read=all", "content.create=all", "content.update=own"}, []uint32{41, 42, 43, 44}},
{"Intern", []string{"content.read=own"}, []uint32{uaIntern}},
{"Content lead", []string{"content.read=all", "content.update=all", "content.publish=all", "content.delete=all"}, []uint32{uaContentLead}},
{"People manager", []string{"users.view=all", "users.invite=all", "users.change_role=all", "content.read=all", "content.create=all", "content.update=own", "content.publish=own", "schema.view=all"}, []uint32{uaPeople}},
{"Viewer", []string{"users.view=all", "roles.view=all"}, []uint32{uaViewer}},
{"Inviter", []string{"users.invite=all"}, []uint32{uaInviter}},
}
type usersAPIWorld struct {
*apiKeyEnv
recorder *contentScopeRecorder
types *mockContentTypeRepo
ids map[string]uint32
}
func newUsersAPIWorld(t *testing.T) *usersAPIWorld {
t.Helper()
recorder := &contentScopeRecorder{}
env := newAPIKeyEnvRecording(t, recorder)
world := &usersAPIWorld{apiKeyEnv: env, recorder: recorder, types: newMockContentTypeRepo(), ids: map[string]uint32{}}
env.users.users = map[uint32]user.User{}
env.users.nextID = 100
env.users.knownRoles = env.roles
env.roles.users = env.users
env.roles.invites = env.invites
world.seedUsers()
world.seedInvites()
world.seedContentTypes()
world.wire()
return world
}
func (w *usersAPIWorld) addMember(id uint32, username string, email string, roleID uint32, verified bool) {
w.users.users[id] = user.User{ID: id, Username: username, Email: email, IsVerified: verified, RoleID: roleID, Password: "hashed:password1"}
}
func (w *usersAPIWorld) seedUsers() {
w.ids["Owner"], w.ids["Admin"], w.ids["Writer"] = ownerRoleID, adminRoleID, writerRoleID
w.addMember(uaOwner, "marta", "marta@renew.example", ownerRoleID, true)
w.addMember(uaAdmin, "jonas", "jonas@renew.example", adminRoleID, true)
w.addMember(uaOtherAdmin, "lea", "lea@renew.example", adminRoleID, true)
w.addMember(uaUnverified, "sam", "sam@renew.example", adminRoleID, false)
w.addMember(uaWriter, "ines", "ines@renew.example", writerRoleID, true)
w.addMember(uaNoRole, "zero", "zero@renew.example", 0, true)
w.addMember(uaGhostRole, "ghost", "ghost@renew.example", 99, true)
for _, spec := range usersAPICustomRoles {
created, _ := w.roles.Create(role.Role{Name: spec.name, Version: 1, Grants: grantRows(spec.grants...)})
w.ids[spec.name] = created.ID
for _, member := range spec.members {
w.addMember(member, "user"+strconv.Itoa(int(member)), "user"+strconv.Itoa(int(member))+"@renew.example", created.ID, true)
}
}
}
func (w *usersAPIWorld) seedInvites() {
now := time.Now()
accepted := now.Add(-24 * time.Hour)
w.invites.seed(invite.Invite{Email: "new1@renew.example", HashedCode: "hashed:code1", InvitedBy: uaAdmin, RoleID: writerRoleID, CreatedAt: now.Add(-time.Hour), ExpiresAt: now.Add(48 * time.Hour)})
w.invites.seed(invite.Invite{Email: "old2@renew.example", HashedCode: "hashed:code2", InvitedBy: uaAdmin, RoleID: w.ids["Contributor"], CreatedAt: now.Add(-73 * time.Hour), ExpiresAt: now.Add(-time.Hour)})
w.invites.seed(invite.Invite{Email: "new3@renew.example", HashedCode: "hashed:code3", InvitedBy: uaAdmin, RoleID: w.ids["Contributor"], CreatedAt: now.Add(-2 * time.Hour), ExpiresAt: now.Add(48 * time.Hour)})
w.invites.seed(invite.Invite{Email: "ines@renew.example", HashedCode: "hashed:code4", InvitedBy: uaAdmin, RoleID: writerRoleID, CreatedAt: now.Add(-48 * time.Hour), ExpiresAt: now.Add(24 * time.Hour), AcceptedAt: &accepted})
}
func (w *usersAPIWorld) seedContentTypes() {
title := contenttype.Field{Key: "title", Label: "Title", Type: contenttype.TypeText, Required: true, Options: []string{}}
w.types.types[1] = contenttype.ContentType{ID: 1, Key: "events", Name: "Events", Fields: []contenttype.Field{title}}
w.types.types[2] = contenttype.ContentType{ID: 2, Key: "venues", Name: "Venues", Fields: []contenttype.Field{title}}
w.types.nextID = 10
}
func (w *usersAPIWorld) wire() {
c := w.container
hasher := &stubHasher{}
url := "https://cms.example.com"
c.ChangeUserRoleUseCase = useCases.NewChangeUserRoleUseCase(w.users, w.roles, w.types, w.recorder)
c.TransferOwnershipUseCase = useCases.NewTransferOwnershipUseCase(w.users, w.roles, w.recorder)
c.CreateInviteUseCase = useCases.NewCreateInviteUseCase(w.users, w.invites, w.roles, w.types, hasher, w.mail, url, w.recorder)
c.RenewInviteUseCase = useCases.NewRenewInviteUseCase(w.users, w.invites, w.roles, w.types, hasher, w.mail, url, w.recorder)
c.CancelInviteUseCase = useCases.NewCancelInviteUseCase(w.invites, w.recorder)
c.ListUsersAndInvitesUseCase = useCases.NewListUsersAndInvitesUseCase(w.users, w.invites, w.roles)
c.AcceptInviteUseCase = useCases.NewAcceptInviteUseCase(w.users, w.invites, w.roles, hasher, w.recorder)
c.CreateUserUseCase = useCases.NewCreateUserUseCase(w.users, w.roles, hasher, w.mail, url, w.recorder)
c.ListRolesUseCase = useCases.NewListRolesUseCase(w.roles, w.users, w.types)
c.GetRoleUseCase = useCases.NewGetRoleUseCase(w.roles, w.users, w.types)
c.CreateRoleUseCase = useCases.NewCreateRoleUseCase(w.roles, w.users, w.types, w.recorder)
c.DeleteRoleUseCase = useCases.NewDeleteRoleUseCase(w.roles, w.users, w.types, w.recorder)
c.ReassignRoleMembersUseCase = useCases.NewReassignRoleMembersUseCase(w.roles, w.users, w.types, w.recorder)
w.rebuildRouter()
}
func (w *usersAPIWorld) id(name string) uint32 {
return w.ids[name]
}
func (w *usersAPIWorld) idText(name string) string {
return strconv.FormatUint(uint64(w.ids[name]), 10)
}
func (w *usersAPIWorld) caller(userID uint32) useCases.ContentCaller {
return useCases.ContentCaller{UserID: userID, Grants: w.roles.roles[w.users.users[userID].RoleID].GrantSet()}
}
func (w *usersAPIWorld) do(t *testing.T, caller uint32, method string, path string, body string) *httptest.ResponseRecorder {
t.Helper()
return w.send(t, apiKeyRequest{method: method, path: path, body: body, cookieUser: caller})
}
func (w *usersAPIWorld) putRole(t *testing.T, caller uint32, target string, roleID string) *httptest.ResponseRecorder {
t.Helper()
return w.do(t, caller, http.MethodPut, "/v1/user/"+target+"/role", `{"role_id": `+roleID+`}`)
}
func (w *usersAPIWorld) storedRoleID(userID uint32) uint32 {
return w.users.users[userID].RoleID
}
func (w *usersAPIWorld) createTemp(grants ...string) uint32 {
created, _ := w.roles.Create(role.Role{Name: "Temp", Version: 1, Grants: grantRows(grants...)})
return created.ID
}
func usersAPIDecode(t *testing.T, rec *httptest.ResponseRecorder) map[string]any {
t.Helper()
var object map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &object); err != nil {
t.Fatalf("expected a JSON object, got %q: %v", rec.Body.String(), err)
}
return object
}
func usersAPIRoleRef(t *testing.T, object map[string]any) map[string]any {
t.Helper()
ref, ok := object["role"].(map[string]any)
if !ok {
t.Fatalf("expected role to be an object, got %v", object["role"])
}
apiKeyAssertExactFields(t, ref, "id", "name", "built_in", "locked")
return ref
}
func usersAPIEventActions(w *usersAPIWorld) []string {
return w.recorder.actions()
}