apps/runner scaffold (app-tier, walking-skeleton story 04): WS server speaking @repo/core-runner-protocol. Every inbound/outbound frame is envelope-wrapped and zod-parsed; hello/ready handshake gates on the workspace-scoped token (constant-time compare, redacted token on rejection replies); named error events for version/schema/auth rejections. Config via env only (token never argv); port announced on stdout for the story-06 provisioner. Runtime deps: ws (the standard Node WS server; ADR-022 traces do not apply to app-tier) and zod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { PROTOCOL_VERSION } from "@repo/core-runner-protocol";
|
|
import { gateInbound } from "@/server";
|
|
|
|
const TOKEN = "workspace-token";
|
|
|
|
function frame(overrides: Record<string, unknown> = {}): string {
|
|
return JSON.stringify({
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
token: TOKEN,
|
|
message: { type: "hello" },
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
describe("gateInbound", () => {
|
|
it("accepts a conformant envelope and returns the inner message", () => {
|
|
const result = gateInbound(frame(), TOKEN);
|
|
expect(result).toEqual({ ok: true, message: { type: "hello" } });
|
|
});
|
|
|
|
it("rejects a frame that is not JSON", () => {
|
|
const result = gateInbound("{nope", TOKEN);
|
|
expect(result).toMatchObject({ ok: false, cause: "invalid-message" });
|
|
});
|
|
|
|
it("rejects JSON that is not an object", () => {
|
|
expect(gateInbound('"hello"', TOKEN)).toMatchObject({
|
|
ok: false,
|
|
cause: "invalid-message",
|
|
});
|
|
expect(gateInbound("[1,2]", TOKEN)).toMatchObject({
|
|
ok: false,
|
|
cause: "invalid-message",
|
|
});
|
|
expect(gateInbound("null", TOKEN)).toMatchObject({
|
|
ok: false,
|
|
cause: "invalid-message",
|
|
});
|
|
});
|
|
|
|
it("rejects an unsupported protocol version with the named cause", () => {
|
|
const result = gateInbound(frame({ protocolVersion: "999" }), TOKEN);
|
|
expect(result).toMatchObject({
|
|
ok: false,
|
|
cause: "unsupported-protocol-version",
|
|
});
|
|
});
|
|
|
|
it("rejects a missing protocol version as unsupported", () => {
|
|
const raw = JSON.stringify({ token: TOKEN, message: { type: "hello" } });
|
|
expect(gateInbound(raw, TOKEN)).toMatchObject({
|
|
ok: false,
|
|
cause: "unsupported-protocol-version",
|
|
});
|
|
});
|
|
|
|
it("rejects a missing token as invalid-message (schema)", () => {
|
|
const raw = JSON.stringify({
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
message: { type: "hello" },
|
|
});
|
|
expect(gateInbound(raw, TOKEN)).toMatchObject({
|
|
ok: false,
|
|
cause: "invalid-message",
|
|
});
|
|
});
|
|
|
|
it("rejects unknown envelope fields (strict schema)", () => {
|
|
const result = gateInbound(frame({ extra: "field" }), TOKEN);
|
|
expect(result).toMatchObject({ ok: false, cause: "invalid-message" });
|
|
});
|
|
|
|
it("rejects an unknown message shape without echoing payload values", () => {
|
|
const result = gateInbound(
|
|
frame({
|
|
message: { type: "clone", gitUrl: "", pat: "super-secret-pat" },
|
|
}),
|
|
TOKEN,
|
|
);
|
|
expect(result).toMatchObject({ ok: false, cause: "invalid-message" });
|
|
expect(JSON.stringify(result)).not.toContain("super-secret-pat");
|
|
});
|
|
|
|
it("rejects a wrong token as unauthorized", () => {
|
|
expect(gateInbound(frame({ token: "wrong" }), TOKEN)).toMatchObject({
|
|
ok: false,
|
|
cause: "unauthorized",
|
|
});
|
|
// Same length, different bytes — exercises the constant-time compare.
|
|
expect(
|
|
gateInbound(frame({ token: "workspace-tokeX" }), TOKEN),
|
|
).toMatchObject({
|
|
ok: false,
|
|
cause: "unauthorized",
|
|
});
|
|
});
|
|
});
|