@shivaedev/test-story 0.14.0 → 0.15.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 (42) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +23 -2
  3. package/dist/server/fakeHttpService.d.ts +16 -4
  4. package/dist/server/fakeHttpService.d.ts.map +1 -1
  5. package/dist/server/fakeHttpService.js +5 -4
  6. package/dist/server/fakeHttpService.js.map +1 -1
  7. package/dist/server/internal/fakeHttpAnswer.d.ts +5 -0
  8. package/dist/server/internal/fakeHttpAnswer.d.ts.map +1 -0
  9. package/dist/server/internal/fakeHttpAnswer.js +37 -0
  10. package/dist/server/internal/fakeHttpAnswer.js.map +1 -0
  11. package/dist/server/internal/fakeHttpControls.d.ts +8 -7
  12. package/dist/server/internal/fakeHttpControls.d.ts.map +1 -1
  13. package/dist/server/internal/fakeHttpControls.js +20 -16
  14. package/dist/server/internal/fakeHttpControls.js.map +1 -1
  15. package/dist/server/internal/fakeHttpGates.d.ts +5 -3
  16. package/dist/server/internal/fakeHttpGates.d.ts.map +1 -1
  17. package/dist/server/internal/fakeHttpGates.js +13 -9
  18. package/dist/server/internal/fakeHttpGates.js.map +1 -1
  19. package/dist/server/internal/fakeHttpReceive.d.ts.map +1 -1
  20. package/dist/server/internal/fakeHttpReceive.js +3 -18
  21. package/dist/server/internal/fakeHttpReceive.js.map +1 -1
  22. package/dist/server/internal/fakeHttpScripts.d.ts +7 -0
  23. package/dist/server/internal/fakeHttpScripts.d.ts.map +1 -0
  24. package/dist/server/internal/fakeHttpScripts.js +35 -0
  25. package/dist/server/internal/fakeHttpScripts.js.map +1 -0
  26. package/dist/server/internal/fakeHttpSelection.d.ts +6 -0
  27. package/dist/server/internal/fakeHttpSelection.d.ts.map +1 -0
  28. package/dist/server/internal/fakeHttpSelection.js +9 -0
  29. package/dist/server/internal/fakeHttpSelection.js.map +1 -0
  30. package/dist/server/internal/fakeHttpSend.d.ts +4 -1
  31. package/dist/server/internal/fakeHttpSend.d.ts.map +1 -1
  32. package/dist/server/internal/fakeHttpSend.js +8 -0
  33. package/dist/server/internal/fakeHttpSend.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/server/fakeHttpService.ts +23 -4
  36. package/src/server/internal/fakeHttpAnswer.ts +48 -0
  37. package/src/server/internal/fakeHttpControls.ts +23 -19
  38. package/src/server/internal/fakeHttpGates.ts +16 -11
  39. package/src/server/internal/fakeHttpReceive.ts +4 -20
  40. package/src/server/internal/fakeHttpScripts.ts +40 -0
  41. package/src/server/internal/fakeHttpSelection.ts +10 -0
  42. package/src/server/internal/fakeHttpSend.ts +10 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.0 - 2026-10-19
4
+
5
+ ### Added
6
+
7
+ - Scope `fakeHttpService` stalls, immediate faults and one-shot replies by decoded path parameters with `FakeHttpScope`.
8
+ - Script future status, drop and reset faults with `scriptFault` and `FakeHttpScriptOptions`, optionally repeat until the returned `FakeHttpScript.lift` control or strike after the route's reply applies its effects.
9
+ - Declare per-route guards alongside the service guard; rejected requests receive 401 before holds, arrival scripts or faults are consumed.
10
+
3
11
  ## 0.14.0 - 2026-10-19
4
12
 
5
13
  ### Added
package/README.md CHANGED
@@ -288,7 +288,7 @@ const api = yield* fakeHttpService({
288
288
  });
289
289
  ```
290
290
 
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. Omit the guard for a public fake. Unmatched paths and methods get 404.
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
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.
294
294
 
@@ -322,6 +322,27 @@ const bytes = yield* Fiber.join(pending);
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`.
326
+
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
+
329
+ ```ts
330
+ const outage = yield* api.scriptFault("pull", { kind: "drop" }, {
331
+ params: { number: "7" },
332
+ repeat: true,
333
+ });
334
+ yield* application.loadPull("7");
335
+ yield* outage.lift;
336
+
337
+ yield* api.scriptFault("comment", {
338
+ kind: "status",
339
+ reply: { status: 503, body: { error: "try later" } },
340
+ }, { params: { number: "7" }, phase: "after" });
341
+ yield* application.postComment("7", "Ready for review");
342
+ ```
343
+
344
+ With `phase: "after"`, the route's reply function runs and applies its effects before the fault strikes. A status fault replaces the reply, a drop closes before headers, and a reset sends the route's status and headers then closes its unfinished body. The comment therefore exists even though the client sees a failure. This lets an application story check retry behavior after a side effect. The default `phase: "before"` skips the reply function when the fault strikes.
345
+
325
346
  All controls validate route names and narrate through the service's story sink. Closing the scope closes active connections and releases holds, stalls and arrival scripts.
326
347
 
327
348
  A `pages` route accepts a one-based `?page=` query and returns `{ items, next }`, with `next: null` on the last page. No query means page 1; a page past the end returns an empty list; a non-positive or non-integer page gets 400. `api.requests` reads a snapshot of the request log, including rejected and held requests, with `method`, `path` (including query), raw `body` Buffer, `headers`, `params` and `query`.
@@ -605,7 +626,7 @@ Import each module with the prefix `@shivaedev/test-story/`; there is no root en
605
626
  | `domNavigate.ts` | `domNavigate(url?)` |
606
627
  | `genreTags.ts` | `genreTags(...kitNames)`, `GenreTag` |
607
628
  | `domEnvironment.config.ts` | Default Vitest environment preserving Node host globals and happy-dom document transport |
608
- | `fakeHttpService.ts` | `fakeHttpService(options)`, `FakeHttpRoute`, `FakeHttpRequest`, `FakeHttpReply`, `FakeHttpFault`, `FakeHttpHold`, `FakeHttpServiceOptions` |
629
+ | `fakeHttpService.ts` | `fakeHttpService(options)`, `FakeHttpRoute`, `FakeHttpRequest`, `FakeHttpReply`, `FakeHttpFault`, `FakeHttpHold`, `FakeHttpScope`, `FakeHttpScriptOptions`, `FakeHttpScript`, `FakeHttpServiceOptions` |
609
630
  | `forwardingProxy.ts` | `forwardingProxy(upstream, options?)`, `ForwardingProxyOptions` |
610
631
  | `otlpReceiver.ts` | `otlpReceiver(options?)`, `OtlpSpan`, `OtlpExport`, `OtlpReceiverOptions` |
611
632
 
@@ -20,7 +20,18 @@ export type FakeHttpFault = {
20
20
  } | {
21
21
  readonly kind: "drop" | "reset";
22
22
  };
23
+ export interface FakeHttpScope {
24
+ readonly params?: Readonly<Record<string, string>>;
25
+ }
26
+ export interface FakeHttpScriptOptions extends FakeHttpScope {
27
+ readonly phase?: "before" | "after";
28
+ readonly repeat?: boolean;
29
+ }
30
+ export interface FakeHttpScript {
31
+ readonly lift: Effect.Effect<void>;
32
+ }
23
33
  export type FakeHttpRoute = {
34
+ readonly guard?: (request: FakeHttpRequest) => boolean;
24
35
  readonly method?: string;
25
36
  readonly path: string;
26
37
  } & ({
@@ -40,14 +51,15 @@ export interface FakeHttpHold {
40
51
  readonly waiting: Effect.Effect<void>;
41
52
  }
42
53
  export declare const fakeHttpService: (options: FakeHttpServiceOptions) => Effect.Effect<{
43
- failOnce: (route: string, reply: FakeHttpReply) => Effect.Effect<void, never, never>;
44
- fault: (route: string, fault: FakeHttpFault) => Effect.Effect<void, never, never>;
54
+ failOnce: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
55
+ fault: (route: string, fault: FakeHttpFault, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
45
56
  hold: (route: string) => Effect.Effect<FakeHttpHold, never, never>;
46
57
  narrate: (sink: (line: string) => void) => void;
47
58
  releaseOnArrival: (route: string, arrival: string) => Effect.Effect<void, never, never>;
48
- replyOnce: (route: string, reply: FakeHttpReply) => Effect.Effect<void, never, never>;
59
+ replyOnce: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
49
60
  readonly requests: readonly FakeHttpRequest[];
50
- stall: (route: string) => Effect.Effect<FakeHttpHold, never, never>;
61
+ scriptFault: (route: string, fault: FakeHttpFault, scriptOptions?: FakeHttpScriptOptions) => Effect.Effect<FakeHttpScript, never, never>;
62
+ stall: (route: string, scope?: FakeHttpScope) => Effect.Effect<FakeHttpHold, never, never>;
51
63
  url: string;
52
64
  }, import("effect/Cause").UnknownError, import("effect/Scope").Scope>;
53
65
  //# sourceMappingURL=fakeHttpService.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpService.d.ts","sourceRoot":"","sources":["../../src/server/fakeHttpService.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,aAAa,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE7H,MAAM,MAAM,aAAa,GAAG;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB,GAAG,CACD;IAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,aAAa,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GACvF;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAC7E,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC;IACvD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACtC;AAED,eAAO,MAAM,eAAe;sBAgBR,MAAM,SAAS,aAAa;mBAC/B,MAAM,SAAS,aAAa;kBAC7B,MAAM;oBACJ,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;8BAGZ,MAAM,WAAW,MAAM;uBAK9B,MAAM,SAAS,aAAa;uBAC/B,SAAS,eAAe,EAAE;mBAG3B,MAAM;;qEAGrB,CAAC"}
1
+ {"version":3,"file":"fakeHttpService.d.ts","sourceRoot":"","sources":["../../src/server/fakeHttpService.ts"],"names":[],"mappings":"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"}
@@ -19,8 +19,8 @@ export const fakeHttpService = Effect.fnUntraced(function* (options) {
19
19
  }
20
20
  }
21
21
  return {
22
- failOnce: (route, reply) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply))),
23
- fault: (route, fault) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.fault(route, fault))),
22
+ failOnce: (route, reply, scope) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply, scope))),
23
+ fault: (route, fault, scope) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.fault(route, fault, scope))),
24
24
  hold: (route) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.hold(route))),
25
25
  narrate: (sink) => {
26
26
  narrate = sink;
@@ -29,11 +29,12 @@ export const fakeHttpService = Effect.fnUntraced(function* (options) {
29
29
  checkRoute(route);
30
30
  checkRoute(arrival);
31
31
  }).pipe(Effect.andThen(controls.releaseOnArrival(route, arrival))),
32
- replyOnce: (route, reply) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply))),
32
+ replyOnce: (route, reply, scope) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply, scope))),
33
33
  get requests() {
34
34
  return requests.slice();
35
35
  },
36
- stall: (route) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.stall(route))),
36
+ scriptFault: (route, fault, scriptOptions) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.scriptFault(route, fault, scriptOptions))),
37
+ stall: (route, scope) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.stall(route, scope))),
37
38
  url,
38
39
  };
39
40
  });
@@ -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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"}
@@ -0,0 +1,5 @@
1
+ import type { ServerResponse } from "node:http";
2
+ import type { FakeHttpRequest, FakeHttpRoute } from "#fakeHttpService.ts";
3
+ import type { fakeHttpControls } from "#internal/fakeHttpControls.ts";
4
+ export declare function fakeHttpAnswer(name: string, route: FakeHttpRoute, request: FakeHttpRequest, response: ServerResponse, controls: ReturnType<typeof fakeHttpControls>, tell: (line: string) => void): Promise<void>;
5
+ //# sourceMappingURL=fakeHttpAnswer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeHttpAnswer.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpAnswer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAiB,eAAe,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AActE,wBAAsB,cAAc,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EAC7C,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,iBAyB5B"}
@@ -0,0 +1,37 @@
1
+ import { fakeHttpSend, fakeHttpStrike } from "#internal/fakeHttpSend.ts";
2
+ function pageReply(route, request) {
3
+ if (route.reply) {
4
+ return route.reply(request);
5
+ }
6
+ const page = Number(request.query.get("page") ?? "1");
7
+ if (!Number.isInteger(page) || page < 1) {
8
+ return { body: { error: "page must be a positive integer" }, status: 400 };
9
+ }
10
+ return { body: { items: route.pages[page - 1] ?? [], next: page < route.pages.length ? page + 1 : null } };
11
+ }
12
+ export async function fakeHttpAnswer(name, route, request, response, controls, tell) {
13
+ const { method, path } = request;
14
+ controls.arrive(name, request, response);
15
+ if (!(await controls.pause(name, response))) {
16
+ return;
17
+ }
18
+ const before = controls.takeFault(name, request, "before");
19
+ if (before && before.kind !== "status") {
20
+ tell(`fake HTTP ${name} fault ${before.kind} before reply`);
21
+ await fakeHttpStrike(response, before);
22
+ return;
23
+ }
24
+ let reply = before?.reply ?? pageReply(route, request);
25
+ const after = before ? undefined : controls.takeFault(name, request, "after");
26
+ if (after) {
27
+ tell(`fake HTTP ${name} fault ${after.kind} after reply`);
28
+ if (after.kind !== "status") {
29
+ await fakeHttpStrike(response, after, reply);
30
+ return;
31
+ }
32
+ reply = after.reply;
33
+ }
34
+ tell(`fake HTTP answered ${method} ${path} with ${reply.status ?? 200}`);
35
+ await fakeHttpSend(response, reply, controls.stallBody(name, request));
36
+ }
37
+ //# sourceMappingURL=fakeHttpAnswer.js.map
@@ -0,0 +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,16 +1,17 @@
1
1
  import type { ServerResponse } from "node:http";
2
2
  import { Effect } from "effect";
3
- import type { FakeHttpFault, FakeHttpReply } from "#fakeHttpService.ts";
3
+ import type { FakeHttpFault, FakeHttpReply, FakeHttpRequest, FakeHttpScope, FakeHttpScriptOptions } from "#fakeHttpService.ts";
4
4
  export declare function fakeHttpControls(tell: (line: string) => void): {
5
- arrive: (route: string, response: ServerResponse) => void;
5
+ arrive: (route: string, request: FakeHttpRequest, response: ServerResponse) => void;
6
6
  close: () => void;
7
- fault: (route: string, fault: FakeHttpFault) => Effect.Effect<void, never, never>;
7
+ fault: (route: string, fault: FakeHttpFault, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
8
8
  hold: (route: string) => Effect.Effect<import("#fakeHttpService.ts").FakeHttpHold, never, never>;
9
9
  pause: (route: string, response: ServerResponse) => Promise<boolean>;
10
- queue: (route: string, reply: FakeHttpReply) => Effect.Effect<void, never, never>;
10
+ queue: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) => Effect.Effect<void, never, never>;
11
11
  releaseOnArrival: (route: string, arrival: string) => Effect.Effect<void, never, never>;
12
- stall: (route: string) => Effect.Effect<import("#fakeHttpService.ts").FakeHttpHold, never, never>;
13
- stallBody: (route: string) => (() => Promise<void>) | undefined;
14
- takeReply: (route: string) => FakeHttpReply | undefined;
12
+ scriptFault: (route: string, fault: FakeHttpFault, options?: FakeHttpScriptOptions) => Effect.Effect<import("#fakeHttpService.ts").FakeHttpScript, never, never>;
13
+ stall: (route: string, scope?: FakeHttpScope) => Effect.Effect<import("#fakeHttpService.ts").FakeHttpHold, never, never>;
14
+ stallBody: (route: string, request: FakeHttpRequest) => (() => Promise<void>) | undefined;
15
+ takeFault: (route: string, request: FakeHttpRequest, phase: "before" | "after") => FakeHttpFault | undefined;
15
16
  };
16
17
  //# sourceMappingURL=fakeHttpControls.d.ts.map
@@ -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,MAAM,qBAAqB,CAAC;AAIxE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;IAO3D,MAAM,UAAU,MAAM,YAAY,cAAc;IAQhD,KAAK;IASL,KAAK,UAAU,MAAM,SAAS,aAAa;IAkB3C,IAAI,UAAU,MAAM;IACpB,KAAK,UAAgB,MAAM,YAAY,cAAc;IAIrD,KAAK,UAAU,MAAM,SAAS,aAAa;IAO3C,gBAAgB,UAAU,MAAM,WAAW,MAAM;IAWjD,KAAK,UAAU,MAAM;IACrB,SAAS,UAAU,MAAM;IAIzB,SAAS,UAAU,MAAM;EAE1B"}
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,15 +1,17 @@
1
1
  import { Effect } from "effect";
2
2
  import { fakeHttpGates } from "#internal/fakeHttpGates.ts";
3
+ import { fakeHttpScripts } from "#internal/fakeHttpScripts.ts";
4
+ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
3
5
  import { fakeHttpConnection, fakeHttpSend } from "#internal/fakeHttpSend.ts";
4
6
  export function fakeHttpControls(tell) {
5
- const replies = new Map();
7
+ const scripts = fakeHttpScripts(tell);
6
8
  const holds = fakeHttpGates(tell, "hold");
7
9
  const stalls = fakeHttpGates(tell, "stall");
8
10
  const arrivals = new Map();
9
11
  const active = new Map();
10
12
  return {
11
- arrive: (route, response) => {
12
- active.set(response, route);
13
+ arrive: (route, request, response) => {
14
+ active.set(response, { request, route });
13
15
  response.once("close", () => active.delete(response));
14
16
  for (const release of arrivals.get(route) ?? []) {
15
17
  release();
@@ -23,14 +25,17 @@ export function fakeHttpControls(tell) {
23
25
  holds.close();
24
26
  stalls.close();
25
27
  arrivals.clear();
26
- replies.clear();
28
+ scripts.clear();
27
29
  },
28
- fault: (route, fault) => Effect.promise(async () => {
29
- const response = [...active].find(([candidate, name]) => name === route && !(candidate.destroyed || candidate.writableEnded) && candidate.headersSent === (fault.kind === "reset"))?.[0];
30
+ fault: (route, fault, scope) => Effect.promise(async () => {
31
+ const selection = fakeHttpSelection(route, scope);
32
+ const response = [...active].find(([candidate, entry]) => selection.matches(entry.route, entry.request)
33
+ && !(candidate.destroyed || candidate.writableEnded)
34
+ && candidate.headersSent === (fault.kind === "reset"))?.[0];
30
35
  if (!response) {
31
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.`);
32
37
  }
33
- tell(`fake HTTP ${route} fault ${fault.kind}`);
38
+ tell(`fake HTTP ${selection.label} fault ${fault.kind}`);
34
39
  if (fault.kind === "status") {
35
40
  await fakeHttpSend(response, fault.reply);
36
41
  }
@@ -43,11 +48,9 @@ export function fakeHttpControls(tell) {
43
48
  await fakeHttpConnection(response).pause(holds.get(route)?.enter());
44
49
  return !(response.destroyed || response.writableEnded || response.headersSent);
45
50
  },
46
- queue: (route, reply) => Effect.sync(() => {
47
- const queue = replies.get(route) ?? [];
48
- queue.push(reply);
49
- replies.set(route, queue);
50
- tell(`fake HTTP ${route} will answer once with ${reply.status ?? 200}`);
51
+ queue: (route, reply, scope) => Effect.sync(() => {
52
+ scripts.make(route, { kind: "status", reply }, scope, false);
53
+ tell(`fake HTTP ${fakeHttpSelection(route, scope).label} will answer once with ${reply.status ?? 200}`);
51
54
  }),
52
55
  releaseOnArrival: (route, arrival) => Effect.sync(() => {
53
56
  const hold = holds.get(route);
@@ -59,12 +62,13 @@ export function fakeHttpControls(tell) {
59
62
  arrivals.set(arrival, releases);
60
63
  tell(`fake HTTP ${route} will release when ${arrival} arrives`);
61
64
  }),
62
- stall: (route) => Effect.sync(() => stalls.make(route)),
63
- stallBody: (route) => {
64
- const stall = stalls.get(route);
65
+ scriptFault: (route, fault, options) => Effect.sync(() => scripts.make(route, fault, options)),
66
+ stall: (route, scope) => Effect.sync(() => stalls.make(route, scope)),
67
+ stallBody: (route, request) => {
68
+ const stall = stalls.get(route, request);
65
69
  return stall ? () => stall.enter() : undefined;
66
70
  },
67
- takeReply: (route) => replies.get(route)?.shift(),
71
+ takeFault: scripts.take,
68
72
  };
69
73
  }
70
74
  //# sourceMappingURL=fakeHttpControls.js.map
@@ -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":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE7E,MAAM,UAAU,gBAAgB,CAAC,IAA4B;IAC5D,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+D,CAAC;IACtF,OAAO;QACN,MAAM,EAAE,CAAC,KAAa,EAAE,OAAwB,EAAE,QAAwB,EAAE,EAAE;YAC7E,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;YACtD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;gBACjD,OAAO,EAAE,CAAC;YACX,CAAC;YACD,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;QACD,KAAK,EAAE,GAAG,EAAE;YACX,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBACtC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACpB,CAAC;YACD,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;QACD,KAAK,EAAE,CAAC,KAAa,EAAE,KAAoB,EAAE,KAAqB,EAAE,EAAE,CACrE,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAClD,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAChC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,CACtB,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;mBAC1C,CAAC,CAAC,SAAS,CAAC,SAAS,IAAI,SAAS,CAAC,aAAa,CAAC;mBACjD,SAAS,CAAC,WAAW,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC,CACtD,EAAE,CAAC,CAAC,CAAC,CAAC;YACP,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CACd,oBAAoB,KAAK,gCAAgC,KAAK,CAAC,IAAI,6GAA6G,CAChL,CAAC;YACH,CAAC;YACD,IAAI,CAAC,aAAa,SAAS,CAAC,KAAK,UAAU,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YAC3C,CAAC;iBAAM,CAAC;gBACP,QAAQ,CAAC,OAAO,EAAE,CAAC;YACpB,CAAC;QACF,CAAC,CAAC;QACH,IAAI,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7D,KAAK,EAAE,KAAK,EAAE,KAAa,EAAE,QAAwB,EAAE,EAAE;YACxD,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACpE,OAAO,CAAC,CAAC,QAAQ,CAAC,SAAS,IAAI,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,WAAW,CAAC,CAAC;QAChF,CAAC;QACD,KAAK,EAAE,CAAC,KAAa,EAAE,KAAoB,EAAE,KAAqB,EAAE,EAAE,CACrE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YAChB,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAC7D,IAAI,CAAC,aAAa,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,KAAK,0BAA0B,KAAK,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;QACzG,CAAC,CAAC;QACH,gBAAgB,EAAE,CAAC,KAAa,EAAE,OAAe,EAAE,EAAE,CACpD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YAChB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CAAC,oBAAoB,KAAK,+EAA+E,CAAC,CAAC;YAC3H,CAAC;YACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,EAAc,CAAC;YAChE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3B,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAChC,IAAI,CAAC,aAAa,KAAK,sBAAsB,OAAO,UAAU,CAAC,CAAC;QACjE,CAAC,CAAC;QACH,WAAW,EAAE,CAAC,KAAa,EAAE,KAAoB,EAAE,OAA+B,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC7I,KAAK,EAAE,CAAC,KAAa,EAAE,KAAqB,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC7F,SAAS,EAAE,CAAC,KAAa,EAAE,OAAwB,EAAE,EAAE;YACtD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACzC,OAAO,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAChD,CAAC;QACD,SAAS,EAAE,OAAO,CAAC,IAAI;KACvB,CAAC;AACH,CAAC"}
@@ -1,10 +1,12 @@
1
- import type { FakeHttpHold } from "#fakeHttpService.ts";
1
+ import type { FakeHttpHold, FakeHttpRequest, FakeHttpScope } from "#fakeHttpService.ts";
2
+ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
2
3
  export declare function fakeHttpGates(tell: (line: string) => void, kind: "hold" | "stall"): {
3
4
  close: () => void;
4
- get: (route: string) => {
5
+ get: (route: string, request?: FakeHttpRequest) => {
5
6
  enter: () => Promise<void>;
6
7
  release: () => void;
8
+ matches: ReturnType<typeof fakeHttpSelection>["matches"];
7
9
  } | undefined;
8
- make: (route: string) => FakeHttpHold;
10
+ make: (route: string, scope?: FakeHttpScope) => FakeHttpHold;
9
11
  };
10
12
  //# 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,MAAM,qBAAqB,CAAC;AAExD,wBAAgB,aAAa,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAGhF,KAAK;IAKL,GAAG,UAAU,MAAM;eAPmB,MAAM,OAAO,CAAC,IAAI,CAAC;iBAAW,MAAM,IAAI;;IAQ9E,IAAI,UAAU,MAAM,KAAG,YAAY;EA0BpC"}
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,4 +1,5 @@
1
1
  import { Deferred, Effect } from "effect";
2
+ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
2
3
  export function fakeHttpGates(tell, kind) {
3
4
  const gates = new Map();
4
5
  return {
@@ -7,30 +8,33 @@ export function fakeHttpGates(tell, kind) {
7
8
  gate.release();
8
9
  }
9
10
  },
10
- get: (route) => gates.get(route),
11
- make: (route) => {
12
- if (gates.has(route)) {
11
+ get: (route, request) => request ? [...gates.values()].find((gate) => gate.matches(route, request)) : gates.get(route),
12
+ make: (route, scope) => {
13
+ const selection = fakeHttpSelection(route, scope);
14
+ const label = selection.label;
15
+ if (gates.has(label)) {
13
16
  throw new Error(`Fake HTTP route "${route}" already has a ${kind}.\nhelp: Release its current ${kind} before scripting another.`);
14
17
  }
15
18
  const waiting = Deferred.makeUnsafe();
16
19
  const released = Promise.withResolvers();
17
20
  const gate = {
18
21
  enter: () => {
19
- tell(kind === "hold" ? `fake HTTP ${route} has a waiting request` : `fake HTTP ${route} body stalled`);
22
+ tell(kind === "hold" ? `fake HTTP ${label} has a waiting request` : `fake HTTP ${label} body stalled`);
20
23
  Deferred.doneUnsafe(waiting, Effect.void);
21
24
  return released.promise;
22
25
  },
26
+ matches: selection.matches,
23
27
  release: () => {
24
- if (gates.get(route) !== gate) {
28
+ if (gates.get(label) !== gate) {
25
29
  return;
26
30
  }
27
- gates.delete(route);
31
+ gates.delete(label);
28
32
  released.resolve();
29
- tell(kind === "hold" ? `fake HTTP ${route} released` : `fake HTTP ${route} body released`);
33
+ tell(kind === "hold" ? `fake HTTP ${label} released` : `fake HTTP ${label} body released`);
30
34
  },
31
35
  };
32
- gates.set(route, gate);
33
- tell(kind === "hold" ? `fake HTTP ${route} held` : `fake HTTP ${route} body will stall`);
36
+ gates.set(label, gate);
37
+ tell(kind === "hold" ? `fake HTTP ${label} held` : `fake HTTP ${label} body will stall`);
34
38
  return { release: Effect.sync(gate.release), waiting: Deferred.await(waiting) };
35
39
  },
36
40
  };
@@ -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;AAG1C,MAAM,UAAU,aAAa,CAAC,IAA4B,EAAE,IAAsB;IACjF,MAAM,KAAK,GAAG,IAAI,GAAG,EAA+D,CAAC;IACrF,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,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;QACxC,IAAI,EAAE,CAAC,KAAa,EAAgB,EAAE;YACrC,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,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":"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 +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,EAAiB,eAAe,EAAiB,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACjH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AA8CtE,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,iBAuD5B"}
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,3 +1,4 @@
1
+ import { fakeHttpAnswer } from "#internal/fakeHttpAnswer.ts";
1
2
  import { fakeHttpSend } from "#internal/fakeHttpSend.ts";
2
3
  function match(route, method, pathname) {
3
4
  if ((route.method ?? "GET").toUpperCase() !== method) {
@@ -31,16 +32,6 @@ function decodeParams(params) {
31
32
  return false;
32
33
  }
33
34
  }
34
- function pageReply(route, request) {
35
- if (route.reply) {
36
- return route.reply(request);
37
- }
38
- const page = Number(request.query.get("page") ?? "1");
39
- if (!Number.isInteger(page) || page < 1) {
40
- return { body: { error: "page must be a positive integer" }, status: 400 };
41
- }
42
- return { body: { items: route.pages[page - 1] ?? [], next: page < route.pages.length ? page + 1 : null } };
43
- }
44
35
  export async function fakeHttpReceive(incoming, response, options, requests, controls, tell) {
45
36
  try {
46
37
  const path = incoming.url ?? "/";
@@ -70,7 +61,7 @@ export async function fakeHttpReceive(incoming, response, options, requests, con
70
61
  await fakeHttpSend(response, { status: 400 });
71
62
  return;
72
63
  }
73
- if (options.guard && !options.guard(request)) {
64
+ if ((options.guard && !options.guard(request)) || (found?.route.guard && !found.route.guard(request))) {
74
65
  tell(`fake HTTP rejected ${method} ${path} with 401`);
75
66
  await fakeHttpSend(response, { status: 401 });
76
67
  return;
@@ -80,13 +71,7 @@ export async function fakeHttpReceive(incoming, response, options, requests, con
80
71
  await fakeHttpSend(response, { status: 404 });
81
72
  return;
82
73
  }
83
- controls.arrive(found.name, response);
84
- if (!(await controls.pause(found.name, response))) {
85
- return;
86
- }
87
- const reply = controls.takeReply(found.name) ?? pageReply(found.route, request);
88
- tell(`fake HTTP answered ${method} ${path} with ${reply.status ?? 200}`);
89
- await fakeHttpSend(response, reply, controls.stallBody(found.name));
74
+ await fakeHttpAnswer(found.name, found.route, request, response, controls, tell);
90
75
  }
91
76
  catch (error) {
92
77
  tell(`fake HTTP could not answer: ${String(error)}`);
@@ -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":"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"}
@@ -0,0 +1,7 @@
1
+ import type { FakeHttpFault, FakeHttpRequest, FakeHttpScript, FakeHttpScriptOptions } from "#fakeHttpService.ts";
2
+ export declare function fakeHttpScripts(tell: (line: string) => void): {
3
+ clear: () => void;
4
+ make: (route: string, fault: FakeHttpFault, options?: FakeHttpScriptOptions, narrate?: boolean) => FakeHttpScript;
5
+ take: (route: string, request: FakeHttpRequest, phase: "before" | "after") => FakeHttpFault | undefined;
6
+ };
7
+ //# sourceMappingURL=fakeHttpScripts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeHttpScripts.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpScripts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGjH,wBAAgB,eAAe,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;IAO1D,KAAK;IACL,IAAI,UAAU,MAAM,SAAS,aAAa,YAAW,qBAAqB,wBAAwB,cAAc;IAehH,IAAI,UAAU,MAAM,WAAW,eAAe,SAAS,QAAQ,GAAG,OAAO;EAY1E"}
@@ -0,0 +1,35 @@
1
+ import { Effect } from "effect";
2
+ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
3
+ export function fakeHttpScripts(tell) {
4
+ const scripts = new Set();
5
+ return {
6
+ clear: () => scripts.clear(),
7
+ make: (route, fault, options = {}, narrate = true) => {
8
+ const selection = fakeHttpSelection(route, options);
9
+ const script = { fault, options, selection };
10
+ scripts.add(script);
11
+ if (narrate) {
12
+ tell(`fake HTTP ${selection.label} will fault ${fault.kind} ${options.phase ?? "before"} reply ${options.repeat ? "until lifted" : "once"}`);
13
+ }
14
+ return {
15
+ lift: Effect.sync(() => {
16
+ if (scripts.delete(script)) {
17
+ tell(`fake HTTP ${selection.label} fault ${fault.kind} lifted`);
18
+ }
19
+ }),
20
+ };
21
+ },
22
+ take: (route, request, phase) => {
23
+ for (const script of scripts) {
24
+ if ((script.options.phase ?? "before") === phase && script.selection.matches(route, request)) {
25
+ if (!script.options.repeat) {
26
+ scripts.delete(script);
27
+ }
28
+ return script.fault;
29
+ }
30
+ }
31
+ return undefined;
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=fakeHttpScripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeHttpScripts.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpScripts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,MAAM,UAAU,eAAe,CAAC,IAA4B;IAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,EAInB,CAAC;IACL,OAAO;QACN,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE;QAC5B,IAAI,EAAE,CAAC,KAAa,EAAE,KAAoB,EAAE,OAAO,GAA0B,EAAE,EAAE,OAAO,GAAG,IAAI,EAAkB,EAAE;YAClH,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;YAC7C,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpB,IAAI,OAAO,EAAE,CAAC;gBACb,IAAI,CAAC,aAAa,SAAS,CAAC,KAAK,eAAe,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,IAAI,QAAQ,UAAU,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YAC9I,CAAC;YACD,OAAO;gBACN,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;oBACtB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;wBAC5B,IAAI,CAAC,aAAa,SAAS,CAAC,KAAK,UAAU,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC;oBACjE,CAAC;gBACF,CAAC,CAAC;aACF,CAAC;QACH,CAAC;QACD,IAAI,EAAE,CAAC,KAAa,EAAE,OAAwB,EAAE,KAAyB,EAAE,EAAE;YAC5E,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;oBAC9F,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;wBAC5B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBACxB,CAAC;oBACD,OAAO,MAAM,CAAC,KAAK,CAAC;gBACrB,CAAC;YACF,CAAC;YACD,OAAO,SAAS,CAAC;QAClB,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { FakeHttpRequest, FakeHttpScope } from "#fakeHttpService.ts";
2
+ export declare function fakeHttpSelection(route: string, scope?: FakeHttpScope): {
3
+ label: string;
4
+ matches: (name: string, request: FakeHttpRequest) => boolean;
5
+ };
6
+ //# sourceMappingURL=fakeHttpSelection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeHttpSelection.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpSelection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE1E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,aAAkB;;oBAKxD,MAAM,WAAW,eAAe;EAEjD"}
@@ -0,0 +1,9 @@
1
+ export function fakeHttpSelection(route, scope = {}) {
2
+ const params = Object.entries(scope.params ?? {}).sort(([left], [right]) => left.localeCompare(right));
3
+ const label = params.length > 0 ? `${route} matching ${JSON.stringify(Object.fromEntries(params))}` : route;
4
+ return {
5
+ label,
6
+ matches: (name, request) => name === route && params.every(([key, value]) => request.params[key] === value),
7
+ };
8
+ }
9
+ //# sourceMappingURL=fakeHttpSelection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fakeHttpSelection.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpSelection.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,KAAK,GAAkB,EAAE;IACzE,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IACvG,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,aAAa,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5G,OAAO;QACN,KAAK;QACL,OAAO,EAAE,CAAC,IAAY,EAAE,OAAwB,EAAE,EAAE,CAAC,IAAI,KAAK,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC;KACpI,CAAC;AACH,CAAC"}
@@ -1,8 +1,11 @@
1
1
  import type { ServerResponse } from "node:http";
2
- import type { FakeHttpReply } from "#fakeHttpService.ts";
2
+ import type { FakeHttpFault, FakeHttpReply } from "#fakeHttpService.ts";
3
3
  export declare function fakeHttpConnection(response: ServerResponse): {
4
4
  closed: Promise<undefined>;
5
5
  pause: (pending: Promise<void> | undefined) => Promise<void>;
6
6
  };
7
7
  export declare function fakeHttpSend(response: ServerResponse, reply: FakeHttpReply, pause?: () => Promise<void>): Promise<void>;
8
+ export declare function fakeHttpStrike(response: ServerResponse, fault: Exclude<FakeHttpFault, {
9
+ kind: "status";
10
+ }>, reply?: FakeHttpReply): Promise<void>;
8
11
  //# sourceMappingURL=fakeHttpSend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpSend.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpSend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc;;qBAIlC,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS;EAMjD;AAwBD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,iBAiB7G"}
1
+ {"version":3,"file":"fakeHttpSend.d.ts","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpSend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAExE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc;;qBAIlC,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS;EAMjD;AAwBD,wBAAsB,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,iBAiB7G;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,EAAE,KAAK,GAAE,aAAkB,iBAO1I"}
@@ -48,4 +48,12 @@ export async function fakeHttpSend(response, reply, pause) {
48
48
  response.end(body);
49
49
  }
50
50
  }
51
+ export async function fakeHttpStrike(response, fault, reply = {}) {
52
+ if (fault.kind === "reset") {
53
+ response.writeHead(reply.status ?? 200, { "content-type": "application/json", ...reply.headers });
54
+ response.flushHeaders();
55
+ await new Promise((resolve) => response.write("", () => resolve()));
56
+ }
57
+ response.destroy();
58
+ }
51
59
  //# sourceMappingURL=fakeHttpSend.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fakeHttpSend.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpSend.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fakeHttpSend.js","sourceRoot":"","sources":["../../../src/server/internal/fakeHttpSend.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.14.0",
3
+ "version": "0.15.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": {
@@ -22,7 +22,21 @@ export interface FakeHttpReply {
22
22
 
23
23
  export type FakeHttpFault = { readonly kind: "status"; readonly reply: FakeHttpReply } | { readonly kind: "drop" | "reset" };
24
24
 
25
+ export interface FakeHttpScope {
26
+ readonly params?: Readonly<Record<string, string>>;
27
+ }
28
+
29
+ export interface FakeHttpScriptOptions extends FakeHttpScope {
30
+ readonly phase?: "before" | "after";
31
+ readonly repeat?: boolean;
32
+ }
33
+
34
+ export interface FakeHttpScript {
35
+ readonly lift: Effect.Effect<void>;
36
+ }
37
+
25
38
  export type FakeHttpRoute = {
39
+ readonly guard?: (request: FakeHttpRequest) => boolean;
26
40
  readonly method?: string;
27
41
  readonly path: string;
28
42
  } & (
@@ -57,8 +71,10 @@ export const fakeHttpService = Effect.fnUntraced(function* (options: FakeHttpSer
57
71
  }
58
72
  }
59
73
  return {
60
- failOnce: (route: string, reply: FakeHttpReply) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply))),
61
- fault: (route: string, fault: FakeHttpFault) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.fault(route, fault))),
74
+ failOnce: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) =>
75
+ Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply, scope))),
76
+ fault: (route: string, fault: FakeHttpFault, scope?: FakeHttpScope) =>
77
+ Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.fault(route, fault, scope))),
62
78
  hold: (route: string) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.hold(route))),
63
79
  narrate: (sink: (line: string) => void) => {
64
80
  narrate = sink;
@@ -68,11 +84,14 @@ export const fakeHttpService = Effect.fnUntraced(function* (options: FakeHttpSer
68
84
  checkRoute(route);
69
85
  checkRoute(arrival);
70
86
  }).pipe(Effect.andThen(controls.releaseOnArrival(route, arrival))),
71
- replyOnce: (route: string, reply: FakeHttpReply) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply))),
87
+ replyOnce: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) =>
88
+ Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.queue(route, reply, scope))),
72
89
  get requests(): readonly FakeHttpRequest[] {
73
90
  return requests.slice();
74
91
  },
75
- stall: (route: string) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.stall(route))),
92
+ scriptFault: (route: string, fault: FakeHttpFault, scriptOptions?: FakeHttpScriptOptions) =>
93
+ Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.scriptFault(route, fault, scriptOptions))),
94
+ stall: (route: string, scope?: FakeHttpScope) => Effect.sync(() => checkRoute(route)).pipe(Effect.andThen(controls.stall(route, scope))),
76
95
  url,
77
96
  };
78
97
  });
@@ -0,0 +1,48 @@
1
+ import type { ServerResponse } from "node:http";
2
+ import type { FakeHttpReply, FakeHttpRequest, FakeHttpRoute } from "#fakeHttpService.ts";
3
+ import type { fakeHttpControls } from "#internal/fakeHttpControls.ts";
4
+ import { fakeHttpSend, fakeHttpStrike } from "#internal/fakeHttpSend.ts";
5
+
6
+ function pageReply(route: FakeHttpRoute, request: FakeHttpRequest): FakeHttpReply {
7
+ if (route.reply) {
8
+ return route.reply(request);
9
+ }
10
+ const page = Number(request.query.get("page") ?? "1");
11
+ if (!Number.isInteger(page) || page < 1) {
12
+ return { body: { error: "page must be a positive integer" }, status: 400 };
13
+ }
14
+ return { body: { items: route.pages[page - 1] ?? [], next: page < route.pages.length ? page + 1 : null } };
15
+ }
16
+
17
+ export async function fakeHttpAnswer(
18
+ name: string,
19
+ route: FakeHttpRoute,
20
+ request: FakeHttpRequest,
21
+ response: ServerResponse,
22
+ controls: ReturnType<typeof fakeHttpControls>,
23
+ tell: (line: string) => void,
24
+ ) {
25
+ const { method, path } = request;
26
+ controls.arrive(name, request, response);
27
+ if (!(await controls.pause(name, response))) {
28
+ return;
29
+ }
30
+ const before = controls.takeFault(name, request, "before");
31
+ if (before && before.kind !== "status") {
32
+ tell(`fake HTTP ${name} fault ${before.kind} before reply`);
33
+ await fakeHttpStrike(response, before);
34
+ return;
35
+ }
36
+ let reply = before?.reply ?? pageReply(route, request);
37
+ const after = before ? undefined : controls.takeFault(name, request, "after");
38
+ if (after) {
39
+ tell(`fake HTTP ${name} fault ${after.kind} after reply`);
40
+ if (after.kind !== "status") {
41
+ await fakeHttpStrike(response, after, reply);
42
+ return;
43
+ }
44
+ reply = after.reply;
45
+ }
46
+ tell(`fake HTTP answered ${method} ${path} with ${reply.status ?? 200}`);
47
+ await fakeHttpSend(response, reply, controls.stallBody(name, request));
48
+ }
@@ -1,18 +1,20 @@
1
1
  import type { ServerResponse } from "node:http";
2
2
  import { Effect } from "effect";
3
- import type { FakeHttpFault, FakeHttpReply } from "#fakeHttpService.ts";
3
+ import type { FakeHttpFault, FakeHttpReply, FakeHttpRequest, FakeHttpScope, FakeHttpScriptOptions } from "#fakeHttpService.ts";
4
4
  import { fakeHttpGates } from "#internal/fakeHttpGates.ts";
5
+ import { fakeHttpScripts } from "#internal/fakeHttpScripts.ts";
6
+ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
5
7
  import { fakeHttpConnection, fakeHttpSend } from "#internal/fakeHttpSend.ts";
6
8
 
7
9
  export function fakeHttpControls(tell: (line: string) => void) {
8
- const replies = new Map<string, FakeHttpReply[]>();
10
+ const scripts = fakeHttpScripts(tell);
9
11
  const holds = fakeHttpGates(tell, "hold");
10
12
  const stalls = fakeHttpGates(tell, "stall");
11
13
  const arrivals = new Map<string, Set<() => void>>();
12
- const active = new Map<ServerResponse, string>();
14
+ const active = new Map<ServerResponse, { route: string; request: FakeHttpRequest }>();
13
15
  return {
14
- arrive: (route: string, response: ServerResponse) => {
15
- active.set(response, route);
16
+ arrive: (route: string, request: FakeHttpRequest, response: ServerResponse) => {
17
+ active.set(response, { request, route });
16
18
  response.once("close", () => active.delete(response));
17
19
  for (const release of arrivals.get(route) ?? []) {
18
20
  release();
@@ -26,20 +28,23 @@ export function fakeHttpControls(tell: (line: string) => void) {
26
28
  holds.close();
27
29
  stalls.close();
28
30
  arrivals.clear();
29
- replies.clear();
31
+ scripts.clear();
30
32
  },
31
- fault: (route: string, fault: FakeHttpFault) =>
33
+ fault: (route: string, fault: FakeHttpFault, scope?: FakeHttpScope) =>
32
34
  Effect.promise(async () => {
35
+ const selection = fakeHttpSelection(route, scope);
33
36
  const response = [...active].find(
34
- ([candidate, name]) =>
35
- name === route && !(candidate.destroyed || candidate.writableEnded) && candidate.headersSent === (fault.kind === "reset"),
37
+ ([candidate, entry]) =>
38
+ selection.matches(entry.route, entry.request)
39
+ && !(candidate.destroyed || candidate.writableEnded)
40
+ && candidate.headersSent === (fault.kind === "reset"),
36
41
  )?.[0];
37
42
  if (!response) {
38
43
  throw new Error(
39
44
  `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.`,
40
45
  );
41
46
  }
42
- tell(`fake HTTP ${route} fault ${fault.kind}`);
47
+ tell(`fake HTTP ${selection.label} fault ${fault.kind}`);
43
48
  if (fault.kind === "status") {
44
49
  await fakeHttpSend(response, fault.reply);
45
50
  } else {
@@ -51,12 +56,10 @@ export function fakeHttpControls(tell: (line: string) => void) {
51
56
  await fakeHttpConnection(response).pause(holds.get(route)?.enter());
52
57
  return !(response.destroyed || response.writableEnded || response.headersSent);
53
58
  },
54
- queue: (route: string, reply: FakeHttpReply) =>
59
+ queue: (route: string, reply: FakeHttpReply, scope?: FakeHttpScope) =>
55
60
  Effect.sync(() => {
56
- const queue = replies.get(route) ?? [];
57
- queue.push(reply);
58
- replies.set(route, queue);
59
- tell(`fake HTTP ${route} will answer once with ${reply.status ?? 200}`);
61
+ scripts.make(route, { kind: "status", reply }, scope, false);
62
+ tell(`fake HTTP ${fakeHttpSelection(route, scope).label} will answer once with ${reply.status ?? 200}`);
60
63
  }),
61
64
  releaseOnArrival: (route: string, arrival: string) =>
62
65
  Effect.sync(() => {
@@ -69,11 +72,12 @@ export function fakeHttpControls(tell: (line: string) => void) {
69
72
  arrivals.set(arrival, releases);
70
73
  tell(`fake HTTP ${route} will release when ${arrival} arrives`);
71
74
  }),
72
- stall: (route: string) => Effect.sync(() => stalls.make(route)),
73
- stallBody: (route: string) => {
74
- const stall = stalls.get(route);
75
+ scriptFault: (route: string, fault: FakeHttpFault, options?: FakeHttpScriptOptions) => Effect.sync(() => scripts.make(route, fault, options)),
76
+ stall: (route: string, scope?: FakeHttpScope) => Effect.sync(() => stalls.make(route, scope)),
77
+ stallBody: (route: string, request: FakeHttpRequest) => {
78
+ const stall = stalls.get(route, request);
75
79
  return stall ? () => stall.enter() : undefined;
76
80
  },
77
- takeReply: (route: string) => replies.get(route)?.shift(),
81
+ takeFault: scripts.take,
78
82
  };
79
83
  }
@@ -1,38 +1,43 @@
1
1
  import { Deferred, Effect } from "effect";
2
- import type { FakeHttpHold } from "#fakeHttpService.ts";
2
+ import type { FakeHttpHold, FakeHttpRequest, FakeHttpScope } from "#fakeHttpService.ts";
3
+ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
3
4
 
4
5
  export function fakeHttpGates(tell: (line: string) => void, kind: "hold" | "stall") {
5
- const gates = new Map<string, { enter: () => Promise<void>; release: () => void }>();
6
+ const gates = new Map<string, { enter: () => Promise<void>; release: () => void; matches: ReturnType<typeof fakeHttpSelection>["matches"] }>();
6
7
  return {
7
8
  close: () => {
8
9
  for (const gate of gates.values()) {
9
10
  gate.release();
10
11
  }
11
12
  },
12
- get: (route: string) => gates.get(route),
13
- make: (route: string): FakeHttpHold => {
14
- if (gates.has(route)) {
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 => {
16
+ const selection = fakeHttpSelection(route, scope);
17
+ const label = selection.label;
18
+ if (gates.has(label)) {
15
19
  throw new Error(`Fake HTTP route "${route}" already has a ${kind}.\nhelp: Release its current ${kind} before scripting another.`);
16
20
  }
17
21
  const waiting = Deferred.makeUnsafe<void>();
18
22
  const released = Promise.withResolvers<void>();
19
23
  const gate = {
20
24
  enter: () => {
21
- tell(kind === "hold" ? `fake HTTP ${route} has a waiting request` : `fake HTTP ${route} body stalled`);
25
+ tell(kind === "hold" ? `fake HTTP ${label} has a waiting request` : `fake HTTP ${label} body stalled`);
22
26
  Deferred.doneUnsafe(waiting, Effect.void);
23
27
  return released.promise;
24
28
  },
29
+ matches: selection.matches,
25
30
  release: () => {
26
- if (gates.get(route) !== gate) {
31
+ if (gates.get(label) !== gate) {
27
32
  return;
28
33
  }
29
- gates.delete(route);
34
+ gates.delete(label);
30
35
  released.resolve();
31
- tell(kind === "hold" ? `fake HTTP ${route} released` : `fake HTTP ${route} body released`);
36
+ tell(kind === "hold" ? `fake HTTP ${label} released` : `fake HTTP ${label} body released`);
32
37
  },
33
38
  };
34
- gates.set(route, gate);
35
- tell(kind === "hold" ? `fake HTTP ${route} held` : `fake HTTP ${route} body will stall`);
39
+ gates.set(label, gate);
40
+ tell(kind === "hold" ? `fake HTTP ${label} held` : `fake HTTP ${label} body will stall`);
36
41
  return { release: Effect.sync(gate.release), waiting: Deferred.await(waiting) };
37
42
  },
38
43
  };
@@ -1,5 +1,6 @@
1
1
  import type { IncomingMessage, ServerResponse } from "node:http";
2
- import type { FakeHttpReply, FakeHttpRequest, FakeHttpRoute, FakeHttpServiceOptions } from "#fakeHttpService.ts";
2
+ import type { FakeHttpRequest, FakeHttpRoute, FakeHttpServiceOptions } from "#fakeHttpService.ts";
3
+ import { fakeHttpAnswer } from "#internal/fakeHttpAnswer.ts";
3
4
  import type { fakeHttpControls } from "#internal/fakeHttpControls.ts";
4
5
  import { fakeHttpSend } from "#internal/fakeHttpSend.ts";
5
6
 
@@ -35,17 +36,6 @@ function decodeParams(params: Record<string, string>) {
35
36
  }
36
37
  }
37
38
 
38
- function pageReply(route: FakeHttpRoute, request: FakeHttpRequest): FakeHttpReply {
39
- if (route.reply) {
40
- return route.reply(request);
41
- }
42
- const page = Number(request.query.get("page") ?? "1");
43
- if (!Number.isInteger(page) || page < 1) {
44
- return { body: { error: "page must be a positive integer" }, status: 400 };
45
- }
46
- return { body: { items: route.pages[page - 1] ?? [], next: page < route.pages.length ? page + 1 : null } };
47
- }
48
-
49
39
  export async function fakeHttpReceive(
50
40
  incoming: IncomingMessage,
51
41
  response: ServerResponse,
@@ -82,7 +72,7 @@ export async function fakeHttpReceive(
82
72
  await fakeHttpSend(response, { status: 400 });
83
73
  return;
84
74
  }
85
- if (options.guard && !options.guard(request)) {
75
+ if ((options.guard && !options.guard(request)) || (found?.route.guard && !found.route.guard(request))) {
86
76
  tell(`fake HTTP rejected ${method} ${path} with 401`);
87
77
  await fakeHttpSend(response, { status: 401 });
88
78
  return;
@@ -92,13 +82,7 @@ export async function fakeHttpReceive(
92
82
  await fakeHttpSend(response, { status: 404 });
93
83
  return;
94
84
  }
95
- controls.arrive(found.name, response);
96
- if (!(await controls.pause(found.name, response))) {
97
- return;
98
- }
99
- const reply = controls.takeReply(found.name) ?? pageReply(found.route, request);
100
- tell(`fake HTTP answered ${method} ${path} with ${reply.status ?? 200}`);
101
- await fakeHttpSend(response, reply, controls.stallBody(found.name));
85
+ await fakeHttpAnswer(found.name, found.route, request, response, controls, tell);
102
86
  } catch (error) {
103
87
  tell(`fake HTTP could not answer: ${String(error)}`);
104
88
  if (response.headersSent) {
@@ -0,0 +1,40 @@
1
+ import { Effect } from "effect";
2
+ import type { FakeHttpFault, FakeHttpRequest, FakeHttpScript, FakeHttpScriptOptions } from "#fakeHttpService.ts";
3
+ import { fakeHttpSelection } from "#internal/fakeHttpSelection.ts";
4
+
5
+ export function fakeHttpScripts(tell: (line: string) => void) {
6
+ const scripts = new Set<{
7
+ selection: ReturnType<typeof fakeHttpSelection>;
8
+ fault: FakeHttpFault;
9
+ options: FakeHttpScriptOptions;
10
+ }>();
11
+ return {
12
+ clear: () => scripts.clear(),
13
+ make: (route: string, fault: FakeHttpFault, options: FakeHttpScriptOptions = {}, narrate = true): FakeHttpScript => {
14
+ const selection = fakeHttpSelection(route, options);
15
+ const script = { fault, options, selection };
16
+ scripts.add(script);
17
+ if (narrate) {
18
+ tell(`fake HTTP ${selection.label} will fault ${fault.kind} ${options.phase ?? "before"} reply ${options.repeat ? "until lifted" : "once"}`);
19
+ }
20
+ return {
21
+ lift: Effect.sync(() => {
22
+ if (scripts.delete(script)) {
23
+ tell(`fake HTTP ${selection.label} fault ${fault.kind} lifted`);
24
+ }
25
+ }),
26
+ };
27
+ },
28
+ take: (route: string, request: FakeHttpRequest, phase: "before" | "after") => {
29
+ for (const script of scripts) {
30
+ if ((script.options.phase ?? "before") === phase && script.selection.matches(route, request)) {
31
+ if (!script.options.repeat) {
32
+ scripts.delete(script);
33
+ }
34
+ return script.fault;
35
+ }
36
+ }
37
+ return undefined;
38
+ },
39
+ };
40
+ }
@@ -0,0 +1,10 @@
1
+ import type { FakeHttpRequest, FakeHttpScope } from "#fakeHttpService.ts";
2
+
3
+ export function fakeHttpSelection(route: string, scope: FakeHttpScope = {}) {
4
+ const params = Object.entries(scope.params ?? {}).sort(([left], [right]) => left.localeCompare(right));
5
+ const label = params.length > 0 ? `${route} matching ${JSON.stringify(Object.fromEntries(params))}` : route;
6
+ return {
7
+ label,
8
+ matches: (name: string, request: FakeHttpRequest) => name === route && params.every(([key, value]) => request.params[key] === value),
9
+ };
10
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ServerResponse } from "node:http";
2
- import type { FakeHttpReply } from "#fakeHttpService.ts";
2
+ import type { FakeHttpFault, FakeHttpReply } from "#fakeHttpService.ts";
3
3
 
4
4
  export function fakeHttpConnection(response: ServerResponse) {
5
5
  const closed = new Promise<undefined>((resolve) => response.once("close", () => resolve(undefined)));
@@ -53,3 +53,12 @@ export async function fakeHttpSend(response: ServerResponse, reply: FakeHttpRepl
53
53
  response.end(body);
54
54
  }
55
55
  }
56
+
57
+ export async function fakeHttpStrike(response: ServerResponse, fault: Exclude<FakeHttpFault, { kind: "status" }>, reply: FakeHttpReply = {}) {
58
+ if (fault.kind === "reset") {
59
+ response.writeHead(reply.status ?? 200, { "content-type": "application/json", ...reply.headers });
60
+ response.flushHeaders();
61
+ await new Promise<void>((resolve) => response.write("", () => resolve()));
62
+ }
63
+ response.destroy();
64
+ }