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
@@ -48,21 +48,21 @@ func newInviteManagementFixture(t *testing.T, maxAttempts int) *inviteManagement
|
||||
token: authtoken.New([]byte("test-secret")),
|
||||
}
|
||||
f.invites.users = f.users
|
||||
f.users.users[9] = user.User{ID: 9, Username: "jonas", Email: "jonas@renew.example", IsVerified: true, Role: user.RoleAdmin, RoleID: writerRoleID}
|
||||
f.users.users[11] = user.User{ID: 11, Username: "unverified", Email: "unverified@renew.example", IsVerified: false, Role: user.RoleAdmin}
|
||||
f.users.users[12] = user.User{ID: 12, Username: "stale", Email: "stale@renew.example", IsVerified: true, Role: user.RoleOwner, TokenVersion: 2}
|
||||
f.users.users[9] = user.User{ID: 9, Username: "jonas", Email: "jonas@renew.example", IsVerified: true, RoleID: writerRoleID}
|
||||
f.users.users[11] = user.User{ID: 11, Username: "unverified", Email: "unverified@renew.example", IsVerified: false, RoleID: adminRoleID}
|
||||
f.users.users[12] = user.User{ID: 12, Username: "stale", Email: "stale@renew.example", IsVerified: true, RoleID: ownerRoleID, TokenVersion: 2}
|
||||
f.users.nextID = 12
|
||||
|
||||
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),
|
||||
useCases.NewCancelInviteUseCase(f.invites, discardRecorder),
|
||||
useCases.NewRenewInviteUseCase(f.users, f.invites, f.hasher, f.mail, invitePublicURL, discardRecorder),
|
||||
useCases.NewListUsersAndInvitesUseCase(f.users, f.invites),
|
||||
useCases.NewRenewInviteUseCase(f.users, f.invites, newSeededMockRoleRepo(), newMockContentTypeRepo(), f.hasher, f.mail, invitePublicURL, discardRecorder),
|
||||
useCases.NewListUsersAndInvitesUseCase(f.users, f.invites, newSeededMockRoleRepo()),
|
||||
)
|
||||
acceptHandler := auth.NewAcceptInviteHandler(
|
||||
useCases.NewGetInviteUseCase(f.invites, f.hasher),
|
||||
useCases.NewAcceptInviteUseCase(f.users, f.invites, f.hasher, discardRecorder),
|
||||
useCases.NewAcceptInviteUseCase(f.users, f.invites, newSeededMockRoleRepo(), f.hasher, discardRecorder),
|
||||
f.token,
|
||||
true,
|
||||
)
|
||||
@@ -145,7 +145,7 @@ func TestInviteManagementHttp(t *testing.T) {
|
||||
|
||||
t.Run("A cancelled invite's link stops working and its address can be invited again", func(t *testing.T) {
|
||||
f := newInviteManagementFixture(t, 100)
|
||||
f.invites.seed(invite.Invite{Email: "sam.oliveira@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 7, CreatedAt: time.Now(), ExpiresAt: time.Now().Add(time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "sam.oliveira@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 7, RoleID: writerRoleID, CreatedAt: time.Now(), ExpiresAt: time.Now().Add(time.Hour)})
|
||||
f.asOwner(t, http.MethodDelete, "/v1/user/invite/1")
|
||||
|
||||
rec := f.send(t, http.MethodGet, "/v1/auth/invite?c=OLD-CODE", nil)
|
||||
@@ -223,7 +223,7 @@ func TestInviteManagementHttp(t *testing.T) {
|
||||
for _, filler := range []string{"a@renew.example", "b@renew.example", "c@renew.example"} {
|
||||
f.invites.seed(pendingInvite(filler))
|
||||
}
|
||||
f.invites.seed(invite.Invite{Email: "lea.martin@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 9, CreatedAt: now.Add(-5 * 24 * time.Hour), ExpiresAt: now.Add(-2 * 24 * time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "lea.martin@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 9, RoleID: writerRoleID, CreatedAt: now.Add(-5 * 24 * time.Hour), ExpiresAt: now.Add(-2 * 24 * time.Hour)})
|
||||
|
||||
rec := f.asOwner(t, http.MethodPost, "/v1/user/invite/4/renew")
|
||||
|
||||
@@ -231,7 +231,7 @@ func TestInviteManagementHttp(t *testing.T) {
|
||||
t.Fatalf("unexpected %d %q", rec.Code, rec.Header().Get("Cache-Control"))
|
||||
}
|
||||
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") != "lea.martin@renew.example" || decodedString(t, decoded, "status") != "pending" {
|
||||
t.Errorf("unexpected body %s", rec.Body.String())
|
||||
}
|
||||
@@ -268,8 +268,8 @@ func TestInviteManagementHttp(t *testing.T) {
|
||||
|
||||
t.Run("A renewed pending invite moves to the top of the list and the old link dies", func(t *testing.T) {
|
||||
f := newInviteManagementFixture(t, 100)
|
||||
f.invites.seed(invite.Invite{Email: "lea.martin@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 7, CreatedAt: time.Now().Add(-48 * time.Hour), ExpiresAt: time.Now().Add(24 * time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "newer@renew.example", HashedCode: "hashed:OTHER", InvitedBy: 7, CreatedAt: time.Now().Add(-24 * time.Hour), ExpiresAt: time.Now().Add(48 * time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "lea.martin@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 7, RoleID: writerRoleID, CreatedAt: time.Now().Add(-48 * time.Hour), ExpiresAt: time.Now().Add(24 * time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "newer@renew.example", HashedCode: "hashed:OTHER", InvitedBy: 7, RoleID: writerRoleID, CreatedAt: time.Now().Add(-24 * time.Hour), ExpiresAt: time.Now().Add(48 * time.Hour)})
|
||||
|
||||
f.asOwner(t, http.MethodPost, "/v1/user/invite/1/renew")
|
||||
|
||||
@@ -288,8 +288,8 @@ func TestInviteManagementHttp(t *testing.T) {
|
||||
|
||||
t.Run("Accepting with a cancelled or replaced code is not found and creates no user", func(t *testing.T) {
|
||||
f := newInviteManagementFixture(t, 100)
|
||||
f.invites.seed(invite.Invite{Email: "lea.martin@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 7, CreatedAt: time.Now().Add(-time.Hour), ExpiresAt: time.Now().Add(24 * time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "sam@renew.example", HashedCode: "hashed:CANCELLED", InvitedBy: 7, CreatedAt: time.Now().Add(-time.Hour), ExpiresAt: time.Now().Add(24 * time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "lea.martin@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 7, RoleID: writerRoleID, CreatedAt: time.Now().Add(-time.Hour), ExpiresAt: time.Now().Add(24 * time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "sam@renew.example", HashedCode: "hashed:CANCELLED", InvitedBy: 7, RoleID: writerRoleID, CreatedAt: time.Now().Add(-time.Hour), ExpiresAt: time.Now().Add(24 * time.Hour)})
|
||||
f.asOwner(t, http.MethodPost, "/v1/user/invite/1/renew")
|
||||
f.asOwner(t, http.MethodDelete, "/v1/user/invite/2")
|
||||
usersBefore := len(f.users.users)
|
||||
@@ -363,7 +363,7 @@ func TestInviteManagementHttp(t *testing.T) {
|
||||
|
||||
t.Run("The old code of a pending invite works until it is renewed", func(t *testing.T) {
|
||||
f := newInviteManagementFixture(t, 100)
|
||||
f.invites.seed(invite.Invite{Email: "lea.martin@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 7, CreatedAt: time.Now().Add(-time.Hour), ExpiresAt: time.Now().Add(24 * time.Hour)})
|
||||
f.invites.seed(invite.Invite{Email: "lea.martin@renew.example", HashedCode: "hashed:OLD-CODE", InvitedBy: 7, RoleID: writerRoleID, CreatedAt: time.Now().Add(-time.Hour), ExpiresAt: time.Now().Add(24 * time.Hour)})
|
||||
|
||||
if before := f.send(t, http.MethodGet, "/v1/auth/invite?c=OLD-CODE", nil); before.Code != http.StatusOK {
|
||||
t.Fatalf("control expected 200, got %d", before.Code)
|
||||
@@ -480,9 +480,9 @@ func TestManageInviteUseCases(t *testing.T) {
|
||||
mail := newMockMailRepo()
|
||||
seeded := invites.seed(pendingInvite("a@renew.example"))
|
||||
mail.fail.failNextWith("Send", errors.New("smtp down"))
|
||||
renew := useCases.NewRenewInviteUseCase(users, invites, &stubHasher{}, mail, invitePublicURL, discardRecorder)
|
||||
renew := useCases.NewRenewInviteUseCase(users, invites, newSeededMockRoleRepo(), newMockContentTypeRepo(), &stubHasher{}, mail, invitePublicURL, discardRecorder)
|
||||
|
||||
_, err := renew.RenewInvite(testCtx, useCases.RenewInviteCommand{ID: 1, RenewedBy: 7})
|
||||
_, err := renew.RenewInvite(testCtx, useCases.RenewInviteCommand{Caller: ownerCaller(7), ID: 1, RenewedBy: 7})
|
||||
|
||||
if !errors.Is(err, invite.ErrInviteEmailNotSent) || errors.Is(err, errRepositoryFailure) || invites.invites[1] != seeded {
|
||||
t.Errorf("unexpected result %v", err)
|
||||
@@ -495,9 +495,9 @@ func TestManageInviteUseCases(t *testing.T) {
|
||||
mail := newMockMailRepo()
|
||||
invites.seed(pendingInvite("a@renew.example"))
|
||||
mail.fail.failNextWith("Send", errors.New("smtp down"))
|
||||
renew := useCases.NewRenewInviteUseCase(users, &renewRestoreFailRepo{inviteFakeRepo: invites}, &stubHasher{}, mail, invitePublicURL, discardRecorder)
|
||||
renew := useCases.NewRenewInviteUseCase(users, &renewRestoreFailRepo{inviteFakeRepo: invites}, newSeededMockRoleRepo(), newMockContentTypeRepo(), &stubHasher{}, mail, invitePublicURL, discardRecorder)
|
||||
|
||||
_, err := renew.RenewInvite(testCtx, useCases.RenewInviteCommand{ID: 1, RenewedBy: 7})
|
||||
_, err := renew.RenewInvite(testCtx, useCases.RenewInviteCommand{Caller: ownerCaller(7), ID: 1, RenewedBy: 7})
|
||||
|
||||
if !errors.Is(err, invite.ErrInviteEmailNotSent) || !errors.Is(err, errRepositoryFailure) {
|
||||
t.Errorf("unexpected result %v", err)
|
||||
@@ -530,9 +530,9 @@ func TestManageInviteUseCases(t *testing.T) {
|
||||
mail := newMockMailRepo()
|
||||
seeded := invites.seed(pendingInvite("a@renew.example"))
|
||||
invites.fail.failNextWith("Renew", errRepositoryFailure)
|
||||
renew := useCases.NewRenewInviteUseCase(users, invites, &stubHasher{}, mail, invitePublicURL, discardRecorder)
|
||||
renew := useCases.NewRenewInviteUseCase(users, invites, newSeededMockRoleRepo(), newMockContentTypeRepo(), &stubHasher{}, mail, invitePublicURL, discardRecorder)
|
||||
|
||||
_, err := renew.RenewInvite(testCtx, useCases.RenewInviteCommand{ID: 1, RenewedBy: 7})
|
||||
_, err := renew.RenewInvite(testCtx, useCases.RenewInviteCommand{Caller: ownerCaller(7), ID: 1, RenewedBy: 7})
|
||||
|
||||
if !errors.Is(err, errRepositoryFailure) || len(mail.SentEmails) != 0 || invites.invites[1] != seeded {
|
||||
t.Errorf("unexpected result %v", err)
|
||||
@@ -544,9 +544,9 @@ func TestManageInviteUseCases(t *testing.T) {
|
||||
invites := newInviteFakeRepo()
|
||||
mail := newMockMailRepo()
|
||||
invites.seed(pendingInvite("a@renew.example"))
|
||||
renew := useCases.NewRenewInviteUseCase(users, invites, &stubHasher{}, mail, invitePublicURL, discardRecorder)
|
||||
renew := useCases.NewRenewInviteUseCase(users, invites, newSeededMockRoleRepo(), newMockContentTypeRepo(), &stubHasher{}, mail, invitePublicURL, discardRecorder)
|
||||
|
||||
if _, err := renew.RenewInvite(testCtx, useCases.RenewInviteCommand{ID: 1, RenewedBy: 7}); err != nil {
|
||||
if _, err := renew.RenewInvite(testCtx, useCases.RenewInviteCommand{Caller: ownerCaller(7), ID: 1, RenewedBy: 7}); err != nil {
|
||||
t.Fatalf("unexpected %v", err)
|
||||
}
|
||||
if inviteMailData(t, mail).InvitedBy != "Marta L." {
|
||||
@@ -565,7 +565,7 @@ func TestInviteRepositoryRenew(t *testing.T) {
|
||||
}
|
||||
repo := persistence.NewInviteRepository(db)
|
||||
now := time.Now().UTC().Truncate(time.Second)
|
||||
created, err := repo.Create(invite.Invite{Email: "a@renew.example", HashedCode: "old", InvitedBy: 7, CreatedAt: now.Add(-96 * time.Hour), ExpiresAt: now.Add(-24 * time.Hour)})
|
||||
created, err := repo.Create(invite.Invite{Email: "a@renew.example", HashedCode: "old", InvitedBy: 7, RoleID: writerRoleID, CreatedAt: now.Add(-96 * time.Hour), ExpiresAt: now.Add(-24 * time.Hour)})
|
||||
if err != nil {
|
||||
t.Fatalf("create failed: %v", err)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user