evOo-local/packages/adapters/src/supermemory-client.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

348 lines
12 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import {
deleteSupermemoryContainer,
MAX_MEMORY_CONTENT_CHARS,
MAX_RECALLED_MEMORIES,
MAX_SUPERMEMORY_RESPONSE_BYTES,
probeSupermemory,
saveSupermemoryMemory,
saveSupermemoryMemoryToContainers,
searchSupermemory,
searchSupermemoryContainers,
} from "./supermemory-client.js";
const config = { baseUrl: "http://localhost:6767", apiKey: "sm_test_key" };
afterEach(() => vi.unstubAllGlobals());
describe("Supermemory request URLs", () => {
const operations = [
{ method: "GET", path: "/v3/container-tags/list", request: probeSupermemory },
{
method: "POST",
path: "/v4/search",
request: (connection: typeof config) => searchSupermemory("query", "fake:tag", connection),
},
{
method: "POST",
path: "/v4/memories",
request: (connection: typeof config) => saveSupermemoryMemory("fact", "fake:tag", connection),
},
{
method: "DELETE",
path: "/v3/container-tags/fake%3Atag",
request: (connection: typeof config) => deleteSupermemoryContainer("fake:tag", connection),
},
];
it.each([
"http://127.0.0.1:8123/internal-action#",
"http://127.0.0.1:8123/internal-action#ignored",
"http://127.0.0.1:8123/internal-action?",
"http://127.0.0.1:8123/internal-action?action=delete",
"http://fake:credential@localhost:6767",
"file:///fake-memory",
])("rejects ambiguous base URLs before every transport call: %s", async (baseUrl) => {
const fetchMock = vi.fn().mockImplementation(async () => Response.json({ results: [] }));
vi.stubGlobal("fetch", fetchMock);
for (const { request } of operations) {
expect(await request({ ...config, baseUrl })).toMatchObject({ ok: false });
}
expect(fetchMock).not.toHaveBeenCalled();
});
it.each(["", "/", "/memory", "/memory/"])(
"preserves the base prefix and provider route for every method: %s",
async (prefix) => {
const fetchMock = vi.fn().mockImplementation(async () => Response.json({ results: [] }));
vi.stubGlobal("fetch", fetchMock);
for (const { request, method, path } of operations) {
expect(await request({ ...config, baseUrl: `http://[::1]:6767${prefix}` })).toMatchObject({
ok: true,
});
const [url, init] = fetchMock.mock.lastCall!;
expect(url).toBe(`http://[::1]:6767${prefix.replace(/\/$/, "")}${path}`);
expect(init).toMatchObject({ method, redirect: "error" });
}
},
);
});
describe("searchSupermemory", () => {
it("posts the query and container tag, returning search results on success", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(
JSON.stringify({
results: [{ memory: "User prefers British English", similarity: 0.9 }],
}),
{ status: 200 },
),
);
vi.stubGlobal("fetch", fetchMock);
const result = await searchSupermemory("spelling preference", "rakazo:bot-123", config);
expect(result).toEqual({
ok: true,
results: [{ memory: "User prefers British English", similarity: 0.9 }],
});
const [url, init] = fetchMock.mock.calls[0]!;
expect(url).toBe("http://localhost:6767/v4/search");
expect(init.headers.Authorization).toBe("Bearer sm_test_key");
expect(init.redirect).toBe("error");
expect(JSON.parse(init.body)).toStrictEqual({
q: "spelling preference",
containerTag: "rakazo:bot-123",
searchMode: "memories",
limit: MAX_RECALLED_MEMORIES,
});
vi.unstubAllGlobals();
});
it("reports a non-OK response instead of throwing", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response("", { status: 500 })));
const result = await searchSupermemory("anything", "rakazo:bot-123", config);
expect(result).toEqual({ ok: false, error: expect.stringContaining("500") });
vi.unstubAllGlobals();
});
it("reports an unreachable server instead of throwing", async () => {
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("connect ECONNREFUSED")));
const result = await searchSupermemory("anything", "rakazo:bot-123", config);
expect(result).toEqual({ ok: false, error: expect.stringContaining("unreachable") });
vi.unstubAllGlobals();
});
it("treats document chunks as memory text when the search result has no memory field", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(
new Response(JSON.stringify({ results: [{ chunk: "Older decision: use Postgres." }] }), {
status: 200,
}),
),
);
const result = await searchSupermemory("database", "rakazo:bot-123", config);
expect(result).toEqual({
ok: true,
results: [{ memory: "Older decision: use Postgres.", similarity: 0 }],
});
vi.unstubAllGlobals();
});
it("drops malformed search hits instead of injecting empty memories", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(
new Response(
JSON.stringify({ results: [{}, { memory: " " }, "nope", { memory: "kept" }] }),
{
status: 200,
},
),
),
);
const result = await searchSupermemory("anything", "rakazo:bot-123", config);
expect(result).toEqual({ ok: true, results: [{ memory: "kept", similarity: 0 }] });
vi.unstubAllGlobals();
});
it("bounds each recalled memory to the provider content limit", async () => {
vi.stubGlobal(
"fetch",
vi
.fn()
.mockResolvedValue(
new Response(JSON.stringify({ results: [{ memory: `kept${"x".repeat(20_000)}` }] })),
),
);
const result = await searchSupermemory("anything", "rakazo:bot-123", config);
expect(result.ok && result.results[0]?.memory).toHaveLength(MAX_MEMORY_CONTENT_CHARS);
vi.unstubAllGlobals();
});
it("rejects an oversized search response before buffering it", async () => {
const cancel = vi.fn().mockResolvedValue(undefined);
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
headers: new Headers({
"content-length": String(MAX_SUPERMEMORY_RESPONSE_BYTES + 1),
}),
body: { cancel },
}),
);
const result = await searchSupermemory("anything", "rakazo:bot-123", config);
expect(result).toEqual({
ok: false,
error: expect.stringContaining("response is too large"),
});
expect(cancel).toHaveBeenCalledOnce();
vi.unstubAllGlobals();
});
it("caps a chunked search response without a content length", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(new Response(new Uint8Array(MAX_SUPERMEMORY_RESPONSE_BYTES + 1))),
);
const result = await searchSupermemory("anything", "rakazo:bot-123", config);
expect(result).toEqual({ ok: false, error: "Supermemory response is too large." });
vi.unstubAllGlobals();
});
});
describe("searchSupermemoryContainers", () => {
it("deduplicates and ranks results from shared and bot containers", async () => {
vi.stubGlobal(
"fetch",
vi
.fn()
.mockResolvedValueOnce(
new Response(JSON.stringify({ results: [{ memory: "shared", similarity: 0.7 }] }), {
status: 200,
}),
)
.mockResolvedValueOnce(
new Response(
JSON.stringify({
results: [
{ memory: "private", similarity: 0.9 },
{ memory: "shared", similarity: 0.6 },
],
}),
{ status: 200 },
),
),
);
await expect(
searchSupermemoryContainers("query", ["rakazo:workspace:ws-1", "rakazo:bot-1"], config),
).resolves.toEqual({
ok: true,
results: [
{ memory: "private", similarity: 0.9 },
{ memory: "shared", similarity: 0.7 },
],
});
vi.unstubAllGlobals();
});
});
describe("saveSupermemoryMemory", () => {
it("posts the content and container tag on success", async () => {
const fetchMock = vi.fn().mockResolvedValue(new Response("", { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const result = await saveSupermemoryMemory(
"User prefers British English",
"rakazo:bot-123",
config,
);
expect(result).toEqual({ ok: true });
const [url, init] = fetchMock.mock.calls[0]!;
expect(url).toBe("http://localhost:6767/v4/memories");
expect(init.redirect).toBe("error");
expect(JSON.parse(init.body)).toEqual({
containerTag: "rakazo:bot-123",
memories: [{ content: "User prefers British English", isStatic: false }],
});
vi.unstubAllGlobals();
});
it("caps oversized content at the API limit", async () => {
const fetchMock = vi.fn().mockResolvedValue(new Response("", { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
await saveSupermemoryMemory(
`prefix ${"x".repeat(MAX_MEMORY_CONTENT_CHARS)}`,
"rakazo:bot-123",
config,
);
const body = JSON.parse(fetchMock.mock.calls[0]![1].body);
expect(body.memories[0].content).toHaveLength(MAX_MEMORY_CONTENT_CHARS);
vi.unstubAllGlobals();
});
it("reports a non-OK response instead of throwing", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response("", { status: 401 })));
const result = await saveSupermemoryMemory("fact", "rakazo:bot-123", config);
expect(result).toEqual({ ok: false, error: expect.stringContaining("401") });
vi.unstubAllGlobals();
});
});
describe("saveSupermemoryMemoryToContainers", () => {
it("writes shared memories to both the workspace and bot containers", async () => {
const fetchMock = vi.fn().mockResolvedValue(new Response("", { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
await expect(
saveSupermemoryMemoryToContainers("fact", ["rakazo:workspace:ws-1", "rakazo:bot-1"], config),
).resolves.toEqual({ ok: true });
expect(fetchMock).toHaveBeenCalledTimes(2);
vi.unstubAllGlobals();
});
});
describe("deleteSupermemoryContainer", () => {
it("deletes the container tag on success", async () => {
const fetchMock = vi.fn().mockResolvedValue(new Response("", { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const result = await deleteSupermemoryContainer("rakazo:bot-123", config);
expect(result).toEqual({ ok: true });
const [url, init] = fetchMock.mock.calls[0]!;
expect(url).toBe("http://localhost:6767/v3/container-tags/rakazo%3Abot-123");
expect(init.method).toBe("DELETE");
expect(init.redirect).toBe("error");
vi.unstubAllGlobals();
});
it("reports a non-OK response instead of throwing", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response("", { status: 404 })));
const result = await deleteSupermemoryContainer("rakazo:bot-123", config);
expect(result).toEqual({ ok: false, error: expect.stringContaining("404") });
vi.unstubAllGlobals();
});
});
describe("probeSupermemory", () => {
it("succeeds when the container-tags endpoint responds", async () => {
const fetchMock = vi.fn().mockResolvedValue(new Response("[]", { status: 200 }));
vi.stubGlobal("fetch", fetchMock);
const result = await probeSupermemory(config);
expect(result).toEqual({ ok: true });
expect(fetchMock.mock.calls[0]![1].redirect).toBe("error");
vi.unstubAllGlobals();
});
it("fails when the endpoint rejects the key", async () => {
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response("", { status: 401 })));
const result = await probeSupermemory(config);
expect(result).toEqual({ ok: false, error: expect.stringContaining("401") });
vi.unstubAllGlobals();
});
it("fails when nothing is listening", async () => {
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("connect ECONNREFUSED")));
const result = await probeSupermemory(config);
expect(result).toEqual({ ok: false, error: expect.stringContaining("unreachable") });
vi.unstubAllGlobals();
});
});