feat(sim): F4.2 the sim protocol, the simulation room and LocalSimHost; M9 measured
packages/protocol holds the one list of /sim messages and checks every field, naming what is wrong. packages/sim has the simulation room for F and the web worker: the document's snapshot at start, the engine's clock on the wall clock with Java's speed as cable delay, subscriptions to cable activity, a card's new frames and a program's new window lines, capture and clock kept over a stop, the command line as first action, a core failure told to every client. LocalSimHost mirrors the document into the worker and back without echo. M9 in Chromium: 17 examples give the same capture as in Node, character for character; the room runs in a real worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
// The one list of messages: what the room accepts from a client, and what
|
||||
// it refuses, each with its reason.
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { CLIENT_MESSAGE_KINDS, HOST_MESSAGE_KINDS, isTopic, parseClientMessage, parseHostMessage } from '../src/index.js';
|
||||
|
||||
describe("a client's messages", () => {
|
||||
it('reads each kind', () => {
|
||||
const messages = [
|
||||
{ v: 1, kind: 'sub', topics: ['activity', 'trace:port-1', 'app:node-1/terminal'] },
|
||||
{ v: 1, kind: 'unsub', topics: ['activity'] },
|
||||
{ v: 1, kind: 'start' },
|
||||
{ v: 1, kind: 'stop' },
|
||||
{ v: 1, kind: 'speed', value: 50 },
|
||||
{ v: 1, kind: 'input', node: 'node-1', app: 'terminal', action: 'enter', args: { line: 'ping 1.2.3.4' }, reqId: 'r1' },
|
||||
];
|
||||
expect(messages.map((each) => each.kind)).toEqual([...CLIENT_MESSAGE_KINDS]);
|
||||
for (const each of messages) expect(parseClientMessage(each)).toEqual({ message: each });
|
||||
});
|
||||
|
||||
it('names what is wrong', () => {
|
||||
const code = (value: unknown) => ('error' in parseClientMessage(value) ? (parseClientMessage(value) as { error: { code: string } }).error.code : 'ok');
|
||||
expect(code(null)).toBe('badMessage');
|
||||
expect(code([])).toBe('badMessage');
|
||||
expect(code({ v: 2, kind: 'start' })).toBe('badVersion');
|
||||
expect(code({ v: 1, kind: 'reset' })).toBe('unknownKind');
|
||||
expect(code({ v: 1, kind: 'sub', topics: 'activity' })).toBe('badMessage');
|
||||
expect(code({ v: 1, kind: 'sub', topics: ['activity', 'trace:'] })).toBe('badTopic');
|
||||
expect(code({ v: 1, kind: 'unsub', topics: ['app:node/paint'] })).toBe('badTopic');
|
||||
expect(code({ v: 1, kind: 'speed', value: '50' })).toBe('badMessage');
|
||||
expect(code({ v: 1, kind: 'speed', value: Number.NaN })).toBe('badMessage');
|
||||
expect(code({ v: 1, kind: 'input', node: 'n', app: 'terminal', action: 'enter', args: {} })).toBe('badMessage');
|
||||
expect(code({ v: 1, kind: 'input', node: 'n', app: 'terminal', action: 'enter', args: [], reqId: 'r' })).toBe('badMessage');
|
||||
expect(code({ v: 1, kind: 'input', node: 'n', app: 'paint', action: 'enter', args: {}, reqId: 'r' })).toBe('unknownApp');
|
||||
});
|
||||
|
||||
it('knows its topics', () => {
|
||||
expect(['activity', 'trace:x', 'app:n/gnutella', 'app:n/webBrowser'].every(isTopic)).toBe(true);
|
||||
expect(['', 'trace:', 'app:/terminal', 'app:n/', 'app:n/terminal/x', 'node:n', 7].some(isTopic)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a host's messages", () => {
|
||||
it('reads each kind, and names an unknown one', () => {
|
||||
for (const kind of HOST_MESSAGE_KINDS) expect('message' in parseHostMessage({ v: 1, kind })).toBe(true);
|
||||
expect(parseHostMessage({ v: 1, kind: 'node' })).toEqual({ error: { code: 'unknownKind', detail: 'kind node' } });
|
||||
expect(parseHostMessage({ v: 0, kind: 'state' })).toEqual({ error: { code: 'badVersion', detail: 'version 0' } });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"composite": false,
|
||||
"declaration": false,
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"."
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user