| ff39a5e8 Andy McCall ↶ |
1 |
// Smoke test against a real Spectra (not part of `npm test`): the bridge |
| 2 |
// signs, the model fills from the API, the stream delivers, a brief saved |
| 3 |
// through the API reaches Spectra. Run with: npm run smoke |
| 4 |
import { spawn, execFileSync } from "node:child_process"; |
| 5 |
import { mkdtempSync, rmSync } from "node:fs"; |
| 6 |
import { tmpdir } from "node:os"; |
| 7 |
import { join } from "node:path"; |
| 8 |
import assert from "node:assert/strict"; |
| 9 |
|
| 10 |
const bin = process.env.SPECTRA_BIN; |
| 11 |
assert.ok(bin, "set SPECTRA_BIN to a spectra binary"); |
| 12 |
const dir = mkdtempSync(join(process.env.SMOKE_DIR || tmpdir(), "spectra-smoke-")); |
| 13 |
const env = { ...process.env, HOME: dir, SPECTRA_HOME: join(dir, "owner"), SPECTRA_DATA: join(dir, "data"), XDG_DATA_HOME: join(dir, "xdg"), NO_COLOR: "1" }; |
| 14 |
Object.assign(process.env, env); |
| 15 |
const sp = (...a) => execFileSync(bin, a, { env }).toString(); |
| 16 |
sp("identity", "create", "--name", "Smoke Owner"); |
| 17 |
sp("init"); |
| 18 |
sp("project", "add", "book", "-d", "the manuscript"); |
| 19 |
sp("agent", "add", "claude-book", "--kind", "claude-code", "--project", "book", "--home", join(dir, "agents/claude-book"), "--yes"); |
| 20 |
const serve = spawn(bin, ["serve", "--bind", "127.0.0.1:0"], { env, stdio: "ignore" }); |
| 21 |
await new Promise((r) => setTimeout(r, 1500)); |
| 22 |
|
| 23 |
const { Bridge } = await import("../out/smoke-bridge.mjs"); |
| 24 |
const { Model } = await import("../out/smoke-model.mjs"); |
| 25 |
const log = { append() {}, appendLine() {} }; |
| 26 |
const bridge = new Bridge(log); |
| 27 |
const model = new Model(bridge); |
| 28 |
try { |
| 29 |
await model.refresh(); |
| 30 |
assert.equal(model.error, undefined); |
| 31 |
assert.equal(model.brief.actor, "human"); |
| 32 |
assert.equal(model.agents[0].name, "claude-book"); |
| 33 |
assert.ok(model.channels.some((c) => c.name === "book")); |
| 34 |
|
| 35 |
const seen = []; |
| 36 |
model.onEvent((e) => seen.push(e.kind)); |
| 37 |
model.start(); |
| 38 |
await new Promise((r) => setTimeout(r, 800)); |
| 39 |
await bridge.request("POST", "/api/v1/channels/book/messages", { body: "hello from the editor" }); |
| 40 |
await bridge.request("PUT", "/api/v1/briefs", { project: "book", text: "Chapter 3 becomes chapter 5." }); |
| 41 |
await new Promise((r) => setTimeout(r, 1200)); |
| 42 |
assert.ok(seen.includes("message.posted"), `the stream delivered: ${seen}`); |
| 43 |
assert.equal(model.briefs.projects[0]?.text, "Chapter 3 becomes chapter 5."); |
| 44 |
assert.match(sp("brief", "show", "--project", "book"), /chapter 5/); |
| 45 |
console.log("smoke: ok"); |
| 46 |
} finally { |
| 47 |
model.dispose(); |
| 48 |
bridge.dispose(); |
| 49 |
serve.kill("SIGTERM"); |
| 50 |
await new Promise((r) => setTimeout(r, 1500)); |
| 51 |
rmSync(dir, { recursive: true, force: true }); |
| 52 |
} |