feat: Users API, ownership transfer and removal of the legacy role string
This commit is contained in:
1 parent
094df63a7f
commit
80f7037d3a
124 files changed
+4644
-824
No files matched your search
+19
-18
@@ -111,7 +111,7 @@ func (m *inviteFakeRepo) AcceptWithUser(id uint32, at time.Time, newUser user.Us
|
||||
var _ invite.Repository = &inviteFakeRepo{}
|
||||
|
||||
func inviteAt(email string, createdAt time.Time, expiresAt time.Time) invite.Invite {
|
||||
return invite.Invite{Email: email, HashedCode: "hashed:seed", InvitedBy: 7, CreatedAt: createdAt, ExpiresAt: expiresAt}
|
||||
return invite.Invite{Email: email, HashedCode: "hashed:seed", InvitedBy: 7, RoleID: writerRoleID, CreatedAt: createdAt, ExpiresAt: expiresAt}
|
||||
}
|
||||
|
||||
func pendingInvite(email string) invite.Invite {
|
||||
@@ -126,7 +126,7 @@ func expiredInvite(email string) invite.Invite {
|
||||
|
||||
func inviteInviterRepo(displayName string) *mockUserRepo {
|
||||
repo := newMockUserRepo()
|
||||
repo.users[7] = user.User{ID: 7, Username: "marta", DisplayName: displayName, Email: "marta@renew.example", IsVerified: true, Role: user.RoleOwner}
|
||||
repo.users[7] = user.User{ID: 7, Username: "marta", DisplayName: displayName, Email: "marta@renew.example", IsVerified: true, RoleID: ownerRoleID}
|
||||
repo.nextID = 7
|
||||
return repo
|
||||
}
|
||||
@@ -152,10 +152,10 @@ func newInviteFixture(t *testing.T, maxAttempts int) *inviteFixture {
|
||||
}
|
||||
getUser := useCases.NewGetUserUseCase(f.users)
|
||||
handler := userhandler.NewInviteHandler(
|
||||
useCases.NewCreateInviteUseCase(f.users, f.invites, f.hasher, f.mail, invitePublicURL, discardRecorder),
|
||||
useCases.NewCreateInviteUseCase(f.users, f.invites, newSeededMockRoleRepo(), newMockContentTypeRepo(), f.hasher, f.mail, invitePublicURL, discardRecorder),
|
||||
nil,
|
||||
nil,
|
||||
useCases.NewListUsersAndInvitesUseCase(f.users, f.invites),
|
||||
useCases.NewListUsersAndInvitesUseCase(f.users, f.invites, newSeededMockRoleRepo()),
|
||||
)
|
||||
f.router = chi.NewRouter()
|
||||
f.router.Route("/v1/user", func(r chi.Router) {
|
||||
@@ -165,6 +165,7 @@ func newInviteFixture(t *testing.T, maxAttempts int) *inviteFixture {
|
||||
r.Use(middleware.RequireVerifiedUser(getUser))
|
||||
r.Get("/", handler.ListUsers)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middleware.RequirePermission(getUser, useCases.NewCheckPermissionUseCase(newSeededMockRoleRepo()), "users.invite"))
|
||||
r.Use(middleware.TrustedProxyRealIP(nil))
|
||||
r.Use(middleware.NewRateLimiter(maxAttempts, time.Hour))
|
||||
r.Post("/invite", handler.CreateInvite)
|
||||
@@ -238,7 +239,7 @@ func TestInvitationsHttp(t *testing.T) {
|
||||
t.Fatalf("expected 201, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
decoded := decodeKeys(t, rec.Body.Bytes())
|
||||
assertExactKeys(t, decoded, "id", "kind", "email", "status", "created_at", "expires_at")
|
||||
assertExactKeys(t, decoded, "id", "kind", "email", "role", "status", "created_at", "expires_at")
|
||||
if decodedString(t, decoded, "kind") != "invite" || decodedString(t, decoded, "email") != "sam.oliveira@renew.example" || decodedString(t, decoded, "status") != "pending" {
|
||||
t.Errorf("unexpected body: %s", rec.Body.String())
|
||||
}
|
||||
@@ -271,7 +272,7 @@ func TestInvitationsHttp(t *testing.T) {
|
||||
|
||||
t.Run("The email names the inviter by display name when there is one", func(t *testing.T) {
|
||||
f := newInviteFixture(t, 100)
|
||||
f.users.users[7] = user.User{ID: 7, Username: "ines", DisplayName: "Ines Duarte", IsVerified: true}
|
||||
f.users.users[7] = user.User{ID: 7, Username: "ines", DisplayName: "Ines Duarte", IsVerified: true, RoleID: ownerRoleID}
|
||||
|
||||
rec := f.post(t, `{"email": "lea.martin@renew.example"}`)
|
||||
|
||||
@@ -493,7 +494,7 @@ func TestInvitationsHttp(t *testing.T) {
|
||||
t.Errorf("item %d unexpected: %v", index, item)
|
||||
}
|
||||
if want.kind == "invite" {
|
||||
assertExactKeys(t, item, "id", "kind", "email", "status", "created_at")
|
||||
assertExactKeys(t, item, "id", "kind", "email", "role", "status", "created_at")
|
||||
} else {
|
||||
assertExactKeys(t, item, "id", "kind", "username", "display_name", "email", "role", "status", "created_at")
|
||||
}
|
||||
@@ -536,7 +537,7 @@ func TestInvitationsDoNotChangeRegistration(t *testing.T) {
|
||||
hasher := &stubHasher{}
|
||||
handler := auth.NewLoginHandler(
|
||||
authtoken.New([]byte("test-secret")),
|
||||
useCases.NewCreateUserUseCase(users, hasher, mail, invitePublicURL, discardRecorder),
|
||||
useCases.NewCreateUserUseCase(users, newSeededMockRoleRepo(), hasher, mail, invitePublicURL, discardRecorder),
|
||||
useCases.NewAuthenticateUserUseCase(users, hasher, discardRecorder),
|
||||
true,
|
||||
)
|
||||
@@ -601,12 +602,12 @@ func TestInvitationsDoNotChangeRegistration(t *testing.T) {
|
||||
func TestCreateInviteUseCase(t *testing.T) {
|
||||
newUseCase := func() (*useCases.CreateInviteUseCase, *mockUserRepo, *inviteFakeRepo, *mockMailRepo, *stubHasher) {
|
||||
users, invites, mail, hasher := inviteInviterRepo("Marta A"), newInviteFakeRepo(), newMockMailRepo(), &stubHasher{}
|
||||
return useCases.NewCreateInviteUseCase(users, invites, hasher, mail, invitePublicURL, discardRecorder), users, invites, mail, hasher
|
||||
return useCases.NewCreateInviteUseCase(users, invites, newSeededMockRoleRepo(), newMockContentTypeRepo(), hasher, mail, invitePublicURL, discardRecorder), users, invites, mail, hasher
|
||||
}
|
||||
|
||||
t.Run("lower-cases the email and returns the stored invite", func(t *testing.T) {
|
||||
uc, _, _, _, _ := newUseCase()
|
||||
got, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Email: "A@B.Example", InvitedBy: 7})
|
||||
got, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Caller: ownerCaller(7), Email: "A@B.Example", InvitedBy: 7})
|
||||
if err != nil || got.Email != "a@b.example" || got.ID == 0 {
|
||||
t.Fatalf("unexpected result %+v, %v", got, err)
|
||||
}
|
||||
@@ -614,7 +615,7 @@ func TestCreateInviteUseCase(t *testing.T) {
|
||||
|
||||
t.Run("unknown inviter", func(t *testing.T) {
|
||||
uc, _, invites, _, _ := newUseCase()
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Email: "a@b.example", InvitedBy: 99})
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Caller: ownerCaller(7), Email: "a@b.example", InvitedBy: 99})
|
||||
if !errors.Is(err, user.ErrUserNotFound) || len(invites.invites) != 0 {
|
||||
t.Errorf("expected ErrUserNotFound, got %v", err)
|
||||
}
|
||||
@@ -623,7 +624,7 @@ func TestCreateInviteUseCase(t *testing.T) {
|
||||
t.Run("existing account is refused case-insensitively", func(t *testing.T) {
|
||||
uc, users, _, _, _ := newUseCase()
|
||||
users.users[8] = user.User{ID: 8, Email: "Jonas@Renew.example"}
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Email: "jonas@renew.example", InvitedBy: 7})
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Caller: ownerCaller(7), Email: "jonas@renew.example", InvitedBy: 7})
|
||||
if !errors.Is(err, invite.ErrEmailHasAccount) {
|
||||
t.Errorf("expected ErrEmailHasAccount, got %v", err)
|
||||
}
|
||||
@@ -632,7 +633,7 @@ func TestCreateInviteUseCase(t *testing.T) {
|
||||
t.Run("pending invite is refused", func(t *testing.T) {
|
||||
uc, _, invites, _, _ := newUseCase()
|
||||
invites.seed(pendingInvite("a@b.example"))
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Email: "a@b.example", InvitedBy: 7})
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Caller: ownerCaller(7), Email: "a@b.example", InvitedBy: 7})
|
||||
if !errors.Is(err, invite.ErrPendingInvite) {
|
||||
t.Errorf("expected ErrPendingInvite, got %v", err)
|
||||
}
|
||||
@@ -645,7 +646,7 @@ func TestCreateInviteUseCase(t *testing.T) {
|
||||
accepted.AcceptedAt = &acceptedAt
|
||||
invites.seed(accepted)
|
||||
invites.seed(pendingInvite("other@b.example"))
|
||||
if _, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Email: "a@b.example", InvitedBy: 7}); err != nil {
|
||||
if _, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Caller: ownerCaller(7), Email: "a@b.example", InvitedBy: 7}); err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
if len(invites.invites) != 3 {
|
||||
@@ -656,7 +657,7 @@ func TestCreateInviteUseCase(t *testing.T) {
|
||||
t.Run("hash failure stores and sends nothing", func(t *testing.T) {
|
||||
uc, _, invites, mail, hasher := newUseCase()
|
||||
hasher.hashErr = errors.New("hash boom")
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Email: "a@b.example", InvitedBy: 7})
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Caller: ownerCaller(7), Email: "a@b.example", InvitedBy: 7})
|
||||
if err == nil || len(invites.invites) != 0 || len(mail.SentEmails) != 0 {
|
||||
t.Errorf("expected failure without side effects, got %v", err)
|
||||
}
|
||||
@@ -666,7 +667,7 @@ func TestCreateInviteUseCase(t *testing.T) {
|
||||
uc, _, invites, mail, _ := newUseCase()
|
||||
invites.seed(expiredInvite("a@b.example"))
|
||||
invites.fail.failNext("Delete")
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Email: "a@b.example", InvitedBy: 7})
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Caller: ownerCaller(7), Email: "a@b.example", InvitedBy: 7})
|
||||
if !errors.Is(err, errRepositoryFailure) || len(mail.SentEmails) != 0 {
|
||||
t.Errorf("expected repository failure, got %v", err)
|
||||
}
|
||||
@@ -676,7 +677,7 @@ func TestCreateInviteUseCase(t *testing.T) {
|
||||
uc, _, invites, mail, _ := newUseCase()
|
||||
mail.fail.failNextWith("Send", errors.New("smtp down"))
|
||||
invites.fail.failNextWith("Delete", errors.New("cleanup down"))
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Email: "a@b.example", InvitedBy: 7})
|
||||
_, err := uc.CreateInvite(testCtx, useCases.CreateInviteCommand{Caller: ownerCaller(7), Email: "a@b.example", InvitedBy: 7})
|
||||
if !errors.Is(err, invite.ErrInviteEmailNotSent) || !strings.Contains(err.Error(), "smtp down") || !strings.Contains(err.Error(), "cleanup down") {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
@@ -742,7 +743,7 @@ func TestInvitationsHandlerUsesPublicUserSafeShape(t *testing.T) {
|
||||
|
||||
func TestListUsersAndInvitesUseCase(t *testing.T) {
|
||||
t.Run("empty repositories give an empty non-nil list", func(t *testing.T) {
|
||||
got := useCases.NewListUsersAndInvitesUseCase(newMockUserRepo(), newInviteFakeRepo()).ListUsersAndInvites()
|
||||
got, _ := useCases.NewListUsersAndInvitesUseCase(newMockUserRepo(), newInviteFakeRepo(), newSeededMockRoleRepo()).ListUsersAndInvites()
|
||||
if got == nil || len(got) != 0 {
|
||||
t.Errorf("expected empty non-nil slice, got %#v", got)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user