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 { once } from "node:events"; import assert from "node:assert/strict"; import { test } from "node:test"; test("real socket supports timeline calls and refuses a second live owner", { timeout: 15000 }, async () => { const dir = mkdtempSync(join(tmpdir(), "timeline-server-")); const env = { ...process.env, DESKPAW_STATE_DIR: dir, DESKPAW_MEMORY_SOCKET: join(dir, "runtime", "memory.sock"), DESKPAW_MEMORY_DB: join(dir, "memory.sqlite"), DESKPAW_MEMORY_BUNDLES: join(dir, "bundles") }; const server = spawn(process.execPath, ["src/server.js"], { env, stdio: ["ignore", "pipe", "pipe"] }); const exited = once(server, "exit"); const call = (method, params) => new Promise((resolve, reject) => { const socket = createConnection(env.DESKPAW_MEMORY_SOCKET); let data = ""; socket.setTimeout(2000, () => socket.destroy(new Error("timeout"))); socket.on("error", reject); socket.on("connect", () => socket.write(JSON.stringify({ id: "test", method, params }) + "\n")); socket.on("data", chunk => { data += chunk; }); socket.on("end", () => { try { resolve(JSON.parse(data)); } catch (error) { reject(error); } }); }); try { await Promise.race([once(server.stdout, "data"), exited.then(() => { throw new Error("Server exited before ready"); })]); assert.equal((await call("timeline.append", { id: "meeting", kind: "user", at: 1, text: "报销会议", source: { sender: "owner" } })).ok, true); assert.equal((await call("timeline.query", { query: "报销" })).result.entries[0].id, "meeting"); const invalid = await call("timeline.read", { id: "missing" }); assert.equal(invalid.ok, false); assert.equal(invalid.id, "test"); const second = spawn(process.execPath, ["src/server.js"], { env, stdio: "ignore" }); assert.notEqual((await once(second, "exit"))[0], 0); assert.equal((await call("health", {})).ok, true); } finally { server.kill("SIGTERM"); await exited; rmSync(dir, { recursive: true, force: true }); } });