@shivaedev/test-story 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 - 2026-10-18
4
+
5
+ ### Added
6
+
7
+ - Let the OTLP receiver's status callback return `undefined` to withhold response headers, or set `answer: null` to send headers without completing the body.
8
+
3
9
  ## 0.5.0 - 2026-10-17
4
10
 
5
11
  ### Added
package/README.md CHANGED
@@ -220,6 +220,8 @@ expect(receiver.requests.some(({ body }) => body.includes(Buffer.from("planted-s
220
220
 
221
221
  `exportWork` is the kit's verb that runs application work and flushes its exporter. The receiver accepts OTLP/HTTP JSON and protobuf. `spans` flattens all exports into decoded trace and span IDs, parent span IDs, names, numeric OTLP kinds and status codes, attributes, events and links. Attributes retain OTLP key/value objects, including lossless decimal strings for integers; each span also has `resourceAttributes`. `requests` retains every request's unmodified `body` Buffer, parsed `headers`, original `rawHeaders` pairs and `path`. Search all body bytes when checking that a secret was not exported. Optional `path`, `status` (a number or attempt callback) and `answer` configure collector responses for failure stories.
222
222
 
223
+ For timeout stories, return `undefined` from the `status` callback to accept an export without sending response headers. Set `answer: null` to send headers without completing the response body.
224
+
223
225
  ### 2. Once per feature: traits and verbs
224
226
 
225
227
  A trait is a line and a change, tagged with a stage:
@@ -328,6 +330,14 @@ After the engine has been created, a failing story test adds the story to its fi
328
330
 
329
331
  The package wraps between words or JSON fields toward 120 columns and prints the story's own lines whole. An indivisible word, field or rerun command component can exceed that width. A passing test adds nothing. A typed failure with no message, such as an `after` hook's `NoBowls`, prints by its tag.
330
332
 
333
+ ### Library package kits
334
+
335
+ A library package has an engine too: its own services or Layer under test. Keep its kit in `src/test-support/`, acquire the real services in `create` with their dependencies supplied by the kit's `layer`, and use real PostgreSQL wherever the package touches it. Traits seed that engine in domain words; verbs call the package's public API. The same division applies as for an application: the kit owns setup and wiring, and a spec names traits, calls verbs and checks results. Following the [root test guidance](https://github.com/ShivaeDev/platform/blob/main/AGENTS.md), reserve plain Vitest tests for pure functions with no engine behind them.
336
+
337
+ The [`backfill` kit](https://github.com/ShivaeDev/platform/blob/main/packages/effect-test-sql/src/test-support/migrationStory.ts) in `effect-test-sql` is a library example. Its `create` returns the package's `migrationSteps(migrations)` API, its worker Layer provides PostgreSQL, and `around: withIsolatedDatabase` isolates each story. The `oldSchema` and `existingContacts` traits put contacts with email addresses into the old schema; the `backfill` verb calls `steps.migrateToLatest`. Its [story](https://github.com/ShivaeDev/platform/blob/main/packages/effect-test-sql/src/server/migrationStory.spec.ts) proves that those existing addresses survive the migration into a new table. It runs only when `PLATFORM_EFFECT_TEST_SQL_TEST_DATABASE_URL` is configured; a skipped story proves nothing.
338
+
339
+ A fake belongs only at a true external boundary, wired in `create`; keep the package's own engine real. The [resume kit](https://github.com/ShivaeDev/platform/blob/main/packages/effect-contract/src/test-support/resumeStory.ts), for example, creates synthetic browser and native event sources while running the package's real `resumeSignal` API. Its [stories](https://github.com/ShivaeDev/platform/blob/main/packages/effect-contract/src/resume.dom.test.ts) exercise refresh on those events and listener cleanup. Those synthetic sources prove the package's response to events, not a physical browser or native application's event delivery.
340
+
331
341
  ### Whole-stack application kits
332
342
 
333
343
  Keep one kit in the application’s `test-support/`. `effect-test` owns Vitest registration, worker Layer acquisition, the per-test Scope, cancellation and clock selection. `test-story` owns engine creation, staged traits, verbs and narration. The application owns database lifecycle, migrations, isolation, seeds, actual server/client wiring and screen interactions.
@@ -9,9 +9,9 @@ export interface OtlpExport {
9
9
  readonly rawHeaders: readonly string[];
10
10
  }
11
11
  export interface OtlpReceiverOptions {
12
- readonly answer?: string;
12
+ readonly answer?: string | null;
13
13
  readonly path?: string;
14
- readonly status?: number | ((attempt: number) => number);
14
+ readonly status?: number | ((attempt: number) => number | undefined);
15
15
  }
16
16
  export declare const otlpReceiver: (options?: OtlpReceiverOptions | undefined) => Effect.Effect<{
17
17
  readonly requests: readonly OtlpExport[];
@@ -1 +1 @@
1
- {"version":3,"file":"otlpReceiver.d.ts","sourceRoot":"","sources":["../../src/server/otlpReceiver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,mBAAmB,EAA6C,MAAM,WAAW,CAAC;AAC9G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9D,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AACD,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;CACzD;AA0BD,eAAO,MAAM,YAAY;uBAqBa,SAAS,UAAU,EAAE;oBAAkB,SAAS,QAAQ,EAAE;kBAAgB,MAAM;qEAMpH,CAAC"}
1
+ {"version":3,"file":"otlpReceiver.d.ts","sourceRoot":"","sources":["../../src/server/otlpReceiver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,mBAAmB,EAA6C,MAAM,WAAW,CAAC;AAC9G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9D,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AACD,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC;CACrE;AAmCD,eAAO,MAAM,YAAY;uBA4Ba,SAAS,UAAU,EAAE;oBAAkB,SAAS,QAAQ,EAAE;kBAAgB,MAAM;qEAMpH,CAAC"}
@@ -19,7 +19,17 @@ async function receive(request, response, requests, spans, options) {
19
19
  return;
20
20
  }
21
21
  spans.push(...otlpReadout(body, contentType));
22
- response.writeHead(typeof options.status === "function" ? options.status(requests.length) : (options.status ?? 200)).end(options.answer ?? "{}");
22
+ const status = typeof options.status === "function" ? options.status(requests.length) : (options.status ?? 200);
23
+ if (status === undefined) {
24
+ return;
25
+ }
26
+ response.writeHead(status);
27
+ if (options.answer === null) {
28
+ response.flushHeaders();
29
+ }
30
+ else {
31
+ response.end(options.answer ?? "{}");
32
+ }
23
33
  }
24
34
  catch {
25
35
  response.writeHead(400).end();
@@ -35,7 +45,10 @@ export const otlpReceiver = Effect.fnUntraced(function* (options = {}) {
35
45
  server.off("error", reject);
36
46
  resolve();
37
47
  });
38
- })), () => Effect.promise(() => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())))));
48
+ })), () => Effect.promise(() => new Promise((resolve, reject) => {
49
+ server.close((error) => (error ? reject(error) : resolve()));
50
+ server.closeAllConnections();
51
+ })));
39
52
  const address = server.address();
40
53
  if (address === null || typeof address === "string") {
41
54
  return yield* Effect.die("Missing OTLP receiver address");
@@ -1 +1 @@
1
- {"version":3,"file":"otlpReceiver.js","sourceRoot":"","sources":["../../src/server/otlpReceiver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"otlpReceiver.js","sourceRoot":"","sources":["../../src/server/otlpReceiver.ts"],"names":[],"mappings":"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"}
package/docs/roadmap.md CHANGED
@@ -1,9 +1,6 @@
1
1
  # Roadmap
2
2
 
3
- This file owns the story kit's implementation, next work and open questions. The
4
- [framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md)
5
- owns composed application features and host-level validation. Those roadmaps link
6
- to each other rather than repeating a completion checklist.
3
+ This file owns the story kit's implementation, next work and open questions. The [framework roadmap](https://github.com/ShivaeDev/platform/blob/main/docs/framework/roadmap.md) owns composed application features and host-level validation. Those roadmaps link to each other rather than repeating a completion checklist.
7
4
 
8
5
  ## Built
9
6
 
@@ -13,10 +10,15 @@ to each other rather than repeating a completion checklist.
13
10
  - [x] `story.runUntil(condition)`: steps the engine until the condition holds, stops on what `run.failed` reports, and stops after a step budget with a diagnosis, keeping every hook's typed failure in its error channel.
14
11
  - [x] A failed test prints a short guide, its story, where it stopped with the spec line there, and the engine's state, inline or in a file under `node_modules/.cache/test-story/`, broken at 120 columns.
15
12
  - [x] `domEnvironment` keeps Node fetch classes for an in-process host and happy-dom fetch for the client; `domFetch(baseUrl)` connects the window to that host, with a starter story proving Better Auth sign-up, sign-in, session reads and sign-out on PostgreSQL through cookies.
13
+ - [x] `domNavigate` loads a routed document into happy-dom, updates its location and returns a response whose body remains readable. [Document transport stories](../src/server/documentTransport.dom.spec.ts) assert the rendered heading, location and response body.
14
+ - [x] `outbound` routes exact hostnames to fake HTTP servers while preserving request paths, queries, methods, headers and bodies, and restores Node routing after successful and failed stories. [Outbound stories](../src/server/outbound.spec.ts) assert routing and restoration; [document transport stories](../src/server/documentTransport.dom.spec.ts) cover redirects, cookies and XMLHttpRequest; [DOM refusal tests](../src/server/domOutbound.spec.ts) assert that unrouted external fetches, document requests and redirects fail the story even when application code catches the error.
15
+ - [x] In-process host creation separates request traces from the story span: requests without trace headers get distinct root traces, supplied trace headers continue their trace, request work stays under the request span, and traits and steps stay under the story span. [Host tracing stories](../src/server/hostTracing.spec.ts) assert span parents, trace IDs and completed request spans.
16
+ - [x] Creation, traits and stage `after` hooks can return Effects directly, with services from the kit's Layer and resources released in the story's Scope, covered by [Effect hook stories](../src/server/effectHooks.spec.ts).
17
+ - [x] Kit hooks and steps support a live clock and a per-story TestClock override. [Live-time stories](../src/server/liveTime.spec.ts) cover polling real workers with `withLive` without advancing virtual token expiry.
16
18
 
17
19
  ## Next
18
20
 
19
- - [ ] Move a first application's hand-written story DSL onto the kit and let what it needs decide the next hook.
21
+ - [ ] Move the Work Fleet engine's hand-written story DSL onto the kit and let what it needs decide the next hook.
20
22
 
21
23
  ## Open questions
22
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/test-story",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Tests that read like stories over the real engine: a story kit per engine with its own it, traits per feature, and a failure that prints the story, where it stopped and the engine's state",
5
5
  "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/test-story#readme",
6
6
  "bugs": {
@@ -47,8 +47,8 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@effect/vitest": "4.0.4",
50
- "@shivaedev/quality": "0.22.0",
51
- "@types/node": "24.19.1",
50
+ "@shivaedev/quality": "0.23.0",
51
+ "@types/node": "24.19.2",
52
52
  "@typescript/native": "npm:typescript@7.0.2",
53
53
  "effect": "4.0.4",
54
54
  "happy-dom": "20.14.6",
@@ -10,9 +10,9 @@ export interface OtlpExport {
10
10
  readonly rawHeaders: readonly string[];
11
11
  }
12
12
  export interface OtlpReceiverOptions {
13
- readonly answer?: string;
13
+ readonly answer?: string | null;
14
14
  readonly path?: string;
15
- readonly status?: number | ((attempt: number) => number);
15
+ readonly status?: number | ((attempt: number) => number | undefined);
16
16
  }
17
17
 
18
18
  async function receive(request: IncomingMessage, response: ServerResponse, requests: OtlpExport[], spans: OtlpSpan[], options: OtlpReceiverOptions) {
@@ -33,7 +33,16 @@ async function receive(request: IncomingMessage, response: ServerResponse, reque
33
33
  return;
34
34
  }
35
35
  spans.push(...otlpReadout(body, contentType));
36
- response.writeHead(typeof options.status === "function" ? options.status(requests.length) : (options.status ?? 200)).end(options.answer ?? "{}");
36
+ const status = typeof options.status === "function" ? options.status(requests.length) : (options.status ?? 200);
37
+ if (status === undefined) {
38
+ return;
39
+ }
40
+ response.writeHead(status);
41
+ if (options.answer === null) {
42
+ response.flushHeaders();
43
+ } else {
44
+ response.end(options.answer ?? "{}");
45
+ }
37
46
  } catch {
38
47
  response.writeHead(400).end();
39
48
  }
@@ -54,7 +63,14 @@ export const otlpReceiver = Effect.fnUntraced(function* (options: OtlpReceiverOp
54
63
  });
55
64
  }),
56
65
  ),
57
- () => Effect.promise(() => new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())))),
66
+ () =>
67
+ Effect.promise(
68
+ () =>
69
+ new Promise<void>((resolve, reject) => {
70
+ server.close((error) => (error ? reject(error) : resolve()));
71
+ server.closeAllConnections();
72
+ }),
73
+ ),
58
74
  );
59
75
  const address = server.address();
60
76
  if (address === null || typeof address === "string") {