72 lines
3.8 KiB
JavaScript
72 lines
3.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawn } from "node:child_process";
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { createConnection } from "node:net";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { test } from "node:test";
|
|
|
|
test("real socket protocol preserves observations and provenance across service restart", async (t) => {
|
|
const dir = mkdtempSync(join(tmpdir(), "dp-memory-protocol-"));
|
|
const socketPath = join(dir, "memory.sock");
|
|
let child;
|
|
async function start() {
|
|
child = spawn(process.execPath, [new URL("../src/server.js", import.meta.url).pathname], {
|
|
env: { DESKPAW_STATE_DIR: dir, DESKPAW_MEMORY_SOCKET: socketPath,
|
|
DESKPAW_MEMORY_DB: join(dir, "data.sqlite"), DESKPAW_MEMORY_BUNDLES: join(dir, "bundles") },
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
await new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => reject(new Error("Isolated memory server did not start")), 5000);
|
|
child.once("error", (error) => { clearTimeout(timeout); reject(error); });
|
|
child.once("exit", (code) => { clearTimeout(timeout); reject(new Error(`Server exited: ${code}`)); });
|
|
child.stdout.once("data", () => { clearTimeout(timeout); resolve(); });
|
|
});
|
|
}
|
|
async function stop() {
|
|
if (!child || child.exitCode !== null) return;
|
|
const exited = new Promise((resolve) => child.once("exit", resolve));
|
|
child.kill("SIGTERM");
|
|
const timeout = setTimeout(() => child.kill("SIGKILL"), 2000);
|
|
await exited;
|
|
clearTimeout(timeout);
|
|
}
|
|
function call(method, params) {
|
|
return new Promise((resolve, reject) => {
|
|
const socket = createConnection(socketPath);
|
|
let text = "";
|
|
socket.setTimeout(3000, () => socket.destroy(new Error("Test socket timed out")));
|
|
socket.once("connect", () => socket.write(JSON.stringify({ id: "test", method, params }) + "\n"));
|
|
socket.on("data", (chunk) => { text += chunk; });
|
|
socket.once("end", () => {
|
|
try {
|
|
const response = JSON.parse(text);
|
|
if (!response.ok) throw new Error(response.error);
|
|
resolve(response.result);
|
|
} catch (error) { reject(error); }
|
|
});
|
|
socket.once("error", reject);
|
|
});
|
|
}
|
|
t.after(async () => { await stop(); rmSync(dir, { recursive: true, force: true }); });
|
|
await start();
|
|
const owner = "device:protocol";
|
|
const event = { event_id: "run:1", subject_id: "schedule:monthly", task_id: "run1",
|
|
kind: "task_completed", source: "runtime", observed_at: Date.now(), text: "十月发票归档完成,等待确认。" };
|
|
assert.equal((await call("memory.events.ingest", { version: 1, owner, events: [event] })).stored, 1);
|
|
const job = await call("memory.observation.claim", { owner });
|
|
const proposal = { job_id: job.job_id, token: job.token, episodes: [{ subject_id: event.subject_id,
|
|
content: "十月发票已整理,等待用户确认。", source_refs: [event.event_id], cues: ["每月报销"] }], candidates: [], discarded_event_ids: [] };
|
|
const receipt = await call("memory.observation.commit", proposal);
|
|
await stop();
|
|
await start();
|
|
assert.deepEqual(await call("memory.observation.commit", proposal), receipt);
|
|
const recalled = await call("memory.continuity.recall", { owner, query: "发票报销" });
|
|
assert.equal(recalled.hits.length, 1);
|
|
const context = await call("context.prepare", { owner, task_id: "run2", objective: "继续报销", schedule_id: "monthly" });
|
|
assert.equal(context.continuity.hits[0].source_refs[0], "run:1");
|
|
assert.equal((await call("memory.evidence.get", { owner, event_ids: ["run:1"] }))[0].text, event.text);
|
|
assert.deepEqual(await call("memory.evidence.get", { owner: "device:another", event_ids: ["run:1"] }), []);
|
|
assert.equal(await call("memory.observation.claim", { owner }), null);
|
|
});
|