feat(SPA): screen to manage API keys (PART 2/3)
This commit is contained in:
1 parent
844842fdae
commit
514a082c96
30 files changed
+3844
-83
No files matched your search
@@ -0,0 +1,228 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
"RenewCMS/internal/domain/apikey"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type apiKeyCreatorLookup struct {
|
||||
user.Repository
|
||||
gets map[uint32]int
|
||||
failOn uint32
|
||||
}
|
||||
|
||||
func newAPIKeyCreatorLookup(inner user.Repository) *apiKeyCreatorLookup {
|
||||
return &apiKeyCreatorLookup{Repository: inner, gets: map[uint32]int{}}
|
||||
}
|
||||
|
||||
func (a *apiKeyCreatorLookup) Get(id uint32) (user.User, error) {
|
||||
a.gets[id]++
|
||||
if id == a.failOn {
|
||||
return user.User{}, errRepositoryFailure
|
||||
}
|
||||
return a.Repository.Get(id)
|
||||
}
|
||||
|
||||
var _ user.Repository = &apiKeyCreatorLookup{}
|
||||
|
||||
func (e *apiKeyEnv) useCreatorLookup(lookup *apiKeyCreatorLookup) {
|
||||
clock := func() time.Time { return e.now }
|
||||
e.container.ListAPIKeysUseCase = useCases.NewListAPIKeysUseCase(e.keys, lookup, clock)
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/v1", api.InitBackendRoutes(e.container, e.settings))
|
||||
e.router = router
|
||||
}
|
||||
|
||||
func (e *apiKeyEnv) nameUsers() {
|
||||
owner := e.users.users[apiKeyOwnerId]
|
||||
owner.DisplayName = "Marta Alves"
|
||||
e.users.users[apiKeyOwnerId] = owner
|
||||
dev := e.users.users[apiKeyDevId]
|
||||
dev.DisplayName = "Dev Two"
|
||||
e.users.users[apiKeyDevId] = dev
|
||||
}
|
||||
|
||||
func TestAPIKeysCreatorNameHttp(t *testing.T) {
|
||||
allFields := []string{"id", "user_id", "name", "prefix", "status", "created_at", "last_used_at", "expires_at", "revoked_at"}
|
||||
|
||||
t.Run("API: an owner's list carries the creator's name", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
env.nameUsers()
|
||||
env.issueKey(apiKeyOwnerId, "a")
|
||||
env.issueKey(apiKeyDevId, "b")
|
||||
env.issueKey(apiKeyPatId, "c")
|
||||
|
||||
rec := env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyOwnerId)
|
||||
|
||||
assertAPIKeyStatus(t, rec, http.StatusOK)
|
||||
entries := apiKeyListedByName(t, rec)
|
||||
want := map[string]string{"a": "Marta Alves", "b": "Dev Two", "c": "pat"}
|
||||
for name, creator := range want {
|
||||
if entries[name]["created_by_name"] != creator {
|
||||
t.Errorf("entry %s: expected created_by_name %q, got %v", name, creator, entries[name]["created_by_name"])
|
||||
}
|
||||
apiKeyAssertExactFields(t, entries[name], append([]string{"created_by_name"}, allFields...)...)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("API: a whitespace-only display name falls back to the username", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
pat := env.users.users[apiKeyPatId]
|
||||
pat.DisplayName = " "
|
||||
env.users.users[apiKeyPatId] = pat
|
||||
env.issueKey(apiKeyPatId, "c")
|
||||
|
||||
entries := apiKeyListedByName(t, env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyOwnerId))
|
||||
|
||||
if entries["c"]["created_by_name"] != "pat" {
|
||||
t.Errorf("expected pat, got %v", entries["c"]["created_by_name"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("API: an admin's list is unchanged", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
env.nameUsers()
|
||||
env.issueKey(apiKeyDevId, "a")
|
||||
env.issueKey(apiKeyDevId, "b")
|
||||
|
||||
rec := env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyDevId)
|
||||
|
||||
assertAPIKeyStatus(t, rec, http.StatusOK)
|
||||
list := apiKeyDecodeList(t, rec)
|
||||
if len(list) != 2 {
|
||||
t.Fatalf("expected two entries, got %d", len(list))
|
||||
}
|
||||
for _, entry := range list {
|
||||
apiKeyAssertExactFields(t, entry, allFields...)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("API: a key whose creator no longer exists", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
env.issueKey(apiKeyPatId, "gone")
|
||||
env.issueKey(apiKeyDevId, "kept")
|
||||
delete(env.users.users, apiKeyPatId)
|
||||
|
||||
rec := env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyOwnerId)
|
||||
|
||||
assertAPIKeyStatus(t, rec, http.StatusOK)
|
||||
entries := apiKeyListedByName(t, rec)
|
||||
if name, ok := entries["gone"]["created_by_name"]; !ok || name != "" {
|
||||
t.Errorf("expected an empty created_by_name, got %v", name)
|
||||
}
|
||||
if entries["kept"]["created_by_name"] != "dev" {
|
||||
t.Errorf("expected dev, got %v", entries["kept"]["created_by_name"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("API: a failing user lookup fails the list", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
env.issueKey(apiKeyDevId, "a")
|
||||
lookup := newAPIKeyCreatorLookup(env.users)
|
||||
lookup.failOn = apiKeyDevId
|
||||
env.useCreatorLookup(lookup)
|
||||
|
||||
rec := env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyOwnerId)
|
||||
|
||||
assertAPIKeyMessage(t, rec, http.StatusInternalServerError, "Internal server error")
|
||||
})
|
||||
|
||||
t.Run("API: the creator name is resolved once per user", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
baseline := newAPIKeyCreatorLookup(env.users)
|
||||
env.useCreatorLookup(baseline)
|
||||
assertAPIKeyStatus(t, env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyOwnerId), http.StatusOK)
|
||||
|
||||
env.issueKey(apiKeyOwnerId, "o1")
|
||||
env.issueKey(apiKeyOwnerId, "o2")
|
||||
env.issueKey(apiKeyOwnerId, "o3")
|
||||
env.issueKey(apiKeyDevId, "d1")
|
||||
env.issueKey(apiKeyDevId, "d2")
|
||||
lookup := newAPIKeyCreatorLookup(env.users)
|
||||
env.useCreatorLookup(lookup)
|
||||
|
||||
assertAPIKeyStatus(t, env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyOwnerId), http.StatusOK)
|
||||
|
||||
if lookup.gets[apiKeyDevId] != 1 {
|
||||
t.Errorf("expected one Get for the admin creator, got %d", lookup.gets[apiKeyDevId])
|
||||
}
|
||||
if lookup.gets[apiKeyOwnerId] != baseline.gets[apiKeyOwnerId] {
|
||||
t.Errorf("expected no extra Get for the owner, got %d against %d", lookup.gets[apiKeyOwnerId], baseline.gets[apiKeyOwnerId])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestListAPIKeysCreatorName(t *testing.T) {
|
||||
seed := func() *mockAPIKeyRepo {
|
||||
repo := newMockAPIKeyRepo()
|
||||
seedStubKey(repo, "rcms_a", func(k *apikey.APIKey) { k.UserID = 1 })
|
||||
seedStubKey(repo, "rcms_b", func(k *apikey.APIKey) { k.UserID = 2 })
|
||||
return repo
|
||||
}
|
||||
users := func() *mockUserRepo {
|
||||
repo := apiKeyUnitUsers()
|
||||
marta := repo.users[1]
|
||||
marta.DisplayName = "Marta Alves"
|
||||
repo.users[1] = marta
|
||||
return repo
|
||||
}
|
||||
|
||||
t.Run("Use case: ListAPIKeys returns the creator name only to an owner", func(t *testing.T) {
|
||||
uc := useCases.NewListAPIKeysUseCase(seed(), users(), apiKeyUnitClock)
|
||||
|
||||
ownerEntries, err := uc.ListAPIKeys(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
names := map[string]string{}
|
||||
for _, entry := range ownerEntries {
|
||||
names[entry.Key.Prefix] = entry.CreatorName
|
||||
}
|
||||
if names["rcms_a"] != "Marta Alves" || names["rcms_b"] != "dev" {
|
||||
t.Errorf("unexpected creator names %v", names)
|
||||
}
|
||||
|
||||
adminEntries, err := uc.ListAPIKeys(2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(adminEntries) != 1 || adminEntries[0].CreatorName != "" {
|
||||
t.Errorf("expected one entry with an empty creator name, got %+v", adminEntries)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type apiKeyDuplicateIdRepo struct {
|
||||
apikey.Repository
|
||||
keys []apikey.APIKey
|
||||
}
|
||||
|
||||
func (a *apiKeyDuplicateIdRepo) ListAll() ([]apikey.APIKey, error) {
|
||||
return append([]apikey.APIKey(nil), a.keys...), nil
|
||||
}
|
||||
|
||||
var _ apikey.Repository = &apiKeyDuplicateIdRepo{}
|
||||
|
||||
func TestListAPIKeysTieKeepsRepositoryOrder(t *testing.T) {
|
||||
createdAt := apiKeyUnitNow.Add(-time.Hour)
|
||||
repo := &apiKeyDuplicateIdRepo{keys: []apikey.APIKey{
|
||||
{ID: 7, UserID: 1, Name: "first", CreatedAt: createdAt},
|
||||
{ID: 7, UserID: 1, Name: "second", CreatedAt: createdAt},
|
||||
}}
|
||||
uc := useCases.NewListAPIKeysUseCase(repo, apiKeyUnitUsers(), apiKeyUnitClock)
|
||||
|
||||
entries, err := uc.ListAPIKeys(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(entries) != 2 || entries[0].Key.Name != "first" || entries[1].Key.Name != "second" {
|
||||
t.Errorf("expected a stable order for identical keys, got %+v", entries)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user