refactor(test): consolidate temp home + vitest setup
This commit is contained in:
37
.github/workflows/workflow-sanity.yml
vendored
Normal file
37
.github/workflows/workflow-sanity.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Workflow Sanity
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
no-tabs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Fail on tabs in workflow files
|
||||||
|
run: |
|
||||||
|
python - <<'PY'
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
root = pathlib.Path(".github/workflows")
|
||||||
|
bad: list[str] = []
|
||||||
|
for path in sorted(root.rglob("*.yml")):
|
||||||
|
if b"\t" in path.read_bytes():
|
||||||
|
bad.append(str(path))
|
||||||
|
|
||||||
|
for path in sorted(root.rglob("*.yaml")):
|
||||||
|
if b"\t" in path.read_bytes():
|
||||||
|
bad.append(str(path))
|
||||||
|
|
||||||
|
if bad:
|
||||||
|
print("Tabs found in workflow file(s):")
|
||||||
|
for path in bad:
|
||||||
|
print(f"- {path}")
|
||||||
|
sys.exit(1)
|
||||||
|
PY
|
||||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
|||||||
|
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome } from "../../test/helpers/temp-home.js";
|
||||||
import {
|
import {
|
||||||
type AuthProfileStore,
|
type AuthProfileStore,
|
||||||
CLAUDE_CLI_PROFILE_ID,
|
CLAUDE_CLI_PROFILE_ID,
|
||||||
@@ -13,40 +14,6 @@ import {
|
|||||||
resolveAuthProfileOrder,
|
resolveAuthProfileOrder,
|
||||||
} from "./auth-profiles.js";
|
} 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", () => {
|
describe("resolveAuthProfileOrder", () => {
|
||||||
const store: AuthProfileStore = {
|
const store: AuthProfileStore = {
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -431,17 +398,14 @@ describe("auth profile cooldowns", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("external CLI credential sync", () => {
|
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(
|
const agentDir = fs.mkdtempSync(
|
||||||
path.join(os.tmpdir(), "clawdbot-cli-sync-"),
|
path.join(os.tmpdir(), "clawdbot-cli-sync-"),
|
||||||
);
|
);
|
||||||
const originalHome = snapshotHomeEnv();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a temp home with Claude CLI credentials
|
// Create a temp home with Claude CLI credentials
|
||||||
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
|
await withTempHome(
|
||||||
setTempHome(tempHome);
|
async (tempHome) => {
|
||||||
|
|
||||||
// Create Claude CLI credentials
|
// Create Claude CLI credentials
|
||||||
const claudeDir = path.join(tempHome, ".claude");
|
const claudeDir = path.join(tempHome, ".claude");
|
||||||
fs.mkdirSync(claudeDir, { recursive: true });
|
fs.mkdirSync(claudeDir, { recursive: true });
|
||||||
@@ -477,32 +441,32 @@ describe("external CLI credential sync", () => {
|
|||||||
const store = ensureAuthProfileStore(agentDir);
|
const store = ensureAuthProfileStore(agentDir);
|
||||||
|
|
||||||
expect(store.profiles["anthropic:default"]).toBeDefined();
|
expect(store.profiles["anthropic:default"]).toBeDefined();
|
||||||
expect((store.profiles["anthropic:default"] as { key: string }).key).toBe(
|
expect(
|
||||||
"sk-default",
|
(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]).toBeDefined();
|
||||||
expect(
|
expect(
|
||||||
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { token: string }).token,
|
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { token: string }).token,
|
||||||
).toBe("fresh-access-token");
|
).toBe("fresh-access-token");
|
||||||
expect(
|
expect(
|
||||||
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { expires: number }).expires,
|
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { expires: number })
|
||||||
|
.expires,
|
||||||
).toBeGreaterThan(Date.now());
|
).toBeGreaterThan(Date.now());
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-home-" },
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
restoreHomeEnv(originalHome);
|
|
||||||
fs.rmSync(agentDir, { recursive: true, force: true });
|
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(
|
const agentDir = fs.mkdtempSync(
|
||||||
path.join(os.tmpdir(), "clawdbot-codex-sync-"),
|
path.join(os.tmpdir(), "clawdbot-codex-sync-"),
|
||||||
);
|
);
|
||||||
const originalHome = snapshotHomeEnv();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
|
await withTempHome(
|
||||||
setTempHome(tempHome);
|
async (tempHome) => {
|
||||||
|
|
||||||
// Create Codex CLI credentials
|
// Create Codex CLI credentials
|
||||||
const codexDir = path.join(tempHome, ".codex");
|
const codexDir = path.join(tempHome, ".codex");
|
||||||
fs.mkdirSync(codexDir, { recursive: true });
|
fs.mkdirSync(codexDir, { recursive: true });
|
||||||
@@ -531,22 +495,21 @@ describe("external CLI credential sync", () => {
|
|||||||
expect(
|
expect(
|
||||||
(store.profiles[CODEX_CLI_PROFILE_ID] as { access: string }).access,
|
(store.profiles[CODEX_CLI_PROFILE_ID] as { access: string }).access,
|
||||||
).toBe("codex-access-token");
|
).toBe("codex-access-token");
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-home-" },
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
restoreHomeEnv(originalHome);
|
|
||||||
fs.rmSync(agentDir, { recursive: true, force: true });
|
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(
|
const agentDir = fs.mkdtempSync(
|
||||||
path.join(os.tmpdir(), "clawdbot-no-overwrite-"),
|
path.join(os.tmpdir(), "clawdbot-no-overwrite-"),
|
||||||
);
|
);
|
||||||
const originalHome = snapshotHomeEnv();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
|
await withTempHome(
|
||||||
setTempHome(tempHome);
|
async (tempHome) => {
|
||||||
|
|
||||||
// Create Claude CLI credentials
|
// Create Claude CLI credentials
|
||||||
const claudeDir = path.join(tempHome, ".claude");
|
const claudeDir = path.join(tempHome, ".claude");
|
||||||
fs.mkdirSync(claudeDir, { recursive: true });
|
fs.mkdirSync(claudeDir, { recursive: true });
|
||||||
@@ -581,26 +544,25 @@ describe("external CLI credential sync", () => {
|
|||||||
const store = ensureAuthProfileStore(agentDir);
|
const store = ensureAuthProfileStore(agentDir);
|
||||||
|
|
||||||
// Should keep the store's API key and still add the CLI profile.
|
// Should keep the store's API key and still add the CLI profile.
|
||||||
expect((store.profiles["anthropic:default"] as { key: string }).key).toBe(
|
expect(
|
||||||
"sk-store",
|
(store.profiles["anthropic:default"] as { key: string }).key,
|
||||||
);
|
).toBe("sk-store");
|
||||||
expect(store.profiles[CLAUDE_CLI_PROFILE_ID]).toBeDefined();
|
expect(store.profiles[CLAUDE_CLI_PROFILE_ID]).toBeDefined();
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-home-" },
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
restoreHomeEnv(originalHome);
|
|
||||||
fs.rmSync(agentDir, { recursive: true, force: true });
|
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(
|
const agentDir = fs.mkdtempSync(
|
||||||
path.join(os.tmpdir(), "clawdbot-cli-no-downgrade-"),
|
path.join(os.tmpdir(), "clawdbot-cli-no-downgrade-"),
|
||||||
);
|
);
|
||||||
const originalHome = snapshotHomeEnv();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
|
await withTempHome(
|
||||||
setTempHome(tempHome);
|
async (tempHome) => {
|
||||||
|
|
||||||
const claudeDir = path.join(tempHome, ".claude");
|
const claudeDir = path.join(tempHome, ".claude");
|
||||||
fs.mkdirSync(claudeDir, { recursive: true });
|
fs.mkdirSync(claudeDir, { recursive: true });
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
@@ -634,29 +596,31 @@ describe("external CLI credential sync", () => {
|
|||||||
expect(
|
expect(
|
||||||
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { token: string }).token,
|
(store.profiles[CLAUDE_CLI_PROFILE_ID] as { token: string }).token,
|
||||||
).toBe("store-access");
|
).toBe("store-access");
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-home-" },
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
restoreHomeEnv(originalHome);
|
|
||||||
fs.rmSync(agentDir, { recursive: true, force: true });
|
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(
|
const agentDir = fs.mkdtempSync(
|
||||||
path.join(os.tmpdir(), "clawdbot-codex-refresh-sync-"),
|
path.join(os.tmpdir(), "clawdbot-codex-refresh-sync-"),
|
||||||
);
|
);
|
||||||
const originalHome = snapshotHomeEnv();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-home-"));
|
await withTempHome(
|
||||||
setTempHome(tempHome);
|
async (tempHome) => {
|
||||||
|
|
||||||
const codexDir = path.join(tempHome, ".codex");
|
const codexDir = path.join(tempHome, ".codex");
|
||||||
fs.mkdirSync(codexDir, { recursive: true });
|
fs.mkdirSync(codexDir, { recursive: true });
|
||||||
const codexAuthPath = path.join(codexDir, "auth.json");
|
const codexAuthPath = path.join(codexDir, "auth.json");
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
codexAuthPath,
|
codexAuthPath,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
tokens: { access_token: "same-access", refresh_token: "new-refresh" },
|
tokens: {
|
||||||
|
access_token: "same-access",
|
||||||
|
refresh_token: "new-refresh",
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
fs.utimesSync(codexAuthPath, new Date(), new Date());
|
fs.utimesSync(codexAuthPath, new Date(), new Date());
|
||||||
@@ -680,10 +644,13 @@ describe("external CLI credential sync", () => {
|
|||||||
|
|
||||||
const store = ensureAuthProfileStore(agentDir);
|
const store = ensureAuthProfileStore(agentDir);
|
||||||
expect(
|
expect(
|
||||||
(store.profiles[CODEX_CLI_PROFILE_ID] as { refresh: string }).refresh,
|
(store.profiles[CODEX_CLI_PROFILE_ID] as { refresh: string })
|
||||||
|
.refresh,
|
||||||
).toBe("new-refresh");
|
).toBe("new-refresh");
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-home-" },
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
restoreHomeEnv(originalHome);
|
|
||||||
fs.rmSync(agentDir, { recursive: true, force: true });
|
fs.rmSync(agentDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
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";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-models-"));
|
return withTempHomeBase(fn, { prefix: "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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MODELS_CONFIG: ClawdbotConfig = {
|
const MODELS_CONFIG: ClawdbotConfig = {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import fs from "node:fs/promises";
|
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
import { loadModelCatalog } from "../agents/model-catalog.js";
|
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||||
import { getReplyFromConfig } from "./reply.js";
|
import { getReplyFromConfig } from "./reply.js";
|
||||||
@@ -22,15 +21,7 @@ vi.mock("../agents/model-catalog.js", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-stream-"));
|
return withTempHomeBase(fn, { prefix: "clawdbot-stream-" });
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("block streaming", () => {
|
describe("block streaming", () => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
import { loadModelCatalog } from "../agents/model-catalog.js";
|
import { loadModelCatalog } from "../agents/model-catalog.js";
|
||||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||||
import {
|
import {
|
||||||
@@ -28,28 +28,30 @@ vi.mock("../agents/model-catalog.js", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-reply-"));
|
return withTempHomeBase(
|
||||||
const previousHome = process.env.HOME;
|
async (home) => {
|
||||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
const previousAgentDir = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
const previousPiAgentDir = process.env.PI_CODING_AGENT_DIR;
|
||||||
process.env.HOME = base;
|
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
|
||||||
process.env.CLAWDBOT_STATE_DIR = path.join(base, ".clawdbot");
|
process.env.CLAWDBOT_AGENT_DIR = path.join(home, ".clawdbot", "agent");
|
||||||
process.env.CLAWDBOT_AGENT_DIR = path.join(base, ".clawdbot", "agent");
|
|
||||||
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
process.env.PI_CODING_AGENT_DIR = process.env.CLAWDBOT_AGENT_DIR;
|
||||||
try {
|
try {
|
||||||
return await fn(base);
|
return await fn(home);
|
||||||
} finally {
|
} finally {
|
||||||
process.env.HOME = previousHome;
|
if (previousStateDir === undefined)
|
||||||
if (previousStateDir === undefined) delete process.env.CLAWDBOT_STATE_DIR;
|
delete process.env.CLAWDBOT_STATE_DIR;
|
||||||
else process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
else process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
||||||
if (previousAgentDir === undefined) delete process.env.CLAWDBOT_AGENT_DIR;
|
if (previousAgentDir === undefined)
|
||||||
|
delete process.env.CLAWDBOT_AGENT_DIR;
|
||||||
else process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
|
else process.env.CLAWDBOT_AGENT_DIR = previousAgentDir;
|
||||||
if (previousPiAgentDir === undefined)
|
if (previousPiAgentDir === undefined)
|
||||||
delete process.env.PI_CODING_AGENT_DIR;
|
delete process.env.PI_CODING_AGENT_DIR;
|
||||||
else process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
else process.env.PI_CODING_AGENT_DIR = previousPiAgentDir;
|
||||||
await fs.rm(base, { recursive: true, force: true });
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-reply-" },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("directive behavior", () => {
|
describe("directive behavior", () => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import fs from "node:fs/promises";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
|
|
||||||
const runEmbeddedPiAgentMock = vi.fn();
|
const runEmbeddedPiAgentMock = vi.fn();
|
||||||
|
|
||||||
vi.mock("../agents/model-fallback.js", () => ({
|
vi.mock("../agents/model-fallback.js", () => ({
|
||||||
@@ -43,16 +43,13 @@ vi.mock("../web/session.js", () => webMocks);
|
|||||||
import { getReplyFromConfig } from "./reply.js";
|
import { getReplyFromConfig } from "./reply.js";
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(join(tmpdir(), "clawdbot-typing-"));
|
return withTempHomeBase(
|
||||||
const previousHome = process.env.HOME;
|
async (home) => {
|
||||||
process.env.HOME = base;
|
|
||||||
try {
|
|
||||||
runEmbeddedPiAgentMock.mockClear();
|
runEmbeddedPiAgentMock.mockClear();
|
||||||
return await fn(base);
|
return await fn(home);
|
||||||
} finally {
|
},
|
||||||
process.env.HOME = previousHome;
|
{ prefix: "clawdbot-typing-" },
|
||||||
await fs.rm(base, { recursive: true, force: true });
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCfg(home: string) {
|
function makeCfg(home: string) {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import fs from "node:fs/promises";
|
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||||
import { getReplyFromConfig } from "./reply.js";
|
import { getReplyFromConfig } from "./reply.js";
|
||||||
|
|
||||||
@@ -28,27 +27,26 @@ function makeResult(text: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-media-note-"));
|
return withTempHomeBase(
|
||||||
const previousHome = process.env.HOME;
|
async (home) => {
|
||||||
const previousBundledSkills = process.env.CLAWDBOT_BUNDLED_SKILLS_DIR;
|
const previousBundledSkills = process.env.CLAWDBOT_BUNDLED_SKILLS_DIR;
|
||||||
process.env.HOME = base;
|
process.env.CLAWDBOT_BUNDLED_SKILLS_DIR = path.join(
|
||||||
process.env.CLAWDBOT_BUNDLED_SKILLS_DIR = path.join(base, "bundled-skills");
|
home,
|
||||||
|
"bundled-skills",
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
return await fn(base);
|
return await fn(home);
|
||||||
} finally {
|
} finally {
|
||||||
process.env.HOME = previousHome;
|
|
||||||
if (previousBundledSkills === undefined) {
|
if (previousBundledSkills === undefined) {
|
||||||
delete process.env.CLAWDBOT_BUNDLED_SKILLS_DIR;
|
delete process.env.CLAWDBOT_BUNDLED_SKILLS_DIR;
|
||||||
} else {
|
} else {
|
||||||
process.env.CLAWDBOT_BUNDLED_SKILLS_DIR = previousBundledSkills;
|
process.env.CLAWDBOT_BUNDLED_SKILLS_DIR = previousBundledSkills;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
await fs.rm(base, { recursive: true, force: true });
|
|
||||||
} catch {
|
|
||||||
// ignore cleanup failures in tests
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-media-note-" },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCfg(home: string) {
|
function makeCfg(home: string) {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import fs from "node:fs/promises";
|
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
import {
|
import {
|
||||||
isEmbeddedPiRunActive,
|
isEmbeddedPiRunActive,
|
||||||
isEmbeddedPiRunStreaming,
|
isEmbeddedPiRunStreaming,
|
||||||
@@ -32,20 +31,13 @@ function makeResult(text: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-queue-"));
|
return withTempHomeBase(
|
||||||
const previousHome = process.env.HOME;
|
async (home) => {
|
||||||
process.env.HOME = base;
|
|
||||||
try {
|
|
||||||
vi.mocked(runEmbeddedPiAgent).mockReset();
|
vi.mocked(runEmbeddedPiAgent).mockReset();
|
||||||
return await fn(base);
|
return await fn(home);
|
||||||
} finally {
|
},
|
||||||
process.env.HOME = previousHome;
|
{ prefix: "clawdbot-queue-" },
|
||||||
try {
|
);
|
||||||
await fs.rm(base, { recursive: true, force: true });
|
|
||||||
} catch {
|
|
||||||
// ignore cleanup failures in tests
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCfg(home: string, queue?: Record<string, unknown>) {
|
function makeCfg(home: string, queue?: Record<string, unknown>) {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { tmpdir } from "node:os";
|
|||||||
import { basename, join } from "node:path";
|
import { basename, join } from "node:path";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
|
|
||||||
vi.mock("../agents/pi-embedded.js", () => ({
|
vi.mock("../agents/pi-embedded.js", () => ({
|
||||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||||
compactEmbeddedPiSession: vi.fn(),
|
compactEmbeddedPiSession: vi.fn(),
|
||||||
@@ -51,37 +53,26 @@ const webMocks = vi.hoisted(() => ({
|
|||||||
vi.mock("../web/session.js", () => webMocks);
|
vi.mock("../web/session.js", () => webMocks);
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(join(tmpdir(), "clawdbot-triggers-"));
|
return withTempHomeBase(
|
||||||
const previousHome = process.env.HOME;
|
async (home) => {
|
||||||
const previousUserProfile = process.env.USERPROFILE;
|
|
||||||
const previousHomeDrive = process.env.HOMEDRIVE;
|
|
||||||
const previousHomePath = process.env.HOMEPATH;
|
|
||||||
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
const previousClawdisStateDir = process.env.CLAWDIS_STATE_DIR;
|
const previousClawdisStateDir = process.env.CLAWDIS_STATE_DIR;
|
||||||
process.env.HOME = base;
|
process.env.CLAWDBOT_STATE_DIR = join(home, ".clawdbot");
|
||||||
process.env.CLAWDBOT_STATE_DIR = join(base, ".clawdbot");
|
process.env.CLAWDIS_STATE_DIR = join(home, ".clawdbot");
|
||||||
process.env.CLAWDIS_STATE_DIR = join(base, ".clawdbot");
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
process.env.USERPROFILE = base;
|
|
||||||
const driveMatch = base.match(/^([A-Za-z]:)(.*)$/);
|
|
||||||
if (driveMatch) {
|
|
||||||
process.env.HOMEDRIVE = driveMatch[1];
|
|
||||||
process.env.HOMEPATH = driveMatch[2] || "\\";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
vi.mocked(runEmbeddedPiAgent).mockClear();
|
vi.mocked(runEmbeddedPiAgent).mockClear();
|
||||||
vi.mocked(abortEmbeddedPiRun).mockClear();
|
vi.mocked(abortEmbeddedPiRun).mockClear();
|
||||||
return await fn(base);
|
return await fn(home);
|
||||||
} finally {
|
} finally {
|
||||||
process.env.HOME = previousHome;
|
if (previousStateDir === undefined) delete process.env.CLAWDBOT_STATE_DIR;
|
||||||
process.env.USERPROFILE = previousUserProfile;
|
else process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
||||||
process.env.HOMEDRIVE = previousHomeDrive;
|
if (previousClawdisStateDir === undefined)
|
||||||
process.env.HOMEPATH = previousHomePath;
|
delete process.env.CLAWDIS_STATE_DIR;
|
||||||
process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
else process.env.CLAWDIS_STATE_DIR = previousClawdisStateDir;
|
||||||
process.env.CLAWDIS_STATE_DIR = previousClawdisStateDir;
|
|
||||||
await fs.rm(base, { recursive: true, force: true });
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-triggers-" },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCfg(home: string) {
|
function makeCfg(home: string) {
|
||||||
@@ -320,7 +311,7 @@ describe("trigger handling", () => {
|
|||||||
);
|
);
|
||||||
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
const text = Array.isArray(res) ? res[0]?.text : res?.text;
|
||||||
expect(text).toContain("api-key");
|
expect(text).toContain("api-key");
|
||||||
expect(text).toContain("…");
|
expect(text).toMatch(/…|\.{3}/);
|
||||||
expect(text).toContain("(anthropic:work)");
|
expect(text).toContain("(anthropic:work)");
|
||||||
expect(text).not.toContain("mixed");
|
expect(text).not.toContain("mixed");
|
||||||
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedPiAgent).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -1,44 +1,10 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { withTempHome } from "../../test/helpers/temp-home.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { buildStatusMessage } from "./status.js";
|
import { buildStatusMessage } from "./status.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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
@@ -260,10 +226,8 @@ describe("buildStatusMessage", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("prefers cached prompt tokens from the session log", async () => {
|
it("prefers cached prompt tokens from the session log", async () => {
|
||||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-status-"));
|
await withTempHome(
|
||||||
const previousHome = snapshotHomeEnv();
|
async (dir) => {
|
||||||
setTempHome(dir);
|
|
||||||
try {
|
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
const { buildStatusMessage: buildStatusMessageDynamic } = await import(
|
const { buildStatusMessage: buildStatusMessageDynamic } = await import(
|
||||||
"./status.js"
|
"./status.js"
|
||||||
@@ -320,9 +284,8 @@ describe("buildStatusMessage", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(text).toContain("Context: 1.0k/32k");
|
expect(text).toContain("Context: 1.0k/32k");
|
||||||
} finally {
|
},
|
||||||
restoreHomeEnv(previousHome);
|
{ prefix: "clawdbot-status-" },
|
||||||
fs.rmSync(dir, { recursive: true, force: true });
|
);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +10,8 @@ import {
|
|||||||
vi,
|
vi,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
|
|
||||||
vi.mock("../agents/pi-embedded.js", () => ({
|
vi.mock("../agents/pi-embedded.js", () => ({
|
||||||
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
abortEmbeddedPiRun: vi.fn().mockReturnValue(false),
|
||||||
runEmbeddedPiAgent: vi.fn(),
|
runEmbeddedPiAgent: vi.fn(),
|
||||||
@@ -39,15 +40,7 @@ const runtime: RuntimeEnv = {
|
|||||||
const configSpy = vi.spyOn(configModule, "loadConfig");
|
const configSpy = vi.spyOn(configModule, "loadConfig");
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-agent-"));
|
return withTempHomeBase(fn, { prefix: "clawdbot-agent-" });
|
||||||
const previousHome = process.env.HOME;
|
|
||||||
process.env.HOME = base;
|
|
||||||
try {
|
|
||||||
return await fn(base);
|
|
||||||
} finally {
|
|
||||||
process.env.HOME = previousHome;
|
|
||||||
fs.rmSync(base, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockConfig(
|
function mockConfig(
|
||||||
|
|||||||
@@ -1,41 +1,13 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-config-"));
|
return withTempHomeBase(fn, { prefix: "clawdbot-config-" });
|
||||||
const previousHome = process.env.HOME;
|
|
||||||
const previousUserProfile = process.env.USERPROFILE;
|
|
||||||
const previousHomeDrive = process.env.HOMEDRIVE;
|
|
||||||
const previousHomePath = process.env.HOMEPATH;
|
|
||||||
process.env.HOME = base;
|
|
||||||
process.env.USERPROFILE = base;
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
const parsed = path.parse(base);
|
|
||||||
process.env.HOMEDRIVE = parsed.root.replace(/\\$/, "");
|
|
||||||
process.env.HOMEPATH = base.slice(Math.max(parsed.root.length - 1, 0));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return await fn(base);
|
|
||||||
} finally {
|
|
||||||
process.env.HOME = previousHome;
|
|
||||||
process.env.USERPROFILE = previousUserProfile;
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
if (previousHomeDrive === undefined) {
|
|
||||||
delete process.env.HOMEDRIVE;
|
|
||||||
} else {
|
|
||||||
process.env.HOMEDRIVE = previousHomeDrive;
|
|
||||||
}
|
|
||||||
if (previousHomePath === undefined) {
|
|
||||||
delete process.env.HOMEPATH;
|
|
||||||
} else {
|
|
||||||
process.env.HOMEPATH = previousHomePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await fs.rm(base, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1277,7 +1249,7 @@ describe("multi-agent agentDir validation", () => {
|
|||||||
it("rejects shared agents.list agentDir", async () => {
|
it("rejects shared agents.list agentDir", async () => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
const { validateConfigObject } = await import("./config.js");
|
const { validateConfigObject } = await import("./config.js");
|
||||||
const shared = path.join(os.tmpdir(), "clawdbot-shared-agentdir");
|
const shared = path.join(tmpdir(), "clawdbot-shared-agentdir");
|
||||||
const res = validateConfigObject({
|
const res = validateConfigObject({
|
||||||
agents: {
|
agents: {
|
||||||
list: [
|
list: [
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js";
|
||||||
import type { CliDeps } from "../cli/deps.js";
|
import type { CliDeps } from "../cli/deps.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import type { CronJob } from "./types.js";
|
import type { CronJob } from "./types.js";
|
||||||
@@ -23,15 +23,7 @@ import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
|||||||
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
|
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
|
||||||
|
|
||||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-cron-"));
|
return withTempHomeBase(fn, { prefix: "clawdbot-cron-" });
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeSessionStore(home: string) {
|
async function writeSessionStore(home: string) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import os from "node:os";
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { withTempHome } from "../../test/helpers/temp-home.js";
|
||||||
import {
|
import {
|
||||||
ensureAuthProfileStore,
|
ensureAuthProfileStore,
|
||||||
listProfilesForProvider,
|
listProfilesForProvider,
|
||||||
@@ -73,45 +73,6 @@ describe("provider usage formatting", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("provider usage loading", () => {
|
describe("provider usage loading", () => {
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
it("loads usage snapshots with injected auth", async () => {
|
it("loads usage snapshots with injected auth", async () => {
|
||||||
const makeResponse = (status: number, body: unknown): Response => {
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
||||||
@@ -175,13 +136,9 @@ describe("provider usage loading", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("discovers Claude usage from token auth profiles", async () => {
|
it("discovers Claude usage from token auth profiles", async () => {
|
||||||
const homeSnapshot = snapshotHomeEnv();
|
await withTempHome(
|
||||||
const stateSnapshot = process.env.CLAWDBOT_STATE_DIR;
|
async (tempHome) => {
|
||||||
const tempHome = fs.mkdtempSync(
|
const previousStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
path.join(os.tmpdir(), "clawdbot-provider-usage-"),
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
setTempHome(tempHome);
|
|
||||||
process.env.CLAWDBOT_STATE_DIR = path.join(tempHome, ".clawdbot");
|
process.env.CLAWDBOT_STATE_DIR = path.join(tempHome, ".clawdbot");
|
||||||
const agentDir = path.join(
|
const agentDir = path.join(
|
||||||
process.env.CLAWDBOT_STATE_DIR,
|
process.env.CLAWDBOT_STATE_DIR,
|
||||||
@@ -189,6 +146,7 @@ describe("provider usage loading", () => {
|
|||||||
"main",
|
"main",
|
||||||
"agent",
|
"agent",
|
||||||
);
|
);
|
||||||
|
try {
|
||||||
fs.mkdirSync(agentDir, { recursive: true, mode: 0o700 });
|
fs.mkdirSync(agentDir, { recursive: true, mode: 0o700 });
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(agentDir, "auth-profiles.json"),
|
path.join(agentDir, "auth-profiles.json"),
|
||||||
@@ -218,7 +176,8 @@ describe("provider usage loading", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const makeResponse = (status: number, body: unknown): Response => {
|
const makeResponse = (status: number, body: unknown): Response => {
|
||||||
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
const payload =
|
||||||
|
typeof body === "string" ? body : JSON.stringify(body);
|
||||||
const headers =
|
const headers =
|
||||||
typeof body === "string"
|
typeof body === "string"
|
||||||
? undefined
|
? undefined
|
||||||
@@ -240,7 +199,10 @@ describe("provider usage loading", () => {
|
|||||||
const headers = (init?.headers ?? {}) as Record<string, string>;
|
const headers = (init?.headers ?? {}) as Record<string, string>;
|
||||||
expect(headers.Authorization).toBe("Bearer token-1");
|
expect(headers.Authorization).toBe("Bearer token-1");
|
||||||
return makeResponse(200, {
|
return makeResponse(200, {
|
||||||
five_hour: { utilization: 20, resets_at: "2026-01-07T01:00:00Z" },
|
five_hour: {
|
||||||
|
utilization: 20,
|
||||||
|
resets_at: "2026-01-07T01:00:00Z",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return makeResponse(404, "not found");
|
return makeResponse(404, "not found");
|
||||||
@@ -259,10 +221,13 @@ describe("provider usage loading", () => {
|
|||||||
expect(claude?.windows[0]?.label).toBe("5h");
|
expect(claude?.windows[0]?.label).toBe("5h");
|
||||||
expect(mockFetch).toHaveBeenCalled();
|
expect(mockFetch).toHaveBeenCalled();
|
||||||
} finally {
|
} finally {
|
||||||
restoreHomeEnv(homeSnapshot);
|
if (previousStateDir === undefined)
|
||||||
if (stateSnapshot === undefined) delete process.env.CLAWDBOT_STATE_DIR;
|
delete process.env.CLAWDBOT_STATE_DIR;
|
||||||
else process.env.CLAWDBOT_STATE_DIR = stateSnapshot;
|
else process.env.CLAWDBOT_STATE_DIR = previousStateDir;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{ prefix: "clawdbot-provider-usage-" },
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to claude.ai web usage when OAuth scope is missing", async () => {
|
it("falls back to claude.ai web usage when OAuth scope is missing", async () => {
|
||||||
|
|||||||
68
test/helpers/temp-home.ts
Normal file
68
test/helpers/temp-home.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
type EnvSnapshot = {
|
||||||
|
home: string | undefined;
|
||||||
|
userProfile: string | undefined;
|
||||||
|
homeDrive: string | undefined;
|
||||||
|
homePath: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
function snapshotEnv(): EnvSnapshot {
|
||||||
|
return {
|
||||||
|
home: process.env.HOME,
|
||||||
|
userProfile: process.env.USERPROFILE,
|
||||||
|
homeDrive: process.env.HOMEDRIVE,
|
||||||
|
homePath: process.env.HOMEPATH,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreEnv(snapshot: EnvSnapshot) {
|
||||||
|
const restoreKey = (key: string, value: string | undefined) => {
|
||||||
|
if (value === undefined) delete process.env[key];
|
||||||
|
else process.env[key] = value;
|
||||||
|
};
|
||||||
|
restoreKey("HOME", snapshot.home);
|
||||||
|
restoreKey("USERPROFILE", snapshot.userProfile);
|
||||||
|
restoreKey("HOMEDRIVE", snapshot.homeDrive);
|
||||||
|
restoreKey("HOMEPATH", snapshot.homePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTempHome(base: string) {
|
||||||
|
process.env.HOME = base;
|
||||||
|
process.env.USERPROFILE = base;
|
||||||
|
|
||||||
|
if (process.platform !== "win32") return;
|
||||||
|
const match = base.match(/^([A-Za-z]:)(.*)$/);
|
||||||
|
if (!match) return;
|
||||||
|
process.env.HOMEDRIVE = match[1];
|
||||||
|
process.env.HOMEPATH = match[2] || "\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function withTempHome<T>(
|
||||||
|
fn: (home: string) => Promise<T>,
|
||||||
|
opts: { prefix?: string } = {},
|
||||||
|
): Promise<T> {
|
||||||
|
const base = await fs.mkdtemp(
|
||||||
|
path.join(os.tmpdir(), opts.prefix ?? "clawdbot-test-home-"),
|
||||||
|
);
|
||||||
|
const snapshot = snapshotEnv();
|
||||||
|
setTempHome(base);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await fn(base);
|
||||||
|
} finally {
|
||||||
|
restoreEnv(snapshot);
|
||||||
|
try {
|
||||||
|
await fs.rm(base, {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
maxRetries: 10,
|
||||||
|
retryDelay: 50,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// ignore cleanup failures in tests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user