fix(ci): stabilize windows tests
This commit is contained in:
@@ -327,14 +327,14 @@ export async function handleDirectiveOnly(params: {
|
||||
aliasIndex,
|
||||
allowedModelKeys,
|
||||
allowedModelCatalog,
|
||||
resetModelOverride,
|
||||
initialModelLabel,
|
||||
formatModelSwitchEvent,
|
||||
currentThinkLevel,
|
||||
currentVerboseLevel,
|
||||
currentReasoningLevel,
|
||||
currentElevatedLevel,
|
||||
} = params;
|
||||
resetModelOverride,
|
||||
initialModelLabel,
|
||||
formatModelSwitchEvent,
|
||||
currentThinkLevel,
|
||||
currentVerboseLevel,
|
||||
currentReasoningLevel,
|
||||
currentElevatedLevel,
|
||||
} = params;
|
||||
|
||||
if (directives.hasModelDirective) {
|
||||
const modelDirective = directives.rawModelDirective?.trim().toLowerCase();
|
||||
|
||||
@@ -7,11 +7,33 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
|
||||
const base = await fs.mkdtemp(path.join(os.tmpdir(), "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 });
|
||||
}
|
||||
}
|
||||
@@ -402,7 +424,7 @@ describe("Nix integration (U3, U5, U9)", () => {
|
||||
{ CLAWDBOT_STATE_DIR: "/custom/state/dir" },
|
||||
async () => {
|
||||
const { STATE_DIR_CLAWDBOT } = await import("./config.js");
|
||||
expect(STATE_DIR_CLAWDBOT).toBe("/custom/state/dir");
|
||||
expect(STATE_DIR_CLAWDBOT).toBe(path.resolve("/custom/state/dir"));
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -412,7 +434,9 @@ describe("Nix integration (U3, U5, U9)", () => {
|
||||
{ CLAWDBOT_CONFIG_PATH: undefined, CLAWDBOT_STATE_DIR: undefined },
|
||||
async () => {
|
||||
const { CONFIG_PATH_CLAWDBOT } = await import("./config.js");
|
||||
expect(CONFIG_PATH_CLAWDBOT).toMatch(/\.clawdbot\/clawdbot\.json$/);
|
||||
expect(CONFIG_PATH_CLAWDBOT).toMatch(
|
||||
/\.clawdbot[\\/]clawdbot\.json$/,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -435,7 +459,9 @@ describe("Nix integration (U3, U5, U9)", () => {
|
||||
},
|
||||
async () => {
|
||||
const { CONFIG_PATH_CLAWDBOT } = await import("./config.js");
|
||||
expect(CONFIG_PATH_CLAWDBOT).toBe("/custom/state/clawdbot.json");
|
||||
expect(CONFIG_PATH_CLAWDBOT).toBe(
|
||||
path.join(path.resolve("/custom/state"), "clawdbot.json"),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { resolveOAuthDir, resolveOAuthPath } from "./paths.js";
|
||||
@@ -9,9 +10,11 @@ describe("oauth paths", () => {
|
||||
CLAWDBOT_STATE_DIR: "/custom/state",
|
||||
} as NodeJS.ProcessEnv;
|
||||
|
||||
expect(resolveOAuthDir(env, "/custom/state")).toBe("/custom/oauth");
|
||||
expect(resolveOAuthDir(env, "/custom/state")).toBe(
|
||||
path.resolve("/custom/oauth"),
|
||||
);
|
||||
expect(resolveOAuthPath(env, "/custom/state")).toBe(
|
||||
"/custom/oauth/oauth.json",
|
||||
path.join(path.resolve("/custom/oauth"), "oauth.json"),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -21,10 +24,10 @@ describe("oauth paths", () => {
|
||||
} as NodeJS.ProcessEnv;
|
||||
|
||||
expect(resolveOAuthDir(env, "/custom/state")).toBe(
|
||||
"/custom/state/credentials",
|
||||
path.join("/custom/state", "credentials"),
|
||||
);
|
||||
expect(resolveOAuthPath(env, "/custom/state")).toBe(
|
||||
"/custom/state/credentials/oauth.json",
|
||||
path.join("/custom/state", "credentials", "oauth.json"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,7 +138,9 @@ describe("sessions", () => {
|
||||
{ CLAWDBOT_STATE_DIR: "/custom/state" } as NodeJS.ProcessEnv,
|
||||
() => "/home/ignored",
|
||||
);
|
||||
expect(dir).toBe("/custom/state/agents/main/sessions");
|
||||
expect(dir).toBe(
|
||||
path.join(path.resolve("/custom/state"), "agents", "main", "sessions"),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to CLAWDIS_STATE_DIR for session transcripts dir", () => {
|
||||
@@ -146,7 +148,9 @@ describe("sessions", () => {
|
||||
{ CLAWDIS_STATE_DIR: "/legacy/state" } as NodeJS.ProcessEnv,
|
||||
() => "/home/ignored",
|
||||
);
|
||||
expect(dir).toBe("/legacy/state/agents/main/sessions");
|
||||
expect(dir).toBe(
|
||||
path.join(path.resolve("/legacy/state"), "agents", "main", "sessions"),
|
||||
);
|
||||
});
|
||||
|
||||
it("includes topic ids in session transcript filenames", () => {
|
||||
@@ -155,7 +159,13 @@ describe("sessions", () => {
|
||||
try {
|
||||
const sessionFile = resolveSessionTranscriptPath("sess-1", "main", 123);
|
||||
expect(sessionFile).toBe(
|
||||
"/custom/state/agents/main/sessions/sess-1-topic-123.jsonl",
|
||||
path.join(
|
||||
path.resolve("/custom/state"),
|
||||
"agents",
|
||||
"main",
|
||||
"sessions",
|
||||
"sess-1-topic-123.jsonl",
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
if (prev === undefined) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const fsMocks = vi.hoisted(() => ({
|
||||
@@ -22,14 +23,16 @@ afterEach(() => {
|
||||
|
||||
describe("resolveGatewayProgramArguments", () => {
|
||||
it("uses realpath-resolved dist entry when running via npx shim", async () => {
|
||||
process.argv = ["node", "/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot"];
|
||||
fsMocks.realpath.mockResolvedValue(
|
||||
const argv1 = path.resolve(
|
||||
"/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot",
|
||||
);
|
||||
const entryPath = path.resolve(
|
||||
"/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/entry.js",
|
||||
);
|
||||
process.argv = ["node", argv1];
|
||||
fsMocks.realpath.mockResolvedValue(entryPath);
|
||||
fsMocks.access.mockImplementation(async (target: string) => {
|
||||
if (
|
||||
target === "/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/entry.js"
|
||||
) {
|
||||
if (target === entryPath) {
|
||||
return;
|
||||
}
|
||||
throw new Error("missing");
|
||||
@@ -39,7 +42,7 @@ describe("resolveGatewayProgramArguments", () => {
|
||||
|
||||
expect(result.programArguments).toEqual([
|
||||
process.execPath,
|
||||
"/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/entry.js",
|
||||
entryPath,
|
||||
"gateway-daemon",
|
||||
"--port",
|
||||
"18789",
|
||||
@@ -47,12 +50,16 @@ describe("resolveGatewayProgramArguments", () => {
|
||||
});
|
||||
|
||||
it("falls back to node_modules package dist when .bin path is not resolved", async () => {
|
||||
process.argv = ["node", "/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot"];
|
||||
const argv1 = path.resolve(
|
||||
"/tmp/.npm/_npx/63c3/node_modules/.bin/clawdbot",
|
||||
);
|
||||
const indexPath = path.resolve(
|
||||
"/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/index.js",
|
||||
);
|
||||
process.argv = ["node", argv1];
|
||||
fsMocks.realpath.mockRejectedValue(new Error("no realpath"));
|
||||
fsMocks.access.mockImplementation(async (target: string) => {
|
||||
if (
|
||||
target === "/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/index.js"
|
||||
) {
|
||||
if (target === indexPath) {
|
||||
return;
|
||||
}
|
||||
throw new Error("missing");
|
||||
@@ -62,7 +69,7 @@ describe("resolveGatewayProgramArguments", () => {
|
||||
|
||||
expect(result.programArguments).toEqual([
|
||||
process.execPath,
|
||||
"/tmp/.npm/_npx/63c3/node_modules/clawdbot/dist/index.js",
|
||||
indexPath,
|
||||
"gateway-daemon",
|
||||
"--port",
|
||||
"18789",
|
||||
|
||||
@@ -17,6 +17,44 @@ import {
|
||||
installGatewayTestHooks();
|
||||
|
||||
describe("gateway server models + voicewake", () => {
|
||||
const setTempHome = (homeDir: string) => {
|
||||
const prevHome = process.env.HOME;
|
||||
const prevUserProfile = process.env.USERPROFILE;
|
||||
const prevHomeDrive = process.env.HOMEDRIVE;
|
||||
const prevHomePath = process.env.HOMEPATH;
|
||||
process.env.HOME = homeDir;
|
||||
process.env.USERPROFILE = homeDir;
|
||||
if (process.platform === "win32") {
|
||||
const parsed = path.parse(homeDir);
|
||||
process.env.HOMEDRIVE = parsed.root.replace(/\\$/, "");
|
||||
process.env.HOMEPATH = homeDir.slice(Math.max(parsed.root.length - 1, 0));
|
||||
}
|
||||
return () => {
|
||||
if (prevHome === undefined) {
|
||||
delete process.env.HOME;
|
||||
} else {
|
||||
process.env.HOME = prevHome;
|
||||
}
|
||||
if (prevUserProfile === undefined) {
|
||||
delete process.env.USERPROFILE;
|
||||
} else {
|
||||
process.env.USERPROFILE = prevUserProfile;
|
||||
}
|
||||
if (process.platform === "win32") {
|
||||
if (prevHomeDrive === undefined) {
|
||||
delete process.env.HOMEDRIVE;
|
||||
} else {
|
||||
process.env.HOMEDRIVE = prevHomeDrive;
|
||||
}
|
||||
if (prevHomePath === undefined) {
|
||||
delete process.env.HOMEPATH;
|
||||
} else {
|
||||
process.env.HOMEPATH = prevHomePath;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
test(
|
||||
"voicewake.get returns defaults and voicewake.set broadcasts",
|
||||
{ timeout: 15_000 },
|
||||
@@ -24,8 +62,7 @@ describe("gateway server models + voicewake", () => {
|
||||
const homeDir = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), "clawdbot-home-"),
|
||||
);
|
||||
const prevHome = process.env.HOME;
|
||||
process.env.HOME = homeDir;
|
||||
const restoreHome = setTempHome(homeDir);
|
||||
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
@@ -72,18 +109,13 @@ describe("gateway server models + voicewake", () => {
|
||||
ws.close();
|
||||
await server.close();
|
||||
|
||||
if (prevHome === undefined) {
|
||||
delete process.env.HOME;
|
||||
} else {
|
||||
process.env.HOME = prevHome;
|
||||
}
|
||||
restoreHome();
|
||||
},
|
||||
);
|
||||
|
||||
test("pushes voicewake.changed to nodes on connect and on updates", async () => {
|
||||
const homeDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-home-"));
|
||||
const prevHome = process.env.HOME;
|
||||
process.env.HOME = homeDir;
|
||||
const restoreHome = setTempHome(homeDir);
|
||||
|
||||
bridgeSendEvent.mockClear();
|
||||
bridgeListConnected.mockReturnValue([{ nodeId: "n1" }]);
|
||||
@@ -124,11 +156,7 @@ describe("gateway server models + voicewake", () => {
|
||||
ws.close();
|
||||
await server.close();
|
||||
|
||||
if (prevHome === undefined) {
|
||||
delete process.env.HOME;
|
||||
} else {
|
||||
process.env.HOME = prevHome;
|
||||
}
|
||||
restoreHome();
|
||||
});
|
||||
|
||||
test("models.list returns model catalog", async () => {
|
||||
|
||||
@@ -49,11 +49,11 @@ describe("brew helpers", () => {
|
||||
it("includes Linuxbrew bin/sbin in path candidates", () => {
|
||||
const env: NodeJS.ProcessEnv = { HOMEBREW_PREFIX: "/custom/prefix" };
|
||||
const dirs = resolveBrewPathDirs({ homeDir: "/home/test", env });
|
||||
expect(dirs).toContain("/custom/prefix/bin");
|
||||
expect(dirs).toContain("/custom/prefix/sbin");
|
||||
expect(dirs).toContain(path.join("/custom/prefix", "bin"));
|
||||
expect(dirs).toContain(path.join("/custom/prefix", "sbin"));
|
||||
expect(dirs).toContain("/home/linuxbrew/.linuxbrew/bin");
|
||||
expect(dirs).toContain("/home/linuxbrew/.linuxbrew/sbin");
|
||||
expect(dirs).toContain("/home/test/.linuxbrew/bin");
|
||||
expect(dirs).toContain("/home/test/.linuxbrew/sbin");
|
||||
expect(dirs).toContain(path.join("/home/test", ".linuxbrew", "bin"));
|
||||
expect(dirs).toContain(path.join("/home/test", ".linuxbrew", "sbin"));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,9 +51,10 @@ describe("control UI assets helpers", () => {
|
||||
});
|
||||
|
||||
it("resolves dist control-ui index path for dist argv1", () => {
|
||||
const argv1 = path.join("/tmp", "pkg", "dist", "index.js");
|
||||
const argv1 = path.resolve("/tmp", "pkg", "dist", "index.js");
|
||||
const distDir = path.dirname(argv1);
|
||||
expect(resolveControlUiDistIndexPath(argv1)).toBe(
|
||||
path.join("/tmp", "pkg", "dist", "control-ui", "index.html"),
|
||||
path.join(distDir, "control-ui", "index.html"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,11 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import { HEARTBEAT_PROMPT } from "../auto-reply/heartbeat.js";
|
||||
import * as replyModule from "../auto-reply/reply.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import {
|
||||
resolveAgentIdFromSessionKey,
|
||||
resolveMainSessionKey,
|
||||
resolveStorePath,
|
||||
} from "../config/sessions.js";
|
||||
import {
|
||||
resolveHeartbeatIntervalMs,
|
||||
resolveHeartbeatPrompt,
|
||||
@@ -192,15 +197,24 @@ describe("runHeartbeatOnce", () => {
|
||||
"{agentId}",
|
||||
"sessions.json",
|
||||
);
|
||||
const storePath = path.join(tmpDir, "agents", "work", "sessions.json");
|
||||
const replySpy = vi.spyOn(replyModule, "getReplyFromConfig");
|
||||
try {
|
||||
const cfg: ClawdbotConfig = {
|
||||
routing: { defaultAgentId: "work" },
|
||||
agent: { heartbeat: { every: "5m" } },
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
session: { store: storeTemplate },
|
||||
};
|
||||
const sessionKey = resolveMainSessionKey(cfg);
|
||||
const agentId = resolveAgentIdFromSessionKey(sessionKey);
|
||||
const storePath = resolveStorePath(storeTemplate, { agentId });
|
||||
|
||||
await fs.mkdir(path.dirname(storePath), { recursive: true });
|
||||
await fs.writeFile(
|
||||
storePath,
|
||||
JSON.stringify(
|
||||
{
|
||||
"agent:work:main": {
|
||||
[sessionKey]: {
|
||||
sessionId: "sid",
|
||||
updatedAt: Date.now(),
|
||||
lastProvider: "whatsapp",
|
||||
@@ -212,13 +226,6 @@ describe("runHeartbeatOnce", () => {
|
||||
),
|
||||
);
|
||||
|
||||
const cfg: ClawdbotConfig = {
|
||||
routing: { defaultAgentId: "work" },
|
||||
agent: { heartbeat: { every: "5m" } },
|
||||
whatsapp: { allowFrom: ["*"] },
|
||||
session: { store: storeTemplate },
|
||||
};
|
||||
|
||||
replySpy.mockResolvedValue({ text: "Hello from heartbeat" });
|
||||
const sendWhatsApp = vi.fn().mockResolvedValue({
|
||||
messageId: "m1",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { DisconnectReason } from "@whiskeysockets/baileys";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -7,12 +9,14 @@ vi.useFakeTimers();
|
||||
|
||||
const rmMock = vi.spyOn(fs, "rm");
|
||||
|
||||
const authDir = path.join(os.tmpdir(), "wa-creds");
|
||||
|
||||
vi.mock("../config/config.js", () => ({
|
||||
loadConfig: () =>
|
||||
({
|
||||
whatsapp: {
|
||||
accounts: {
|
||||
default: { enabled: true, authDir: "/tmp/wa-creds" },
|
||||
default: { enabled: true, authDir },
|
||||
},
|
||||
},
|
||||
}) as never,
|
||||
@@ -29,9 +33,9 @@ vi.mock("./session.js", () => {
|
||||
createWaSocket,
|
||||
waitForWaConnection,
|
||||
formatError,
|
||||
WA_WEB_AUTH_DIR: "/tmp/wa-creds",
|
||||
WA_WEB_AUTH_DIR: authDir,
|
||||
logoutWeb: vi.fn(async (params: { authDir?: string }) => {
|
||||
await fs.rm(params.authDir ?? "/tmp/wa-creds", {
|
||||
await fs.rm(params.authDir ?? authDir, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
@@ -75,7 +79,7 @@ describe("loginWeb coverage", () => {
|
||||
await expect(
|
||||
loginWeb(false, "web", waitForWaConnection as never),
|
||||
).rejects.toThrow(/cache cleared/i);
|
||||
expect(rmMock).toHaveBeenCalledWith("/tmp/wa-creds", {
|
||||
expect(rmMock).toHaveBeenCalledWith(authDir, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user