File size: 6,478 Bytes
bc575bc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { InMemoryModelsStore } from "@earendil-works/pi-ai";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { parseArgs } from "../src/cli/args.ts";
import { checkProviderAuth, createAuthCheckModelRuntime, getProviderCredential } from "../src/cli/auth-check.ts";
import { parseAuthCommand } from "../src/cli/auth-command.ts";
import { AuthStorage, ReadOnlyAuthStorage } from "../src/core/auth-storage.ts";
import { ModelRuntime } from "../src/core/model-runtime.ts";
const tempDir = join(tmpdir(), `pi-test-auth-check-${Date.now()}-${Math.random().toString(36).slice(2)}`);
async function createRuntime(credentials: AuthStorage | ReadOnlyAuthStorage): Promise<ModelRuntime> {
return ModelRuntime.create({
credentials,
modelsPath: null,
modelsStore: new InMemoryModelsStore(),
allowModelNetwork: false,
refreshOnCreate: false,
});
}
describe("auth check command", () => {
beforeEach(() => {
if (existsSync(tempDir)) rmSync(tempDir, { recursive: true });
mkdirSync(tempDir, { recursive: true });
});
afterEach(() => {
if (existsSync(tempDir)) rmSync(tempDir, { recursive: true });
});
test("reports a configured provider as ready", async () => {
const runtime = await createRuntime(AuthStorage.inMemory({ openai: { type: "api_key", key: "test-key" } }));
await expect(checkProviderAuth(parseArgs(["--provider", "openai"]), runtime)).resolves.toEqual({
status: "ready",
provider: "openai",
authType: "api_key",
});
});
test("resolves the provider from --model", async () => {
const runtime = await createRuntime(AuthStorage.inMemory({ openai: { type: "api_key", key: "test-key" } }));
await expect(checkProviderAuth(parseArgs(["--model", "openai/gpt-5.5"]), runtime)).resolves.toEqual({
status: "ready",
provider: "openai",
authType: "api_key",
});
await expect(
checkProviderAuth(parseArgs(["--provider", "openai", "--model", "gpt-5.5"]), runtime),
).resolves.toMatchObject({ status: "ready", provider: "openai" });
});
test("reads credentials without refreshing OAuth when requested", async () => {
const apiCredentials = AuthStorage.inMemory({ openai: { type: "api_key", key: "test-key" } });
const apiRuntime = await createRuntime(apiCredentials);
await expect(getProviderCredential("openai", apiRuntime, apiCredentials, { refresh: false })).resolves.toBe(
"test-key",
);
const credentials = AuthStorage.inMemory({
"openai-codex": { type: "oauth", access: "old-token", refresh: "refresh-token", expires: 0 },
});
const oauthRuntime = await createRuntime(credentials);
const oauth = oauthRuntime.getProvider("openai-codex")?.auth.oauth;
if (!oauth) throw new Error("OpenAI Codex OAuth provider is not registered");
const refresh = vi.fn(oauth.refresh);
oauth.refresh = refresh;
await expect(getProviderCredential("openai-codex", oauthRuntime, credentials, { refresh: false })).resolves.toBe(
"old-token",
);
expect(refresh).not.toHaveBeenCalled();
});
test("refreshes OAuth by default", async () => {
const credentials = AuthStorage.inMemory({
"openai-codex": { type: "oauth", access: "old-token", refresh: "refresh-token", expires: 0 },
});
const runtime = await createRuntime(credentials);
const oauth = runtime.getProvider("openai-codex")?.auth.oauth;
if (!oauth) throw new Error("OpenAI Codex OAuth provider is not registered");
const refresh = vi.fn(async () => ({
type: "oauth" as const,
access: "fresh-token",
refresh: "refresh-token",
expires: Date.now() + 60 * 60 * 1000,
}));
oauth.refresh = refresh;
await expect(
checkProviderAuth(parseArgs(["--provider", "openai-codex"]), runtime, { refresh: true }),
).resolves.toMatchObject({
status: "ready",
});
expect(refresh).toHaveBeenCalledOnce();
});
test("reports an unknown provider as not ready", async () => {
const runtime = await createRuntime(AuthStorage.inMemory());
await expect(checkProviderAuth(parseArgs(["--provider", "not-installed"]), runtime)).resolves.toEqual({
status: "not_ready",
provider: "not-installed",
reason: "provider_not_found",
});
});
test("does not treat an unresolved stored environment reference as configured", async () => {
const authPath = join(tempDir, "auth.json");
writeFileSync(authPath, JSON.stringify({ openai: { type: "api_key", key: "$MISSING_AUTH_CHECK_KEY" } }), "utf-8");
const runtime = await createRuntime(new ReadOnlyAuthStorage(authPath));
await expect(checkProviderAuth(parseArgs(["--provider", "openai"]), runtime)).resolves.toEqual({
status: "not_ready",
provider: "openai",
reason: "credentials_not_configured",
});
});
test("reports malformed auth state as invalid", async () => {
const authPath = join(tempDir, "auth.json");
writeFileSync(authPath, "{invalid-json", "utf-8");
const runtime = await createRuntime(new ReadOnlyAuthStorage(authPath));
await expect(checkProviderAuth(parseArgs(["--provider", "openai"]), runtime)).resolves.toEqual({
status: "invalid",
provider: "openai",
reason: "invalid_state",
});
});
test("does not create an auth file or its parent directory", async () => {
const authPath = join(tempDir, "agent", "auth.json");
const runtime = await createRuntime(new ReadOnlyAuthStorage(authPath));
await expect(checkProviderAuth(parseArgs(["--provider", "openai"]), runtime)).resolves.toMatchObject({
status: "not_ready",
reason: "credentials_not_configured",
});
expect(existsSync(authPath)).toBe(false);
expect(existsSync(join(tempDir, "agent"))).toBe(false);
});
test("accepts optional JSON output, credential output, and --no-refresh", () => {
expect(parseAuthCommand(["auth", "check", "--provider", "openai"])).toEqual({
kind: "check",
args: ["--provider", "openai"],
json: false,
credentials: false,
noRefresh: false,
});
expect(
parseAuthCommand(["auth", "check", "--json", "--credentials", "--no-refresh", "--provider", "openai"]),
).toEqual({
kind: "check",
args: ["--provider", "openai"],
json: true,
credentials: true,
noRefresh: true,
});
});
test("creates an auth-check runtime without catalog storage", async () => {
const runtime = await createAuthCheckModelRuntime(AuthStorage.inMemory());
expect(runtime.getProvider("openai")).toBeDefined();
});
});
|