File size: 4,996 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
import { InMemoryModelsStore } from "@earendil-works/pi-ai";
import { describe, expect, test, vi } from "vitest";
import { parseArgs } from "../src/cli/args.ts";
import { AuthCommandError, isAuthCommandHelp, parseAuthCommand } from "../src/cli/auth-command.ts";
import { resolveCredentialForPrint } from "../src/cli/credential-print.ts";
import { AuthStorage } from "../src/core/auth-storage.ts";
import { ModelRuntime } from "../src/core/model-runtime.ts";
import { main } from "../src/main.ts";

async function createRuntime(credentials: AuthStorage): Promise<ModelRuntime> {
	return ModelRuntime.create({
		credentials,
		modelsPath: null,
		modelsStore: new InMemoryModelsStore(),
		allowModelNetwork: false,
	});
}

describe("credential print commands", () => {
	test("prints a resolved API key", async () => {
		const runtime = await createRuntime(AuthStorage.inMemory({ openai: { type: "api_key", key: "test-api-key" } }));
		const args = parseArgs(["--provider", "openai"]);

		await expect(resolveCredentialForPrint(args, runtime, "api_key")).resolves.toBe("test-api-key");
	});

	test("prints bearer tokens resolved from an Authorization header", async () => {
		const runtime = await createRuntime(
			AuthStorage.inMemory({
				"kimi-coding": {
					type: "oauth",
					access: "header-test-token",
					refresh: "test-refresh-token",
					expires: Date.now() + 60 * 60 * 1000,
				},
			}),
		);
		const args = parseArgs(["--provider", "kimi-coding"]);

		await expect(resolveCredentialForPrint(args, runtime, "bearer_token")).resolves.toBe("header-test-token");
	});

	test("refreshes an expired OAuth token before printing it", async () => {
		const storage = AuthStorage.inMemory({
			"openai-codex": {
				type: "oauth",
				access: "old-test-token",
				refresh: "test-refresh-token",
				expires: 0,
			},
		});
		const runtime = await createRuntime(storage);
		const refresh = vi.fn(async () => ({
			type: "oauth" as const,
			access: "fresh-test-token",
			refresh: "test-refresh-token",
			expires: Date.now() + 60 * 60 * 1000,
		}));
		const oauth = runtime.getProvider("openai-codex")?.auth.oauth;
		if (!oauth) throw new Error("OpenAI Codex OAuth provider is not registered");
		oauth.refresh = refresh;
		const args = parseArgs(["--provider", "openai-codex"]);

		await expect(resolveCredentialForPrint(args, runtime, "bearer_token")).resolves.toBe("fresh-test-token");
		expect(refresh).toHaveBeenCalledOnce();
		expect(await storage.read("openai-codex")).toMatchObject({ access: "fresh-test-token" });
	});

	test("reports unknown auth options like package commands", async () => {
		const originalExitCode = process.exitCode;
		const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
		try {
			process.exitCode = undefined;
			await main(["auth", "check", "--provider", "openai-codex", "--credentails"]);
			const stderr = errorSpy.mock.calls.map(([message]) => String(message)).join("\n");
			expect(stderr).toContain('Unknown option --credentails for "auth check".');
			expect(stderr).toContain(
				'Use "pi --help" or "pi auth check --provider <provider> [--json] [--credentials] [--no-refresh]".',
			);
			expect(process.exitCode).toBe(1);
		} finally {
			process.exitCode = originalExitCode;
			errorSpy.mockRestore();
		}
	});

	test("parses credential commands and rejects invalid arguments or credential types", async () => {
		const runtime = await createRuntime(
			AuthStorage.inMemory({
				"openai-codex": {
					type: "oauth",
					access: "test-token-not-to-be-printed",
					refresh: "test-refresh-token",
					expires: Date.now() + 60 * 60 * 1000,
				},
			}),
		);

		expect(parseAuthCommand(["auth", "print-api-key", "--provider", "openai"])).toEqual({
			kind: "api_key",
			args: ["--provider", "openai"],
			json: false,
			credentials: false,
			noRefresh: false,
		});
		expect(parseAuthCommand(["auth", "print-bearer-token"])).toMatchObject({ kind: "bearer_token" });
		expect(parseAuthCommand(["auth", "print-bearer-token", "--min-expiry", "30m"])).toEqual({
			kind: "bearer_token",
			args: [],
			json: false,
			credentials: false,
			noRefresh: false,
			minExpiryMs: 30 * 60_000,
		});
		expect(() => parseAuthCommand(["auth", "print-api-key", "--min-expiry", "30m"])).toThrow(
			"only supported by print-bearer-token",
		);
		expect(isAuthCommandHelp(["auth", "--help"])).toBe(true);
		expect(isAuthCommandHelp(["auth", "print-api-key", "--help"])).toBe(true);
		expect(isAuthCommandHelp(["auth", "print-bearer-token", "-h"])).toBe(true);
		expect(isAuthCommandHelp(["auth", "check", "--help"])).toBe(true);
		expect(() => parseAuthCommand(["auth", "unknown"])).toThrow(AuthCommandError);
		await expect(resolveCredentialForPrint(parseArgs([]), runtime, "api_key")).rejects.toThrow(
			"requires --provider <provider> or --model <model>",
		);
		await expect(
			resolveCredentialForPrint(parseArgs(["--provider", "openai-codex"]), runtime, "api_key"),
		).rejects.toThrow("configured with OAuth");
	});
});