184 lines
7.8 KiB
Go
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()
|
|
}
|