mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04:00
chore: Fix types in tests 6/N.
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user