refactor(test): consolidate temp home + vitest setup

This commit is contained in:
Peter Steinberger
2026-01-09 16:39:02 +01:00
parent 1eecce9a15
commit 4ffbd9802a
15 changed files with 549 additions and 629 deletions

View File

@@ -4,6 +4,7 @@ import path from "node:path";
import { describe, expect, it } from "vitest";
import { withTempHome } from "../../test/helpers/temp-home.js";
import {
type AuthProfileStore,
CLAUDE_CLI_PROFILE_ID,
@@ -13,40 +14,6 @@ import {
resolveAuthProfileOrder,
} from "./auth-profiles.js";
const HOME_ENV_KEYS = ["HOME", "USERPROFILE", "HOMEDRIVE", "HOMEPATH"] as const;
type HomeEnvSnapshot = Record<
(typeof HOME_ENV_KEYS)[number],
string | undefined
>;
const snapshotHomeEnv = (): HomeEnvSnapshot => ({
HOME: process.env.HOME,
USERPROFILE: process.env.USERPROFILE,
HOMEDRIVE: process.env.HOMEDRIVE,
HOMEPATH: process.env.HOMEPATH,
});
const restoreHomeEnv = (snapshot: HomeEnvSnapshot) => {
for (const key of HOME_ENV_KEYS) {
const value = snapshot[key];
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
};
const setTempHome = (tempHome: string) => {
process.env.HOME = tempHome;
if (process.platform === "win32") {
process.env.USERPROFILE = tempHome;
const root = path.parse(tempHome).root;
process.env.HOMEDRIVE = root.replace(/\\$/, "");
process.env.HOMEPATH = tempHome.slice(root.length - 1);
}
};
describe("resolveAuthProfileOrder", () => {
const store: AuthProfileStore = {
version: 1,
@@ -431,259 +398,259 @@ describe("auth profile cooldowns", () => {
});
describe("external CLI credential sync", () => {
it("syncs Claude CLI credentials into anthropic:claude-cli", () => {
it("syncs Claude CLI credentials into anthropic:claude-cli", async () => {
const agentDir = fs.mkdtempSync(
path.join(os.tmpdir(), "clawdbot-cli-sync-"),
);
const originalHome = snapshotHomeEnv();
try {
// Create a temp home with Claude CLI credentials
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
setTempHome(tempHome);
// Create Claude CLI credentials
const claudeDir = path.join(tempHome, ".claude");
fs.mkdirSync(claudeDir, { recursive: true });
const claudeCreds = {
claudeAiOauth: {
accessToken: "fresh-access-token",
refreshToken: "fresh-refresh-token",
expiresAt: Date.now() + 60 * 60 * 1000, // 1 hour from now
},
};
fs.writeFileSync(
path.join(claudeDir, ".credentials.json"),
JSON.stringify(claudeCreds),
);
// Create empty auth-profiles.json
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {
"anthropic:default": {
type: "api_key",
provider: "anthropic",
key: "sk-default",
await withTempHome(
async (tempHome) => {
// Create Claude CLI credentials
const claudeDir = path.join(tempHome, ".claude");
fs.mkdirSync(claudeDir, { recursive: true });
const claudeCreds = {
claudeAiOauth: {
accessToken: "fresh-access-token",
refreshToken: "fresh-refresh-token",
expiresAt: Date.now() + 60 * 60 * 1000, // 1 hour from now
},
},
}),
);
};
fs.writeFileSync(
path.join(claudeDir, ".credentials.json"),
JSON.stringify(claudeCreds),
);
// Load the store - should sync from CLI
const store = ensureAuthProfileStore(agentDir);
// Create empty auth-profiles.json
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {
"anthropic:default": {
type: "api_key",
provider: "anthropic",
key: "sk-default",
},
},
}),
);
expect(store.profiles["anthropic:default"]).toBeDefined();
expect((store.profiles["anthropic:default"] as { key: string }).key).toBe(
"sk-default",
// Load the store - should sync from CLI
const store = ensureAuthProfileStore(agentDir);
expect(store.profiles["anthropic:default"]).toBeDefined();
expect(
(store.profiles["anthropic:default"] as { key: string }).key,
).toBe("sk-default");
expect(store.profiles[CLAUDE_CLI_PROFILE_ID]).toBeDefined();
expect(
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { token: string }).token,
).toBe("fresh-access-token");
expect(
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { expires: number })
.expires,
).toBeGreaterThan(Date.now());
},
{ prefix: "clawdbot-home-" },
);
expect(store.profiles[CLAUDE_CLI_PROFILE_ID]).toBeDefined();
expect(
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { token: string }).token,
).toBe("fresh-access-token");
expect(
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { expires: number }).expires,
).toBeGreaterThan(Date.now());
} finally {
restoreHomeEnv(originalHome);
fs.rmSync(agentDir, { recursive: true, force: true });
}
});
it("syncs Codex CLI credentials into openai-codex:codex-cli", () => {
it("syncs Codex CLI credentials into openai-codex:codex-cli", async () => {
const agentDir = fs.mkdtempSync(
path.join(os.tmpdir(), "clawdbot-codex-sync-"),
);
const originalHome = snapshotHomeEnv();
try {
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
setTempHome(tempHome);
await withTempHome(
async (tempHome) => {
// Create Codex CLI credentials
const codexDir = path.join(tempHome, ".codex");
fs.mkdirSync(codexDir, { recursive: true });
const codexCreds = {
tokens: {
access_token: "codex-access-token",
refresh_token: "codex-refresh-token",
},
};
const codexAuthPath = path.join(codexDir, "auth.json");
fs.writeFileSync(codexAuthPath, JSON.stringify(codexCreds));
// Create Codex CLI credentials
const codexDir = path.join(tempHome, ".codex");
fs.mkdirSync(codexDir, { recursive: true });
const codexCreds = {
tokens: {
access_token: "codex-access-token",
refresh_token: "codex-refresh-token",
// Create empty auth-profiles.json
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {},
}),
);
const store = ensureAuthProfileStore(agentDir);
expect(store.profiles[CODEX_CLI_PROFILE_ID]).toBeDefined();
expect(
(store.profiles[CODEX_CLI_PROFILE_ID] as { access: string }).access,
).toBe("codex-access-token");
},
};
const codexAuthPath = path.join(codexDir, "auth.json");
fs.writeFileSync(codexAuthPath, JSON.stringify(codexCreds));
// Create empty auth-profiles.json
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {},
}),
{ prefix: "clawdbot-home-" },
);
const store = ensureAuthProfileStore(agentDir);
expect(store.profiles[CODEX_CLI_PROFILE_ID]).toBeDefined();
expect(
(store.profiles[CODEX_CLI_PROFILE_ID] as { access: string }).access,
).toBe("codex-access-token");
} finally {
restoreHomeEnv(originalHome);
fs.rmSync(agentDir, { recursive: true, force: true });
}
});
it("does not overwrite API keys when syncing external CLI creds", () => {
it("does not overwrite API keys when syncing external CLI creds", async () => {
const agentDir = fs.mkdtempSync(
path.join(os.tmpdir(), "clawdbot-no-overwrite-"),
);
const originalHome = snapshotHomeEnv();
try {
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
setTempHome(tempHome);
// Create Claude CLI credentials
const claudeDir = path.join(tempHome, ".claude");
fs.mkdirSync(claudeDir, { recursive: true });
const claudeCreds = {
claudeAiOauth: {
accessToken: "cli-access",
refreshToken: "cli-refresh",
expiresAt: Date.now() + 30 * 60 * 1000,
},
};
fs.writeFileSync(
path.join(claudeDir, ".credentials.json"),
JSON.stringify(claudeCreds),
);
// Create auth-profiles.json with an API key
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {
"anthropic:default": {
type: "api_key",
provider: "anthropic",
key: "sk-store",
await withTempHome(
async (tempHome) => {
// Create Claude CLI credentials
const claudeDir = path.join(tempHome, ".claude");
fs.mkdirSync(claudeDir, { recursive: true });
const claudeCreds = {
claudeAiOauth: {
accessToken: "cli-access",
refreshToken: "cli-refresh",
expiresAt: Date.now() + 30 * 60 * 1000,
},
},
}),
);
};
fs.writeFileSync(
path.join(claudeDir, ".credentials.json"),
JSON.stringify(claudeCreds),
);
const store = ensureAuthProfileStore(agentDir);
// Create auth-profiles.json with an API key
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {
"anthropic:default": {
type: "api_key",
provider: "anthropic",
key: "sk-store",
},
},
}),
);
// Should keep the store's API key and still add the CLI profile.
expect((store.profiles["anthropic:default"] as { key: string }).key).toBe(
"sk-store",
const store = ensureAuthProfileStore(agentDir);
// Should keep the store's API key and still add the CLI profile.
expect(
(store.profiles["anthropic:default"] as { key: string }).key,
).toBe("sk-store");
expect(store.profiles[CLAUDE_CLI_PROFILE_ID]).toBeDefined();
},
{ prefix: "clawdbot-home-" },
);
expect(store.profiles[CLAUDE_CLI_PROFILE_ID]).toBeDefined();
} finally {
restoreHomeEnv(originalHome);
fs.rmSync(agentDir, { recursive: true, force: true });
}
});
it("does not overwrite fresher store token with older Claude CLI credentials", () => {
it("does not overwrite fresher store token with older Claude CLI credentials", async () => {
const agentDir = fs.mkdtempSync(
path.join(os.tmpdir(), "clawdbot-cli-no-downgrade-"),
);
const originalHome = snapshotHomeEnv();
try {
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
setTempHome(tempHome);
await withTempHome(
async (tempHome) => {
const claudeDir = path.join(tempHome, ".claude");
fs.mkdirSync(claudeDir, { recursive: true });
fs.writeFileSync(
path.join(claudeDir, ".credentials.json"),
JSON.stringify({
claudeAiOauth: {
accessToken: "cli-access",
refreshToken: "cli-refresh",
expiresAt: Date.now() + 30 * 60 * 1000,
},
}),
);
const claudeDir = path.join(tempHome, ".claude");
fs.mkdirSync(claudeDir, { recursive: true });
fs.writeFileSync(
path.join(claudeDir, ".credentials.json"),
JSON.stringify({
claudeAiOauth: {
accessToken: "cli-access",
refreshToken: "cli-refresh",
expiresAt: Date.now() + 30 * 60 * 1000,
},
}),
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {
[CLAUDE_CLI_PROFILE_ID]: {
type: "token",
provider: "anthropic",
token: "store-access",
expires: Date.now() + 60 * 60 * 1000,
},
},
}),
);
const store = ensureAuthProfileStore(agentDir);
expect(
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { token: string }).token,
).toBe("store-access");
},
{ prefix: "clawdbot-home-" },
);
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {
[CLAUDE_CLI_PROFILE_ID]: {
type: "token",
provider: "anthropic",
token: "store-access",
expires: Date.now() + 60 * 60 * 1000,
},
},
}),
);
const store = ensureAuthProfileStore(agentDir);
expect(
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { token: string }).token,
).toBe("store-access");
} finally {
restoreHomeEnv(originalHome);
fs.rmSync(agentDir, { recursive: true, force: true });
}
});
it("updates codex-cli profile when Codex CLI refresh token changes", () => {
it("updates codex-cli profile when Codex CLI refresh token changes", async () => {
const agentDir = fs.mkdtempSync(
path.join(os.tmpdir(), "clawdbot-codex-refresh-sync-"),
);
const originalHome = snapshotHomeEnv();
try {
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
setTempHome(tempHome);
await withTempHome(
async (tempHome) => {
const codexDir = path.join(tempHome, ".codex");
fs.mkdirSync(codexDir, { recursive: true });
const codexAuthPath = path.join(codexDir, "auth.json");
fs.writeFileSync(
codexAuthPath,
JSON.stringify({
tokens: {
access_token: "same-access",
refresh_token: "new-refresh",
},
}),
);
fs.utimesSync(codexAuthPath, new Date(), new Date());
const codexDir = path.join(tempHome, ".codex");
fs.mkdirSync(codexDir, { recursive: true });
const codexAuthPath = path.join(codexDir, "auth.json");
fs.writeFileSync(
codexAuthPath,
JSON.stringify({
tokens: { access_token: "same-access", refresh_token: "new-refresh" },
}),
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {
[CODEX_CLI_PROFILE_ID]: {
type: "oauth",
provider: "openai-codex",
access: "same-access",
refresh: "old-refresh",
expires: Date.now() - 1000,
},
},
}),
);
const store = ensureAuthProfileStore(agentDir);
expect(
(store.profiles[CODEX_CLI_PROFILE_ID] as { refresh: string })
.refresh,
).toBe("new-refresh");
},
{ prefix: "clawdbot-home-" },
);
fs.utimesSync(codexAuthPath, new Date(), new Date());
const authPath = path.join(agentDir, "auth-profiles.json");
fs.writeFileSync(
authPath,
JSON.stringify({
version: 1,
profiles: {
[CODEX_CLI_PROFILE_ID]: {
type: "oauth",
provider: "openai-codex",
access: "same-access",
refresh: "old-refresh",
expires: Date.now() - 1000,
},
},
}),
);
const store = ensureAuthProfileStore(agentDir);
expect(
(store.profiles[CODEX_CLI_PROFILE_ID] as { refresh: string }).refresh,
).toBe("new-refresh");
} finally {
restoreHomeEnv(originalHome);
fs.rmSync(agentDir, { recursive: true, force: true });
}
});

View File

@@ -1,20 +1,12 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
import type { ClawdbotConfig } from "../config/config.js";
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-models-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
return await fn(base);
} finally {
process.env.HOME = previousHome;
await fs.rm(base, { recursive: true, force: true });
}
return withTempHomeBase(fn, { prefix: "clawdbot-models-" });
}
const MODELS_CONFIG: ClawdbotConfig = {