@shivaedev/test-story 0.14.0 → 0.16.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 (47) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +25 -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/dist/server/screenDriver.d.ts +3 -0
  35. package/dist/server/screenDriver.d.ts.map +1 -1
  36. package/dist/server/screenDriver.js +14 -1
  37. package/dist/server/screenDriver.js.map +1 -1
  38. package/package.json +2 -2
  39. package/src/server/fakeHttpService.ts +23 -4
  40. package/src/server/internal/fakeHttpAnswer.ts +48 -0
  41. package/src/server/internal/fakeHttpControls.ts +23 -19
  42. package/src/server/internal/fakeHttpGates.ts +16 -11
  43. package/src/server/internal/fakeHttpReceive.ts +4 -20
  44. package/src/server/internal/fakeHttpScripts.ts +40 -0
  45. package/src/server/internal/fakeHttpSelection.ts +10 -0
  46. package/src/server/internal/fakeHttpSend.ts +10 -1
  47. package/src/server/screenDriver.ts +19 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0 - 2026-10-19
4
+
5
+ ### Added
6
+
7
+ - `screenDriver.find(target, { timeout }?)` waits for a unique element by selector or role and accessible name, narrates the lookup, and returns the element with the actions' candidate diagnostics when no unique target appears.
8
+
9
+ ## 0.15.0 - 2026-10-19
10
+
11
+ ### Added
12
+
13
+ - Scope `fakeHttpService` stalls, immediate faults and one-shot replies by decoded path parameters with `FakeHttpScope`.
14
+ - 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.
15
+ - Declare per-route guards alongside the service guard; rejected requests receive 401 before holds, arrival scripts or faults are consumed.
16
+
3
17
  ## 0.14.0 - 2026-10-19
4
18
 
5
19
  ### 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`.
@@ -583,6 +604,8 @@ verbs: ({ view, requests }, story) => screenDriver({
583
604
 
584
605
  `click`, `enter`, `submit`, `text` and `value` also accept a `ScreenTarget` object such as `{ role: "button", name: "Save" }` or `{ role: "textbox", name: /email/i }`. The role and computed accessible name must match exactly when strings are used; regular expressions match the name. Like Testing Library's role queries, these targets exclude hidden elements, including `aria-hidden` ancestors. Missing or ambiguous targets fail with the requested role, name and the visible candidates on screen. Narration names the role and name instead of a selector.
585
606
 
607
+ `find(target, { timeout }?)` returns an Effect that waits for exactly one matching rendered element and returns that `HTMLElement`. It accepts the same selectors and role/name targets as the actions, narrates the lookup, and uses the driver's live-time `timeoutMs` limit unless `timeout` overrides it in milliseconds. If no unique element appears before the limit, it fails with the same target diagnostics as the actions, including visible candidates for role/name targets.
608
+
586
609
  `accessible(selector)` returns `{ name, role }` computed by `dom-accessibility-api`, including native roles and label references that take precedence over `aria-label`. It narrates the readout and uses the same single rendered target checks as other readouts. Assert on this result when checking a control's accessible name; reading its raw attributes does not compute that name. This DOM computation does not inspect a browser's accessibility tree or prove speech from a screen reader.
587
610
 
588
611
  `accessible(element)` uses the same computation on an `Element` the kit already holds and tells no story line.
@@ -605,7 +628,7 @@ Import each module with the prefix `@shivaedev/test-story/`; there is no root en
605
628
  | `domNavigate.ts` | `domNavigate(url?)` |
606
629
  | `genreTags.ts` | `genreTags(...kitNames)`, `GenreTag` |
607
630
  | `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` |
631
+ | `fakeHttpService.ts` | `fakeHttpService(options)`, `FakeHttpRoute`, `FakeHttpRequest`, `FakeHttpReply`, `FakeHttpFault`, `FakeHttpHold`, `FakeHttpScope`, `FakeHttpScriptOptions`, `FakeHttpScript`, `FakeHttpServiceOptions` |
609
632
  | `forwardingProxy.ts` | `forwardingProxy(upstream, options?)`, `ForwardingProxyOptions` |
610
633
  | `otlpReceiver.ts` | `otlpReceiver(options?)`, `OtlpSpan`, `OtlpExport`, `OtlpReceiverOptions` |
611
634
 
@@ -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":"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"}
@@ -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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"}
@@ -18,6 +18,9 @@ export declare function screenDriver({ root, pending, tell, act, timeoutMs }: Sc
18
18
  };
19
19
  click: (selector: ScreenTarget) => Effect.Effect<void, never, never>;
20
20
  enter: (selector: ScreenTarget, value: string) => Effect.Effect<void, never, never>;
21
+ find: (target: ScreenTarget, options?: {
22
+ readonly timeout?: number;
23
+ }) => Effect.Effect<HTMLElement, import("effect/Cause").UnknownError, never>;
21
24
  settle: (options?: {
22
25
  readonly timeoutMs?: number;
23
26
  }) => Effect.Effect<void, never, never>;
@@ -1 +1 @@
1
- {"version":3,"file":"screenDriver.d.ts","sourceRoot":"","sources":["../../src/server/screenDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAE9F,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3D,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAA0B,EAAE,SAAgB,EAAE,EAAE,mBAAmB;2BAgC9F,MAAM,GAAG,OAAO;;;;sBASrB,YAAY;sBAOZ,YAAY,SAAS,MAAM;uBAvCrB;QAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;wBAyCpC,YAAY;sBACb,YAAY;sBAKZ,YAAY;EAM/B"}
1
+ {"version":3,"file":"screenDriver.d.ts","sourceRoot":"","sources":["../../src/server/screenDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAY,MAAM,QAAQ,CAAC;AAK1C,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AAE9F,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,CAAC,CAAC;IACjD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAE3D,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAA0B,EAAE,SAAgB,EAAE,EAAE,mBAAmB;2BAiD9F,MAAM,GAAG,OAAO;;;;sBASrB,YAAY;sBAOZ,YAAY,SAAS,MAAM;mBAlCxB,YAAY,YAAW;QAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;uBAtBjD;QAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;wBA2DpC,YAAY;sBACb,YAAY;sBAKZ,YAAY;EAM/B"}
@@ -1,4 +1,4 @@
1
- import { Effect } from "effect";
1
+ import { Effect, Schedule } from "effect";
2
2
  import { withLive } from "@shivaedev/effect-test/withLive.ts";
3
3
  import { accessibility, targetName } from "#internal/screenAccessibility.ts";
4
4
  import { screenElements } from "#internal/screenElements.ts";
@@ -29,6 +29,18 @@ export function screenDriver({ root, pending, tell, act = (action) => action(),
29
29
  }
30
30
  return withLive(wait.pipe(Effect.timeoutOrElse({ duration: limit, orElse: timedOut })));
31
31
  }
32
+ function find(target, options = {}) {
33
+ return withLive(Effect.gen(function* () {
34
+ tell(`find ${targetName(target)}`);
35
+ return yield* Effect.tryPromise(async () => {
36
+ await act(async () => undefined);
37
+ return elements.element(target);
38
+ }).pipe(Effect.retry(Schedule.spaced(10)), Effect.timeoutOrElse({
39
+ duration: options.timeout ?? timeoutMs,
40
+ orElse: () => Effect.sync(() => elements.element(target)),
41
+ }));
42
+ }));
43
+ }
32
44
  return {
33
45
  accessible: (selector) => {
34
46
  if (typeof selector !== "string") {
@@ -46,6 +58,7 @@ export function screenDriver({ root, pending, tell, act = (action) => action(),
46
58
  element.click();
47
59
  }),
48
60
  enter: (selector, value) => perform(`enter a value in ${targetName(selector)}`, () => elements.enter(selector, value)),
61
+ find,
49
62
  settle,
50
63
  submit: (selector = "form") => perform(`submit ${targetName(selector)}`, () => elements.form(selector).requestSubmit()),
51
64
  text: (selector) => {
@@ -1 +1 @@
1
- {"version":3,"file":"screenDriver.js","sourceRoot":"","sources":["../../src/server/screenDriver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"screenDriver.js","sourceRoot":"","sources":["../../src/server/screenDriver.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.16.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": {
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@effect/vitest": "4.0.4",
49
- "@shivaedev/quality": "0.29.0",
49
+ "@shivaedev/quality": "0.29.1",
50
50
  "@types/node": "24.19.2",
51
51
  "effect": "4.0.4",
52
52
  "happy-dom": "20.14.6",
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import { Effect } from "effect";
1
+ import { Effect, Schedule } from "effect";
2
2
  import { withLive } from "@shivaedev/effect-test/withLive.ts";
3
3
  import { accessibility, targetName } from "#internal/screenAccessibility.ts";
4
4
  import { screenElements } from "#internal/screenElements.ts";
@@ -46,6 +46,23 @@ export function screenDriver({ root, pending, tell, act = (action) => action(),
46
46
  }
47
47
  return withLive(wait.pipe(Effect.timeoutOrElse({ duration: limit, orElse: timedOut })));
48
48
  }
49
+ function find(target: ScreenTarget, options: { readonly timeout?: number } = {}) {
50
+ return withLive(
51
+ Effect.gen(function* () {
52
+ tell(`find ${targetName(target)}`);
53
+ return yield* Effect.tryPromise(async () => {
54
+ await act(async () => undefined);
55
+ return elements.element(target);
56
+ }).pipe(
57
+ Effect.retry(Schedule.spaced(10)),
58
+ Effect.timeoutOrElse({
59
+ duration: options.timeout ?? timeoutMs,
60
+ orElse: () => Effect.sync(() => elements.element(target)),
61
+ }),
62
+ );
63
+ }),
64
+ );
65
+ }
49
66
  return {
50
67
  accessible: (selector: string | Element) => {
51
68
  if (typeof selector !== "string") {
@@ -64,6 +81,7 @@ export function screenDriver({ root, pending, tell, act = (action) => action(),
64
81
  element.click();
65
82
  }),
66
83
  enter: (selector: ScreenTarget, value: string) => perform(`enter a value in ${targetName(selector)}`, () => elements.enter(selector, value)),
84
+ find,
67
85
  settle,
68
86
  submit: (selector: ScreenTarget = "form") => perform(`submit ${targetName(selector)}`, () => elements.form(selector).requestSubmit()),
69
87
  text: (selector?: ScreenTarget) => {