tests(google): inject oauth credential fs stubs
This commit is contained in:
parent
4234d9b42c
commit
a413da9cca
@ -1,7 +1,27 @@
|
||||
import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
||||
import type { Dirent } from "node:fs";
|
||||
import { delimiter, dirname, join } from "node:path";
|
||||
import { CLIENT_ID_KEYS, CLIENT_SECRET_KEYS } from "./oauth.shared.js";
|
||||
|
||||
type CredentialFs = {
|
||||
existsSync: (path: Parameters<typeof existsSync>[0]) => ReturnType<typeof existsSync>;
|
||||
readFileSync: (path: Parameters<typeof readFileSync>[0], encoding: "utf8") => string;
|
||||
realpathSync: (path: Parameters<typeof realpathSync>[0]) => string;
|
||||
readdirSync: (
|
||||
path: Parameters<typeof readdirSync>[0],
|
||||
options: { withFileTypes: true },
|
||||
) => Dirent[];
|
||||
};
|
||||
|
||||
const defaultFs: CredentialFs = {
|
||||
existsSync,
|
||||
readFileSync,
|
||||
realpathSync,
|
||||
readdirSync,
|
||||
};
|
||||
|
||||
let credentialFs: CredentialFs = defaultFs;
|
||||
|
||||
function resolveEnv(keys: string[]): string | undefined {
|
||||
for (const key of keys) {
|
||||
const value = process.env[key]?.trim();
|
||||
@ -18,6 +38,10 @@ export function clearCredentialsCache(): void {
|
||||
cachedGeminiCliCredentials = null;
|
||||
}
|
||||
|
||||
export function setOAuthCredentialsFsForTest(overrides?: Partial<CredentialFs>): void {
|
||||
credentialFs = overrides ? { ...defaultFs, ...overrides } : defaultFs;
|
||||
}
|
||||
|
||||
export function extractGeminiCliCredentials(): { clientId: string; clientSecret: string } | null {
|
||||
if (cachedGeminiCliCredentials) {
|
||||
return cachedGeminiCliCredentials;
|
||||
@ -29,7 +53,7 @@ export function extractGeminiCliCredentials(): { clientId: string; clientSecret:
|
||||
return null;
|
||||
}
|
||||
|
||||
const resolvedPath = realpathSync(geminiPath);
|
||||
const resolvedPath = credentialFs.realpathSync(geminiPath);
|
||||
const geminiCliDirs = resolveGeminiCliDirs(geminiPath, resolvedPath);
|
||||
|
||||
let content: string | null = null;
|
||||
@ -55,10 +79,9 @@ export function extractGeminiCliCredentials(): { clientId: string; clientSecret:
|
||||
"oauth2.js",
|
||||
),
|
||||
];
|
||||
|
||||
for (const path of searchPaths) {
|
||||
if (existsSync(path)) {
|
||||
content = readFileSync(path, "utf8");
|
||||
if (credentialFs.existsSync(path)) {
|
||||
content = credentialFs.readFileSync(path, "utf8");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -67,7 +90,7 @@ export function extractGeminiCliCredentials(): { clientId: string; clientSecret:
|
||||
}
|
||||
const found = findFile(geminiCliDir, "oauth2.js", 10);
|
||||
if (found) {
|
||||
content = readFileSync(found, "utf8");
|
||||
content = credentialFs.readFileSync(found, "utf8");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -116,7 +139,7 @@ function findInPath(name: string): string | null {
|
||||
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
||||
for (const ext of exts) {
|
||||
const path = join(dir, name + ext);
|
||||
if (existsSync(path)) {
|
||||
if (credentialFs.existsSync(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@ -129,7 +152,7 @@ function findFile(dir: string, name: string, depth: number): string | null {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
for (const entry of credentialFs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const path = join(dir, entry.name);
|
||||
if (entry.isFile() && entry.name === name) {
|
||||
return path;
|
||||
|
||||
@ -21,23 +21,11 @@ vi.mock("../../src/infra/net/fetch-guard.js", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock fs module before importing the module under test
|
||||
const mockExistsSync = vi.fn();
|
||||
const mockReadFileSync = vi.fn();
|
||||
const mockRealpathSync = vi.fn();
|
||||
const mockReaddirSync = vi.fn();
|
||||
|
||||
vi.mock("node:fs", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:fs")>();
|
||||
return {
|
||||
...actual,
|
||||
existsSync: (...args: Parameters<typeof actual.existsSync>) => mockExistsSync(...args),
|
||||
readFileSync: (...args: Parameters<typeof actual.readFileSync>) => mockReadFileSync(...args),
|
||||
realpathSync: (...args: Parameters<typeof actual.realpathSync>) => mockRealpathSync(...args),
|
||||
readdirSync: (...args: Parameters<typeof actual.readdirSync>) => mockReaddirSync(...args),
|
||||
};
|
||||
});
|
||||
|
||||
describe("extractGeminiCliCredentials", () => {
|
||||
const normalizePath = (value: string) =>
|
||||
value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
||||
@ -51,6 +39,20 @@ describe("extractGeminiCliCredentials", () => {
|
||||
|
||||
let originalPath: string | undefined;
|
||||
|
||||
async function loadCredentialsModule() {
|
||||
return await import("./oauth.credentials.js");
|
||||
}
|
||||
|
||||
async function installMockFs() {
|
||||
const { setOAuthCredentialsFsForTest } = await loadCredentialsModule();
|
||||
setOAuthCredentialsFsForTest({
|
||||
existsSync: (...args) => mockExistsSync(...args),
|
||||
readFileSync: (...args) => mockReadFileSync(...args),
|
||||
realpathSync: (...args) => mockRealpathSync(...args),
|
||||
readdirSync: (...args) => mockReaddirSync(...args),
|
||||
});
|
||||
}
|
||||
|
||||
function makeFakeLayout() {
|
||||
const binDir = join(rootDir, "fake", "bin");
|
||||
const geminiPath = join(binDir, "gemini");
|
||||
@ -157,17 +159,20 @@ describe("extractGeminiCliCredentials", () => {
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
originalPath = process.env.PATH;
|
||||
await installMockFs();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
process.env.PATH = originalPath;
|
||||
const { setOAuthCredentialsFsForTest } = await loadCredentialsModule();
|
||||
setOAuthCredentialsFsForTest();
|
||||
});
|
||||
|
||||
it("returns null when gemini binary is not in PATH", async () => {
|
||||
process.env.PATH = "/nonexistent";
|
||||
mockExistsSync.mockReturnValue(false);
|
||||
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await import("./oauth.js");
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await loadCredentialsModule();
|
||||
clearCredentialsCache();
|
||||
expect(extractGeminiCliCredentials()).toBeNull();
|
||||
});
|
||||
@ -175,7 +180,7 @@ describe("extractGeminiCliCredentials", () => {
|
||||
it("extracts credentials from oauth2.js in known path", async () => {
|
||||
installGeminiLayout({ oauth2Exists: true, oauth2Content: FAKE_OAUTH2_CONTENT });
|
||||
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await import("./oauth.js");
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await loadCredentialsModule();
|
||||
clearCredentialsCache();
|
||||
const result = extractGeminiCliCredentials();
|
||||
|
||||
@ -185,7 +190,7 @@ describe("extractGeminiCliCredentials", () => {
|
||||
it("extracts credentials when PATH entry is an npm global shim", async () => {
|
||||
installNpmShimLayout({ oauth2Exists: true, oauth2Content: FAKE_OAUTH2_CONTENT });
|
||||
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await import("./oauth.js");
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await loadCredentialsModule();
|
||||
clearCredentialsCache();
|
||||
const result = extractGeminiCliCredentials();
|
||||
|
||||
@ -195,7 +200,7 @@ describe("extractGeminiCliCredentials", () => {
|
||||
it("returns null when oauth2.js cannot be found", async () => {
|
||||
installGeminiLayout({ oauth2Exists: false, readdir: [] });
|
||||
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await import("./oauth.js");
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await loadCredentialsModule();
|
||||
clearCredentialsCache();
|
||||
expect(extractGeminiCliCredentials()).toBeNull();
|
||||
});
|
||||
@ -203,7 +208,7 @@ describe("extractGeminiCliCredentials", () => {
|
||||
it("returns null when oauth2.js lacks credentials", async () => {
|
||||
installGeminiLayout({ oauth2Exists: true, oauth2Content: "// no credentials here" });
|
||||
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await import("./oauth.js");
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await loadCredentialsModule();
|
||||
clearCredentialsCache();
|
||||
expect(extractGeminiCliCredentials()).toBeNull();
|
||||
});
|
||||
@ -211,7 +216,7 @@ describe("extractGeminiCliCredentials", () => {
|
||||
it("caches credentials after first extraction", async () => {
|
||||
installGeminiLayout({ oauth2Exists: true, oauth2Content: FAKE_OAUTH2_CONTENT });
|
||||
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await import("./oauth.js");
|
||||
const { extractGeminiCliCredentials, clearCredentialsCache } = await loadCredentialsModule();
|
||||
clearCredentialsCache();
|
||||
|
||||
// First call
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user