feat(FN-1196): support API-key providers in dashboard auth flow

- Wrap dashboard auth storage with API-key provider helpers derived from model registry providers
- Normalize provider display names and bridge set/clear/has API-key operations to AuthStorage credentials
- Expand route and onboarding tests for mixed OAuth/API-key states and API-key-authenticated setup paths
- Stabilize assignment-trigger heartbeat timing test by replacing fixed delays with waitFor assertions
This commit is contained in:
gsxdsm
2026-04-08 13:54:30 -07:00
parent 8c0d30d206
commit 8354a86a82
6 changed files with 242 additions and 7 deletions

View File

@@ -2440,6 +2440,31 @@ describe("GET /auth/status", () => {
expect(authStorage.reload).toHaveBeenCalled();
});
it("includes oauth and model-registry-derived API key providers in one response", async () => {
(authStorage.getOAuthProviders as ReturnType<typeof vi.fn>).mockReturnValue([
{ id: "anthropic", name: "Anthropic" },
{ id: "github-copilot", name: "GitHub Copilot" },
]);
(authStorage.getApiKeyProviders as ReturnType<typeof vi.fn>).mockReturnValue([
{ id: "openrouter", name: "OpenRouter" },
{ id: "kimi-coding", name: "Kimi" },
{ id: "acme-extension", name: "Acme Extension" },
]);
(authStorage.hasAuth as ReturnType<typeof vi.fn>).mockImplementation((provider: string) => provider === "anthropic");
(authStorage.hasApiKey as ReturnType<typeof vi.fn>).mockImplementation((provider: string) => provider === "acme-extension");
const res = await GET(buildApp(), "/api/auth/status");
expect(res.status).toBe(200);
expect(res.body.providers).toEqual([
{ id: "anthropic", name: "Anthropic", authenticated: true, type: "oauth" },
{ id: "github-copilot", name: "GitHub Copilot", authenticated: false, type: "oauth" },
{ id: "openrouter", name: "OpenRouter", authenticated: false, type: "api_key" },
{ id: "kimi-coding", name: "Kimi", authenticated: false, type: "api_key" },
{ id: "acme-extension", name: "Acme Extension", authenticated: true, type: "api_key" },
]);
});
it("returns unauthenticated status", async () => {
(authStorage.hasAuth as ReturnType<typeof vi.fn>).mockReturnValue(false);
@@ -2646,6 +2671,21 @@ describe("POST /auth/api-key", () => {
expect(res.body.error).toContain("apiKey is required");
});
it("accepts API key providers discovered from model registry-backed auth storage", async () => {
(authStorage.getApiKeyProviders as ReturnType<typeof vi.fn>).mockReturnValue([
{ id: "acme-extension", name: "Acme Extension" },
]);
const res = await REQUEST(buildApp(), "POST", "/api/auth/api-key", JSON.stringify({
provider: "acme-extension",
apiKey: "acme-secret-key",
}), { "Content-Type": "application/json" });
expect(res.status).toBe(200);
expect(res.body.success).toBe(true);
expect(authStorage.setApiKey).toHaveBeenCalledWith("acme-extension", "acme-secret-key");
});
it("returns 400 for unknown provider", async () => {
const res = await REQUEST(buildApp(), "POST", "/api/auth/api-key", JSON.stringify({
provider: "unknown-provider",