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