Files
agentic-dev/packages/core-runner-protocol/src/messages.test.ts
Danijel Martinek 52f3b19dfe feat(core-runner-protocol): v0 message schemas with round-trip tests
Envelope pins protocolVersion "0" and carries the workspace-scoped
auth token; discriminated union on type covers hello/ready, clone,
install, scan, adapter-start, render-frame, status (stage + elapsed),
and error (named causes). All .strict() so the control plane, editor,
and runner cannot drift apart silently (ADR-027, PRD user story 7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
2026-07-12 21:47:34 +02:00

311 lines
8.0 KiB
TypeScript

import { describe, it, expect } from "vitest";
import type { z } from "zod";
import {
RUNNER_STAGES,
RUNNER_ERROR_CAUSES,
helloMessageSchema,
readyMessageSchema,
cloneMessageSchema,
installMessageSchema,
scanMessageSchema,
adapterStartMessageSchema,
renderFrameMessageSchema,
statusMessageSchema,
errorMessageSchema,
runnerMessageSchema,
type RunnerMessage,
} from "@/messages";
/** Serialize → deserialize → parse: the honest wire round-trip. */
function wireRoundTrip(schema: z.ZodTypeAny, value: unknown): unknown {
return schema.parse(JSON.parse(JSON.stringify(value)));
}
describe("helloMessageSchema", () => {
it("accepts a wire round-trip", () => {
const msg = { type: "hello" };
expect(wireRoundTrip(helloMessageSchema, msg)).toEqual(msg);
});
it("rejects unknown fields", () => {
expect(() =>
helloMessageSchema.parse({ type: "hello", extra: true }),
).toThrow();
});
it("rejects a wrong type literal", () => {
expect(() => helloMessageSchema.parse({ type: "ready" })).toThrow();
});
});
describe("readyMessageSchema", () => {
it("accepts a wire round-trip", () => {
const msg = { type: "ready" };
expect(wireRoundTrip(readyMessageSchema, msg)).toEqual(msg);
});
it("rejects unknown fields", () => {
expect(() =>
readyMessageSchema.parse({ type: "ready", capabilities: [] }),
).toThrow();
});
});
describe("cloneMessageSchema", () => {
it("accepts a wire round-trip with a PAT", () => {
const msg = {
type: "clone",
gitUrl: "https://git.example.com/acme/design-system.git",
pat: "ghp_workspace-scoped",
};
expect(wireRoundTrip(cloneMessageSchema, msg)).toEqual(msg);
});
it("accepts a public clone without a PAT", () => {
const msg = { type: "clone", gitUrl: "https://git.example.com/oss.git" };
expect(wireRoundTrip(cloneMessageSchema, msg)).toEqual(msg);
});
it("rejects a missing gitUrl", () => {
expect(() => cloneMessageSchema.parse({ type: "clone" })).toThrow();
});
it("rejects an empty gitUrl", () => {
expect(() =>
cloneMessageSchema.parse({ type: "clone", gitUrl: "" }),
).toThrow();
});
it("rejects unknown fields", () => {
expect(() =>
cloneMessageSchema.parse({
type: "clone",
gitUrl: "https://git.example.com/x.git",
branch: "main",
}),
).toThrow();
});
});
describe("installMessageSchema", () => {
it("accepts a wire round-trip", () => {
const msg = { type: "install" };
expect(wireRoundTrip(installMessageSchema, msg)).toEqual(msg);
});
it("rejects unknown fields", () => {
expect(() =>
installMessageSchema.parse({ type: "install", packageManager: "pnpm" }),
).toThrow();
});
});
describe("scanMessageSchema", () => {
it("accepts a wire round-trip", () => {
const msg = { type: "scan" };
expect(wireRoundTrip(scanMessageSchema, msg)).toEqual(msg);
});
it("rejects unknown fields", () => {
expect(() =>
scanMessageSchema.parse({ type: "scan", glob: "src/**" }),
).toThrow();
});
});
describe("adapterStartMessageSchema", () => {
it("accepts a wire round-trip", () => {
const msg = { type: "adapter-start" };
expect(wireRoundTrip(adapterStartMessageSchema, msg)).toEqual(msg);
});
it("rejects unknown fields", () => {
expect(() =>
adapterStartMessageSchema.parse({ type: "adapter-start", port: 5173 }),
).toThrow();
});
});
describe("renderFrameMessageSchema", () => {
it("accepts a wire round-trip with props", () => {
const msg = {
type: "render-frame",
componentId: "button",
props: { label: "Save", disabled: false },
};
expect(wireRoundTrip(renderFrameMessageSchema, msg)).toEqual(msg);
});
it("accepts default props by omission", () => {
const msg = { type: "render-frame", componentId: "button" };
expect(wireRoundTrip(renderFrameMessageSchema, msg)).toEqual(msg);
});
it("rejects a missing componentId", () => {
expect(() =>
renderFrameMessageSchema.parse({ type: "render-frame" }),
).toThrow();
});
it("rejects unknown fields", () => {
expect(() =>
renderFrameMessageSchema.parse({
type: "render-frame",
componentId: "button",
viewport: "mobile",
}),
).toThrow();
});
});
describe("statusMessageSchema", () => {
it.each(RUNNER_STAGES)("accepts a wire round-trip for stage %s", (stage) => {
const msg = { type: "status", stage, elapsedMs: 1200 };
expect(wireRoundTrip(statusMessageSchema, msg)).toEqual(msg);
});
it("rejects an unknown stage", () => {
expect(() =>
statusMessageSchema.parse({
type: "status",
stage: "compiling",
elapsedMs: 0,
}),
).toThrow();
});
it("rejects a missing elapsedMs", () => {
expect(() =>
statusMessageSchema.parse({ type: "status", stage: "cloning" }),
).toThrow();
});
it("rejects a negative elapsedMs", () => {
expect(() =>
statusMessageSchema.parse({
type: "status",
stage: "cloning",
elapsedMs: -1,
}),
).toThrow();
});
it("rejects a non-integer elapsedMs", () => {
expect(() =>
statusMessageSchema.parse({
type: "status",
stage: "cloning",
elapsedMs: 3.5,
}),
).toThrow();
});
it("rejects unknown fields", () => {
expect(() =>
statusMessageSchema.parse({
type: "status",
stage: "cloning",
elapsedMs: 0,
percent: 40,
}),
).toThrow();
});
});
describe("errorMessageSchema", () => {
it.each(RUNNER_ERROR_CAUSES)(
"accepts a wire round-trip for cause %s",
(cause) => {
const msg = { type: "error", cause, message: "named cause surfaced" };
expect(wireRoundTrip(errorMessageSchema, msg)).toEqual(msg);
},
);
it("accepts an error pinned to a stage", () => {
const msg = {
type: "error",
cause: "install-failed",
message: "pnpm install exited 1",
stage: "installing",
};
expect(wireRoundTrip(errorMessageSchema, msg)).toEqual(msg);
});
it("rejects an unknown cause", () => {
expect(() =>
errorMessageSchema.parse({
type: "error",
cause: "mystery",
message: "?",
}),
).toThrow();
});
it("rejects an empty message", () => {
expect(() =>
errorMessageSchema.parse({
type: "error",
cause: "clone-failed",
message: "",
}),
).toThrow();
});
it("rejects an unknown stage", () => {
expect(() =>
errorMessageSchema.parse({
type: "error",
cause: "clone-failed",
message: "x",
stage: "compiling",
}),
).toThrow();
});
it("rejects unknown fields", () => {
expect(() =>
errorMessageSchema.parse({
type: "error",
cause: "clone-failed",
message: "x",
retryable: true,
}),
).toThrow();
});
});
describe("runnerMessageSchema (discriminated union)", () => {
const oneOfEach: RunnerMessage[] = [
{ type: "hello" },
{ type: "ready" },
{ type: "clone", gitUrl: "https://git.example.com/acme.git", pat: "tok" },
{ type: "install" },
{ type: "scan" },
{ type: "adapter-start" },
{ type: "render-frame", componentId: "button", props: { label: "Go" } },
{ type: "status", stage: "cloning", elapsedMs: 42 },
{ type: "error", cause: "clone-failed", message: "boom", stage: "cloning" },
];
it.each(oneOfEach.map((msg) => [msg.type, msg] as const))(
"routes %s through the union on a wire round-trip",
(_type, msg) => {
expect(wireRoundTrip(runnerMessageSchema, msg)).toEqual(msg);
},
);
it("rejects an unknown type", () => {
expect(() => runnerMessageSchema.parse({ type: "watch" })).toThrow();
});
it("rejects a missing type", () => {
expect(() => runnerMessageSchema.parse({})).toThrow();
});
it("rejects a known type with another type's fields", () => {
expect(() =>
runnerMessageSchema.parse({ type: "install", stage: "installing" }),
).toThrow();
});
});