79 lines
3.9 KiB
JavaScript
79 lines
3.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
|
|
import { SimSession, CONTEXT_ID } from "./sim-session.mjs";
|
|
import { ReplayTimeline, fnv1a64 } from "./replay.mjs";
|
|
import { createWorkspacePackage, migrateWorkspacePackage } from "../workspace/migration.mjs";
|
|
import {
|
|
SCHEMAS,
|
|
validateCommandEnvelope,
|
|
validateEventLog,
|
|
validateSimTask,
|
|
validateSnapshot,
|
|
validateWorkspacePackage,
|
|
} from "./contracts.mjs";
|
|
|
|
const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url));
|
|
const core = await createModule({ wasmBinary });
|
|
const sessionId = "contract-session";
|
|
const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: sessionId };
|
|
|
|
function command(kind, seq, payload = {}, operation_id = undefined) {
|
|
return { ...base, kind, seq, ...(operation_id ? { operation_id } : {}), payload };
|
|
}
|
|
|
|
validateCommandEnvelope(command("HELLO", 1), { contextId: CONTEXT_ID, sessionId });
|
|
validateCommandEnvelope({ ...command("PING", 2), sent_at_monotonic_ms: 12.5 });
|
|
validateCommandEnvelope(command("X".repeat(128), 2));
|
|
for (const invalid of [
|
|
{ ...command("PING", 2), schema_version: 2 },
|
|
{ ...command("PING", 2), seq: 0 },
|
|
{ ...command("PING", 2), payload: [] },
|
|
{ ...command("PING", 2), payload: { value: Number.NaN } },
|
|
{ ...command("PING", 2), unknown: true },
|
|
command("SIM_STEP", 2, { cycles: 1 }),
|
|
command("X".repeat(129), 2),
|
|
]) {
|
|
assert.throws(() => validateCommandEnvelope(invalid), /^Error: contract_/);
|
|
}
|
|
assert.throws(() => validateCommandEnvelope(command("PING", 2), { contextId: "other" }), /contract_command_context_mismatch/);
|
|
|
|
const taskUnsigned = {
|
|
schema: SCHEMAS.task,
|
|
context_id: CONTEXT_ID,
|
|
session_id: sessionId,
|
|
state: "READY",
|
|
mode: "MANUAL",
|
|
program_status: "idle",
|
|
seq: 2,
|
|
simulation_tick: 0,
|
|
current_line: null,
|
|
};
|
|
validateSimTask({ ...taskUnsigned, state_hash: fnv1a64(taskUnsigned) }, { contextId: CONTEXT_ID, sessionId });
|
|
assert.throws(() => validateSimTask({ ...taskUnsigned, state_hash: "fnv1a64:0000000000000000" }), /contract_task_hash/);
|
|
assert.throws(() => validateSimTask({ ...taskUnsigned, state: "UNKNOWN", state_hash: fnv1a64(taskUnsigned) }), /contract_task_state/);
|
|
|
|
const session = new SimSession(core, { sessionId });
|
|
assert.equal(session.accept(command("HELLO", 1)).kind, "CAPABILITIES");
|
|
assert.equal(session.accept(command("SESSION_ATTACH", 2, {}, "attach")).kind, "SESSION_READY");
|
|
assert.equal(session.accept(command("LOAD_WORKSPACE", 3, { root_path: "/workspace" }, "load")).payload.status, "verified");
|
|
const stepped = session.accept(command("SIM_STEP", 4, { cycles: 5 }, "step"));
|
|
assert.equal(stepped.simulation_tick, 5);
|
|
validateSnapshot(stepped.payload.snapshot);
|
|
const eventLog = session.timeline.events.export();
|
|
validateEventLog(eventLog);
|
|
assert.equal(core._sim_protocol_last_seq(), eventLog.cursor);
|
|
assert.equal(core._sim_event_count(), eventLog.events.length);
|
|
assert.equal(core._sim_protocol_accept(2, 5, "PING", "", 5), -100);
|
|
|
|
const timeline = new ReplayTimeline(session.timeline.export());
|
|
const workspace = createWorkspacePackage({ contextId: CONTEXT_ID, timeline, committedSeq: eventLog.cursor });
|
|
validateWorkspacePackage(workspace, { contextId: CONTEXT_ID });
|
|
const migrated = migrateWorkspacePackage(workspace, 2);
|
|
validateWorkspacePackage(migrated, { contextId: CONTEXT_ID });
|
|
assert.throws(() => validateWorkspacePackage({ ...workspace, package_hash: "fnv1a64:0000000000000000" }), /contract_workspace_package_hash/);
|
|
assert.throws(() => validateWorkspacePackage({ ...workspace, committed_seq: Number.POSITIVE_INFINITY }), /contract_workspace_committed_seq/);
|
|
assert.equal(migrateWorkspacePackage(migrated, 1).package_hash, workspace.package_hash);
|
|
|
|
console.log(`contracts=passed schemas=${Object.values(SCHEMAS).join(",")} tick=${stepped.simulation_tick} cursor=${eventLog.cursor}`);
|