UNPKG

ethercalc

Version:

Multi-User Spreadsheet Server — TypeScript rewrite (Cloudflare fullstack)

130 lines (114 loc) 4.77 kB
/** * Oracle scenario schema — see AGENTS.md §4. * * A scenario is a deterministic recipe that can be replayed against the * oracle (current `main` branch of EtherCalc) and against the new worker. * The oracle recorder fills in `expect` fields during recording; the * replay step asserts those expectations against the target. * * Request/response bodies are base64-encoded so binary formats (xlsx, ods) * round-trip cleanly through JSON. */ export interface HttpRequestDef { readonly method: string; readonly path: string; readonly headers?: Readonly<Record<string, string>>; /** Base64-encoded body. Omit for GET/HEAD/DELETE. */ readonly bodyBase64?: string; } export type BodyMatcher = | 'exact' // byte-identical | 'html' // structurally equal after linkedom normalization | 'xlsx' // structurally equal after unzip + XML canonical form | 'ods' // structurally equal after unzip + XML canonical form | 'scsave' // SocialCalc save: ignore version line + metadata ordering | 'json' // parse both and deepEqual | 'rooms-empty' // JSON room list `[]` ignoring form-clone suffix rooms (F-13) | 'roomtimes-empty' // JSON `{}` ignoring form-clone room keys (F-13) | 'roomlinks-empty' // JSON `[]` ignoring form-clone room links (F-13) | 'command-echo' // POST /_/:room 202: legacy wraps command in array, worker returns string | 'ignore'; // body not compared export interface HttpResponseExpectation { readonly status: number; /** * Headers to assert. Case-insensitive lookup. Other headers are ignored. * Typically: `Content-Type`, `Location`, `Content-Disposition`. */ readonly headers: Readonly<Record<string, string>>; /** Base64-encoded body. `null` means "body ignored". */ readonly bodyBase64: string | null; /** How to compare bodies. Defaults to `exact`. */ readonly bodyMatcher?: BodyMatcher; } export interface HttpScenario { readonly name: string; readonly kind: 'http'; readonly request: HttpRequestDef; /** Filled in by the recorder when replaying against the oracle. */ readonly expect?: HttpResponseExpectation; } // ─── WebSocket scenarios ────────────────────────────────────────────────── export interface WsConnectStep { readonly type: 'connect'; readonly url: string; /** Optional client-id label — referenced by `send`/`expect` steps to route to a specific WS in multi-client scenarios. */ readonly id?: string; } export interface WsSendStep { readonly type: 'send'; readonly id?: string; readonly msg: unknown; } export interface WsExpectStep { readonly type: 'expect'; readonly id?: string; /** Match rules: exact deep-equal, or a partial object (subset match). */ readonly msg: unknown; readonly match?: 'exact' | 'partial'; /** Max ms to wait for this frame before declaring a failure. */ readonly timeoutMs?: number; } export interface WsHttpStep { readonly type: 'http'; readonly request: HttpRequestDef; readonly expect?: HttpResponseExpectation; } export interface WsSleepStep { readonly type: 'sleep'; readonly ms: number; } export interface WsCloseStep { readonly type: 'close'; readonly id?: string; } export type WsStep = | WsConnectStep | WsSendStep | WsExpectStep | WsHttpStep | WsSleepStep | WsCloseStep; export interface WsScenario { readonly name: string; readonly kind: 'ws'; readonly steps: readonly WsStep[]; } export type Scenario = HttpScenario | WsScenario; /** * Human-readable normalization rules, documented here so test authors know * what to expect. Actual comparison logic lives in `packages/worker/src/lib/` * (HTML/XLSX normalizers) and in the oracle-harness package. */ export const NORMALIZATION_RULES: Readonly<Record<BodyMatcher, string>> = { exact: 'Byte-identical comparison.', html: 'Parsed via linkedom, re-serialized, whitespace-only text nodes dropped.', xlsx: 'Unzip; sort entries by name; compare each XML after canonical formatting.', ods: 'Unzip; sort entries by name; compare each XML after canonical formatting.', scsave: 'SocialCalc save: drop `version:...` line; compare sheet/cell lines exactly; metadata section ordering ignored.', json: 'Parse both sides; deep structural equality.', 'rooms-empty': 'JSON array; drop `oracle-phase3-template_<uuid>` clone rooms before comparing to `[]`.', 'roomtimes-empty': 'JSON object; drop clone-room keys before comparing to `{}`.', 'roomlinks-empty': 'JSON array; drop entries whose link matches the form-clone pattern before comparing to `[]`.', 'command-echo': 'POST 202 JSON: compare command text; legacy uses array, worker uses string.', ignore: 'Body not checked.', };