A 182-line stand-in for the engine's TCP port: six checks, one wrong token, one dropped socket, nothing installed.
Every harness that talks to a socket starts the same way: someone mocks the client's own methods. The test then proves that the mock returns what the mock was told to return, the real client never runs, and the thing that actually breaks in production — framing, the handshake, a half-written frame — is never exercised.
What you have to fake is not your code. It is the peer: a process that reads bytes off a socket and answers frames. If that peer is a line protocol, the stand-in can be a hundred lines of node:net, and it will catch the failures a method-level mock cannot see.
The protocol in ArtCraft's control channel is four frames long — auth, system.ping, engine.execute, engine.batch — and the first one is the whole security model. This post is the stub that speaks it, the six checks it makes possible, and why a fake peer is a better test than a fake client.
Repo: github.com/edgarmilvus/artcraft-book · GitHub: @edgarmilvus · Ebook: ArtCraft: The Agentic Creative Studio in TypeScript
One JSON object per line, and nothing else
The channel is JSON-lines: every frame is one object, serialised, terminated by \n. No length prefix, no delimiter escaping, no binary preamble. The receiver keeps a buffer, splits on newlines, and parses each complete line:
sock.on('data', (chunk) => { buf += chunk.toString('utf8'); let nl: number; while ((nl = buf.indexOf('\n')) !== -1) { const line = buf.slice(0, nl).trim(); buf = buf.slice(nl + 1); if (!line) continue; void this.onFrame(JSON.parse(line) as Frame, sock); } }); That loop is the entire framing layer, and it is also the first place a client can be wrong: a frame split across two chunk events is the classic bug, and no mock of sendCommand() will ever produce it.
The first frame is the handshake
Nothing else is accepted before auth succeeds. The stub compares the presented token against the expected one, and on mismatch it answers once and destroys the socket:
if (presented !== expected) { this.send(sock, { id: 'auth', ok: false, error: { code: 'auth_failed', message: 'token mismatch' } }); sock.destroy(); return; } this.send(sock, { id: 'auth', ok: true }); Two things are worth copying from that snippet. The failure is a frame, not a silent disconnect — the client can distinguish "wrong token" from "process died". And the connection does not stay open in a half-authenticated state, because that is the state attackers look for.
The stub can also run in challenge mode: it sends auth.challenge with a 16-byte nonce first, and the peer answers HMAC-SHA256(token, nonce) instead of presenting the token. The challenge: true path is driven by the chapter's advanced client, which also compares the server's reply with timingSafeEqual — same handshake, one flag, and the secret never crosses the wire in a form you can replay.
A stub is a specification you can execute
The rest of the stub is one switch per method, and a deliberate decision at the bottom:
| frame | what the stub does |
|---|---|
auth | compares the token, or the HMAC of the nonce; closes on failure |
system.ping | replies { pong: true, at } for the heartbeat |
engine.execute | applies one command, records it, returns the result |
engine.batch | applies every step, returns the applied list |
ui.screenshot | writes a 1×1 PNG to the requested path, returns its bytes |
| anything else | replies unknown_method and logs it |
That last row is the point of a stub over a mock: an unimplemented method is reported, not silently swallowed. When the client starts calling something new, the test fails with the method name instead of passing because a looser fake returned undefined.
There is also dropConnections(), which destroys every live socket without stopping the listener. That is the only way to test reconnection logic honestly — the client has to observe a real close event, back off, and reconnect to the same port while the peer keeps listening.
The stub
/** * A protocol-faithful stand-in for the engine's loopback control port. * * The shipped CLIs speak MCP over stdio; the chapter's TCP protocol * (`auth` → `system.ping` → `engine.execute`) has no upstream process to * talk to, so both harnesses here drive the book's client against this. */ import { createServer, type Server, type Socket } from 'node:net'; import { createHmac, randomBytes } from 'node:crypto'; import * as fs from 'node:fs'; import * as path from 'node:path'; // 1x1 transparent PNG, enough for a screenshot round-trip. const PNG_B64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='; type Frame = { id?: string | number; method?: string; params?: Record<string, unknown>; ok?: boolean; result?: unknown; error?: string | { code: string | number; message: string }; }; // ─── the engine stub ──────────────────────────────────────────────────────── interface StubOptions { token: string; challenge: boolean; port: number; } export class EngineStub { private srv: Server; private sockets = new Set<Socket>(); public calls: string[] = []; public commands: string[] = []; public layers: Array<{ id: number; name: string }> = []; public nextLayerId = 1; public docCount = 1; private nonce = ''; constructor(private opts: StubOptions) { this.srv = createServer((s) => this.onConnection(s)); } public start(): Promise<void> { return new Promise((resolve) => this.srv.listen(this.opts.port, '127.0.0.1', resolve)); } /** Kill every live connection without stopping the listener (reconnect tests). */ public dropConnections(): number { const n = this.sockets.size; for (const s of this.sockets) s.destroy(); this.sockets.clear(); return n; } public stop(): Promise<void> { for (const s of this.sockets) s.destroy(); return new Promise((resolve) => this.srv.close(() => resolve())); } private onConnection(sock: Socket): void { this.sockets.add(sock); let buf = ''; this.nonce = randomBytes(16).toString('hex'); if (this.opts.challenge) { sock.write(JSON.stringify({ method: 'auth.challenge', params: { nonce: this.nonce } }) + '\n'); } sock.on('data', (chunk) => { buf += chunk.toString('utf8'); let nl: number; while ((nl = buf.indexOf('\n')) !== -1) { const line = buf.slice(0, nl).trim(); buf = buf.slice(nl + 1); if (!line) continue; void this.onFrame(JSON.parse(line) as Frame, sock); } }); sock.on('error', () => this.sockets.delete(sock)); sock.on('close', () => this.sockets.delete(sock)); } private send(sock: Socket, f: Frame): void { sock.write(JSON.stringify(f) + '\n'); } private async onFrame(f: Frame, sock: Socket): Promise<void> { this.calls.push(String(f.method)); if (f.method === 'auth') { const presented = String(f.params?.['token'] ?? ''); const expected = this.opts.challenge ? createHmac('sha256', Buffer.from(this.opts.token, 'hex')) .update(Buffer.from(this.nonce, 'hex')) .digest('hex') : this.opts.token; if (presented !== expected) { this.send(sock, { id: 'auth', ok: false, error: { code: 'auth_failed', message: 'token mismatch' } }); sock.destroy(); return; } this.send(sock, { id: 'auth', ok: true }); return; } if (f.id === 'hb' || f.method === 'system.ping') { this.send(sock, { id: f.id, ok: true, result: { pong: true, at: Date.now() } }); return; } // The session exposes both shapes: `invoke` sends a raw method, while // `executeCommand` wraps `engine.execute`. `screenshot()` uses the raw // form, so the stub answers it directly. if (f.method === 'ui.screenshot') { // The protocol lets the caller name a path; the engine writes the PNG // there and still returns the bytes, so callers can pick either. const target = f.params?.['path']; if (typeof target === 'string') { fs.mkdirSync(path.dirname(target), { recursive: true }); fs.writeFileSync(target, Buffer.from(PNG_B64, 'base64')); } this.send(sock, { id: f.id, ok: true, result: { data: PNG_B64, path: typeof target === 'string' ? target : '/tmp/stub-shot.png' }, }); return; } if (f.method === 'system.version') { this.send(sock, { id: f.id, ok: true, result: { name: 'stub', version: '1.0.0' } }); return; } if (f.method === 'engine.batch') { const steps = (f.params?.steps ?? []) as Array<{ id: string }>; const applied = steps.map((s) => this.apply(s.id, {})); this.send(sock, { id: f.id, ok: true, result: { applied } }); return; } if (f.method === 'engine.execute') { const command = String(f.params?.command ?? ''); this.commands.push(command); const params = (f.params?.params ?? {}) as Record<string, unknown>; try { this.send(sock, { id: f.id, ok: true, result: this.apply(command, params) }); } catch (e) { this.send(sock, { id: f.id, ok: false, error: { code: 'command_failed', message: (e as Error).message } }); } return; } console.error(`stub: unknown method ${String(f.method)}`); this.send(sock, { id: f.id, ok: false, error: { code: 'unknown_method', message: String(f.method) } }); } private apply(command: string, params: Record<string, unknown>): unknown { switch (command) { case 'doc.inspect': case 'document.inspect': return { document: { width: 1920, height: 1080, mode: 'rgb' }, layers: this.layers }; case 'file.new': { this.layers = []; return { index: this.docCount++ }; } case 'file.save': return { path: String(params.path ?? 'saved.pcraft'), bytes: 1024 }; case 'layer.new.layer': { const layer = { id: this.nextLayerId++, name: String(params.name ?? 'Layer') }; this.layers.push(layer); return layer; } case 'ui.screenshot': return { data: PNG_B64, path: '/tmp/stub-shot.png' }; default: throw new Error(`stub does not implement ${command}`); } } } What it buys
The exercise's client is 396 lines — backpressure through drain, a withDocument scope, a heartbeat. The harness drives that real client against this stub and asserts six things:
PASS wrong token rejected AuthFailedError PASS run doc.inspect PASS run layer.new.layer id=1 PASS batch (2 steps) PASS withDocument scope layers before=0 PASS survives a dropped socket dropped=1 6/6 checks passed Note what those checks are testing. Not "does the client format a URL" — the real client serialises real frames onto a real socket, the real stub parses them, and the assertions are on the replies. survives a dropped socket is a genuine reconnect: the stub killed the connection mid-session.
Run it
No engine, no Docker, no API key — the point of the stub is that the test needs nothing installed:
git clone https://github.com/edgarmilvus/artcraft-book cd artcraft-book npm ci cd chapter4/solutions_and_explanations/exercise_1_a-backpressure-aware-json-lines-channel-client-for-photocraft-banner-batch/files npx tsx run_channel_client.ts It takes about fourteen seconds, and almost none of it is work: the heartbeat fires every five seconds and a missed beat is only a failure after two of them, and the reconnect test waits out a 600 ms backoff. Those waits are the test.
The rule
When the thing on the other side of a boundary is a protocol, fake the peer and keep the code under test real. The stub is 182 lines, it fails loudly on methods it does not implement, and it doubles as the clearest description of the protocol you have — more useful than the paragraph in the docs, because this one runs.
Repo: github.com/edgarmilvus/artcraft-book — MIT, twenty chapters, every harness executed against the real binaries
GitHub: @edgarmilvus — the other books and repositories
...

