Files
PaperClipAI/server/src/__tests__/ai-auth-failure.test.ts
T

50 lines
3.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { aiBindingForAuthRecovery, isAiAuthenticationBlocked, isAiAuthenticationRepairable } from "../services/ai-auth-failure.js";
import { classifyRunLiveness } from "../services/run-liveness.js";
const missingClaudeKey = {
bindingType: "user_secret_ref", envKey: "ANTHROPIC_API_KEY",
configPath: "env.ANTHROPIC_API_KEY", errorCode: "user_secret_missing",
consumerType: "agent", responsibleUserId: "new-teammate",
};
const failure = (missingBindings: unknown, reason = "secret_binding_missing") => ({
errorCode: "configuration_incomplete",
resultJson: { configurationIncomplete: { reason, missingBindings } },
});
describe("personal AI credential preflight recovery", () => {
it.each(["user_secret_missing", "secret_inactive"])("offers Claude setup for %s without adopting another provider", errorCode => {
const run = failure([{ ...missingClaudeKey, errorCode }]);
expect(isAiAuthenticationRepairable(run)).toBe(true);
expect(aiBindingForAuthRecovery("claude_local", {}, run)).toEqual({
provider: "anthropic", method: "subscription", mode: "responsible_user",
});
expect(aiBindingForAuthRecovery("codex_local", {}, run)).toBeUndefined();
expect(isAiAuthenticationBlocked({ ...run, livenessState: "failed" })).toBe(false);
expect(isAiAuthenticationBlocked({ ...run, livenessState: "blocked" })).toBe(true);
});
it.each([
["tool credential", [{ ...missingClaudeKey, envKey: "GITHUB_TOKEN", configPath: "env.GITHUB_TOKEN" }]],
["company secret", [{ ...missingClaudeKey, bindingType: "secret_ref" }]],
["unknown error", [{ ...missingClaudeKey, errorCode: "user_secret_definition_missing" }]],
["adapter field", [{ ...missingClaudeKey, configPath: "apiKey" }]],
["additional tool blocker", [missingClaudeKey, { ...missingClaudeKey, envKey: "GITHUB_TOKEN", configPath: "env.GITHUB_TOKEN" }]],
["different providers", [missingClaudeKey, { ...missingClaudeKey, envKey: "OPENAI_API_KEY", configPath: "env.OPENAI_API_KEY" }]],
["empty evidence", []], ["malformed evidence", [null]], ["missing evidence", null],
])("keeps %s on the existing configuration recovery path", (_name, bindings) => {
expect(isAiAuthenticationRepairable(failure(bindings))).toBe(false);
expect(isAiAuthenticationBlocked({ ...failure(bindings), livenessState: "blocked" })).toBe(false);
});
it("only classifies a failed preflight as an inline wait after the card is persisted", () => {
const input = { ...failure([missingClaudeKey]), runStatus: "failed", issue: { status: "blocked" },
stdoutExcerpt: null, stderrExcerpt: null, error: null, continuationAttempt: 0, evidence: null };
expect(classifyRunLiveness(input).livenessState).toBe("failed");
expect(classifyRunLiveness({ ...input, authenticationRepairRequested: true })).toMatchObject({
livenessState: "blocked", nextAction: "Complete the connection request to continue this task",
});
expect(classifyRunLiveness({ ...input, ...failure([]), authenticationRepairRequested: true }).livenessState).toBe("failed");
});
});