test: sync updated specs
parent
800c7a1e1f
commit
38659f5d3e
|
|
@ -1,4 +1,4 @@
|
||||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
// Mocks must be defined via vi.hoisted to avoid TDZ with ESM hoisting.
|
// Mocks must be defined via vi.hoisted to avoid TDZ with ESM hoisting.
|
||||||
const { monitorWebProvider, pickProvider, logWebSelfId, monitorTwilio } =
|
const { monitorWebProvider, pickProvider, logWebSelfId, monitorTwilio } =
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import { deriveSessionKey } from "./sessions.js";
|
||||||
|
|
||||||
describe("sessions", () => {
|
describe("sessions", () => {
|
||||||
it("returns normalized per-sender key", () => {
|
it("returns normalized per-sender key", () => {
|
||||||
expect(
|
expect(deriveSessionKey("per-sender", { From: "whatsapp:+1555" })).toBe(
|
||||||
deriveSessionKey("per-sender", { From: "whatsapp:+1555" }),
|
"+1555",
|
||||||
).toBe("+1555");
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to unknown when sender missing", () => {
|
it("falls back to unknown when sender missing", () => {
|
||||||
|
|
@ -17,4 +17,3 @@ describe("sessions", () => {
|
||||||
expect(deriveSessionKey("global", { From: "+1" })).toBe("global");
|
expect(deriveSessionKey("global", { From: "+1" })).toBe("global");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { createMockTwilio } from "../test/mocks/twilio.js";
|
import { createMockTwilio } from "../test/mocks/twilio.js";
|
||||||
import { statusCommand } from "./commands/status.js";
|
import { statusCommand } from "./commands/status.js";
|
||||||
import { createDefaultDeps } from "./index.js";
|
import { createDefaultDeps } from "./index.js";
|
||||||
import { defaultRuntime } from "./runtime.js";
|
|
||||||
import * as providerWeb from "./provider-web.js";
|
import * as providerWeb from "./provider-web.js";
|
||||||
|
import { defaultRuntime } from "./runtime.js";
|
||||||
|
|
||||||
vi.mock("twilio", () => {
|
vi.mock("twilio", () => {
|
||||||
const { factory } = createMockTwilio();
|
const { factory } = createMockTwilio();
|
||||||
|
|
@ -67,16 +67,7 @@ describe("CLI commands", () => {
|
||||||
const twilio = (await import("twilio")).default;
|
const twilio = (await import("twilio")).default;
|
||||||
const wait = vi.spyOn(index, "waitForFinalStatus").mockResolvedValue();
|
const wait = vi.spyOn(index, "waitForFinalStatus").mockResolvedValue();
|
||||||
await index.program.parseAsync(
|
await index.program.parseAsync(
|
||||||
[
|
["send", "--to", "+1555", "--message", "hi", "--wait", "0", "--dry-run"],
|
||||||
"send",
|
|
||||||
"--to",
|
|
||||||
"+1555",
|
|
||||||
"--message",
|
|
||||||
"hi",
|
|
||||||
"--wait",
|
|
||||||
"0",
|
|
||||||
"--dry-run",
|
|
||||||
],
|
|
||||||
{ from: "user" },
|
{ from: "user" },
|
||||||
);
|
);
|
||||||
expect(twilio._client.messages.create).not.toHaveBeenCalled();
|
expect(twilio._client.messages.create).not.toHaveBeenCalled();
|
||||||
|
|
@ -88,20 +79,11 @@ describe("CLI commands", () => {
|
||||||
twilio._client.messages.create.mockResolvedValue({ sid: "SMJSON" });
|
twilio._client.messages.create.mockResolvedValue({ sid: "SMJSON" });
|
||||||
const logSpy = vi.spyOn(defaultRuntime, "log");
|
const logSpy = vi.spyOn(defaultRuntime, "log");
|
||||||
await index.program.parseAsync(
|
await index.program.parseAsync(
|
||||||
[
|
["send", "--to", "+1555", "--message", "hi", "--wait", "0", "--json"],
|
||||||
"send",
|
|
||||||
"--to",
|
|
||||||
"+1555",
|
|
||||||
"--message",
|
|
||||||
"hi",
|
|
||||||
"--wait",
|
|
||||||
"0",
|
|
||||||
"--json",
|
|
||||||
],
|
|
||||||
{ from: "user" },
|
{ from: "user" },
|
||||||
);
|
);
|
||||||
expect(logSpy).toHaveBeenCalledWith(
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
expect.stringContaining("\"sid\": \"SMJSON\""),
|
expect.stringContaining('"sid": "SMJSON"'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -341,7 +341,7 @@ describe("config and templating", () => {
|
||||||
cfg,
|
cfg,
|
||||||
runSpy,
|
runSpy,
|
||||||
);
|
);
|
||||||
expect(first?.text).toBe("cmd output");
|
expect(first?.text).toBe("cmd output");
|
||||||
const argvFirst = runSpy.mock.calls[0][0];
|
const argvFirst = runSpy.mock.calls[0][0];
|
||||||
expect(argvFirst).toEqual([
|
expect(argvFirst).toEqual([
|
||||||
"echo",
|
"echo",
|
||||||
|
|
@ -357,7 +357,7 @@ describe("config and templating", () => {
|
||||||
cfg,
|
cfg,
|
||||||
runSpy,
|
runSpy,
|
||||||
);
|
);
|
||||||
expect(second?.text).toBe("cmd output");
|
expect(second?.text).toBe("cmd output");
|
||||||
const argvSecond = runSpy.mock.calls[1][0];
|
const argvSecond = runSpy.mock.calls[1][0];
|
||||||
expect(argvSecond[2]).toBe("--resume");
|
expect(argvSecond[2]).toBe("--resume");
|
||||||
});
|
});
|
||||||
|
|
@ -420,15 +420,15 @@ describe("config and templating", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await index.getReplyFromConfig(
|
const result = await index.getReplyFromConfig(
|
||||||
{ Body: "hi", From: "+1", To: "+2" },
|
{ Body: "hi", From: "+1", To: "+2" },
|
||||||
undefined,
|
undefined,
|
||||||
cfg,
|
cfg,
|
||||||
runSpy,
|
runSpy,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result?.text).toBe("hello world");
|
expect(result?.text).toBe("hello world");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("parses Claude JSON output even without explicit claudeOutputFormat when using claude bin", async () => {
|
it("parses Claude JSON output even without explicit claudeOutputFormat when using claude bin", async () => {
|
||||||
const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockResolvedValue({
|
const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockResolvedValue({
|
||||||
|
|
@ -448,17 +448,17 @@ describe("config and templating", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await index.getReplyFromConfig(
|
const result = await index.getReplyFromConfig(
|
||||||
{ Body: "hi", From: "+1", To: "+2" },
|
{ Body: "hi", From: "+1", To: "+2" },
|
||||||
undefined,
|
undefined,
|
||||||
cfg,
|
cfg,
|
||||||
runSpy,
|
runSpy,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result?.text).toBe("Sure! What's up?");
|
expect(result?.text).toBe("Sure! What's up?");
|
||||||
const argv = runSpy.mock.calls[0][0];
|
const argv = runSpy.mock.calls[0][0];
|
||||||
expect(argv.at(-1)).toContain("You are Clawd (Claude)");
|
expect(argv.at(-1)).toContain("You are Clawd (Claude)");
|
||||||
expect(argv.at(-1)).toContain("/Users/steipete/clawd");
|
expect(argv.at(-1)).toContain("/Users/steipete/clawd");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("serializes command auto-replies via the queue", async () => {
|
it("serializes command auto-replies via the queue", async () => {
|
||||||
|
|
@ -625,7 +625,11 @@ describe("webhook and messaging", () => {
|
||||||
const hostModule = await import("./media/host.js");
|
const hostModule = await import("./media/host.js");
|
||||||
const hostSpy = vi
|
const hostSpy = vi
|
||||||
.spyOn(hostModule, "ensureMediaHosted")
|
.spyOn(hostModule, "ensureMediaHosted")
|
||||||
.mockResolvedValue({ url: "https://ts.net/media/abc", id: "abc", size: 123 });
|
.mockResolvedValue({
|
||||||
|
url: "https://ts.net/media/abc",
|
||||||
|
id: "abc",
|
||||||
|
size: 123,
|
||||||
|
});
|
||||||
vi.spyOn(replies, "getReplyFromConfig").mockResolvedValue({
|
vi.spyOn(replies, "getReplyFromConfig").mockResolvedValue({
|
||||||
text: "Auto",
|
text: "Auto",
|
||||||
mediaUrl: "/tmp/pic.png",
|
mediaUrl: "/tmp/pic.png",
|
||||||
|
|
@ -923,29 +927,29 @@ describe("monitoring", () => {
|
||||||
expect(runtime.exit).toHaveBeenCalledWith(1);
|
expect(runtime.exit).toHaveBeenCalledWith(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("monitorWebProvider triggers replies and stops when asked", async () => {
|
it("monitorWebProvider triggers replies and stops when asked", async () => {
|
||||||
const replySpy = vi.fn();
|
const replySpy = vi.fn();
|
||||||
const sendMediaSpy = vi.fn();
|
const sendMediaSpy = vi.fn();
|
||||||
const listenerFactory = vi.fn(
|
const listenerFactory = vi.fn(
|
||||||
async (
|
async (
|
||||||
opts: Parameters<typeof index.monitorWebProvider>[1] extends undefined
|
opts: Parameters<typeof index.monitorWebProvider>[1] extends undefined
|
||||||
? never
|
? never
|
||||||
: NonNullable<Parameters<typeof index.monitorWebProvider>[1]>,
|
: NonNullable<Parameters<typeof index.monitorWebProvider>[1]>,
|
||||||
) => {
|
) => {
|
||||||
await opts.onMessage({
|
await opts.onMessage({
|
||||||
body: "hello",
|
body: "hello",
|
||||||
from: "+1",
|
from: "+1",
|
||||||
to: "+2",
|
to: "+2",
|
||||||
id: "id1",
|
id: "id1",
|
||||||
sendComposing: vi.fn(),
|
sendComposing: vi.fn(),
|
||||||
reply: replySpy,
|
reply: replySpy,
|
||||||
sendMedia: sendMediaSpy,
|
sendMedia: sendMediaSpy,
|
||||||
});
|
});
|
||||||
return { close: vi.fn() };
|
return { close: vi.fn() };
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const resolver = vi.fn().mockResolvedValue({ text: "auto" });
|
const resolver = vi.fn().mockResolvedValue({ text: "auto" });
|
||||||
await index.monitorWebProvider(false, listenerFactory, false, resolver);
|
await index.monitorWebProvider(false, listenerFactory, false, resolver);
|
||||||
expect(replySpy).toHaveBeenCalledWith("auto");
|
expect(replySpy).toHaveBeenCalledWith("auto");
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import { ensurePortAvailable, handlePortError, PortInUseError } from "./ports.js";
|
import {
|
||||||
|
ensurePortAvailable,
|
||||||
|
handlePortError,
|
||||||
|
PortInUseError,
|
||||||
|
} from "./ports.js";
|
||||||
|
|
||||||
describe("ports helpers", () => {
|
describe("ports helpers", () => {
|
||||||
it("ensurePortAvailable rejects when port busy", async () => {
|
it("ensurePortAvailable rejects when port busy", async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getTailnetHostname,
|
|
||||||
ensureGoInstalled,
|
ensureGoInstalled,
|
||||||
ensureTailscaledInstalled,
|
ensureTailscaledInstalled,
|
||||||
|
getTailnetHostname,
|
||||||
} from "./tailscale.js";
|
} from "./tailscale.js";
|
||||||
|
|
||||||
describe("tailscale helpers", () => {
|
describe("tailscale helpers", () => {
|
||||||
|
|
|
||||||
|
|
@ -38,21 +38,18 @@ describe("command queue", () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||||
});
|
});
|
||||||
|
|
||||||
const second = enqueueCommand(
|
const second = enqueueCommand(async () => {}, {
|
||||||
async () => {},
|
warnAfterMs: 5,
|
||||||
{
|
onWait: (ms, ahead) => {
|
||||||
warnAfterMs: 5,
|
waited = ms;
|
||||||
onWait: (ms, ahead) => {
|
queuedAhead = ahead;
|
||||||
waited = ms;
|
|
||||||
queuedAhead = ahead;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
await Promise.all([first, second]);
|
await Promise.all([first, second]);
|
||||||
|
|
||||||
expect(waited).not.toBeNull();
|
expect(waited).not.toBeNull();
|
||||||
expect((waited as number)).toBeGreaterThanOrEqual(5);
|
expect(waited as number).toBeGreaterThanOrEqual(5);
|
||||||
expect(queuedAhead).toBe(0);
|
expect(queuedAhead).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,24 @@ describe("monitorTwilio", () => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const autoReplyIfConfigured = vi.fn().mockResolvedValue(undefined);
|
const autoReplyIfConfigured = vi.fn().mockResolvedValue(undefined);
|
||||||
const readEnv = vi.fn(() => ({ accountSid: "AC", whatsappFrom: "whatsapp:+1", auth: { accountSid: "AC", authToken: "t" } }));
|
const readEnv = vi.fn(() => ({
|
||||||
const createClient = vi.fn(() => ({ messages: { create: vi.fn() } } as never));
|
accountSid: "AC",
|
||||||
|
whatsappFrom: "whatsapp:+1",
|
||||||
|
auth: { accountSid: "AC", authToken: "t" },
|
||||||
|
}));
|
||||||
|
const createClient = vi.fn(
|
||||||
|
() => ({ messages: { create: vi.fn() } }) as never,
|
||||||
|
);
|
||||||
const sleep = vi.fn().mockResolvedValue(undefined);
|
const sleep = vi.fn().mockResolvedValue(undefined);
|
||||||
|
|
||||||
await monitorTwilio(0, 0, {
|
await monitorTwilio(0, 0, {
|
||||||
deps: { autoReplyIfConfigured, listRecentMessages, readEnv, createClient, sleep },
|
deps: {
|
||||||
|
autoReplyIfConfigured,
|
||||||
|
listRecentMessages,
|
||||||
|
readEnv,
|
||||||
|
createClient,
|
||||||
|
sleep,
|
||||||
|
},
|
||||||
maxIterations: 1,
|
maxIterations: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -31,4 +43,3 @@ describe("monitorTwilio", () => {
|
||||||
expect(autoReplyIfConfigured).toHaveBeenCalledTimes(1);
|
expect(autoReplyIfConfigured).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,19 @@ describe("twilio send helpers", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("waitForFinalStatus exits on failure", async () => {
|
it("waitForFinalStatus exits on failure", async () => {
|
||||||
const fetch = vi.fn().mockResolvedValue({ status: "failed", errorMessage: "boom" });
|
const fetch = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ status: "failed", errorMessage: "boom" });
|
||||||
const client = { messages: vi.fn(() => ({ fetch })) } as never;
|
const client = { messages: vi.fn(() => ({ fetch })) } as never;
|
||||||
const runtime = { log: console.log, error: () => {}, exit: vi.fn(() => { throw new Error("exit"); }) } as never;
|
const runtime = {
|
||||||
await expect(waitForFinalStatus(client, "SM1", 1, 0.01, runtime)).rejects.toBeInstanceOf(Error);
|
log: console.log,
|
||||||
|
error: () => {},
|
||||||
|
exit: vi.fn(() => {
|
||||||
|
throw new Error("exit");
|
||||||
|
}),
|
||||||
|
} as never;
|
||||||
|
await expect(
|
||||||
|
waitForFinalStatus(client, "SM1", 1, 0.01, runtime),
|
||||||
|
).rejects.toBeInstanceOf(Error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { describe, expect, it, beforeEach, afterEach } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
findIncomingNumberSid,
|
findIncomingNumberSid,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue