mirror of https://github.com/garrytan/gstack.git
134 lines
4.4 KiB
TypeScript
134 lines
4.4 KiB
TypeScript
/**
|
|
* Tests for $D OpenAI auth source reporting (#1278, closes #1248).
|
|
*
|
|
* Verifies that resolveApiKey + requireApiKey:
|
|
* - prefer ~/.gstack/openai.json over OPENAI_API_KEY
|
|
* - report when the env-var key matches a cwd .env / .env.local
|
|
* - never echo the key itself to stderr (only the source label)
|
|
*/
|
|
|
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
import * as fs from "fs";
|
|
import * as os from "os";
|
|
import * as path from "path";
|
|
import {
|
|
describeApiKeySource,
|
|
requireApiKey,
|
|
resolveApiKey,
|
|
resolveApiKeyInfo,
|
|
saveApiKey,
|
|
} from "../src/auth";
|
|
|
|
let tmpDir: string;
|
|
let tmpHome: string;
|
|
let originalHome: string | undefined;
|
|
let originalKey: string | undefined;
|
|
let originalNodeEnv: string | undefined;
|
|
let originalCwd: string;
|
|
|
|
beforeEach(() => {
|
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gstack-design-auth-"));
|
|
tmpHome = path.join(tmpDir, "home");
|
|
fs.mkdirSync(tmpHome, { recursive: true });
|
|
|
|
originalHome = process.env.HOME;
|
|
originalKey = process.env.OPENAI_API_KEY;
|
|
originalNodeEnv = process.env.NODE_ENV;
|
|
originalCwd = process.cwd();
|
|
|
|
process.env.HOME = tmpHome;
|
|
delete process.env.OPENAI_API_KEY;
|
|
delete process.env.NODE_ENV;
|
|
process.chdir(tmpDir);
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.chdir(originalCwd);
|
|
if (originalHome === undefined) delete process.env.HOME;
|
|
else process.env.HOME = originalHome;
|
|
if (originalKey === undefined) delete process.env.OPENAI_API_KEY;
|
|
else process.env.OPENAI_API_KEY = originalKey;
|
|
if (originalNodeEnv === undefined) delete process.env.NODE_ENV;
|
|
else process.env.NODE_ENV = originalNodeEnv;
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe("resolveApiKeyInfo", () => {
|
|
test("uses ~/.gstack/openai.json before OPENAI_API_KEY", () => {
|
|
saveApiKey("sk-config");
|
|
process.env.OPENAI_API_KEY = "sk-env";
|
|
|
|
const resolution = resolveApiKeyInfo();
|
|
|
|
expect(resolution?.key).toBe("sk-config");
|
|
expect(resolution?.source).toBe("config");
|
|
expect(describeApiKeySource(resolution!)).toBe("~/.gstack/openai.json");
|
|
expect(resolveApiKey()).toBe("sk-config");
|
|
});
|
|
|
|
test("uses OPENAI_API_KEY when no config file exists", () => {
|
|
process.env.OPENAI_API_KEY = "sk-env";
|
|
|
|
const resolution = resolveApiKeyInfo();
|
|
|
|
expect(resolution?.key).toBe("sk-env");
|
|
expect(resolution?.source).toBe("env");
|
|
expect(resolution?.envFile).toBeUndefined();
|
|
expect(describeApiKeySource(resolution!)).toBe("OPENAI_API_KEY environment variable");
|
|
});
|
|
|
|
test("reports when OPENAI_API_KEY matches current-directory .env", () => {
|
|
fs.writeFileSync(path.join(tmpDir, ".env"), "OPENAI_API_KEY=sk-project\n");
|
|
process.env.OPENAI_API_KEY = "sk-project";
|
|
|
|
const resolution = resolveApiKeyInfo();
|
|
|
|
expect(resolution?.key).toBe("sk-project");
|
|
expect(resolution?.envFile).toBe(".env");
|
|
expect(describeApiKeySource(resolution!)).toBe("OPENAI_API_KEY environment variable (matches .env in current directory)");
|
|
expect(resolution?.warning).toContain("may bill that project's OpenAI account");
|
|
});
|
|
|
|
test("detects quoted and exported env-file values", () => {
|
|
fs.writeFileSync(path.join(tmpDir, ".env.local"), "export OPENAI_API_KEY=\"sk-local\"\n");
|
|
process.env.OPENAI_API_KEY = "sk-local";
|
|
|
|
const resolution = resolveApiKeyInfo();
|
|
|
|
expect(resolution?.envFile).toBe(".env.local");
|
|
expect(resolution?.warning).toContain(".env.local");
|
|
});
|
|
|
|
test("does not claim env-file source when values differ", () => {
|
|
fs.writeFileSync(path.join(tmpDir, ".env"), "OPENAI_API_KEY=sk-other\n");
|
|
process.env.OPENAI_API_KEY = "sk-shell";
|
|
|
|
const resolution = resolveApiKeyInfo();
|
|
|
|
expect(resolution?.key).toBe("sk-shell");
|
|
expect(resolution?.envFile).toBeUndefined();
|
|
expect(resolution?.warning).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("requireApiKey", () => {
|
|
test("prints source disclosure without leaking the key", () => {
|
|
process.env.OPENAI_API_KEY = "sk-secret-value";
|
|
const messages: string[] = [];
|
|
const originalError = console.error;
|
|
console.error = (...args: unknown[]) => {
|
|
messages.push(args.map(String).join(" "));
|
|
};
|
|
|
|
try {
|
|
expect(requireApiKey()).toBe("sk-secret-value");
|
|
} finally {
|
|
console.error = originalError;
|
|
}
|
|
|
|
const stderr = messages.join("\n");
|
|
expect(stderr).toContain("Using OpenAI key from OPENAI_API_KEY environment variable.");
|
|
expect(stderr).not.toContain("sk-secret-value");
|
|
});
|
|
});
|