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.
348 lines
12 KiB
TypeScript
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();
|
|
});
|
|
});
|