feat: more role backend and admin/writer roles additions and adjustments

This commit is contained in:
Florian Sylvain committed 2026-10-26 18:15:26 +01:00
1 parent 8c6c6456da
commit de142f6c5f
131 files changed
+2663 -435

No files matched your search

+29 -26
View File
@@ -2,6 +2,7 @@ package test
import (
"RenewCMS/internal/domain/apikey"
"RenewCMS/internal/domain/role"
"RenewCMS/internal/domain/user"
"RenewCMS/internal/infrastructure/hashing"
"RenewCMS/internal/infrastructure/useCases"
@@ -173,6 +174,11 @@ func seedStubKey(repo *mockAPIKeyRepo, raw string, mutate func(*apikey.APIKey))
return stored
}
var (
apiKeyOwnScope = role.NewGrantSet([]role.Grant{{Key: role.KeyAPIKeysView, Scope: role.ScopeOwn}, {Key: role.KeyAPIKeysRevoke, Scope: role.ScopeOwn}})
apiKeyAllScope = role.NewGrantSet([]role.Grant{{Key: role.KeyAPIKeysView, Scope: role.ScopeAll}, {Key: role.KeyAPIKeysRevoke, Scope: role.ScopeAll}})
)
func apiKeyUnitUsers() *mockUserRepo {
users := newMockUserRepo()
users.users[1] = user.User{ID: 1, Username: "marta", IsVerified: true, Role: user.RoleOwner}
@@ -402,10 +408,10 @@ func TestListAPIKeysUseCase(t *testing.T) {
return result
}
t.Run("An admin lists only their own keys, newest first", func(t *testing.T) {
t.Run("A caller with own scope lists only their own keys, newest first", func(t *testing.T) {
repo := newMockAPIKeyRepo()
seed(repo)
entries, err := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock).ListAPIKeys(2)
entries, err := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock).ListAPIKeys(2, apiKeyOwnScope)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -414,10 +420,10 @@ func TestListAPIKeysUseCase(t *testing.T) {
}
})
t.Run("The owner lists every key, newest first", func(t *testing.T) {
t.Run("A caller with all scope lists every key, newest first", func(t *testing.T) {
repo := newMockAPIKeyRepo()
seed(repo)
entries, err := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock).ListAPIKeys(1)
entries, err := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock).ListAPIKeys(1, apiKeyAllScope)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -436,7 +442,7 @@ func TestListAPIKeysUseCase(t *testing.T) {
for _, raw := range []string{"rcms_first", "rcms_second", "rcms_third"} {
seedStubKey(repo, raw, func(k *apikey.APIKey) { k.CreatedAt = same })
}
entries, err := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock).ListAPIKeys(2)
entries, err := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock).ListAPIKeys(2, apiKeyOwnScope)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -455,7 +461,7 @@ func TestListAPIKeysUseCase(t *testing.T) {
seedStubKey(repo, "rcms_expired", func(k *apikey.APIKey) { k.ExpiresAt = &expired })
seedStubKey(repo, "rcms_both", func(k *apikey.APIKey) { k.ExpiresAt = &expired; k.RevokedAt = &revoked })
entries, _ := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock).ListAPIKeys(2)
entries, _ := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock).ListAPIKeys(2, apiKeyOwnScope)
statuses := map[string]apikey.Status{}
for _, entry := range entries {
statuses[entry.Key.Prefix] = entry.Status
@@ -468,15 +474,15 @@ func TestListAPIKeysUseCase(t *testing.T) {
t.Run("An unknown requester and repository failures are returned", func(t *testing.T) {
repo := newMockAPIKeyRepo()
uc := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock)
if _, err := uc.ListAPIKeys(99); !errors.Is(err, user.ErrUserNotFound) {
if _, err := uc.ListAPIKeys(99, apiKeyOwnScope); !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected ErrUserNotFound, got %v", err)
}
repo.fail.failNext("ListByUser")
if _, err := uc.ListAPIKeys(2); !errors.Is(err, errRepositoryFailure) {
if _, err := uc.ListAPIKeys(2, apiKeyOwnScope); !errors.Is(err, errRepositoryFailure) {
t.Errorf("expected the repository failure, got %v", err)
}
repo.fail.failNext("ListAll")
if _, err := uc.ListAPIKeys(1); !errors.Is(err, errRepositoryFailure) {
if _, err := uc.ListAPIKeys(1, apiKeyAllScope); !errors.Is(err, errRepositoryFailure) {
t.Errorf("expected the repository failure, got %v", err)
}
})
@@ -486,9 +492,9 @@ func TestRevokeAPIKeyUseCase(t *testing.T) {
t.Run("A creator revokes their own key at the clock's now", func(t *testing.T) {
repo := newMockAPIKeyRepo()
stored := seedStubKey(repo, "rcms_a", nil)
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock, discardRecorder)
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitClock, discardRecorder)
if err := uc.RevokeAPIKey(testCtx, 2, stored.ID); err != nil {
if err := uc.RevokeAPIKey(testCtx, 2, stored.ID, apiKeyOwnScope); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got := repo.keys[stored.ID].RevokedAt; got == nil || !got.Equal(apiKeyUnitNow) {
@@ -496,12 +502,12 @@ func TestRevokeAPIKeyUseCase(t *testing.T) {
}
})
t.Run("Another admin gets not found and the key stays active", func(t *testing.T) {
t.Run("Another own-scope caller gets not found and the key stays active", func(t *testing.T) {
repo := newMockAPIKeyRepo()
stored := seedStubKey(repo, "rcms_a", nil)
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock, discardRecorder)
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitClock, discardRecorder)
if err := uc.RevokeAPIKey(testCtx, 3, stored.ID); !errors.Is(err, apikey.ErrAPIKeyNotFound) {
if err := uc.RevokeAPIKey(testCtx, 3, stored.ID, apiKeyOwnScope); !errors.Is(err, apikey.ErrAPIKeyNotFound) {
t.Errorf("expected ErrAPIKeyNotFound, got %v", err)
}
if repo.keys[stored.ID].RevokedAt != nil || repo.revokeCalls != 0 {
@@ -509,12 +515,12 @@ func TestRevokeAPIKeyUseCase(t *testing.T) {
}
})
t.Run("The owner revokes any key", func(t *testing.T) {
t.Run("A caller with all scope revokes any key", func(t *testing.T) {
repo := newMockAPIKeyRepo()
stored := seedStubKey(repo, "rcms_a", func(k *apikey.APIKey) { k.UserID = 3 })
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock, discardRecorder)
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitClock, discardRecorder)
if err := uc.RevokeAPIKey(testCtx, 1, stored.ID); err != nil {
if err := uc.RevokeAPIKey(testCtx, 1, stored.ID, apiKeyAllScope); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if repo.keys[stored.ID].RevokedAt == nil {
@@ -526,9 +532,9 @@ func TestRevokeAPIKeyUseCase(t *testing.T) {
repo := newMockAPIKeyRepo()
original := apiKeyUnitNow.Add(-time.Hour)
stored := seedStubKey(repo, "rcms_a", func(k *apikey.APIKey) { k.RevokedAt = &original })
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock, discardRecorder)
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitClock, discardRecorder)
if err := uc.RevokeAPIKey(testCtx, 2, stored.ID); err != nil {
if err := uc.RevokeAPIKey(testCtx, 2, stored.ID, apiKeyOwnScope); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got := repo.keys[stored.ID].RevokedAt; got == nil || !got.Equal(original) {
@@ -536,19 +542,16 @@ func TestRevokeAPIKeyUseCase(t *testing.T) {
}
})
t.Run("An unknown key, an unknown requester and repository failures are returned", func(t *testing.T) {
t.Run("An unknown key and repository failures are returned", func(t *testing.T) {
repo := newMockAPIKeyRepo()
stored := seedStubKey(repo, "rcms_a", nil)
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock, discardRecorder)
uc := useCases.NewRevokeAPIKeyUseCase(repo, apiKeyUnitClock, discardRecorder)
if err := uc.RevokeAPIKey(testCtx, 2, 999); !errors.Is(err, apikey.ErrAPIKeyNotFound) {
if err := uc.RevokeAPIKey(testCtx, 2, 999, apiKeyOwnScope); !errors.Is(err, apikey.ErrAPIKeyNotFound) {
t.Errorf("expected ErrAPIKeyNotFound, got %v", err)
}
if err := uc.RevokeAPIKey(testCtx, 99, stored.ID); !errors.Is(err, user.ErrUserNotFound) {
t.Errorf("expected ErrUserNotFound, got %v", err)
}
repo.fail.failNext("Revoke")
if err := uc.RevokeAPIKey(testCtx, 2, stored.ID); !errors.Is(err, errRepositoryFailure) {
if err := uc.RevokeAPIKey(testCtx, 2, stored.ID, apiKeyOwnScope); !errors.Is(err, errRepositoryFailure) {
t.Errorf("expected the repository failure, got %v", err)
}
})