package server_test import ( "net/http" "testing" "gitea.meghsakha.com/platform/tenant-registry/internal/store" ) func TestCreateAPIKey_then_verify(t *testing.T) { eachStore(t, func(t *testing.T, h *testHarness) { resp, body := h.do("POST", "/v1/api-keys", map[string]any{ "tenant_id": h.tenant.ID, "name": "ci-bot", "product": "certifai", "scopes": []string{"certifai:read", "certifai:write"}, }) if resp.StatusCode != http.StatusCreated { t.Fatalf("create status = %d, body=%s", resp.StatusCode, body) } created := decode[struct { APIKey store.APIKey `json:"api_key"` Plaintext string `json:"plaintext"` }](t, body) if len(created.Plaintext) < 30 || created.Plaintext[:3] != "bp_" { t.Fatalf("bad plaintext: %q", created.Plaintext) } if len(created.APIKey.Scopes) != 2 || created.APIKey.Product != "certifai" { t.Errorf("unexpected key: %+v", created.APIKey) } // Verify with the plaintext key. resp, body = h.do("POST", "/v1/internal/api-keys/verify", map[string]any{ "key": created.Plaintext, }) if resp.StatusCode != 200 { t.Fatalf("verify status = %d, body=%s", resp.StatusCode, body) } v := decode[struct { Valid bool `json:"valid"` TenantID string `json:"tenant_id"` Product string `json:"product"` Scopes []string `json:"scopes"` }](t, body) if !v.Valid || v.TenantID != h.tenant.ID || v.Product != "certifai" || len(v.Scopes) != 2 { t.Errorf("verify returned %+v", v) } // Revoke; verify now returns valid=false. resp, _ = h.do("DELETE", "/v1/api-keys/"+created.APIKey.ID, nil) if resp.StatusCode != http.StatusNoContent { t.Fatalf("revoke status = %d", resp.StatusCode) } resp, body = h.do("POST", "/v1/internal/api-keys/verify", map[string]any{"key": created.Plaintext}) if resp.StatusCode != 200 { t.Fatalf("verify-after-revoke status = %d", resp.StatusCode) } v = decode[struct { Valid bool `json:"valid"` TenantID string `json:"tenant_id"` Product string `json:"product"` Scopes []string `json:"scopes"` }](t, body) if v.Valid { t.Error("revoked key still verifies") } }) } func TestVerifyAPIKey_garbage(t *testing.T) { eachStore(t, func(t *testing.T, h *testHarness) { for _, key := range []string{"", "not-a-key", "bp_short", "ax_wrongprefix1234567"} { resp, body := h.do("POST", "/v1/internal/api-keys/verify", map[string]any{"key": key}) if resp.StatusCode != 200 { t.Fatalf("status = %d for key %q", resp.StatusCode, key) } v := decode[struct { Valid bool `json:"valid"` }](t, body) if v.Valid { t.Errorf("garbage key %q verified as valid", key) } } }) } func TestCreateAPIKey_unknownProduct(t *testing.T) { eachStore(t, func(t *testing.T, h *testHarness) { resp, _ := h.do("POST", "/v1/api-keys", map[string]any{ "tenant_id": h.tenant.ID, "name": "k", "product": "bogus", }) if resp.StatusCode != http.StatusBadRequest { t.Fatalf("status = %d", resp.StatusCode) } }) } func TestListAPIKeys(t *testing.T) { eachStore(t, func(t *testing.T, h *testHarness) { respA, bodyA := h.do("POST", "/v1/api-keys", map[string]any{ "tenant_id": h.tenant.ID, "name": "alpha", }) if respA.StatusCode != http.StatusCreated { t.Fatalf("alpha create: status=%d body=%s", respA.StatusCode, bodyA) } respB, bodyB := h.do("POST", "/v1/api-keys", map[string]any{ "tenant_id": h.tenant.ID, "name": "beta", }) if respB.StatusCode != http.StatusCreated { t.Fatalf("beta create: status=%d body=%s", respB.StatusCode, bodyB) } resp, body := h.do("GET", "/v1/api-keys?tenant_id="+h.tenant.ID, nil) if resp.StatusCode != 200 { t.Fatalf("status = %d", resp.StatusCode) } out := decode[struct { Items []store.APIKey `json:"items"` }](t, body) if len(out.Items) < 2 { t.Errorf("expected ≥2 keys, got %d", len(out.Items)) } // Plaintext / hash must NOT leak in the list response. for _, k := range out.Items { rawJSON, _ := h.do("GET", "/v1/api-keys?tenant_id="+h.tenant.ID, nil) _ = rawJSON if k.Prefix == "" { t.Error("prefix missing") } } }) }