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
49 lines
1.6 KiB
TypeScript
49 lines
1.6 KiB
TypeScript
/**
|
|
* Spawn-based integration suite: the runner as the provisioner (story 06)
|
|
* will actually run it — a real child process, config via env only, port
|
|
* discovered from stdout, real WS from the outside.
|
|
*/
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { spawnRunner, type SpawnedRunner } from "./spawn-runner";
|
|
import {
|
|
connectProtocolClient,
|
|
expectNamedError,
|
|
handshake,
|
|
type ProtocolClient,
|
|
} from "./protocol-client";
|
|
|
|
const TOKEN = "spawned-runner-token";
|
|
|
|
let runner: SpawnedRunner | undefined;
|
|
let clients: ProtocolClient[] = [];
|
|
|
|
afterEach(async () => {
|
|
for (const client of clients) client.close();
|
|
clients = [];
|
|
await runner?.stop();
|
|
runner = undefined;
|
|
});
|
|
|
|
describe("spawned runner process", () => {
|
|
it("boots from env config, announces its port, and completes the handshake over real WS", async () => {
|
|
runner = await spawnRunner({ token: TOKEN });
|
|
expect(runner.port).toBeGreaterThan(0);
|
|
|
|
const client = await connectProtocolClient(runner.port, TOKEN);
|
|
clients.push(client);
|
|
const ready = await handshake(client);
|
|
expect(ready).toEqual({ type: "ready" });
|
|
});
|
|
|
|
it("rejects a bad workspace token from a real client and closes the socket", async () => {
|
|
runner = await spawnRunner({ token: TOKEN });
|
|
const client = await connectProtocolClient(runner.port, "not-the-token");
|
|
clients.push(client);
|
|
client.send({ type: "hello" });
|
|
await expectNamedError(client, "unauthorized");
|
|
await expect(client.closed).resolves.toBe(1008);
|
|
// The runner never writes the workspace token to its logs.
|
|
expect(runner.output()).not.toContain(TOKEN);
|
|
});
|
|
});
|