chore: Fix types in tests 6/N.

This commit is contained in:
cpojer
2026-02-17 10:52:25 +09:00
parent b6d4f7c00e
commit 003d6c45d6
6 changed files with 180 additions and 116 deletions

View File

@@ -7,12 +7,23 @@ const loadConfig = vi.fn(() => ({
auth: { token: "ltok" },
},
}));
const resolveGatewayPort = vi.fn(() => 18789);
const discoverGatewayBeacons = vi.fn(async () => []);
const resolveGatewayPort = vi.fn((_cfg?: unknown) => 18789);
const discoverGatewayBeacons = vi.fn(
async (_opts?: unknown): Promise<Array<{ tailnetDns: string }>> => [],
);
const pickPrimaryTailnetIPv4 = vi.fn(() => "100.64.0.10");
const sshStop = vi.fn(async () => {});
const resolveSshConfig = vi.fn(async () => null);
const startSshPortForward = vi.fn(async () => ({
const resolveSshConfig = vi.fn(
async (
_opts?: unknown,
): Promise<{
user: string;
host: string;
port: number;
identityFiles: string[];
} | null> => null,
);
const startSshPortForward = vi.fn(async (_opts?: unknown) => ({
parsedTarget: { user: "me", host: "studio", port: 22 },
localPort: 18789,
remotePort: 18789,
@@ -20,7 +31,8 @@ const startSshPortForward = vi.fn(async () => ({
stderr: [],
stop: sshStop,
}));
const probeGateway = vi.fn(async ({ url }: { url: string }) => {
const probeGateway = vi.fn(async (opts: { url: string }) => {
const { url } = opts;
if (url.includes("127.0.0.1")) {
return {
ok: true,
@@ -80,32 +92,32 @@ const probeGateway = vi.fn(async ({ url }: { url: string }) => {
});
vi.mock("../config/config.js", () => ({
loadConfig: () => loadConfig(),
resolveGatewayPort: (cfg: unknown) => resolveGatewayPort(cfg),
loadConfig,
resolveGatewayPort,
}));
vi.mock("../infra/bonjour-discovery.js", () => ({
discoverGatewayBeacons: (opts: unknown) => discoverGatewayBeacons(opts),
discoverGatewayBeacons,
}));
vi.mock("../infra/tailnet.js", () => ({
pickPrimaryTailnetIPv4: () => pickPrimaryTailnetIPv4(),
pickPrimaryTailnetIPv4,
}));
vi.mock("../infra/ssh-tunnel.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("../infra/ssh-tunnel.js")>();
return {
...actual,
startSshPortForward: (opts: unknown) => startSshPortForward(opts),
startSshPortForward,
};
});
vi.mock("../infra/ssh-config.js", () => ({
resolveSshConfig: (opts: unknown) => resolveSshConfig(opts),
resolveSshConfig,
}));
vi.mock("../gateway/probe.js", () => ({
probeGateway: (opts: unknown) => probeGateway(opts),
probeGateway,
}));
function createRuntimeCapture() {
@@ -198,7 +210,8 @@ describe("gateway-status command", () => {
loadConfig.mockReturnValueOnce({
gateway: {
mode: "remote",
remote: {},
remote: { url: "", token: "" },
auth: { token: "ltok" },
},
});
discoverGatewayBeacons.mockResolvedValueOnce([
@@ -226,6 +239,7 @@ describe("gateway-status command", () => {
gateway: {
mode: "remote",
remote: { url: "ws://peters-mac-studio-1.sheep-coho.ts.net:18789", token: "rtok" },
auth: { token: "ltok" },
},
});
resolveSshConfig.mockResolvedValueOnce({
@@ -259,6 +273,7 @@ describe("gateway-status command", () => {
gateway: {
mode: "remote",
remote: { url: "ws://studio.example:18789", token: "rtok" },
auth: { token: "ltok" },
},
});
resolveSshConfig.mockResolvedValueOnce(null);
@@ -284,6 +299,7 @@ describe("gateway-status command", () => {
gateway: {
mode: "remote",
remote: { url: "ws://studio.example:18789", token: "rtok" },
auth: { token: "ltok" },
},
});
resolveSshConfig.mockResolvedValueOnce({

View File

@@ -1,4 +1,5 @@
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import type { Mock } from "vitest";
import { captureEnv } from "../test-utils/env.js";
let envSnapshot: ReturnType<typeof captureEnv>;
@@ -323,10 +324,12 @@ const runtime = {
exit: vi.fn(),
};
const runtimeLogMock = runtime.log as Mock<(...args: unknown[]) => void>;
describe("statusCommand", () => {
it("prints JSON when requested", async () => {
await statusCommand({ json: true }, runtime as never);
const payload = JSON.parse((runtime.log as vi.Mock).mock.calls[0][0]);
const payload = JSON.parse(String(runtimeLogMock.mock.calls[0]?.[0]));
expect(payload.linkChannel.linked).toBe(true);
expect(payload.memory.agentId).toBe("main");
expect(payload.memoryPlugin.enabled).toBe(true);
@@ -348,9 +351,9 @@ describe("statusCommand", () => {
it("surfaces unknown usage when totalTokens is missing", async () => {
await withUnknownUsageStore(async () => {
(runtime.log as vi.Mock).mockClear();
runtimeLogMock.mockClear();
await statusCommand({ json: true }, runtime as never);
const payload = JSON.parse((runtime.log as vi.Mock).mock.calls.at(-1)?.[0]);
const payload = JSON.parse(String(runtimeLogMock.mock.calls.at(-1)?.[0]));
expect(payload.sessions.recent[0].totalTokens).toBeNull();
expect(payload.sessions.recent[0].totalTokensFresh).toBe(false);
expect(payload.sessions.recent[0].percentUsed).toBeNull();
@@ -360,37 +363,37 @@ describe("statusCommand", () => {
it("prints unknown usage in formatted output when totalTokens is missing", async () => {
await withUnknownUsageStore(async () => {
(runtime.log as vi.Mock).mockClear();
runtimeLogMock.mockClear();
await statusCommand({}, runtime as never);
const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0]));
const logs = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0]));
expect(logs.some((line) => line.includes("unknown/") && line.includes("(?%)"))).toBe(true);
});
});
it("prints formatted lines otherwise", async () => {
(runtime.log as vi.Mock).mockClear();
runtimeLogMock.mockClear();
await statusCommand({}, runtime as never);
const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0]));
expect(logs.some((l) => l.includes("OpenClaw status"))).toBe(true);
expect(logs.some((l) => l.includes("Overview"))).toBe(true);
expect(logs.some((l) => l.includes("Security audit"))).toBe(true);
expect(logs.some((l) => l.includes("Summary:"))).toBe(true);
expect(logs.some((l) => l.includes("CRITICAL"))).toBe(true);
expect(logs.some((l) => l.includes("Dashboard"))).toBe(true);
expect(logs.some((l) => l.includes("macos 14.0 (arm64)"))).toBe(true);
expect(logs.some((l) => l.includes("Memory"))).toBe(true);
expect(logs.some((l) => l.includes("Channels"))).toBe(true);
expect(logs.some((l) => l.includes("WhatsApp"))).toBe(true);
expect(logs.some((l) => l.includes("Sessions"))).toBe(true);
expect(logs.some((l) => l.includes("+1000"))).toBe(true);
expect(logs.some((l) => l.includes("50%"))).toBe(true);
expect(logs.some((l) => l.includes("LaunchAgent"))).toBe(true);
expect(logs.some((l) => l.includes("FAQ:"))).toBe(true);
expect(logs.some((l) => l.includes("Troubleshooting:"))).toBe(true);
expect(logs.some((l) => l.includes("Next steps:"))).toBe(true);
const logs = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0]));
expect(logs.some((l: string) => l.includes("OpenClaw status"))).toBe(true);
expect(logs.some((l: string) => l.includes("Overview"))).toBe(true);
expect(logs.some((l: string) => l.includes("Security audit"))).toBe(true);
expect(logs.some((l: string) => l.includes("Summary:"))).toBe(true);
expect(logs.some((l: string) => l.includes("CRITICAL"))).toBe(true);
expect(logs.some((l: string) => l.includes("Dashboard"))).toBe(true);
expect(logs.some((l: string) => l.includes("macos 14.0 (arm64)"))).toBe(true);
expect(logs.some((l: string) => l.includes("Memory"))).toBe(true);
expect(logs.some((l: string) => l.includes("Channels"))).toBe(true);
expect(logs.some((l: string) => l.includes("WhatsApp"))).toBe(true);
expect(logs.some((l: string) => l.includes("Sessions"))).toBe(true);
expect(logs.some((l: string) => l.includes("+1000"))).toBe(true);
expect(logs.some((l: string) => l.includes("50%"))).toBe(true);
expect(logs.some((l: string) => l.includes("LaunchAgent"))).toBe(true);
expect(logs.some((l: string) => l.includes("FAQ:"))).toBe(true);
expect(logs.some((l: string) => l.includes("Troubleshooting:"))).toBe(true);
expect(logs.some((l: string) => l.includes("Next steps:"))).toBe(true);
expect(
logs.some(
(l) =>
(l: string) =>
l.includes("openclaw status --all") ||
l.includes("openclaw --profile isolated status --all") ||
l.includes("openclaw status --all") ||
@@ -414,10 +417,10 @@ describe("statusCommand", () => {
presence: [],
configSnapshot: null,
});
(runtime.log as vi.Mock).mockClear();
runtimeLogMock.mockClear();
await statusCommand({}, runtime as never);
const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0]));
expect(logs.some((l) => l.includes("auth token"))).toBe(true);
const logs = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0]));
expect(logs.some((l: string) => l.includes("auth token"))).toBe(true);
} finally {
if (prevToken === undefined) {
delete process.env.OPENCLAW_GATEWAY_TOKEN;
@@ -462,9 +465,9 @@ describe("statusCommand", () => {
},
});
(runtime.log as vi.Mock).mockClear();
runtimeLogMock.mockClear();
await statusCommand({}, runtime as never);
const logs = (runtime.log as vi.Mock).mock.calls.map((c) => String(c[0]));
const logs = runtimeLogMock.mock.calls.map((c: unknown[]) => String(c[0]));
expect(logs.join("\n")).toMatch(/Signal/i);
expect(logs.join("\n")).toMatch(/iMessage/i);
expect(logs.join("\n")).toMatch(/gateway:/i);
@@ -507,7 +510,7 @@ describe("statusCommand", () => {
});
await statusCommand({ json: true }, runtime as never);
const payload = JSON.parse((runtime.log as vi.Mock).mock.calls.at(-1)?.[0]);
const payload = JSON.parse(String(runtimeLogMock.mock.calls.at(-1)?.[0]));
expect(payload.sessions.count).toBe(2);
expect(payload.sessions.paths.length).toBe(2);
expect(

View File

@@ -19,11 +19,18 @@ vi.mock("./node-llama.js", () => ({
}));
const createFetchMock = () =>
vi.fn(async () => ({
vi.fn(async (_input?: unknown, _init?: unknown) => ({
ok: true,
status: 200,
json: async () => ({ data: [{ embedding: [1, 2, 3] }] }),
})) as unknown as typeof fetch;
}));
function requireProvider(result: Awaited<ReturnType<typeof createEmbeddingProvider>>) {
if (!result.provider) {
throw new Error("Expected embedding provider");
}
return result.provider;
}
describe("embedding provider remote overrides", () => {
afterEach(() => {
@@ -69,10 +76,12 @@ describe("embedding provider remote overrides", () => {
fallback: "openai",
});
await result.provider.embedQuery("hello");
const provider = requireProvider(result);
await provider.embedQuery("hello");
expect(authModule.resolveApiKeyForProvider).not.toHaveBeenCalled();
const [url, init] = fetchMock.mock.calls[0] ?? [];
const url = fetchMock.mock.calls[0]?.[0];
const init = fetchMock.mock.calls[0]?.[1] as RequestInit | undefined;
expect(url).toBe("https://remote.example/v1/embeddings");
const headers = (init?.headers ?? {}) as Record<string, string>;
expect(headers.Authorization).toBe("Bearer remote-key");
@@ -112,19 +121,21 @@ describe("embedding provider remote overrides", () => {
fallback: "openai",
});
await result.provider.embedQuery("hello");
const provider = requireProvider(result);
await provider.embedQuery("hello");
expect(authModule.resolveApiKeyForProvider).toHaveBeenCalledTimes(1);
const headers = (fetchMock.mock.calls[0]?.[1]?.headers as Record<string, string>) ?? {};
const init = fetchMock.mock.calls[0]?.[1] as RequestInit | undefined;
const headers = (init?.headers as Record<string, string>) ?? {};
expect(headers.Authorization).toBe("Bearer provider-key");
});
it("builds Gemini embeddings requests with api key header", async () => {
const fetchMock = vi.fn(async () => ({
const fetchMock = vi.fn(async (_input?: unknown, _init?: unknown) => ({
ok: true,
status: 200,
json: async () => ({ embedding: { values: [1, 2, 3] } }),
})) as unknown as typeof fetch;
}));
vi.stubGlobal("fetch", fetchMock);
vi.mocked(authModule.resolveApiKeyForProvider).mockResolvedValue({
apiKey: "provider-key",
@@ -152,9 +163,11 @@ describe("embedding provider remote overrides", () => {
fallback: "openai",
});
await result.provider.embedQuery("hello");
const provider = requireProvider(result);
await provider.embedQuery("hello");
const [url, init] = fetchMock.mock.calls[0] ?? [];
const url = fetchMock.mock.calls[0]?.[0];
const init = fetchMock.mock.calls[0]?.[1] as RequestInit | undefined;
expect(url).toBe(
"https://generativelanguage.googleapis.com/v1beta/models/text-embedding-004:embedContent",
);
@@ -186,15 +199,16 @@ describe("embedding provider auto selection", () => {
});
expect(result.requestedProvider).toBe("auto");
expect(result.provider.id).toBe("openai");
const provider = requireProvider(result);
expect(provider.id).toBe("openai");
});
it("uses gemini when openai is missing", async () => {
const fetchMock = vi.fn(async () => ({
const fetchMock = vi.fn(async (_input?: unknown, _init?: unknown) => ({
ok: true,
status: 200,
json: async () => ({ embedding: { values: [1, 2, 3] } }),
})) as unknown as typeof fetch;
}));
vi.stubGlobal("fetch", fetchMock);
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
if (provider === "openai") {
@@ -214,8 +228,9 @@ describe("embedding provider auto selection", () => {
});
expect(result.requestedProvider).toBe("auto");
expect(result.provider.id).toBe("gemini");
await result.provider.embedQuery("hello");
const provider = requireProvider(result);
expect(provider.id).toBe("gemini");
await provider.embedQuery("hello");
const [url] = fetchMock.mock.calls[0] ?? [];
expect(url).toBe(
`https://generativelanguage.googleapis.com/v1beta/models/${DEFAULT_GEMINI_EMBEDDING_MODEL}:embedContent`,
@@ -223,11 +238,11 @@ describe("embedding provider auto selection", () => {
});
it("keeps explicit model when openai is selected", async () => {
const fetchMock = vi.fn(async () => ({
const fetchMock = vi.fn(async (_input?: unknown, _init?: unknown) => ({
ok: true,
status: 200,
json: async () => ({ data: [{ embedding: [1, 2, 3] }] }),
})) as unknown as typeof fetch;
}));
vi.stubGlobal("fetch", fetchMock);
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
if (provider === "openai") {
@@ -244,11 +259,13 @@ describe("embedding provider auto selection", () => {
});
expect(result.requestedProvider).toBe("auto");
expect(result.provider.id).toBe("openai");
await result.provider.embedQuery("hello");
const [url, init] = fetchMock.mock.calls[0] ?? [];
const provider = requireProvider(result);
expect(provider.id).toBe("openai");
await provider.embedQuery("hello");
const url = fetchMock.mock.calls[0]?.[0];
const init = fetchMock.mock.calls[0]?.[1] as RequestInit | undefined;
expect(url).toBe("https://api.openai.com/v1/embeddings");
const payload = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
const payload = JSON.parse(init?.body as string) as { model?: string };
expect(payload.model).toBe("text-embedding-3-small");
});
});
@@ -282,7 +299,8 @@ describe("embedding provider local fallback", () => {
fallback: "openai",
});
expect(result.provider.id).toBe("openai");
const provider = requireProvider(result);
expect(provider.id).toBe("openai");
expect(result.fallbackFrom).toBe("local");
expect(result.fallbackReason).toContain("node-llama-cpp");
});
@@ -365,7 +383,8 @@ describe("local embedding normalization", () => {
const result = await createLocalProviderForTest();
const embedding = await result.provider.embedQuery("test query");
const provider = requireProvider(result);
const embedding = await provider.embedQuery("test query");
const magnitude = Math.sqrt(embedding.reduce((sum, x) => sum + x * x, 0));
@@ -380,7 +399,8 @@ describe("local embedding normalization", () => {
const result = await createLocalProviderForTest();
const embedding = await result.provider.embedQuery("test");
const provider = requireProvider(result);
const embedding = await provider.embedQuery("test");
expect(embedding).toEqual([0, 0, 0, 0]);
expect(embedding.every((value) => Number.isFinite(value))).toBe(true);
@@ -393,7 +413,8 @@ describe("local embedding normalization", () => {
const result = await createLocalProviderForTest();
const embedding = await result.provider.embedQuery("test");
const provider = requireProvider(result);
const embedding = await provider.embedQuery("test");
expect(embedding).toEqual([1, 0, 0, 0]);
expect(embedding.every((value) => Number.isFinite(value))).toBe(true);
@@ -424,7 +445,8 @@ describe("local embedding normalization", () => {
const result = await createLocalProviderForTest();
const embeddings = await result.provider.embedBatch(["text1", "text2", "text3"]);
const provider = requireProvider(result);
const embeddings = await provider.embedBatch(["text1", "text2", "text3"]);
for (const embedding of embeddings) {
const magnitude = Math.sqrt(embedding.reduce((sum, x) => sum + x * x, 0));

View File

@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { Mock } from "vitest";
const { logWarnMock, logDebugMock, logInfoMock } = vi.hoisted(() => ({
logWarnMock: vi.fn(),
@@ -68,7 +69,7 @@ vi.mock("../logging/subsystem.js", () => ({
},
}));
vi.mock(import("node:child_process"), async (importOriginal) => {
vi.mock("node:child_process", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:child_process")>();
return {
...actual,
@@ -81,7 +82,7 @@ import type { OpenClawConfig } from "../config/config.js";
import { resolveMemoryBackendConfig } from "./backend-config.js";
import { QmdMemoryManager } from "./qmd-manager.js";
const spawnMock = mockedSpawn as unknown as vi.Mock;
const spawnMock = mockedSpawn as unknown as Mock;
describe("QmdMemoryManager", () => {
let fixtureRoot: string;
@@ -195,7 +196,7 @@ describe("QmdMemoryManager", () => {
const { manager } = await createManager({ mode: "full" });
expect(releaseUpdate).not.toBeNull();
releaseUpdate?.();
(releaseUpdate as (() => void) | null)?.();
await manager?.close();
});
@@ -256,7 +257,7 @@ describe("QmdMemoryManager", () => {
});
await new Promise<void>((resolve) => setImmediate(resolve));
expect(created).toBe(false);
releaseUpdate?.();
(releaseUpdate as (() => void) | null)?.();
const manager = await createPromise;
await manager?.close();
});
@@ -340,7 +341,7 @@ describe("QmdMemoryManager", () => {
expect(manager).toBeTruthy();
await manager?.close();
const commands = spawnMock.mock.calls.map((call) => call[1] as string[]);
const commands = spawnMock.mock.calls.map((call: unknown[]) => call[1] as string[]);
const removeSessions = commands.find(
(args) =>
args[0] === "collection" && args[1] === "remove" && args[2] === sessionCollectionName,
@@ -389,7 +390,7 @@ describe("QmdMemoryManager", () => {
const { manager } = await createManager({ mode: "full" });
await manager.close();
const commands = spawnMock.mock.calls.map((call) => call[1] as string[]);
const commands = spawnMock.mock.calls.map((call: unknown[]) => call[1] as string[]);
const removeSessions = commands.find(
(args) =>
args[0] === "collection" && args[1] === "remove" && args[2] === sessionCollectionName,
@@ -485,12 +486,12 @@ describe("QmdMemoryManager", () => {
await expect(manager.sync({ reason: "manual" })).resolves.toBeUndefined();
const removeCalls = spawnMock.mock.calls
.map((call) => call[1] as string[])
.filter((args) => args[0] === "collection" && args[1] === "remove")
.map((call: unknown[]) => call[1] as string[])
.filter((args: string[]) => args[0] === "collection" && args[1] === "remove")
.map((args) => args[2]);
const addCalls = spawnMock.mock.calls
.map((call) => call[1] as string[])
.filter((args) => args[0] === "collection" && args[1] === "add")
.map((call: unknown[]) => call[1] as string[])
.filter((args: string[]) => args[0] === "collection" && args[1] === "add")
.map((args) => args[args.indexOf("--name") + 1]);
expect(updateCalls).toBe(2);
@@ -536,8 +537,8 @@ describe("QmdMemoryManager", () => {
);
const removeCalls = spawnMock.mock.calls
.map((call) => call[1] as string[])
.filter((args) => args[0] === "collection" && args[1] === "remove");
.map((call: unknown[]) => call[1] as string[])
.filter((args: string[]) => args[0] === "collection" && args[1] === "remove");
expect(removeCalls).toHaveLength(0);
await manager.close();
@@ -575,7 +576,9 @@ describe("QmdMemoryManager", () => {
manager.search("test", { sessionKey: "agent:main:slack:dm:u123" }),
).resolves.toEqual([]);
const searchCall = spawnMock.mock.calls.find((call) => call[1]?.[0] === "search");
const searchCall = spawnMock.mock.calls.find(
(call: unknown[]) => (call[1] as string[])?.[0] === "search",
);
expect(searchCall?.[1]).toEqual([
"search",
"test",
@@ -585,7 +588,9 @@ describe("QmdMemoryManager", () => {
"-c",
"workspace-main",
]);
expect(spawnMock.mock.calls.some((call) => call[1]?.[0] === "query")).toBe(false);
expect(
spawnMock.mock.calls.some((call: unknown[]) => (call[1] as string[])?.[0] === "query"),
).toBe(false);
expect(maxResults).toBeGreaterThan(0);
await manager.close();
});
@@ -628,7 +633,7 @@ describe("QmdMemoryManager", () => {
).resolves.toEqual([]);
const searchAndQueryCalls = spawnMock.mock.calls
.map((call) => call[1])
.map((call: unknown[]) => call[1])
.filter(
(args): args is string[] => Array.isArray(args) && ["search", "query"].includes(args[0]),
);
@@ -684,7 +689,7 @@ describe("QmdMemoryManager", () => {
if (!releaseFirstUpdate) {
throw new Error("first update release missing");
}
releaseFirstUpdate();
(releaseFirstUpdate as () => void)();
await Promise.all([inFlight, forced]);
expect(updateCalls).toBe(2);
@@ -744,7 +749,7 @@ describe("QmdMemoryManager", () => {
if (!releaseFirstUpdate) {
throw new Error("first update release missing");
}
releaseFirstUpdate();
(releaseFirstUpdate as () => void)();
await secondUpdateSpawned.promise;
const forcedTwo = manager.sync({ reason: "manual-again", force: true });
@@ -752,7 +757,7 @@ describe("QmdMemoryManager", () => {
if (!releaseSecondUpdate) {
throw new Error("second update release missing");
}
releaseSecondUpdate();
(releaseSecondUpdate as () => void)();
await Promise.all([inFlight, forcedOne, forcedTwo]);
expect(updateCalls).toBe(3);
@@ -787,7 +792,9 @@ describe("QmdMemoryManager", () => {
const { manager, resolved } = await createManager();
await manager.search("test", { sessionKey: "agent:main:slack:dm:u123" });
const searchCall = spawnMock.mock.calls.find((call) => call[1]?.[0] === "search");
const searchCall = spawnMock.mock.calls.find(
(call: unknown[]) => (call[1] as string[])?.[0] === "search",
);
const maxResults = resolved.qmd?.limits.maxResults;
if (!maxResults) {
throw new Error("qmd maxResults missing");
@@ -843,8 +850,8 @@ describe("QmdMemoryManager", () => {
).resolves.toEqual([]);
const queryCalls = spawnMock.mock.calls
.map((call) => call[1] as string[])
.filter((args) => args[0] === "query");
.map((call: unknown[]) => call[1] as string[])
.filter((args: string[]) => args[0] === "query");
expect(queryCalls).toEqual([
["query", "test", "--json", "-n", String(maxResults), "-c", "workspace-main"],
["query", "test", "--json", "-n", String(maxResults), "-c", "notes-main"],
@@ -894,8 +901,8 @@ describe("QmdMemoryManager", () => {
).resolves.toEqual([]);
const searchAndQueryCalls = spawnMock.mock.calls
.map((call) => call[1] as string[])
.filter((args) => args[0] === "search" || args[0] === "query");
.map((call: unknown[]) => call[1] as string[])
.filter((args: string[]) => args[0] === "search" || args[0] === "query");
expect(searchAndQueryCalls).toEqual([
[
"search",
@@ -931,7 +938,9 @@ describe("QmdMemoryManager", () => {
const results = await manager.search("test", { sessionKey: "agent:main:slack:dm:u123" });
expect(results).toEqual([]);
expect(spawnMock.mock.calls.some((call) => call[1]?.[0] === "query")).toBe(false);
expect(
spawnMock.mock.calls.some((call: unknown[]) => (call[1] as string[])?.[0] === "query"),
).toBe(false);
await manager.close();
});
@@ -1081,12 +1090,13 @@ describe("QmdMemoryManager", () => {
"utf-8",
);
const currentMemory = cfg.memory;
cfg = {
...cfg,
memory: {
...cfg.memory,
...currentMemory,
qmd: {
...cfg.memory.qmd,
...currentMemory?.qmd,
sessions: {
enabled: true,
},

View File

@@ -1,19 +1,26 @@
import type { TUI } from "@mariozechner/pi-tui";
import { describe, expect, it, vi } from "vitest";
import type { ChatLog } from "./components/chat-log.js";
import { createEventHandlers } from "./tui-event-handlers.js";
import type { AgentEvent, ChatEvent, TuiStateAccess } from "./tui-types.js";
type MockChatLog = Pick<
ChatLog,
| "startTool"
| "updateToolResult"
| "addSystem"
| "updateAssistant"
| "finalizeAssistant"
| "dropAssistant"
>;
type MockTui = Pick<TUI, "requestRender">;
type MockFn = ReturnType<typeof vi.fn>;
type HandlerChatLog = {
startTool: (...args: unknown[]) => void;
updateToolResult: (...args: unknown[]) => void;
addSystem: (...args: unknown[]) => void;
updateAssistant: (...args: unknown[]) => void;
finalizeAssistant: (...args: unknown[]) => void;
dropAssistant: (...args: unknown[]) => void;
};
type HandlerTui = { requestRender: (...args: unknown[]) => void };
type MockChatLog = {
startTool: MockFn;
updateToolResult: MockFn;
addSystem: MockFn;
updateAssistant: MockFn;
finalizeAssistant: MockFn;
dropAssistant: MockFn;
};
type MockTui = { requestRender: MockFn };
describe("tui-event-handlers: handleAgentEvent", () => {
const makeState = (overrides?: Partial<TuiStateAccess>): TuiStateAccess => ({
@@ -40,15 +47,15 @@ describe("tui-event-handlers: handleAgentEvent", () => {
});
const makeContext = (state: TuiStateAccess) => {
const chatLog: MockChatLog = {
const chatLog = {
startTool: vi.fn(),
updateToolResult: vi.fn(),
addSystem: vi.fn(),
updateAssistant: vi.fn(),
finalizeAssistant: vi.fn(),
dropAssistant: vi.fn(),
};
const tui: MockTui = { requestRender: vi.fn() };
} as unknown as MockChatLog & HandlerChatLog;
const tui = { requestRender: vi.fn() } as unknown as MockTui & HandlerTui;
const setActivityStatus = vi.fn();
const loadHistory = vi.fn();
const localRunIds = new Set<string>();
@@ -136,7 +143,8 @@ describe("tui-event-handlers: handleAgentEvent", () => {
addSystem: vi.fn(),
updateAssistant: vi.fn(),
finalizeAssistant: vi.fn(),
},
dropAssistant: vi.fn(),
} as unknown as HandlerChatLog,
tui,
state,
setActivityStatus,

View File

@@ -1,12 +1,17 @@
import type { TUI } from "@mariozechner/pi-tui";
import type { ChatLog } from "./components/chat-log.js";
import { asString, extractTextFromMessage, isCommandMessage } from "./tui-formatters.js";
import { TuiStreamAssembler } from "./tui-stream-assembler.js";
import type { AgentEvent, ChatEvent, TuiStateAccess } from "./tui-types.js";
type EventHandlerContext = {
chatLog: ChatLog;
tui: TUI;
chatLog: {
startTool: (...args: unknown[]) => void;
updateToolResult: (...args: unknown[]) => void;
addSystem: (...args: unknown[]) => void;
updateAssistant: (...args: unknown[]) => void;
finalizeAssistant: (...args: unknown[]) => void;
dropAssistant: (...args: unknown[]) => void;
};
tui: { requestRender: (...args: unknown[]) => void };
state: TuiStateAccess;
setActivityStatus: (text: string) => void;
refreshSessionInfo?: () => Promise<void>;