@shivaedev/test-story 0.18.2 → 0.19.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +19 -6
  3. package/dist/server/fakeHttpService.d.ts +13 -6
  4. package/dist/server/fakeHttpService.d.ts.map +1 -1
  5. package/dist/server/fakeHttpService.js +6 -5
  6. package/dist/server/fakeHttpService.js.map +1 -1
  7. package/dist/server/internal/fakeHttpAnswer.js +3 -3
  8. package/dist/server/internal/fakeHttpAnswer.js.map +1 -1
  9. package/dist/server/internal/fakeHttpControls.d.ts +7 -6
  10. package/dist/server/internal/fakeHttpControls.d.ts.map +1 -1
  11. package/dist/server/internal/fakeHttpControls.js +13 -11
  12. package/dist/server/internal/fakeHttpControls.js.map +1 -1
  13. package/dist/server/internal/fakeHttpGates.d.ts +7 -2
  14. package/dist/server/internal/fakeHttpGates.d.ts.map +1 -1
  15. package/dist/server/internal/fakeHttpGates.js +6 -3
  16. package/dist/server/internal/fakeHttpGates.js.map +1 -1
  17. package/dist/server/internal/fakeHttpReceive.d.ts +2 -2
  18. package/dist/server/internal/fakeHttpReceive.d.ts.map +1 -1
  19. package/dist/server/internal/fakeHttpReceive.js +24 -3
  20. package/dist/server/internal/fakeHttpReceive.js.map +1 -1
  21. package/dist/server/internal/storyIt.d.ts.map +1 -1
  22. package/dist/server/internal/storyIt.js +1 -1
  23. package/dist/server/internal/storyIt.js.map +1 -1
  24. package/package.json +3 -3
  25. package/src/server/fakeHttpService.ts +22 -10
  26. package/src/server/internal/fakeHttpAnswer.ts +3 -3
  27. package/src/server/internal/fakeHttpControls.ts +24 -13
  28. package/src/server/internal/fakeHttpGates.ts +6 -4
  29. package/src/server/internal/fakeHttpReceive.ts +28 -6
  30. package/src/server/internal/storyIt.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.19.0 - 2026-10-25
4
+
5
+ ### Added
6
+
7
+ - Build fake HTTP fault replies from the answered request and the service’s captured Effect clock, through `scriptFault`, `fault`, `failOnce` and `replyOnce`.
8
+ - Read completed response statuses, pending requests and unanswered connections from the fake HTTP request log.
9
+ - Hold a parameter scope or only its first matching request, and release a scoped hold on another route’s arrival.
10
+ - Match trailing `:name*` route parameters across one or more percent-decoded path segments.
11
+
12
+ ## 0.18.3 - 2026-10-25
13
+
14
+ ### Changed
15
+
16
+ - Release with updated workspace dependencies.
17
+
3
18
  ## 0.18.2 - 2026-10-24
4
19
 
5
20
  ### Changed
package/README.md CHANGED
@@ -274,7 +274,7 @@ For timeout stories, return `undefined` from the `status` callback to accept an
274
274
 
275
275
  #### Faking an outbound HTTP API
276
276
 
277
- Use `fakeHttpService` from `@shivaedev/test-story/server/fakeHttpService.ts` in a kit's scoped `create` hook. Like the OTLP receiver, it listens on an allocated loopback port and closes with the scope. Routes have names for controls, a method (default `GET`), and a path whose `:segments` become decoded `request.params`. A reply receives the request and returns a JSON `body` (or a `Uint8Array`/Buffer sent as raw bytes), optional `status` (default 200), and optional response `headers`.
277
+ Use `fakeHttpService` from `@shivaedev/test-story/server/fakeHttpService.ts` in a kit's scoped `create` hook. Like the OTLP receiver, it listens on an allocated loopback port and closes with the scope. Routes have names for controls, a method (default `GET`), and a path whose `:segments` become decoded `request.params`. Plain `:name` matches exactly one segment. A final `:name*` matches one or more remaining segments, decodes each segment and joins them with `/` in `params.name`: `/repos/:owner/:repo/contents/:path*` matches nested content such as `contents/a/b/package.json`. A rest parameter cannot appear before the last segment. A reply receives the request and returns a JSON `body` (or a `Uint8Array`/Buffer sent as raw bytes), optional `status` (default 200), and optional response `headers`.
278
278
 
279
279
  ```ts
280
280
  import { fakeHttpService } from "@shivaedev/test-story/server/fakeHttpService.ts";
@@ -290,7 +290,7 @@ const api = yield* fakeHttpService({
290
290
 
291
291
  Point the application's real HTTP client at `api.url`, or use the kit's `outbound` map to route its external hostname there. A service-wide `guard` returns a boolean; a missing or wrong credential gets 401 before holds or scripted replies are consumed. Each route can also declare a `guard(request)`; both guards must accept the request before holds, arrival scripts or faults run. A route's guard applies only to that route, so an installation token check can sit beside public routes. Omit the guard for a public fake. Unmatched paths and methods get 404.
292
292
 
293
- In `verbs`, bind `api.narrate(story.tell)` to tell received requests, replies, rejections and hold controls through the story. An optional `tell` callback can also be supplied at acquisition. The service keeps request headers and bytes in its log; narration of normal requests includes only method, path and status.
293
+ In `verbs`, bind `api.narrate(story.tell)` to tell received requests, replies, rejections and hold controls through the story. An optional `tell` callback can also be supplied at acquisition. The service keeps request headers and bytes in `api.requests`. Each entry has a `status`: the numeric status of a completed response, `"pending"` while waiting (including a stalled body), or `"unanswered"` when the connection closes without completing a response. Dropped connections, resets after headers and clients that hang up are unanswered. Statuses include guard rejections, unmatched routes and replies replaced by faults. Readouts can format entries as `GET /repos/o/r/actions/runs/7/artifacts 500`. Narration of normal requests includes only method, path and status.
294
294
 
295
295
  ```ts
296
296
  api.narrate(story.tell);
@@ -302,7 +302,7 @@ yield* paused.release;
302
302
  const user = yield* Fiber.join(pending);
303
303
  ```
304
304
 
305
- `application.loadUser` is the application's real client call. `failOnce` affects the next authorized matching request to that named route, then the route answers normally. `hold` pauses that route until `release`; `waiting` completes when a request reaches the hold, so a story needs no polling to know when to release it. Other routes still answer. Effects that wait for HTTP should use a live-clock kit.
305
+ `application.loadUser` is the application's real client call. `failOnce` affects the next authorized matching request to that named route, then the route answers normally. `hold` pauses that route until `release`; `waiting` completes when a request reaches the hold, so a story needs no polling to know when to release it. Other routes still answer. Use a live-clock kit for HTTP stories that need real-time waits; a TestClock kit can still drive the real HTTP client while controlling the fake’s reply time.
306
306
 
307
307
  Use the controls through kit verbs after acquiring the service. `replyOnce(route, reply)` scripts the next authorized reply, including raw bytes or `chunks`, a synchronous or asynchronous iterable of strings and byte arrays. Set `headers["content-type"]` to the story's media type; the default is JSON. Chunks are written in iteration order as they become available, so an async iterable can wait between pieces.
308
308
 
@@ -318,14 +318,27 @@ yield* stalled.release;
318
318
  const bytes = yield* Fiber.join(pending);
319
319
  ```
320
320
 
321
- `stall(route)` sends the reply's status and headers, then pauses its body. Its `waiting` effect completes at that pause. Its `release` lets the body continue; a client that gives up also stops waiting on the server. Like a hold, a stall remains installed for matching requests until released. Holds pause before headers. `releaseOnArrival(heldRoute, arrivingRoute)` releases the current hold when an authorized request reaches the named route; script it before that arrival. Rejected requests do not trigger release.
321
+ `stall(route)` sends the reply's status and headers, then pauses its body. Its `waiting` effect completes at that pause. Its `release` lets the body continue; a client that gives up also stops waiting on the server. Like a hold, a stall remains installed for matching requests until released. Holds pause before headers. `releaseOnArrival(heldRoute, arrivingRoute, scope?)` releases the current hold when an authorized request reaches the named route; script it before that arrival. Rejected requests do not trigger release.
322
322
 
323
323
  `fault(route, fault)` acts on an arrived request in the required phase. `{ kind: "status", reply: { status: 503, body: { error: "unavailable" } } }` replaces a held reply before headers. `{ kind: "drop" }` drops a connection before headers. `{ kind: "reset" }` terminates a reply after headers, including after body chunks have arrived. Wait for the hold, stall or client observation before injecting the fault. A fault fails with guidance when no request is in the required phase; it does not queue a failure for a future call. `failOnce` continues to queue a status reply for the next request.
324
324
 
325
- `stall(route, scope)`, `fault(route, fault, scope)`, `failOnce(route, reply, scope)` and `replyOnce(route, reply, scope)` accept an optional `{ params: { number: "7" } }`. Every supplied parameter must equal the decoded request parameter. Other resources on the same named route answer normally and do not consume the scoped reply. A scoped stall still returns `waiting` and `release`.
325
+ `hold(route, scope)`, `stall(route, scope)`, `fault(route, fault, scope)`, `failOnce(route, reply, scope)` and `replyOnce(route, reply, scope)` accept an optional `{ params: { number: "7" } }`. Every supplied parameter must equal the decoded request parameter. Other resources on the same named route answer normally and do not consume the scoped reply. Scoped holds and stalls return `waiting` and `release`. `hold(route, { once: true, params: { number: "7" } })` holds only the first matching request; later matching requests answer while it remains held. Omit `params` to hold only the first request across the route. Omit `once` to keep holding every matching request until release. Pass the same parameter scope as the third argument to `releaseOnArrival` to release that hold.
326
326
 
327
327
  Use `scriptFault(route, fault, options)` to install a fault for future authorized matching requests. It returns a control with a narrated `lift` effect. The default strikes once before the route's reply runs; `{ repeat: true }` keeps it installed until lifted. Status faults can repeat too.
328
328
 
329
+ A status fault’s `reply`, and the reply argument of `failOnce` or `replyOnce`, can be a fixed `FakeHttpReply` or a function `(request, now) => FakeHttpReply`. The fake evaluates the function when it answers, using the request being answered (including a held request for `fault`) and milliseconds from the Effect `Clock` captured when the service was created. A repeating fault rebuilds its reply for every request. TestClock stories therefore get deterministic refusal times:
330
+
331
+ ```ts
332
+ yield* api.scriptFault("user", {
333
+ kind: "status",
334
+ reply: (request, now) => ({
335
+ status: 429,
336
+ headers: { "x-ratelimit-reset": String(now + 60_000) },
337
+ body: { user: request.params.id },
338
+ }),
339
+ });
340
+ ```
341
+
329
342
  ```ts
330
343
  const outage = yield* api.scriptFault("pull", { kind: "drop" }, {
331
344
  params: { number: "7" },
@@ -628,7 +641,7 @@ Import each module with the prefix `@shivaedev/test-story/`; there is no root en
628
641
  | `server/domNavigate.ts` | `domNavigate(url?)` |
629
642
  | `server/genreTags.ts` | `genreTags(...kitNames)`, `GenreTag` |
630
643
  | `server/domEnvironment.config.ts` | Default Vitest environment preserving Node host globals and happy-dom document transport |
631
- | `server/fakeHttpService.ts` | `fakeHttpService(options)`, `FakeHttpRoute`, `FakeHttpRequest`, `FakeHttpReply`, `FakeHttpFault`, `FakeHttpHold`, `FakeHttpScope`, `FakeHttpScriptOptions`, `FakeHttpScript`, `FakeHttpServiceOptions` |
644
+ | `server/fakeHttpService.ts` | `fakeHttpService(options)`, `FakeHttpRoute`, `FakeHttpRequest`, `FakeHttpRequestRecord` (`status`: number, `"pending"` or `"unanswered"`), `FakeHttpReply`, `FakeHttpReplySource` (fixed or request/time function), `FakeHttpFault`, `FakeHttpHold`, `FakeHttpHoldOptions` (`params`, `once`), `FakeHttpScope`, `FakeHttpScriptOptions`, `FakeHttpScript`, `FakeHttpServiceOptions`; reply functions receive `(request, now)`, request records expose answered statuses, holds accept a scope and `once`, `releaseOnArrival` accepts a held scope, and routes accept final `:name*` rest params |
632
645
  | `server/forwardingProxy.ts` | `forwardingProxy(upstream, options?)`, `ForwardingProxyOptions` |
633
646
  | `server/otlpReceiver.ts` | `otlpReceiver(options?)`, `OtlpSpan`, `OtlpExport`, `OtlpReceiverOptions` |
634
647
 
@@ -14,15 +14,22 @@ export interface FakeHttpReply {
14
14
  readonly headers?: Readonly<Record<string, string>>;
15
15
  readonly status?: number;
16
16
  }
17
+ export type FakeHttpReplySource = FakeHttpReply | ((request: FakeHttpRequest, now: number) => FakeHttpReply);
18
+ export interface FakeHttpRequestRecord extends FakeHttpRequest {
19
+ readonly status: number | "pending" | "unanswered";
20
+ }
17
21
  export type FakeHttpFault = {
18
22
  readonly kind: "status";
19
- readonly reply: FakeHttpReply;
23
+ readonly reply: FakeHttpReplySource;
20
24
  } | {
21
25
  readonly kind: "drop" | "reset";
22
26
  };
23
27
  export interface FakeHttpScope {
24
28
  readonly params?: Readonly<Record<string, string>>;
25
29
  }
30
+ export interface FakeHttpHoldOptions extends FakeHttpScope {
31
+ readonly once?: boolean;
32
+ }
26
33
  export interface FakeHttpScriptOptions extends FakeHttpScope {
27
34
  readonly phase?: "before" | "after";
28
35
  readonly repeat?: boolean;
@@ -51,13 +58,13 @@ export interface FakeHttpHold {
51
58
  readonly waiting: Effect.Effect<void>;
52
59
  }
53
60
  export declare const fakeHttpService: (options: FakeHttpServiceOptions) => Effect.Effect<{
54
- failOnce: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
61
+ failOnce: (route: string, reply: FakeHttpReplySource, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
55
62
  fault: (route: string, fault: FakeHttpFault, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
56
- hold: (route: string) => Effect.Effect<FakeHttpHold, never, never>;
63
+ hold: (route: string, holdOptions?: FakeHttpHoldOptions) => Effect.Effect<FakeHttpHold, never, never>;
57
64
  narrate: (sink: (line: string) => void) => void;
58
- releaseOnArrival: (route: string, arrival: string) => Effect.Effect<void, never, never>;
59
- replyOnce: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
60
- readonly requests: readonly FakeHttpRequest[];
65
+ releaseOnArrival: (route: string, arrival: string, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
66
+ replyOnce: (route: string, reply: FakeHttpReplySource, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
67
+ readonly requests: readonly FakeHttpRequestRecord[];
61
68
  scriptFault: (route: string, fault: FakeHttpFault, scriptOptions?: FakeHttpScriptOptions) => Effect.Effect<FakeHttpScript, never, never>;
62
69
  stall: (route: string, scope?: FakeHttpScope) => Effect.Effect<FakeHttpHold, never, never>;
63
70
  url: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpService.d.ts","sourceRoot":"","sources":["../../src/server/fakeHttpService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fakeHttpService.d.ts","sourceRoot":"","sources":["../../src/server/fakeHttpService.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,16 @@
1
1
  import { createServer } from "node:http";
2
- import { Effect } from "effect";
2
+ import { Clock, Effect } from "effect";
3
3
  import { fakeHttpControls } from "#internal/fakeHttpControls.ts";
4
4
  import { fakeHttpReceive } from "#internal/fakeHttpReceive.ts";
5
5
  import { loopbackServer } from "#internal/loopback.ts";
6
6
  export const fakeHttpService = Effect.fnUntraced(function* (options) {
7
+ const clock = yield* Clock.Clock;
7
8
  const requests = [];
8
9
  let narrate = options.tell;
9
10
  function tell(line) {
10
11
  narrate?.(line);
11
12
  }
12
- const controls = fakeHttpControls(tell);
13
+ const controls = fakeHttpControls(tell, () => clock.currentTimeMillisUnsafe());
13
14
  const server = createServer((request, response) => fakeHttpReceive(request, response, options, requests, controls, tell));
14
15
  const url = yield* loopbackServer(server);
15
16
  yield* Effect.addFinalizer(() => Effect.sync(controls.close));
@@ -21,14 +22,14 @@ export const fakeHttpService = Effect.fnUntraced(function* (options) {
21
22
  return {
22
23
  failOnce: (route, reply, scope) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply, scope))),
23
24
  fault: (route, fault, scope) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.fault(route, fault, scope))),
24
- hold: (route) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.hold(route))),
25
+ hold: (route, holdOptions) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.hold(route, holdOptions))),
25
26
  narrate: (sink) => {
26
27
  narrate = sink;
27
28
  },
28
- releaseOnArrival: (route, arrival) => Effect.sync(() => {
29
+ releaseOnArrival: (route, arrival, scope) => Effect.sync(() => {
29
30
  checkRoute(route);
30
31
  checkRoute(arrival);
31
- }).pipe(Effect.andThen(controls.releaseOnArrival(route, arrival))),
32
+ }).pipe(Effect.andThen(controls.releaseOnArrival(route, arrival, scope))),
32
33
  replyOnce: (route, reply, scope) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply, scope))),
33
34
  get requests() {
34
35
  return requests.slice();
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpService.js","sourceRoot":"","sources":["../../src/server/fakeHttpService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fakeHttpService.js","sourceRoot":"","sources":["../../src/server/fakeHttpService.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@ function pageReply(route, request) {
12
12
  export async function fakeHttpAnswer(name, route, request, response, controls, tell) {
13
13
  const { method, path } = request;
14
14
  controls.arrive(name, request, response);
15
- if (!(await controls.pause(name, response))) {
15
+ if (!(await controls.pause(name, request, response))) {
16
16
  return;
17
17
  }
18
18
  const before = controls.takeFault(name, request, "before");
@@ -21,7 +21,7 @@ export async function fakeHttpAnswer(name, route, request, response, controls, t
21
21
  await fakeHttpStrike(response, before);
22
22
  return;
23
23
  }
24
- let reply = before?.reply ?? pageReply(route, request);
24
+ let reply = before ? controls.reply(before.reply, request) : pageReply(route, request);
25
25
  const after = before ? undefined : controls.takeFault(name, request, "after");
26
26
  if (after) {
27
27
  tell(`fake HTTP ${name} fault ${after.kind} after reply`);
@@ -29,7 +29,7 @@ export async function fakeHttpAnswer(name, route, request, response, controls, t
29
29
  await fakeHttpStrike(response, after, reply);
30
30
  return;
31
31
  }
32
- reply = after.reply;
32
+ reply = controls.reply(after.reply, request);
33
33
  }
34
34
  tell(`fake HTTP answered ${method} ${path} with ${reply.status ?? 200}`);
35
35
  await fakeHttpSend(response, reply, controls.stallBody(name, request));
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpAnswer.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpAnswer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEzE,SAAS,SAAS,CAAC,KAAoB,EAAE,OAAwB;IAChE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IAC5E,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;AAC5G,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,IAAY,EACZ,KAAoB,EACpB,OAAwB,EACxB,QAAwB,EACxB,QAA6C,EAC7C,IAA4B;IAE5B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC7C,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC3D,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACxC,IAAI,CAAC,aAAa,IAAI,UAAU,MAAM,CAAC,IAAI,eAAe,CAAC,CAAC;QAC5D,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACvC,OAAO;IACR,CAAC;IACD,IAAI,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9E,IAAI,KAAK,EAAE,CAAC;QACX,IAAI,CAAC,aAAa,IAAI,UAAU,KAAK,CAAC,IAAI,cAAc,CAAC,CAAC;QAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,cAAc,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAC7C,OAAO;QACR,CAAC;QACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IACD,IAAI,CAAC,sBAAsB,MAAM,IAAI,IAAI,SAAS,KAAK,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;IACzE,MAAM,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AACxE,CAAC"}
1
+ {"version":3,"file":"fakeHttpAnswer.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpAnswer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEzE,SAAS,SAAS,CAAC,KAAoB,EAAE,OAAwB;IAChE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;IAC5E,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;AAC5G,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,IAAY,EACZ,KAAoB,EACpB,OAAwB,EACxB,QAAwB,EACxB,QAA6C,EAC7C,IAA4B;IAE5B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;QACtD,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC3D,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACxC,IAAI,CAAC,aAAa,IAAI,UAAU,MAAM,CAAC,IAAI,eAAe,CAAC,CAAC;QAC5D,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACvC,OAAO;IACR,CAAC;IACD,IAAI,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACvF,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9E,IAAI,KAAK,EAAE,CAAC;QACX,IAAI,CAAC,aAAa,IAAI,UAAU,KAAK,CAAC,IAAI,cAAc,CAAC,CAAC;QAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,cAAc,CAAC,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAC7C,OAAO;QACR,CAAC;QACD,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC,sBAAsB,MAAM,IAAI,IAAI,SAAS,KAAK,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;IACzE,MAAM,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AACxE,CAAC"}
@@ -1,14 +1,15 @@
1
1
  import type { ServerResponse } from "node:http";
2
2
  import { Effect } from "effect";
3
- import type { FakeHttpFault, FakeHttpReply, FakeHttpRequest, FakeHttpScope, FakeHttpScriptOptions } from "#fakeHttpService.ts";
4
- export declare function fakeHttpControls(tell: (line: string) => void): {
3
+ import type { FakeHttpFault, FakeHttpHoldOptions, FakeHttpReplySource, FakeHttpRequest, FakeHttpScope, FakeHttpScriptOptions } from "#fakeHttpService.ts";
4
+ export declare function fakeHttpControls(tell: (line: string) => void, now: () => number): {
5
5
  arrive: (route: string, request: FakeHttpRequest, response: ServerResponse) => void;
6
6
  close: () => void;
7
7
  fault: (route: string, fault: FakeHttpFault, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
8
- hold: (route: string) => Effect.Effect<import("#fakeHttpService.ts").FakeHttpHold, never, never>;
9
- pause: (route: string, response: ServerResponse) => Promise<boolean>;
10
- queue: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
11
- releaseOnArrival: (route: string, arrival: string) => Effect.Effect<void, never, never>;
8
+ hold: (route: string, options?: FakeHttpHoldOptions) => Effect.Effect<import("#fakeHttpService.ts").FakeHttpHold, never, never>;
9
+ pause: (route: string, request: FakeHttpRequest, response: ServerResponse) => Promise<boolean>;
10
+ queue: (route: string, reply: FakeHttpReplySource, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
11
+ releaseOnArrival: (route: string, arrival: string, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
12
+ reply: (source: FakeHttpReplySource, request: FakeHttpRequest) => import("#fakeHttpService.ts").FakeHttpReply;
12
13
  scriptFault: (route: string, fault: FakeHttpFault, options?: FakeHttpScriptOptions) => Effect.Effect<import("#fakeHttpService.ts").FakeHttpScript, never, never>;
13
14
  stall: (route: string, scope?: FakeHttpScope) => Effect.Effect<import("#fakeHttpService.ts").FakeHttpHold, never, never>;
14
15
  stallBody: (route: string, request: FakeHttpRequest) => (() => Promise<void>) | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpControls.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpControls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAM/H,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;IAO3D,MAAM,UAAU,MAAM,WAAW,eAAe,YAAY,cAAc;IAQ1E,KAAK;IASL,KAAK,UAAU,MAAM,SAAS,aAAa,UAAU,aAAa;IAqBlE,IAAI,UAAU,MAAM;IACpB,KAAK,UAAgB,MAAM,YAAY,cAAc;IAIrD,KAAK,UAAU,MAAM,SAAS,aAAa,UAAU,aAAa;IAKlE,gBAAgB,UAAU,MAAM,WAAW,MAAM;IAWjD,WAAW,UAAU,MAAM,SAAS,aAAa,YAAY,qBAAqB;IAClF,KAAK,UAAU,MAAM,UAAU,aAAa;IAC5C,SAAS,UAAU,MAAM,WAAW,eAAe;IAInD,SAAS;EAEV"}
1
+ {"version":3,"file":"fakeHttpControls.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpControls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EACX,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,qBAAqB,EACrB,MAAM,qBAAqB,CAAC;AAM7B,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,GAAG,EAAE,MAAM,MAAM;IAO9E,MAAM,UAAU,MAAM,WAAW,eAAe,YAAY,cAAc;IAQ1E,KAAK;IASL,KAAK,UAAU,MAAM,SAAS,aAAa,UAAU,aAAa;IAsBlE,IAAI,UAAU,MAAM,YAAY,mBAAmB;IACnD,KAAK,UAAgB,MAAM,WAAW,eAAe,YAAY,cAAc;IAI/E,KAAK,UAAU,MAAM,SAAS,mBAAmB,UAAU,aAAa;IAOxE,gBAAgB,UAAU,MAAM,WAAW,MAAM,UAAU,aAAa;IAWxE,KAAK,WAAW,mBAAmB,WAAW,eAAe;IAC7D,WAAW,UAAU,MAAM,SAAS,aAAa,YAAY,qBAAqB;IAClF,KAAK,UAAU,MAAM,UAAU,aAAa;IAC5C,SAAS,UAAU,MAAM,WAAW,eAAe;IAInD,SAAS;EAEV"}
@@ -3,7 +3,7 @@ import { fakeHttpGates } from "#internal/fakeHttpGates.ts";
3
3
  import { fakeHttpScripts } from "#internal/fakeHttpScripts.ts";
4
4
  import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
5
5
  import { fakeHttpConnection, fakeHttpSend } from "#internal/fakeHttpSend.ts";
6
- export function fakeHttpControls(tell) {
6
+ export function fakeHttpControls(tell, now) {
7
7
  const scripts = fakeHttpScripts(tell);
8
8
  const holds = fakeHttpGates(tell, "hold");
9
9
  const stalls = fakeHttpGates(tell, "stall");
@@ -29,31 +29,32 @@ export function fakeHttpControls(tell) {
29
29
  },
30
30
  fault: (route, fault, scope) => Effect.promise(async () => {
31
31
  const selection = fakeHttpSelection(route, scope);
32
- const response = [...active].find(([candidate, entry]) => selection.matches(entry.route, entry.request)
32
+ const found = [...active].find(([candidate, entry]) => selection.matches(entry.route, entry.request)
33
33
  && !(candidate.destroyed || candidate.writableEnded)
34
- && candidate.headersSent === (fault.kind === "reset"))?.[0];
35
- if (!response) {
34
+ && candidate.headersSent === (fault.kind === "reset"));
35
+ if (!found) {
36
36
  throw new Error(`Fake HTTP route "${route}" has no request ready for a ${fault.kind} fault.\nhelp: Wait for a hold before a status or drop fault, or for response headers before a reset fault.`);
37
37
  }
38
+ const [response, { request }] = found;
38
39
  tell(`fake HTTP ${selection.label} fault ${fault.kind}`);
39
40
  if (fault.kind === "status") {
40
- await fakeHttpSend(response, fault.reply);
41
+ await fakeHttpSend(response, typeof fault.reply === "function" ? fault.reply(request, now()) : fault.reply);
41
42
  }
42
43
  else {
43
44
  response.destroy();
44
45
  }
45
46
  }),
46
- hold: (route) => Effect.sync(() => holds.make(route)),
47
- pause: async (route, response) => {
48
- await fakeHttpConnection(response).pause(holds.get(route)?.enter());
47
+ hold: (route, options) => Effect.sync(() => holds.make(route, options, options?.once)),
48
+ pause: async (route, request, response) => {
49
+ await fakeHttpConnection(response).pause(holds.get(route, request)?.enter());
49
50
  return !(response.destroyed || response.writableEnded || response.headersSent);
50
51
  },
51
52
  queue: (route, reply, scope) => Effect.sync(() => {
52
53
  scripts.make(route, { kind: "status", reply }, scope, false);
53
- tell(`fake HTTP ${fakeHttpSelection(route, scope).label} will answer once with ${reply.status ?? 200}`);
54
+ tell(`fake HTTP ${fakeHttpSelection(route, scope).label} will answer once with ${typeof reply === "function" ? "a request-time reply" : (reply.status ?? 200)}`);
54
55
  }),
55
- releaseOnArrival: (route, arrival) => Effect.sync(() => {
56
- const hold = holds.get(route);
56
+ releaseOnArrival: (route, arrival, scope) => Effect.sync(() => {
57
+ const hold = holds.at(route, scope);
57
58
  if (!hold) {
58
59
  throw new Error(`Fake HTTP route "${route}" has no hold.\nhelp: Hold the route before scripting its release on arrival.`);
59
60
  }
@@ -62,6 +63,7 @@ export function fakeHttpControls(tell) {
62
63
  arrivals.set(arrival, releases);
63
64
  tell(`fake HTTP ${route} will release when ${arrival} arrives`);
64
65
  }),
66
+ reply: (source, request) => (typeof source === "function" ? source(request, now()) : source),
65
67
  scriptFault: (route, fault, options) => Effect.sync(() => scripts.make(route, fault, options)),
66
68
  stall: (route, scope) => Effect.sync(() => stalls.make(route, scope)),
67
69
  stallBody: (route, request) => {
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpControls.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpControls.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fakeHttpControls.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpControls.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,17 @@
1
1
  import type { FakeHttpHold, FakeHttpRequest, FakeHttpScope } from "#fakeHttpService.ts";
2
2
  import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
3
3
  export declare function fakeHttpGates(tell: (line: string) => void, kind: "hold" | "stall"): {
4
+ at: (route: string, scope?: FakeHttpScope) => {
5
+ enter: () => Promise<void>;
6
+ release: () => void;
7
+ matches: ReturnType<typeof fakeHttpSelection>["matches"];
8
+ } | undefined;
4
9
  close: () => void;
5
- get: (route: string, request?: FakeHttpRequest) => {
10
+ get: (route: string, request: FakeHttpRequest) => {
6
11
  enter: () => Promise<void>;
7
12
  release: () => void;
8
13
  matches: ReturnType<typeof fakeHttpSelection>["matches"];
9
14
  } | undefined;
10
- make: (route: string, scope?: FakeHttpScope) => FakeHttpHold;
15
+ make: (route: string, scope?: FakeHttpScope, once?: boolean) => FakeHttpHold;
11
16
  };
12
17
  //# sourceMappingURL=fakeHttpGates.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpGates.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpGates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,wBAAgB,aAAa,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAGhF,KAAK;IAKL,GAAG,UAAU,MAAM,YAAY,eAAe;eAPR,MAAM,OAAO,CAAC,IAAI,CAAC;iBAAW,MAAM,IAAI;iBAAW,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,SAAS,CAAC;;IASxI,IAAI,UAAU,MAAM,UAAU,aAAa,KAAG,YAAY;EA6B3D"}
1
+ {"version":3,"file":"fakeHttpGates.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpGates.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,wBAAgB,aAAa,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAGhF,EAAE,UAAU,MAAM,UAAU,aAAa;eAFH,MAAM,OAAO,CAAC,IAAI,CAAC;iBAAW,MAAM,IAAI;iBAAW,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,SAAS,CAAC;;IAGxI,KAAK;IAKL,GAAG,UAAU,MAAM,WAAW,eAAe;;;;;IAC7C,IAAI,UAAU,MAAM,UAAU,aAAa,qBAAiB,YAAY;EA+BzE"}
@@ -3,13 +3,14 @@ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
3
3
  export function fakeHttpGates(tell, kind) {
4
4
  const gates = new Map();
5
5
  return {
6
+ at: (route, scope) => gates.get(fakeHttpSelection(route, scope).label),
6
7
  close: () => {
7
8
  for (const gate of gates.values()) {
8
9
  gate.release();
9
10
  }
10
11
  },
11
- get: (route, request) => request ? [...gates.values()].find((gate) => gate.matches(route, request)) : gates.get(route),
12
- make: (route, scope) => {
12
+ get: (route, request) => [...gates.values()].find((gate) => gate.matches(route, request)),
13
+ make: (route, scope, once = false) => {
13
14
  const selection = fakeHttpSelection(route, scope);
14
15
  const label = selection.label;
15
16
  if (gates.has(label)) {
@@ -17,13 +18,15 @@ export function fakeHttpGates(tell, kind) {
17
18
  }
18
19
  const waiting = Deferred.makeUnsafe();
19
20
  const released = Promise.withResolvers();
21
+ let entered = false;
20
22
  const gate = {
21
23
  enter: () => {
24
+ entered = true;
22
25
  tell(kind === "hold" ? `fake HTTP ${label} has a waiting request` : `fake HTTP ${label} body stalled`);
23
26
  Deferred.doneUnsafe(waiting, Effect.void);
24
27
  return released.promise;
25
28
  },
26
- matches: selection.matches,
29
+ matches: (name, request) => !(once && entered) && selection.matches(name, request),
27
30
  release: () => {
28
31
  if (gates.get(label) !== gate) {
29
32
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpGates.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpGates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,MAAM,UAAU,aAAa,CAAC,IAA4B,EAAE,IAAsB;IACjF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyH,CAAC;IAC/I,OAAO;QACN,KAAK,EAAE,GAAG,EAAE;YACX,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBACnC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,CAAC;QACF,CAAC;QACD,GAAG,EAAE,CAAC,KAAa,EAAE,OAAyB,EAAE,EAAE,CACjD,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;QAC9F,IAAI,EAAE,CAAC,KAAa,EAAE,KAAqB,EAAgB,EAAE;YAC5D,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAClD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC;YAC9B,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,oBAAoB,KAAK,mBAAmB,IAAI,gCAAgC,IAAI,4BAA4B,CAAC,CAAC;YACnI,CAAC;YACD,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,EAAQ,CAAC;YAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAC;YAC/C,MAAM,IAAI,GAAG;gBACZ,KAAK,EAAE,GAAG,EAAE;oBACX,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa,KAAK,wBAAwB,CAAC,CAAC,CAAC,aAAa,KAAK,eAAe,CAAC,CAAC;oBACvG,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;oBAC1C,OAAO,QAAQ,CAAC,OAAO,CAAC;gBACzB,CAAC;gBACD,OAAO,EAAE,SAAS,CAAC,OAAO;gBAC1B,OAAO,EAAE,GAAG,EAAE;oBACb,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;wBAC/B,OAAO;oBACR,CAAC;oBACD,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBACpB,QAAQ,CAAC,OAAO,EAAE,CAAC;oBACnB,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa,KAAK,WAAW,CAAC,CAAC,CAAC,aAAa,KAAK,gBAAgB,CAAC,CAAC;gBAC5F,CAAC;aACD,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACvB,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,KAAK,kBAAkB,CAAC,CAAC;YACzF,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACjF,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"fakeHttpGates.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpGates.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import type { FakeHttpRequest, FakeHttpServiceOptions } from "#fakeHttpService.ts";
2
+ import type { FakeHttpRequestRecord, FakeHttpServiceOptions } from "#fakeHttpService.ts";
3
3
  import type { fakeHttpControls } from "#internal/fakeHttpControls.ts";
4
- export declare function fakeHttpReceive(incoming: IncomingMessage, response: ServerResponse, options: FakeHttpServiceOptions, requests: FakeHttpRequest[], controls: ReturnType<typeof fakeHttpControls>, tell: (line: string) => void): Promise<void>;
4
+ export declare function fakeHttpReceive(incoming: IncomingMessage, response: ServerResponse, options: FakeHttpServiceOptions, requests: FakeHttpRequestRecord[], controls: ReturnType<typeof fakeHttpControls>, tell: (line: string) => void): Promise<void>;
5
5
  //# sourceMappingURL=fakeHttpReceive.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpReceive.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpReceive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAiB,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAmCtE,wBAAsB,eAAe,CACpC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,sBAAsB,EAC/B,QAAQ,EAAE,eAAe,EAAE,EAC3B,QAAQ,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EAC7C,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,iBAiD5B"}
1
+ {"version":3,"file":"fakeHttpReceive.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpReceive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,qBAAqB,EAAiB,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAExG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAsDtE,wBAAsB,eAAe,CACpC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,sBAAsB,EAC/B,QAAQ,EAAE,qBAAqB,EAAE,EACjC,QAAQ,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EAC7C,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,iBAoD5B"}
@@ -6,14 +6,26 @@ function match(route, method, pathname) {
6
6
  }
7
7
  const pattern = route.path.split("/");
8
8
  const segments = pathname.split("/");
9
- if (pattern.length !== segments.length) {
9
+ const rest = pattern.at(-1)?.startsWith(":") && pattern.at(-1)?.endsWith("*");
10
+ if (rest ? segments.length < pattern.length : pattern.length !== segments.length) {
10
11
  return;
11
12
  }
13
+ return pathParams(pattern, segments);
14
+ }
15
+ function pathParams(pattern, segments) {
12
16
  const params = {};
13
17
  for (const [index, part] of pattern.entries()) {
14
18
  const segment = segments[index] ?? "";
15
19
  if (part.startsWith(":")) {
16
- params[part.slice(1)] = segment;
20
+ if (part.endsWith("*")) {
21
+ if (index !== pattern.length - 1 || !segment) {
22
+ return;
23
+ }
24
+ params[part.slice(1, -1)] = segments.slice(index).join("/");
25
+ }
26
+ else {
27
+ params[part.slice(1)] = segment;
28
+ }
17
29
  }
18
30
  else if (part !== segment) {
19
31
  return;
@@ -24,7 +36,7 @@ function match(route, method, pathname) {
24
36
  function decodeParams(params) {
25
37
  try {
26
38
  for (const [name, value] of Object.entries(params)) {
27
- params[name] = decodeURIComponent(value);
39
+ params[name] = value.split("/").map(decodeURIComponent).join("/");
28
40
  }
29
41
  return true;
30
42
  }
@@ -32,6 +44,12 @@ function decodeParams(params) {
32
44
  return false;
33
45
  }
34
46
  }
47
+ function requestStatus(response) {
48
+ if (response.writableFinished) {
49
+ return response.statusCode;
50
+ }
51
+ return response.destroyed ? "unanswered" : "pending";
52
+ }
35
53
  export async function fakeHttpReceive(incoming, response, options, requests, controls, tell) {
36
54
  try {
37
55
  const path = incoming.url ?? "/";
@@ -53,6 +71,9 @@ export async function fakeHttpReceive(incoming, response, options, requests, con
53
71
  params,
54
72
  path,
55
73
  query: url.searchParams,
74
+ get status() {
75
+ return requestStatus(response);
76
+ },
56
77
  };
57
78
  requests.push(request);
58
79
  tell(`fake HTTP received ${method} ${path}`);
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpReceive.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpReceive.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fakeHttpReceive.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpReceive.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,SAAS,KAAK,CAAC,KAAoB,EAAE,MAAc,EAAE,QAAgB;IACpE,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;QACtD,OAAO;IACR,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC9E,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClF,OAAO;IACR,CAAC;IACD,OAAO,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,UAAU,CAAC,OAAiB,EAAE,QAAkB;IACxD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,IAAI,KAAK,KAAK,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;oBAC9C,OAAO;gBACR,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7D,CAAC;iBAAM,CAAC;gBACP,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;YACjC,CAAC;QACF,CAAC;aAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YAC7B,OAAO;QACR,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,MAA8B;IACnD,IAAI,CAAC;QACJ,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACpD,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED,SAAS,aAAa,CAAC,QAAwB;IAC9C,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;QAC/B,OAAO,QAAQ,CAAC,UAAU,CAAC;IAC5B,CAAC;IACD,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,QAAyB,EACzB,QAAwB,EACxB,OAA+B,EAC/B,QAAiC,EACjC,QAA6C,EAC7C,IAA4B;IAE5B,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,CAAC;QACjC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,KAAK,CAAC;QACxC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;YACtE,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;YAClD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACN,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC;QACnC,MAAM,OAAO,GAA0B;YACtC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YAC3B,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,MAAM;YACN,MAAM;YACN,IAAI;YACJ,KAAK,EAAE,GAAG,CAAC,YAAY;YACvB,IAAI,MAAM;gBACT,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;SACD,CAAC;QACF,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,IAAI,CAAC,sBAAsB,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,sBAAsB,MAAM,IAAI,IAAI,WAAW,CAAC,CAAC;YACtD,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9C,OAAO;QACR,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACvG,IAAI,CAAC,sBAAsB,MAAM,IAAI,IAAI,WAAW,CAAC,CAAC;YACtD,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9C,OAAO;QACR,CAAC;QACD,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,IAAI,CAAC,sBAAsB,MAAM,IAAI,IAAI,WAAW,CAAC,CAAC;YACtD,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YAC9C,OAAO;QACR,CAAC;QACD,MAAM,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IAClF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,+BAA+B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACrD,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC1B,QAAQ,CAAC,OAAO,EAAE,CAAC;QACpB,CAAC;aAAM,CAAC;YACP,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAC/C,CAAC;IACF,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"storyIt.d.ts","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGrF,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIrE,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;AAE1G,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,iBAAiB,KAAK,IAAI,CAAC;AAE9H,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;CACnD;AAgCD,wBAAgB,OAAO,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CA0B5D"}
1
+ {"version":3,"file":"storyIt.d.ts","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAErF,OAAO,EAAE,KAAK,SAAS,EAAU,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAIrE,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,KAAK,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,SAAS,CAAC;AAE1G,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,iBAAiB,KAAK,IAAI,CAAC;AAE9H,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,SAAS,CAAC;CACnD;AAgCD,wBAAgB,OAAO,CAAC,UAAU,EAAE,eAAe,GAAG,OAAO,CA0B5D"}
@@ -1,5 +1,5 @@
1
1
  import { Effect, Layer } from "effect";
2
- import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
2
+ import { makeEffectIt } from "@shivaedev/effect-test/makeEffectIt.ts";
3
3
  import { genreOf } from "#internal/genre.ts";
4
4
  import { hooked } from "#internal/hooked.ts";
5
5
  import { start } from "#internal/start.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"storyIt.js","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storyIt.js","sourceRoot":"","sources":["../../../src/server/internal/storyIt.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/test-story",
3
- "version": "0.18.2",
3
+ "version": "0.19.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": {
@@ -41,12 +41,12 @@
41
41
  "README.md"
42
42
  ],
43
43
  "dependencies": {
44
- "@shivaedev/effect-test": "0.7.2",
44
+ "@shivaedev/effect-test": "0.8.1",
45
45
  "dom-accessibility-api": "0.7.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@effect/vitest": "4.0.5",
49
- "@shivaedev/quality": "0.34.0",
49
+ "@shivaedev/quality": "0.38.0",
50
50
  "@types/node": "24.19.2",
51
51
  "effect": "4.0.5",
52
52
  "happy-dom": "21.0.3",
@@ -1,5 +1,5 @@
1
1
  import { createServer, type IncomingHttpHeaders } from "node:http";
2
- import { Effect } from "effect";
2
+ import { Clock, Effect } from "effect";
3
3
  import { fakeHttpControls } from "#internal/fakeHttpControls.ts";
4
4
  import { fakeHttpReceive } from "#internal/fakeHttpReceive.ts";
5
5
  import { loopbackServer } from "#internal/loopback.ts";
@@ -20,12 +20,22 @@ export interface FakeHttpReply {
20
20
  readonly status?: number;
21
21
  }
22
22
 
23
- export type FakeHttpFault = { readonly kind: "status"; readonly reply: FakeHttpReply } | { readonly kind: "drop" | "reset" };
23
+ export type FakeHttpReplySource = FakeHttpReply | ((request: FakeHttpRequest, now: number) => FakeHttpReply);
24
+
25
+ export interface FakeHttpRequestRecord extends FakeHttpRequest {
26
+ readonly status: number | "pending" | "unanswered";
27
+ }
28
+
29
+ export type FakeHttpFault = { readonly kind: "status"; readonly reply: FakeHttpReplySource } | { readonly kind: "drop" | "reset" };
24
30
 
25
31
  export interface FakeHttpScope {
26
32
  readonly params?: Readonly<Record<string, string>>;
27
33
  }
28
34
 
35
+ export interface FakeHttpHoldOptions extends FakeHttpScope {
36
+ readonly once?: boolean;
37
+ }
38
+
29
39
  export interface FakeHttpScriptOptions extends FakeHttpScope {
30
40
  readonly phase?: "before" | "after";
31
41
  readonly repeat?: boolean;
@@ -56,12 +66,13 @@ export interface FakeHttpHold {
56
66
  }
57
67
 
58
68
  export const fakeHttpService = Effect.fnUntraced(function* (options: FakeHttpServiceOptions) {
59
- const requests: FakeHttpRequest[] = [];
69
+ const clock = yield* Clock.Clock;
70
+ const requests: FakeHttpRequestRecord[] = [];
60
71
  let narrate = options.tell;
61
72
  function tell(line: string) {
62
73
  narrate?.(line);
63
74
  }
64
- const controls = fakeHttpControls(tell);
75
+ const controls = fakeHttpControls(tell, () => clock.currentTimeMillisUnsafe());
65
76
  const server = createServer((request, response) => fakeHttpReceive(request, response, options, requests, controls, tell));
66
77
  const url = yield* loopbackServer(server);
67
78
  yield* Effect.addFinalizer(() => Effect.sync(controls.close));
@@ -71,22 +82,23 @@ export const fakeHttpService = Effect.fnUntraced(function* (options: FakeHttpSer
71
82
  }
72
83
  }
73
84
  return {
74
- failOnce: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) =>
85
+ failOnce: (route: string, reply: FakeHttpReplySource, scope?: FakeHttpScope) =>
75
86
  Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply, scope))),
76
87
  fault: (route: string, fault: FakeHttpFault, scope?: FakeHttpScope) =>
77
88
  Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.fault(route, fault, scope))),
78
- hold: (route: string) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.hold(route))),
89
+ hold: (route: string, holdOptions?: FakeHttpHoldOptions) =>
90
+ Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.hold(route, holdOptions))),
79
91
  narrate: (sink: (line: string) => void) => {
80
92
  narrate = sink;
81
93
  },
82
- releaseOnArrival: (route: string, arrival: string) =>
94
+ releaseOnArrival: (route: string, arrival: string, scope?: FakeHttpScope) =>
83
95
  Effect.sync(() => {
84
96
  checkRoute(route);
85
97
  checkRoute(arrival);
86
- }).pipe(Effect.andThen(controls.releaseOnArrival(route, arrival))),
87
- replyOnce: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) =>
98
+ }).pipe(Effect.andThen(controls.releaseOnArrival(route, arrival, scope))),
99
+ replyOnce: (route: string, reply: FakeHttpReplySource, scope?: FakeHttpScope) =>
88
100
  Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply, scope))),
89
- get requests(): readonly FakeHttpRequest[] {
101
+ get requests(): readonly FakeHttpRequestRecord[] {
90
102
  return requests.slice();
91
103
  },
92
104
  scriptFault: (route: string, fault: FakeHttpFault, scriptOptions?: FakeHttpScriptOptions) =>
@@ -24,7 +24,7 @@ export async function fakeHttpAnswer(
24
24
  ) {
25
25
  const { method, path } = request;
26
26
  controls.arrive(name, request, response);
27
- if (!(await controls.pause(name, response))) {
27
+ if (!(await controls.pause(name, request, response))) {
28
28
  return;
29
29
  }
30
30
  const before = controls.takeFault(name, request, "before");
@@ -33,7 +33,7 @@ export async function fakeHttpAnswer(
33
33
  await fakeHttpStrike(response, before);
34
34
  return;
35
35
  }
36
- let reply = before?.reply ?? pageReply(route, request);
36
+ let reply = before ? controls.reply(before.reply, request) : pageReply(route, request);
37
37
  const after = before ? undefined : controls.takeFault(name, request, "after");
38
38
  if (after) {
39
39
  tell(`fake HTTP ${name} fault ${after.kind} after reply`);
@@ -41,7 +41,7 @@ export async function fakeHttpAnswer(
41
41
  await fakeHttpStrike(response, after, reply);
42
42
  return;
43
43
  }
44
- reply = after.reply;
44
+ reply = controls.reply(after.reply, request);
45
45
  }
46
46
  tell(`fake HTTP answered ${method} ${path} with ${reply.status ?? 200}`);
47
47
  await fakeHttpSend(response, reply, controls.stallBody(name, request));
@@ -1,12 +1,19 @@
1
1
  import type { ServerResponse } from "node:http";
2
2
  import { Effect } from "effect";
3
- import type { FakeHttpFault, FakeHttpReply, FakeHttpRequest, FakeHttpScope, FakeHttpScriptOptions } from "#fakeHttpService.ts";
3
+ import type {
4
+ FakeHttpFault,
5
+ FakeHttpHoldOptions,
6
+ FakeHttpReplySource,
7
+ FakeHttpRequest,
8
+ FakeHttpScope,
9
+ FakeHttpScriptOptions,
10
+ } from "#fakeHttpService.ts";
4
11
  import { fakeHttpGates } from "#internal/fakeHttpGates.ts";
5
12
  import { fakeHttpScripts } from "#internal/fakeHttpScripts.ts";
6
13
  import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
7
14
  import { fakeHttpConnection, fakeHttpSend } from "#internal/fakeHttpSend.ts";
8
15
 
9
- export function fakeHttpControls(tell: (line: string) => void) {
16
+ export function fakeHttpControls(tell: (line: string) => void, now: () => number) {
10
17
  const scripts = fakeHttpScripts(tell);
11
18
  const holds = fakeHttpGates(tell, "hold");
12
19
  const stalls = fakeHttpGates(tell, "stall");
@@ -33,37 +40,40 @@ export function fakeHttpControls(tell: (line: string) => void) {
33
40
  fault: (route: string, fault: FakeHttpFault, scope?: FakeHttpScope) =>
34
41
  Effect.promise(async () => {
35
42
  const selection = fakeHttpSelection(route, scope);
36
- const response = [...active].find(
43
+ const found = [...active].find(
37
44
  ([candidate, entry]) =>
38
45
  selection.matches(entry.route, entry.request)
39
46
  && !(candidate.destroyed || candidate.writableEnded)
40
47
  && candidate.headersSent === (fault.kind === "reset"),
41
- )?.[0];
42
- if (!response) {
48
+ );
49
+ if (!found) {
43
50
  throw new Error(
44
51
  `Fake HTTP route "${route}" has no request ready for a ${fault.kind} fault.\nhelp: Wait for a hold before a status or drop fault, or for response headers before a reset fault.`,
45
52
  );
46
53
  }
54
+ const [response, { request }] = found;
47
55
  tell(`fake HTTP ${selection.label} fault ${fault.kind}`);
48
56
  if (fault.kind === "status") {
49
- await fakeHttpSend(response, fault.reply);
57
+ await fakeHttpSend(response, typeof fault.reply === "function" ? fault.reply(request, now()) : fault.reply);
50
58
  } else {
51
59
  response.destroy();
52
60
  }
53
61
  }),
54
- hold: (route: string) => Effect.sync(() => holds.make(route)),
55
- pause: async (route: string, response: ServerResponse) => {
56
- await fakeHttpConnection(response).pause(holds.get(route)?.enter());
62
+ hold: (route: string, options?: FakeHttpHoldOptions) => Effect.sync(() => holds.make(route, options, options?.once)),
63
+ pause: async (route: string, request: FakeHttpRequest, response: ServerResponse) => {
64
+ await fakeHttpConnection(response).pause(holds.get(route, request)?.enter());
57
65
  return !(response.destroyed || response.writableEnded || response.headersSent);
58
66
  },
59
- queue: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) =>
67
+ queue: (route: string, reply: FakeHttpReplySource, scope?: FakeHttpScope) =>
60
68
  Effect.sync(() => {
61
69
  scripts.make(route, { kind: "status", reply }, scope, false);
62
- tell(`fake HTTP ${fakeHttpSelection(route, scope).label} will answer once with ${reply.status ?? 200}`);
70
+ tell(
71
+ `fake HTTP ${fakeHttpSelection(route, scope).label} will answer once with ${typeof reply === "function" ? "a request-time reply" : (reply.status ?? 200)}`,
72
+ );
63
73
  }),
64
- releaseOnArrival: (route: string, arrival: string) =>
74
+ releaseOnArrival: (route: string, arrival: string, scope?: FakeHttpScope) =>
65
75
  Effect.sync(() => {
66
- const hold = holds.get(route);
76
+ const hold = holds.at(route, scope);
67
77
  if (!hold) {
68
78
  throw new Error(`Fake HTTP route "${route}" has no hold.\nhelp: Hold the route before scripting its release on arrival.`);
69
79
  }
@@ -72,6 +82,7 @@ export function fakeHttpControls(tell: (line: string) => void) {
72
82
  arrivals.set(arrival, releases);
73
83
  tell(`fake HTTP ${route} will release when ${arrival} arrives`);
74
84
  }),
85
+ reply: (source: FakeHttpReplySource, request: FakeHttpRequest) => (typeof source === "function" ? source(request, now()) : source),
75
86
  scriptFault: (route: string, fault: FakeHttpFault, options?: FakeHttpScriptOptions) => Effect.sync(() => scripts.make(route, fault, options)),
76
87
  stall: (route: string, scope?: FakeHttpScope) => Effect.sync(() => stalls.make(route, scope)),
77
88
  stallBody: (route: string, request: FakeHttpRequest) => {
@@ -5,14 +5,14 @@ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
5
5
  export function fakeHttpGates(tell: (line: string) => void, kind: "hold" | "stall") {
6
6
  const gates = new Map<string, { enter: () => Promise<void>; release: () => void; matches: ReturnType<typeof fakeHttpSelection>["matches"] }>();
7
7
  return {
8
+ at: (route: string, scope?: FakeHttpScope) => gates.get(fakeHttpSelection(route, scope).label),
8
9
  close: () => {
9
10
  for (const gate of gates.values()) {
10
11
  gate.release();
11
12
  }
12
13
  },
13
- get: (route: string, request?: FakeHttpRequest) =>
14
- request ? [...gates.values()].find((gate) => gate.matches(route, request)) : gates.get(route),
15
- make: (route: string, scope?: FakeHttpScope): FakeHttpHold => {
14
+ get: (route: string, request: FakeHttpRequest) => [...gates.values()].find((gate) => gate.matches(route, request)),
15
+ make: (route: string, scope?: FakeHttpScope, once = false): FakeHttpHold => {
16
16
  const selection = fakeHttpSelection(route, scope);
17
17
  const label = selection.label;
18
18
  if (gates.has(label)) {
@@ -20,13 +20,15 @@ export function fakeHttpGates(tell: (line: string) => void, kind: "hold" | "stal
20
20
  }
21
21
  const waiting = Deferred.makeUnsafe<void>();
22
22
  const released = Promise.withResolvers<void>();
23
+ let entered = false;
23
24
  const gate = {
24
25
  enter: () => {
26
+ entered = true;
25
27
  tell(kind === "hold" ? `fake HTTP ${label} has a waiting request` : `fake HTTP ${label} body stalled`);
26
28
  Deferred.doneUnsafe(waiting, Effect.void);
27
29
  return released.promise;
28
30
  },
29
- matches: selection.matches,
31
+ matches: (name: string, request: FakeHttpRequest) => !(once && entered) && selection.matches(name, request),
30
32
  release: () => {
31
33
  if (gates.get(label) !== gate) {
32
34
  return;
@@ -1,5 +1,5 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import type { FakeHttpRequest, FakeHttpRoute, FakeHttpServiceOptions } from "#fakeHttpService.ts";
2
+ import type { FakeHttpRequestRecord, FakeHttpRoute, FakeHttpServiceOptions } from "#fakeHttpService.ts";
3
3
  import { fakeHttpAnswer } from "#internal/fakeHttpAnswer.ts";
4
4
  import type { fakeHttpControls } from "#internal/fakeHttpControls.ts";
5
5
  import { fakeHttpSend } from "#internal/fakeHttpSend.ts";
@@ -10,14 +10,26 @@ function match(route: FakeHttpRoute, method: string, pathname: string) {
10
10
  }
11
11
  const pattern = route.path.split("/");
12
12
  const segments = pathname.split("/");
13
- if (pattern.length !== segments.length) {
13
+ const rest = pattern.at(-1)?.startsWith(":") && pattern.at(-1)?.endsWith("*");
14
+ if (rest ? segments.length < pattern.length : pattern.length !== segments.length) {
14
15
  return;
15
16
  }
17
+ return pathParams(pattern, segments);
18
+ }
19
+
20
+ function pathParams(pattern: string[], segments: string[]) {
16
21
  const params: Record<string, string> = {};
17
22
  for (const [index, part] of pattern.entries()) {
18
23
  const segment = segments[index] ?? "";
19
24
  if (part.startsWith(":")) {
20
- params[part.slice(1)] = segment;
25
+ if (part.endsWith("*")) {
26
+ if (index !== pattern.length - 1 || !segment) {
27
+ return;
28
+ }
29
+ params[part.slice(1, -1)] = segments.slice(index).join("/");
30
+ } else {
31
+ params[part.slice(1)] = segment;
32
+ }
21
33
  } else if (part !== segment) {
22
34
  return;
23
35
  }
@@ -28,7 +40,7 @@ function match(route: FakeHttpRoute, method: string, pathname: string) {
28
40
  function decodeParams(params: Record<string, string>) {
29
41
  try {
30
42
  for (const [name, value] of Object.entries(params)) {
31
- params[name] = decodeURIComponent(value);
43
+ params[name] = value.split("/").map(decodeURIComponent).join("/");
32
44
  }
33
45
  return true;
34
46
  } catch {
@@ -36,11 +48,18 @@ function decodeParams(params: Record<string, string>) {
36
48
  }
37
49
  }
38
50
 
51
+ function requestStatus(response: ServerResponse) {
52
+ if (response.writableFinished) {
53
+ return response.statusCode;
54
+ }
55
+ return response.destroyed ? "unanswered" : "pending";
56
+ }
57
+
39
58
  export async function fakeHttpReceive(
40
59
  incoming: IncomingMessage,
41
60
  response: ServerResponse,
42
61
  options: FakeHttpServiceOptions,
43
- requests: FakeHttpRequest[],
62
+ requests: FakeHttpRequestRecord[],
44
63
  controls: ReturnType<typeof fakeHttpControls>,
45
64
  tell: (line: string) => void,
46
65
  ) {
@@ -57,13 +76,16 @@ export async function fakeHttpReceive(
57
76
  chunks.push(Buffer.from(chunk));
58
77
  }
59
78
  const params = found?.params ?? {};
60
- const request: FakeHttpRequest = {
79
+ const request: FakeHttpRequestRecord = {
61
80
  body: Buffer.concat(chunks),
62
81
  headers: incoming.headers,
63
82
  method,
64
83
  params,
65
84
  path,
66
85
  query: url.searchParams,
86
+ get status() {
87
+ return requestStatus(response);
88
+ },
67
89
  };
68
90
  requests.push(request);
69
91
  tell(`fake HTTP received ${method} ${path}`);
@@ -1,7 +1,7 @@
1
1
  import type { TestContext } from "@effect/vitest";
2
2
  import { Effect, Layer } from "effect";
3
+ import { makeEffectIt } from "@shivaedev/effect-test/makeEffectIt.ts";
3
4
  import type { EffectTest, EffectTestOptions } from "@shivaedev/effect-test/types.ts";
4
- import { makeEffectIt } from "@shivaedev/effect-test/vitest.ts";
5
5
  import { type Genre, genreOf } from "#internal/genre.ts";
6
6
  import { type AnyEffect, hooked } from "#internal/hooked.ts";
7
7
  import type { LooseDefinition, LooseTrait } from "#internal/seed.ts";