@hellaweb3/wiremock-testcontainers-nodejs 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,405 +1,162 @@
1
- <!--suppress HtmlDeprecatedAttribute -->
2
- <div align="center">
1
+ # WireMock Testcontainers for Node.js
3
2
 
4
- <h1>WireMock Testcontainers Node.js Typescript</h1>
3
+ [![Build](https://github.com/hella-web3/wiremock-testcontainers-nodejs/actions/workflows/main.yml/badge.svg)](https://github.com/hella-web3/wiremock-testcontainers-nodejs/actions/workflows/main.yml)
4
+ [![npm](https://img.shields.io/npm/v/%40hellaweb3%2Fwiremock-testcontainers-nodejs)](https://www.npmjs.com/package/@hellaweb3/wiremock-testcontainers-nodejs)
5
+ [![downloads](https://img.shields.io/npm/dm/%40hellaweb3%2Fwiremock-testcontainers-nodejs)](https://www.npmjs.com/package/@hellaweb3/wiremock-testcontainers-nodejs)
5
6
 
6
- <p>A library for easily using WireMock with Testcontainers in Node.js applications.</p>
7
+ Run [WireMock](https://wiremock.org/) in [Testcontainers](https://node.testcontainers.org/) with a
8
+ fluent TypeScript API modelled on the official
9
+ [Java module](https://github.com/wiremock/wiremock-testcontainers-java): load stubs from
10
+ directories, files, npm packages or inline JSON; reset state between tests without restarting;
11
+ verify calls through WireMock's request journal.
7
12
 
8
- <a href="https://github.com/hella-web3/wiremock-testcontainers-nodejs/actions/workflows/main.yml ">
9
- <img alt="Build Status" src="https://github.com/hella-web3/wiremock-testcontainers-nodejs/actions/workflows/main.yml/badge.svg" />
10
- </a>
11
-
12
- <a href="https://www.npmjs.com/package/@hellaweb3/wiremock-testcontainers-nodejs">
13
- <img alt="npm version" src="https://img.shields.io/npm/v/%40hellaweb3%2Fwiremock-testcontainers-nodejs" />
14
- </a>
15
-
16
- <a href="https://www.npmjs.com/package/@hellaweb3/wiremock-testcontainers-nodejs">
17
- <img alt="npm downloads" src="https://img.shields.io/npm/dm/%40hellaweb3%2Fwiremock-testcontainers-nodejs" />
18
- </a>
19
-
20
- </div>
21
-
22
- ## Description
23
-
24
- `wiremock-testcontainers-nodejs` is a Typescript nodejs library that spins up
25
- a [WireMock](https://wiremock.org/)
26
- instance with [Testcontainers](https://node.testcontainers.org/). It provides a
27
- fluent API to configure and start WireMock
28
- containers from recorded WireMock directories, individual named mappings
29
- (local files, npm packages, module-relative files or inline JSON) and static
30
- files – an API modelled on the official
31
- [Java module](https://github.com/wiremock/wiremock-testcontainers-java).
32
-
33
- ## Installation
13
+ ## Install
34
14
 
35
15
  ```bash
36
- pnpm add -D @hellaweb3/wiremock-testcontainers-nodejs testcontainers
16
+ npm i -D @hellaweb3/wiremock-testcontainers-nodejs testcontainers
37
17
  ```
38
18
 
39
- Requires Node.js >= 22.22 and a reachable Docker daemon (anything
40
- [Testcontainers](https://node.testcontainers.org/supported-container-runtimes/)
41
- supports).
42
-
43
- **What you get:** a ~17 kB package – dual ESM (`dist/index.js`) and CJS
44
- (`dist/index.cjs`) builds with matching declarations, `sideEffects: false`
45
- for tree-shaking, `testcontainers` as the single runtime dependency (never
46
- bundled), and the TypeScript source for go-to-definition.
47
-
48
- **pnpm users:** pnpm >= 10 may report `Ignored build scripts: ssh2, protobufjs,
49
- cpu-features`. These are transitive dependencies of `testcontainers`; the
50
- scripts only build an *optional* native crypto binding for `ssh2`, which falls
51
- back to pure JavaScript. It is safe to leave them ignored.
52
-
53
- ## Usage
54
-
55
- ### Basic Example
56
-
57
- 1. Record or hand-write your stubs in the standard WireMock layout, e.g.
58
- `test/__mocks__/wiremock/mappings/*.json` and
59
- `test/__mocks__/wiremock/__files/*.json`.
60
- 2. Start a `WiremockContainer` once per suite, point it at that directory and
61
- use `rpcUrl` as the base URL of the API under test.
62
- 3. Stop the container when the suite finishes.
63
-
64
- ```typescript
65
- import { afterAll, beforeAll, describe, expect, it } from "vitest"; // or jest
66
- import {
67
- StartedWiremockContainer,
68
- WiremockContainer,
69
- } from "@hellaweb3/wiremock-testcontainers-nodejs";
70
-
71
- describe("Cards API", () => {
72
- let container: StartedWiremockContainer;
73
-
74
- beforeAll(async () => {
75
- container = await new WiremockContainer() // wiremock/wiremock:latest
76
- .withMappings("./test/__mocks__/wiremock") // mappings/ + __files/
77
- .start(); // waits for port 8080
78
- }, 60_000); // image pull can be slow
79
-
80
- afterAll(async () => {
81
- await container.stop();
82
- });
83
-
84
- it("serves the recorded GET /cards stub", async () => {
85
- const response = await fetch(`${container.rpcUrl}/cards`);
86
- expect(response.status).toBe(200);
87
-
88
- const body = await response.json();
89
- expect(body.items).toHaveLength(6);
90
- });
91
-
92
- it("exposes the WireMock admin API", async () => {
93
- const response = await fetch(`${container.rpcUrl}/__admin/mappings`);
94
- const { meta } = await response.json();
95
- expect(meta.total).toBe(2);
96
- });
97
- });
98
- ```
19
+ Node.js >= 22.22 and a [container runtime](https://node.testcontainers.org/supported-container-runtimes/).
20
+ ESM and CJS, fully typed, `testcontainers` is the only dependency. pnpm >= 10 may report
21
+ `Ignored build scripts: ssh2, …` – optional native bindings of `testcontainers`, safe to ignore.
99
22
 
100
- `WiremockContainer` extends Testcontainers' `GenericContainer`, so every
101
- builder method is available – for example to pin an image, name the container
102
- or pass WireMock CLI flags:
23
+ ## Quick start
103
24
 
104
25
  ```typescript
105
- const container = await new WiremockContainer("wiremock/wiremock:3.13.0")
106
- .withName("wiremock-cards-api")
107
- .withCommand(["--verbose", "--global-response-templating"])
108
- .withMappings("./test/__mocks__/wiremock")
109
- .start();
110
- ```
111
-
112
- ### With Mappings and Files
113
-
114
- WireMock uses a specific directory structure for mappings and static files. This
115
- library makes it easy to mount these into the container.
116
-
117
- **Project structure:**
118
-
119
- ```
120
- test/
121
- └── __mocks__/
122
- └── wiremock/
123
- ├── mappings/
124
- │ └── my-mapping.json
125
- └── __files/
126
- └── some-data.json
127
- ```
128
-
129
- ```typescript
130
- import { WiremockContainer } from "@hellaweb3/wiremock-testcontainers-nodejs";
131
-
132
- const container = await new WiremockContainer()
133
- .withMappings("./test/__mocks__/wiremock")
134
- .start();
135
- ```
136
-
137
- ### Individual mappings and files
138
-
139
- `withMapping(name, source)` registers one stub at `mappings/<name>.json` and
140
- `withFiles(...files)` registers response bodies in `__files/` – no recorded
141
- directory required. A mapping file holds one WireMock stub, or several under
142
- `"mappings"`:
143
-
144
- ```json
145
- { "request": { "method": "GET", "url": "/health" }, "response": { "status": 200, "jsonBody": { "status": "UP" } } }
146
- ```
26
+ import { afterAll, afterEach, beforeAll, expect, it } from "vitest"; // or Jest
27
+ import { type StartedWiremockContainer, WiremockContainer } from "@hellaweb3/wiremock-testcontainers-nodejs";
147
28
 
148
- #### Mapping sources
29
+ let wiremock: StartedWiremockContainer;
149
30
 
150
- **Local file** – a path relative to the working directory, or absolute:
31
+ beforeAll(async () => {
32
+ wiremock = await new WiremockContainer()
33
+ .withMappings("./test/__mocks__/wiremock") // mappings/ + __files/
34
+ .start();
35
+ await wiremock.waitUntilReady();
36
+ }, 60_000); // the first image pull can be slow
151
37
 
152
- ```typescript
153
- new WiremockContainer().withMapping("health", "./test/fixtures/health.json"); // → mappings/health.json
154
- ```
38
+ afterEach(() => wiremock.reset()); // pristine stubs, empty journal – no restart
39
+ afterAll(() => wiremock.stop());
155
40
 
156
- **`file:` URL** – e.g. relative to the current module:
41
+ it("serves GET /cards", async () => {
42
+ const response = await fetch(wiremock.getUrl("/cards"));
157
43
 
158
- ```typescript
159
- new WiremockContainer().withMapping("health", new URL("./fixtures/health.json", import.meta.url));
44
+ expect(response.status).toBe(200);
45
+ expect(await wiremock.countRequests({ method: "GET", url: "/cards" })).toBe(1);
46
+ });
160
47
  ```
161
48
 
162
- **`{ file }`** – a path or URL in object form:
49
+ ## Registering stubs
163
50
 
164
51
  ```typescript
165
- new WiremockContainer().withMapping("health", { file: "./test/fixtures/health.json" });
166
- ```
167
-
168
- **`{ package, file }`** – a file inside an installed npm package, e.g. shared
169
- fixtures. Resolved like `require()` from the working directory; the file does
170
- not need to be exported and may not leave the package:
52
+ new WiremockContainer("wiremock/wiremock:3.13.0") // default: wiremock/wiremock
53
+ .withMappings("./test/__mocks__/wiremock") // a recorded directory
54
+ .withMapping("health", "./fixtures/health.json")
55
+ .withMapping("ping", { json: { request: { url: "/ping" }, response: { status: 200, body: "pong" } } })
56
+ .withFiles("./fixtures/body.json", { file: "./logo.png", name: "img/logo.png" })
57
+ .withCommand(["--global-response-templating"]); // any Testcontainers builder method
58
+ ```
59
+
60
+ - **`withMappings(dir)`** copies `dir/mappings/` (required) and `dir/__files/` (optional).
61
+ - **`withMapping(name, source)`** registers one stub at `mappings/<name>.json`; `name` may
62
+ contain `/`. The file may hold one stub or `{ "mappings": [...] }`.
63
+ - **`withFiles(...sources)`** registers response bodies at `__files/<name>`, the base name unless
64
+ `name` is given. All or nothing.
65
+
66
+ | Source | Example |
67
+ |------------------------------------|----------------------------------------------------------------|
68
+ | Path (relative to cwd) or `file:` URL | `"./fixtures/a.json"`, `new URL("./a.json", import.meta.url)` |
69
+ | `{ file }` | `{ file: "./fixtures/a.json" }` |
70
+ | `{ package, file }` – any file of an installed package | `{ package: "@acme/fixtures", file: "wiremock/a.json" }` |
71
+ | `{ module, file }` – next to a module | `{ module: import.meta.url, file: "./a.json" }` (CJS: `__dirname`) |
72
+ | `{ json }` – mappings only | an object (serialised) or a string (copied verbatim) |
73
+
74
+ Registrations are validated before any image is pulled: sources must exist, mappings must be
75
+ WireMock stubs, and nothing may overwrite another registration or reuse a stub `id`.
76
+
77
+ ## Test utilities
78
+
79
+ `start()` resolves to a `StartedWiremockContainer`:
80
+
81
+ | Member | Purpose |
82
+ |-------------------------------------------|-------------------------------------------------------------------------|
83
+ | `rpcUrl` | Base URL, e.g. `http://localhost:32768` |
84
+ | `getUrl(path?)` | `getUrl("cards?page=1")` → `<rpcUrl>/cards?page=1` |
85
+ | `getAdminUrl()` / `getMappingUrl()` | `<rpcUrl>/__admin` / `<rpcUrl>/__admin/mappings` (runtime stubs) |
86
+ | `isRunning()` | `true` if WireMock is healthy; never throws |
87
+ | `waitUntilReady({ timeout?, interval? })` | Polls until healthy (defaults: 30 s, every 250 ms) |
88
+ | `reset()` | Restore registered stubs and scenarios, clear the journal – no restart |
89
+ | `resetRequests()` | Clear the journal only |
90
+ | `countRequests(criteria)` | Number of matching requests since the last reset |
91
+ | `findRequests(criteria)` | The matching `RecordedRequest`s (url, method, headers, body, …) |
92
+ | `stop()`, `await using` | Stop the container |
93
+
94
+ `criteria` (`WiremockRequestCriteria`) uses WireMock's [request matching](https://wiremock.org/docs/request-matching/):
171
95
 
172
96
  ```typescript
173
- new WiremockContainer().withMapping("cards/list", { package: "@acme/api-fixtures", file: "wiremock/cards.json" }); // → mappings/cards/list.json
97
+ const [request] = await wiremock.findRequests({
98
+ method: "POST",
99
+ urlPath: "/cards",
100
+ headers: { "Content-Type": { equalTo: "application/json" } },
101
+ });
102
+ expect(JSON.parse(request.body ?? "")).toMatchObject({ cardType: "virtual" });
174
103
  ```
175
104
 
176
- **`{ module, file }`** – a file relative to a module, like a Java classpath
177
- resource:
105
+ Also exported: `WaitUntilReadyOptions`, and the source types `WiremockMappingSource`,
106
+ `WiremockStaticFile`, `WiremockFileSource`, `WiremockPath` and `WiremockStubMapping`.
178
107
 
179
- ```typescript
180
- new WiremockContainer().withMapping("balance", { module: import.meta.url, file: "./__mocks__/balance.json" }); // ESM
181
- new WiremockContainer().withMapping("balance", { module: __dirname, file: "./__mocks__/balance.json" }); // CJS (or __filename)
182
- ```
108
+ **Tips:** start one container per suite and `reset()` between tests, which is far faster than
109
+ restarting; raise the `beforeAll` timeout for the first image pull; assert with
110
+ `countRequests()`/`findRequests()` – they verify what actually went over the wire.
183
111
 
184
- **`{ json }` (object)** – an inline stub, serialised as-is:
112
+ ## Without Docker (local development only)
185
113
 
186
- ```typescript
187
- new WiremockContainer().withMapping("ping", {
188
- json: { request: { method: "GET", url: "/ping" }, response: { status: 200, body: "pong" } },
189
- });
190
- ```
114
+ No container runtime? With Java 11+, `@hellaweb3/wiremock-testcontainers-nodejs/no-docker`
115
+ serves every `WiremockContainer` from a local WireMock JVM. Tests stay unchanged: stubs, files
116
+ and `withCommand()` flags apply as in the container.
191
117
 
192
- **`{ json }` (string)** – inline JSON, copied verbatim:
118
+ > [!WARNING]
119
+ > Never the default, never for CI/CD: it refuses to run when CI is detected (`CI`,
120
+ > `GITHUB_ACTIONS`, …) and warns on every start. Results can differ from the Docker image; run
121
+ > with Docker before merging.
193
122
 
194
- ```typescript
195
- new WiremockContainer().withMapping("ping", { json: '{ "request": { "url": "/ping" }, "response": { "status": 200 } }' });
123
+ ```bash
124
+ npm i -D wiremock # bundles the WireMock JAR (optional peer dependency)
196
125
  ```
197
126
 
198
- **Several stubs in one mapping** – any source may hold `{ "mappings": [...] }`:
199
-
200
127
  ```typescript
201
- new WiremockContainer().withMapping("greetings", {
202
- json: {
203
- mappings: [
204
- { request: { url: "/hello" }, response: { status: 200, body: "Hello" } },
205
- { request: { url: "/bye" }, response: { status: 200, body: "Bye" } },
206
- ],
207
- },
208
- });
209
- ```
210
-
211
- #### Static files
128
+ // test/no-docker.setup.ts
129
+ import { installNoDockerRuntime } from "@hellaweb3/wiremock-testcontainers-nodejs/no-docker";
130
+ installNoDockerRuntime();
212
131
 
213
- `withFiles()` takes the same file sources (not `{ json }`). A file keeps its
214
- base name unless an object source sets `name`; mappings reference it as
215
- `bodyFileName`:
216
-
217
- ```typescript
218
- new WiremockContainer().withFiles(
219
- "./test/fixtures/body-cards.json", // → __files/body-cards.json
220
- { file: "./test/fixtures/logo.png", name: "images/logo.png" }, // → __files/images/logo.png
221
- { package: "@acme/api-fixtures", file: "wiremock/__files/user.json" }, // → __files/user.json
222
- );
132
+ // vitest.config.ts – opt-in only
133
+ setupFiles: process.env.WIREMOCK_NO_DOCKER === "1" ? ["./test/no-docker.setup.ts"] : [],
223
134
  ```
224
135
 
225
- #### Combining them
226
-
227
- All three methods chain with each other and with every Testcontainers builder:
136
+ Set `WIREMOCK_NO_DOCKER=1` from a `--no-docker` script flag – see
137
+ [`examples/scripts/test-e2e.mjs`](./examples/scripts/test-e2e.mjs), which supports
138
+ `npm test --no-docker`.
228
139
 
229
- ```typescript
230
- const container = await new WiremockContainer()
231
- .withMappings("./test/__mocks__/wiremock")
232
- .withMapping("health", "./test/fixtures/health.json")
233
- .withMapping("cards/list", { package: "@acme/api-fixtures", file: "wiremock/cards.json" })
234
- .withFiles({ file: "./test/fixtures/cards.json", name: "cards/body.json" })
235
- .start();
236
- ```
140
+ | Option (`installNoDockerRuntime`) | Environment | Default |
141
+ |-----------------------------------|------------------------------|------------------------------------|
142
+ | `jar` | `WIREMOCK_JAR` | the `wiremock` package's JAR |
143
+ | `java` | `WIREMOCK_JAVA`, `JAVA_HOME` | `java` |
144
+ | `startupTimeout` | – | `60_000` ms |
145
+ | `cacheDir` (`false` disables) | – | `<tmpdir>/wiremock-testcontainers` |
146
+ | `log` | – | `process.emitWarning` |
147
+ | `env` – for the CI check and defaults | – | `process.env` |
237
148
 
238
- Registrations are validated immediately, before any image is pulled: sources
239
- must exist, mappings must be WireMock stubs, and no registration may overwrite a
240
- file of another or reuse a stub `id` (WireMock would refuse to start).
241
- Registering the same file at the same path again is a no-op. Sources are only
242
- read – mappings are copied byte-for-byte as of registration.
243
-
244
- ## API
245
-
246
- ### `WiremockContainer`
247
-
248
- - `constructor(image?: string)`: Create a new container. Defaults to
249
- `wiremock/wiremock`.
250
- - `withMappings(directory: string)`: Copies `mappings/` (required) and
251
- `__files/` (optional) from the specified local directory into the container.
252
- Throws immediately if `mappings/` does not exist, so a typo fails before any
253
- image is pulled.
254
- - `withMapping(name: string, source: WiremockMappingSource)`: Registers one stub
255
- at `mappings/<name>.json` (`name` may contain `/`; `.json` is optional).
256
- - `withFiles(...files: WiremockStaticFile[])`: Registers files at
257
- `__files/<name>` – the base name unless an object source sets `name`. A call
258
- registers all files or none.
259
- - `start()`: Starts the container and returns a `StartedWiremockContainer`.
260
- Runtime failures (no daemon, image pull denied, start-up timeout, name or
261
- port conflicts) are propagated unchanged.
262
-
263
- ### `StartedWiremockContainer`
264
-
265
- - `rpcUrl`: Returns the base URL of the running WireMock instance (e.g.,
266
- `http://localhost:32768`).
267
- - `stop()`: Stops the container (inherited from Testcontainers).
268
- - `[Symbol.asyncDispose]`: `await using container = await new WiremockContainer().start();`
269
- stops the container automatically when the scope exits (TypeScript 5.2+).
270
-
271
- ## Best Practices
272
-
273
- 1. **Test Isolation**: Start a new container for each test suite (or even each
274
- test if performance allows) to ensure a clean state.
275
- 2. **Mapping Organization**: Keep your mappings organized in a dedicated
276
- directory structure that mirrors your API endpoints.
277
- 3. **Timeouts**: Docker container startup can sometimes exceed the default test
278
- runner timeout (e.g. 5 s in Jest, 10 s in Vitest). Always set a higher timeout for
279
- `beforeAll` hooks that start containers.
280
- 4. **Resource Management**: Always ensure `container.stop()` is called in an
281
- `afterAll` or `afterEach` block to prevent orphaned containers.
282
-
283
- ## Development
284
-
285
- Tooling: [Vitest](https://vitest.dev/) (tests + v8 coverage),
286
- [oxlint](https://oxc.rs/docs/guide/usage/linter) (lint), `tsc` (typecheck)
287
- and [tsdown](https://tsdown.dev/) (build – rolldown + oxc, emitting
288
- declarations natively via `isolatedDeclarations` in `tsconfig.build.json`).
289
- Node version: see `.nvmrc`.
290
-
291
- ### Scripts
292
-
293
- | Command | What it does | Needs Docker |
294
- |----------------------|--------------------------------------------------------------------------|--------------|
295
- | `pnpm build` | ESM + CJS + declarations into `dist/`, then `publint` + `attw` checks | No |
296
- | `pnpm typecheck` | `tsc --noEmit` over `src/` and `test/` | No |
297
- | `pnpm lint` | `oxlint --deny-warnings` with the rules in `.oxlintrc.json` | No |
298
- | `pnpm test:unit` | `test/unit/**` – container wrapper logic, registration API and fixture integrity checks | No |
299
- | `pnpm test:e2e` | `test/index.test.ts` – a real WireMock container serving the fixtures | **Yes** |
300
- | `pnpm test` | Both suites, no coverage (fast local loop) | **Yes** |
301
- | `pnpm test:coverage` | Both suites with coverage of `src/`, thresholds enforced | **Yes** |
302
- | `pnpm check` | `build` + `typecheck` + `lint` + `test:coverage` – the full CI gate | **Yes** |
303
-
304
- Append `--coverage` to any test script to collect coverage for that run, e.g.
305
- `pnpm test:unit --coverage`. Coverage thresholds are **95 %** for statements,
306
- branches, functions and lines; the unit suite alone currently reaches 100 %
307
- and completes in well under two seconds of test time.
308
-
309
- ### Running the E2E suite in containerised environments
310
-
311
- The E2E suite starts a real container through Testcontainers, so the test
312
- process needs a container runtime it can talk to. Without one, `pnpm test:e2e`,
313
- `pnpm test` and `pnpm check` fail in the E2E suite with:
149
+ - **Speed:** a C1-only JIT and a class-data archive per JAR and Java in `cacheDir`. The first run
150
+ records it (a slower `stop()`); later runs boot ~2× faster and stop instantly. `JAVA_OPTS`
151
+ override the JVM flags.
152
+ - **Differences:** WireMock listens on `127.0.0.1`; `exec()`, networks and copying after start
153
+ throw; leftover JVMs are killed on exit.
154
+ - **Also exported:** `detectCI(env?)` – the CI variable set, if any; `isNoDockerRequested(env?)` –
155
+ whether `NO_DOCKER_ENV` (`WIREMOCK_NO_DOCKER`) is `1` or `true`.
314
156
 
315
- ```
316
- Could not find a working container runtime strategy
317
- ```
157
+ ## More
318
158
 
319
- In that situation the Docker-free loop still covers build, types, lint and
320
- 100 % of `src/`:
321
-
322
- ```bash
323
- pnpm build && pnpm typecheck && pnpm lint && pnpm test:unit --coverage
324
- ```
159
+ - [`examples/`](./examples) – a standalone project using the package via `npm link`.
160
+ - [CONTRIBUTING.md](./CONTRIBUTING.md) – scripts, CI and tests.
325
161
 
326
- Testcontainers resolves a runtime in this order; pick whichever your
327
- environment can provide:
328
-
329
- | Environment | How to enable E2E |
330
- |-----------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
331
- | GitHub Codespaces / VS Code Dev Containers | Use the provided `.devcontainer/devcontainer.json` – it adds the `docker-in-docker` feature, so a daemon runs inside the dev container. |
332
- | Dev container with the host socket mounted | Add the `docker-outside-of-docker` feature (or mount `/var/run/docker.sock`). Set `TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal` if port lookups fail. |
333
- | Remote or rootless daemon | `DOCKER_HOST=tcp://host:2375` (or `unix:///path/to/docker.sock`). |
334
- | Podman | `DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock` and `TESTCONTAINERS_RYUK_DISABLED=true` (or run Ryuk privileged). |
335
- | No runtime can be installed (locked-down sandbox) | [Testcontainers Cloud](https://testcontainers.com/cloud/): run the agent and export `TC_CLOUD_TOKEN`; containers start remotely, tests run locally. |
336
-
337
- Sandboxes that have **no runtime binary and no socket at all** (as is the case
338
- for some hosted dev VMs) cannot run the E2E suite; the suite states this in its
339
- header comment and `pnpm test:unit` is the supported loop there.
340
-
341
- CI (`.github/workflows/main.yml`) runs build, typecheck, lint, unit tests
342
- (with coverage) and E2E tests as separate steps on `ubuntu-latest`, which
343
- ships with Docker, for every active Node LTS line.
344
-
345
- ### Supply-chain controls
346
-
347
- - `pnpm install --frozen-lockfile` everywhere; the lockfile is the source of truth.
348
- - `pnpm-workspace.yaml` allow-lists the only packages permitted to run install
349
- scripts (`ssh2`, `cpu-features`, `protobufjs` – all transitive via
350
- `testcontainers`) and sets `minimumReleaseAge: 1440` so a version must be at
351
- least 24 h old before it can be resolved.
352
- - CI runs `pnpm audit --prod --audit-level=high` on every push and PR;
353
- Dependabot (`.github/dependabot.yml`) proposes weekly updates with a 3-day
354
- cooldown, grouping dev tooling and leaving `testcontainers` bumps individual.
355
- - `pnpm build` runs [publint](https://publint.dev/) and
356
- [Are the types wrong?](https://arethetypeswrong.github.io/) against the
357
- emitted package; `size.yml` fails a PR if the tarball exceeds 25 kB.
358
- - Releases publish with npm provenance (`--provenance`, OIDC) after a
359
- Docker-free verify step.
360
-
361
- ### Fixtures and schema-based assertions
362
-
363
- The recorded WireMock session lives in `test/__mocks__/wiremock`:
364
-
365
- - `mappings/*.json` – one stub per endpoint (`request.url`, `response.status`,
366
- `response.headers`, `response.bodyFileName`).
367
- - `__files/*.json` – the response bodies referenced by the mappings.
368
-
369
- `test/helpers/fixtures.ts` loads these from disk and declares the TypeScript
370
- shapes of every JSON payload. `test/helpers/assertions.ts` declares a
371
- `Schema<T>` per payload – stub mappings, the admin API response and each
372
- recorded body. The compiler requires a schema to cover **every** field of
373
- `T`; `expectObject` then asserts each field's shape, its recorded value and
374
- the exact key set (`partialOf` relaxes the key set for third-party shapes).
375
-
376
- The E2E suite runs a mapping-driven contract test for every file in
377
- `mappings/` (status, headers and full JSON body), so adding a new recorded
378
- mapping automatically puts it under test. Endpoint-specific suites add
379
- schema assertions, which the unit suite also runs against the fixture files
380
- themselves to catch drift early.
381
-
382
- Negative cases are hand-written stubs alongside the recorded ones:
383
-
384
- - `mapping-cards-bad-request-400.json` – `GET /cards?page=0` → 400 with a
385
- validation error envelope.
386
- - `mapping-account-balance-server-error-500.json` – `GET /account/balance`
387
- → 500 **only** when the request carries `X-Mock-Scenario: server-error`.
388
- It sets `"priority": 1` so it wins over the recorded 200 stub for the same
389
- URL; without the header the 200 stub is still served. This is the standard
390
- WireMock pattern for simulating outages on demand.
391
-
392
- Unmatched requests (unknown path, wrong method) get WireMock's default 404.
393
-
394
- `test/__mocks__/standalone` holds loose fixtures for `withMapping()` and
395
- `withFiles()`. Their unit tests need no Docker: `test/helpers/container.ts`
396
- reads the tar archive Testcontainers would upload and asserts exactly which
397
- files land where.
398
-
399
- ## Maintainers
400
-
401
- Maintained by [Hella Labs](https://hellaweb3.com/).
402
-
403
- ## License
404
-
405
- MIT
162
+ Maintained by [Hella Labs](https://hellaweb3.com/). [MIT](./LICENSE) licensed.