runa/vscode/test/protocol.test.ts

125 lines
4.1 KiB
TypeScript
Raw Normal View History

import assert from "node:assert/strict";
import test from "node:test";
import { parse } from "lossless-json";
import {
decodeResponse,
decodeValue,
formatSnapshot,
ProtocolError,
ReadCommand,
} from "../src/protocol";
import { actor, json, REF } from "./fixtures";
const command: ReadCommand = { operation: "list_actors" };
const envelope = (reply: unknown) =>
json({ version: 3, request_id: "test", result: { Ok: reply } });
test("v3 snapshots preserve all value variants, i64 limits, u64 telemetry, and mailbox order", () => {
const snapshot = actor();
const reply = decodeResponse(envelope({ kind: "actors", data: [snapshot] }), "test", command);
assert.deepEqual(reply, { kind: "actors", data: [snapshot] });
const formatted = formatSnapshot(snapshot, "[::1]:3737", "2026-11-03T00:00:00Z", false);
assert.match(formatted, /9223372036854775807/);
assert.match(formatted, /-9223372036854775808/);
assert.match(formatted, /18446744073709551615/);
assert.deepEqual(parse(formatted, undefined, BigInt), {
connection: { endpoint: "[::1]:3737", connected: false },
captured_at: "2026-11-03T00:00:00Z",
actor: snapshot,
});
});
test("version, correlation, result, reply kind, and actor identity are checked", () => {
for (const change of [
{ version: 2 },
{ request_id: "wrong" },
{ result: { Ok: { kind: "actor", data: actor() } } },
{ result: { Ok: {}, Err: {} } },
{ result: {} },
]) {
assert.throws(() =>
decodeResponse(
json({
version: 3,
request_id: "test",
result: { Ok: { kind: "actors", data: [] } },
...change,
}),
"test",
command,
),
);
}
assert.throws(() =>
decodeResponse(envelope({ kind: "actors", data: [actor(), actor()] }), "test", command),
);
assert.throws(() =>
decodeResponse(envelope({ kind: "actor", data: actor() }), "test", {
operation: "inspect_actor",
parameters: { actor_ref: "ABCDEFGHIJKLMNOPQRSTU" },
}),
);
});
test("server errors are explicit, typed, and not treated as empty actor lists", () => {
assert.throws(
() =>
decodeResponse(
json({
version: 3,
request_id: "test",
result: { Err: { code: "not_found", message: "No actor" } },
}),
"test",
command,
),
(error) => error instanceof ProtocolError && error.code === "not_found",
);
});
test("malformed states and snapshots fail instead of being coerced", () => {
for (const state of [
{ type: "integer", data: 9223372036854775808n },
{ type: "integer", data: -9223372036854775809n },
{ type: "integer", data: 1.5 },
{ type: "actor_ref", data: "guessable" },
{ type: "string", data: REF, extra: true },
{ type: "null", data: null },
{ type: "float", data: 1 },
]) {
assert.throws(() =>
decodeResponse(envelope({ kind: "actors", data: [{ ...actor(), state }] }), "test", command),
);
}
for (const change of [
{ status: { status: "unknown" } },
{ status: { status: "failed" } },
{ incarnation: -1 },
{ mailbox_id: 18446744073709551616n },
{ execution: { vm_steps: "20" } },
{ reserved: undefined },
{ queued: null },
{ actor_ref: "@invalid" },
]) {
assert.throws(() =>
decodeResponse(
envelope({ kind: "actors", data: [{ ...actor(), ...change }] }),
"test",
command,
),
);
}
});
test("portable value limits have inclusive depth, node, and UTF-8 byte boundaries", () => {
let nested: unknown = { type: "null" };
for (let i = 0; i < 32; i++) nested = { type: "list", data: [nested] };
assert.doesNotThrow(() => decodeValue(nested));
assert.throws(() => decodeValue({ type: "list", data: [nested] }), /depth/);
const data = Array.from({ length: 4095 }, () => ({ type: "null" }));
assert.doesNotThrow(() => decodeValue({ type: "list", data }));
assert.throws(() => decodeValue({ type: "list", data: [...data, { type: "null" }] }), /node/);
assert.doesNotThrow(() => decodeValue({ type: "string", data: "é".repeat(524288) }));
assert.throws(() => decodeValue({ type: "string", data: "é".repeat(524288) + "a" }), /byte/);
});