import assert from "node:assert/strict"; import test from "node:test"; import { ImageClient } from "../src/client"; import { ProtocolError } from "../src/protocol"; import { parseValue } from "../src/values"; import { startImage } from "./server"; test( "extension controls spawn source paths, send lossless values, and apply system messages", { timeout: 10000 }, async (t) => { const image = await startImage(); let client: ImageClient | undefined; t.after(async () => { await client?.close(); await image.stop(); }); client = await ImageClient.connect(image.address, (error) => assert.equal(error, undefined)); assert.deepEqual( (await client.listSources()).map((file) => file.path), ["actors/counter"], ); await assert.rejects(client.spawn("actors/missing", parseValue("0")), ProtocolError); const ref = await client.spawn("actors/counter", parseValue("5"), 2); assert.match(ref, /^[A-Za-z0-9_-]{21}$/); await client.send(ref, parseValue('{"op":"add","value":7}')); await client.system(ref, { kind: "pause" }); await client.tick(100); let snapshot = await client.inspectActor(ref); assert.equal(snapshot.status.status, "paused"); assert.deepEqual(snapshot.state, parseValue("5")); assert.equal(snapshot.queued.length, 1); await client.system(ref, { kind: "resume" }); await client.tick(100); snapshot = await client.inspectActor(ref); assert.deepEqual(snapshot.state, parseValue("12")); assert.deepEqual(snapshot.queued, []); await client.system(ref, { kind: "restart" }); snapshot = await client.inspectActor(ref); assert.equal(snapshot.incarnation, 2n); assert.deepEqual(snapshot.state, parseValue("12")); const implementation = snapshot.implementation_id; // Restart forces a new incarnation even when running, keeping implementation and state. await client.system(ref, { kind: "restart" }); snapshot = await client.inspectActor(ref); assert.equal(snapshot.incarnation, 3n); assert.equal(snapshot.implementation_id, implementation); assert.deepEqual(snapshot.state, parseValue("12")); await client.system(ref, { kind: "reload" }); await client.tick(10); assert.equal((await client.inspectActor(ref)).status.status, "idle"); await assert.rejects( client.system(ref, { kind: "become", path: "actors/missing" }), ProtocolError, ); const payload = parseValue(`{"nested":[9223372036854775807,{"$actor":"${ref}"}]}`); await client.send(ref, payload); assert.deepEqual((await client.inspectActor(ref)).queued, [payload]); // Whole-request size includes tagged values and the envelope. await assert.rejects( client.send(ref, { type: "string", data: "x".repeat(1024 * 1024) }), /1 MiB/, ); assert.equal((await client.listActors()).length, 1); await client.system(ref, { kind: "stop" }); assert.equal((await client.inspectActor(ref)).actor_ref, ref); await client.tick(100); await assert.rejects(client.inspectActor(ref), /unknown actor/); await assert.rejects(client.system(ref, { kind: "restart" }), /unknown actor/); assert.deepEqual(await client.listActors(), []); const failed = await client.spawn("actors/counter", parseValue("0")); await client.send(failed, parseValue('"bad message"')); await client.tick(100); assert.equal((await client.inspectActor(failed)).status.status, "failed"); await client.system(failed, { kind: "kill" }); await assert.rejects(client.inspectActor(failed), /unknown actor/); assert.deepEqual(await client.listActors(), []); }, );