All files oracle-scenarios.ts

100% Statements 1/1
100% Branches 0/0
100% Functions 0/0
100% Lines 1/1

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122                                                                                                                                                                                                                                1x                  
/**
 * Oracle scenario schema — see CLAUDE.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
  | '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.',
  ignore: 'Body not checked.',
};