import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import { once } from "node:events"; import { resolve } from "node:path"; import test from "node:test"; import { ImageClient } from "../src/client"; import { startImage } from "./server"; test( "the real REPL mutates the same WebSocket image and notifies the inspection client", { timeout: 10000 }, async (t) => { const image = await startImage(); let inspector: ImageClient | undefined; t.after(async () => { inspector?.close(); await image.stop(); }); const revisions: bigint[] = []; let onChanged = () => {}; const changed = new Promise((resolve) => { onChanged = resolve; }); inspector = await ImageClient.connect( image.address, (error) => assert.equal(error, undefined), (revision) => { revisions.push(revision); onChanged(); }, ); const executable = resolve( __dirname, "../../../target/debug", process.platform === "win32" ? "runa-cli.exe" : "runa-cli", ); const repl = spawn(executable, [], { env: { ...process.env, RUNA_CONNECT: image.address }, stdio: ["pipe", "pipe", "pipe"], }); t.after(() => { if (repl.exitCode === null && repl.signalCode === null) repl.kill(); }); let stdout = ""; let stderr = ""; repl.stdout.on("data", (chunk: Buffer) => { stdout += chunk.toString(); }); repl.stderr.on("data", (chunk: Buffer) => { stderr += chunk.toString(); }); const exited = once(repl, "exit"); repl.stdin.end( [ "let actor = spawn counter 5", 'send actor {"op":"add","value":7}', "tick 100", "inspect actor", "quit", ].join("\n") + "\n", ); const [code] = await exited; assert.equal(code, 0, stderr); assert.doesNotMatch(stdout, /error:/i); await changed; assert.ok(revisions.length > 0); const [actor] = await inspector.listActors(); assert.ok(actor); assert.ok(stdout.includes(actor.actor_ref)); assert.deepEqual(actor.state, { type: "integer", data: 12n }); assert.deepEqual(actor.queued, []); assert.equal(actor.reserved, null); const overridden = spawn(executable, ["--connect", image.address], { env: { ...process.env, RUNA_CONNECT: "invalid" }, stdio: ["pipe", "pipe", "pipe"], }); t.after(() => { if (overridden.exitCode === null && overridden.signalCode === null) overridden.kill(); }); const overriddenExit = once(overridden, "exit"); overridden.stdin.end("actors\nquit\n"); const [overrideCode] = await overriddenExit; assert.equal(overrideCode, 0); }, );