@nifrajs/testing 2.1.0 → 2.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 +4 -4
- package/dist/adversarial.js +1 -1
- package/dist/cookies.d.ts +2 -2
- package/dist/cookies.js +2 -2
- package/dist/incident.d.ts +5 -5
- package/dist/incident.js +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/session.d.ts +4 -4
- package/dist/session.js +1 -1
- package/package.json +4 -4
- package/src/adversarial.ts +1 -1
- package/src/cookies.ts +2 -2
- package/src/incident.ts +5 -5
- package/src/index.ts +1 -1
- package/src/session.ts +4 -4
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ const report = await assertAdversarialContract(app, {
|
|
|
65
65
|
|
|
66
66
|
`runAdversarialContract` returns the same structured report without throwing. Every failure includes
|
|
67
67
|
`replay: { seed, caseId, runtime }`; rerun one case with `only: caseId`. Contract runs execute one valid
|
|
68
|
-
request for each declared response schema, so use an isolated test app/database
|
|
68
|
+
request for each declared response schema, so use an isolated test app/database-never point the
|
|
69
69
|
laboratory at production.
|
|
70
70
|
|
|
71
71
|
`nifra levels` L4 uses this same deep engine through the explicitly configured isolated executor;
|
|
@@ -149,7 +149,7 @@ await client.auth.logout.post() // a Max-Age=0 Set-Cookie cle
|
|
|
149
149
|
expect(cookies.get("sid")).toBeUndefined()
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
Same in-process client as `testClient`
|
|
152
|
+
Same in-process client as `testClient` - the app's own `fetch`, no server/port/network, the full real
|
|
153
153
|
lifecycle (validation, middleware, contracts, auth) and end-to-end types from `App`. The only addition is
|
|
154
154
|
that every call carries and captures cookies via a shared jar.
|
|
155
155
|
|
|
@@ -162,14 +162,14 @@ that every call carries and captures cookies via a shared jar.
|
|
|
162
162
|
- `certifyAdapter({ profile, adapterId, createAdapter })` → capability matrix + SHA-256 evidence.
|
|
163
163
|
- `verifyAdapterCertification(report)` → recompute and verify portable certification evidence.
|
|
164
164
|
- `testSession<App>(app, { origin?, cookies? })` → `{ client: Treaty<App>, cookies: CookieJar }`.
|
|
165
|
-
- `cookieJar()` → `CookieJar`
|
|
165
|
+
- `cookieJar()` → `CookieJar` - `header()` · `applyTo(headers)` · `store(response)` · `set` · `get` · `clear` · `size`.
|
|
166
166
|
Honours removal (`Max-Age=0` / past `Expires`); other cookie attributes are ignored (in-process, same-origin).
|
|
167
167
|
|
|
168
168
|
For a **stateless** request (no cookies), use `testClient` from `@nifrajs/client` directly.
|
|
169
169
|
|
|
170
170
|
## For AI agents
|
|
171
171
|
|
|
172
|
-
Start with [`LLM.md`](./LLM.md)
|
|
172
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
173
173
|
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
174
174
|
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
175
175
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
package/dist/adversarial.js
CHANGED
|
@@ -20,7 +20,7 @@ export class AdversarialContractError extends Error {
|
|
|
20
20
|
const details = [
|
|
21
21
|
...report.failures
|
|
22
22
|
.slice(0, 5)
|
|
23
|
-
.map((failure) => `${failure.runtime}: ${failure.id}
|
|
23
|
+
.map((failure) => `${failure.runtime}: ${failure.id} - ${failure.message} (replay seed ${report.seed})`),
|
|
24
24
|
...report.gaps
|
|
25
25
|
.slice(0, 5)
|
|
26
26
|
.map((gap) => `${gap.route ?? "contract"}${gap.target ? ` ${gap.target}` : ""}: ${gap.message}`),
|
package/dist/cookies.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A tiny cookie jar for in-process tests
|
|
2
|
+
* A tiny cookie jar for in-process tests - parses `Set-Cookie` off responses and emits a `Cookie`
|
|
3
3
|
* request header, so a login → authenticated-request flow works without threading headers by hand. It
|
|
4
4
|
* honours removal (`Max-Age=0` / a past `Expires`) so logout clears the cookie; other attributes
|
|
5
|
-
* (Domain/Path/Secure/SameSite) are ignored
|
|
5
|
+
* (Domain/Path/Secure/SameSite) are ignored - everything is same-origin in-process.
|
|
6
6
|
*/
|
|
7
7
|
export interface CookieJar {
|
|
8
8
|
/** The current `Cookie` request-header value (`"a=1; b=2"`, or `""` when empty). */
|
package/dist/cookies.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A tiny cookie jar for in-process tests
|
|
2
|
+
* A tiny cookie jar for in-process tests - parses `Set-Cookie` off responses and emits a `Cookie`
|
|
3
3
|
* request header, so a login → authenticated-request flow works without threading headers by hand. It
|
|
4
4
|
* honours removal (`Max-Age=0` / a past `Expires`) so logout clears the cookie; other attributes
|
|
5
|
-
* (Domain/Path/Secure/SameSite) are ignored
|
|
5
|
+
* (Domain/Path/Secure/SameSite) are ignored - everything is same-origin in-process.
|
|
6
6
|
*/
|
|
7
7
|
/** Read `Set-Cookie` values portably: `getSetCookie()` (Bun/Node/edge) with a single-header fallback. */
|
|
8
8
|
function setCookies(headers) {
|
package/dist/incident.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Turn a failed request into a committed regression test
|
|
2
|
+
* Turn a failed request into a committed regression test - the one thing a generic error tracker
|
|
3
3
|
* (Sentry, PostHog) can't do, because it needs the framework's contract + in-process replay. Capture
|
|
4
4
|
* the request + observed response, replay it against the CURRENT app, and assert the response contract
|
|
5
5
|
* (status, optionally shape) still reproduces. This does NOT replace error tracking or store incidents
|
|
6
|
-
*
|
|
6
|
+
* - it converts one into a permanent test.
|
|
7
7
|
*
|
|
8
8
|
* Two surfaces, deliberately separate on the PII axis:
|
|
9
|
-
* - `replayIncident` / `assertIncidentReplays` run in memory against the REAL captured inputs
|
|
9
|
+
* - `replayIncident` / `assertIncidentReplays` run in memory against the REAL captured inputs - nothing
|
|
10
10
|
* is written, so reproduction is exact and there is no leak surface.
|
|
11
11
|
* - `generateRegressionTest` emits a file you commit, so it redacts request values BY DEFAULT and
|
|
12
12
|
* bannered a sanitize step: a committed fixture must never carry raw PII/secrets.
|
|
@@ -74,7 +74,7 @@ export declare class IncidentReplayError extends Error {
|
|
|
74
74
|
export declare function assertIncidentReplays(app: AppLike, capsule: IncidentCapsule, options?: ReplayIncidentOptions): Promise<void>;
|
|
75
75
|
/**
|
|
76
76
|
* Redact leaf string values by default (unless the dotted key path is allow-listed). Non-strings are
|
|
77
|
-
* kept
|
|
77
|
+
* kept - they carry the structure that makes the fixture reproduce - so review the emitted file. This
|
|
78
78
|
* is intentionally aggressive: a committed fixture must not leak PII/secrets.
|
|
79
79
|
*/
|
|
80
80
|
export declare function redactForEmission(value: unknown, allow: ReadonlySet<string>, path?: string): unknown;
|
|
@@ -90,7 +90,7 @@ export interface GenerateRegressionTestOptions {
|
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* Emit a committable regression test from a capsule. Request string values are redacted BY DEFAULT with
|
|
93
|
-
* a sanitize banner
|
|
93
|
+
* a sanitize banner - replace the `<redacted>` placeholders with safe, reproducing values before you
|
|
94
94
|
* commit. The test asserts the response contract via {@link assertIncidentReplays}.
|
|
95
95
|
*/
|
|
96
96
|
export declare function generateRegressionTest(capsule: IncidentCapsule, options?: GenerateRegressionTestOptions): string;
|
package/dist/incident.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Turn a failed request into a committed regression test
|
|
2
|
+
* Turn a failed request into a committed regression test - the one thing a generic error tracker
|
|
3
3
|
* (Sentry, PostHog) can't do, because it needs the framework's contract + in-process replay. Capture
|
|
4
4
|
* the request + observed response, replay it against the CURRENT app, and assert the response contract
|
|
5
5
|
* (status, optionally shape) still reproduces. This does NOT replace error tracking or store incidents
|
|
6
|
-
*
|
|
6
|
+
* - it converts one into a permanent test.
|
|
7
7
|
*
|
|
8
8
|
* Two surfaces, deliberately separate on the PII axis:
|
|
9
|
-
* - `replayIncident` / `assertIncidentReplays` run in memory against the REAL captured inputs
|
|
9
|
+
* - `replayIncident` / `assertIncidentReplays` run in memory against the REAL captured inputs - nothing
|
|
10
10
|
* is written, so reproduction is exact and there is no leak surface.
|
|
11
11
|
* - `generateRegressionTest` emits a file you commit, so it redacts request values BY DEFAULT and
|
|
12
12
|
* bannered a sanitize step: a committed fixture must never carry raw PII/secrets.
|
|
@@ -162,7 +162,7 @@ export async function assertIncidentReplays(app, capsule, options = {}) {
|
|
|
162
162
|
const REDACTED = "<redacted>";
|
|
163
163
|
/**
|
|
164
164
|
* Redact leaf string values by default (unless the dotted key path is allow-listed). Non-strings are
|
|
165
|
-
* kept
|
|
165
|
+
* kept - they carry the structure that makes the fixture reproduce - so review the emitted file. This
|
|
166
166
|
* is intentionally aggressive: a committed fixture must not leak PII/secrets.
|
|
167
167
|
*/
|
|
168
168
|
export function redactForEmission(value, allow, path = "") {
|
|
@@ -181,7 +181,7 @@ export function redactForEmission(value, allow, path = "") {
|
|
|
181
181
|
}
|
|
182
182
|
/**
|
|
183
183
|
* Emit a committable regression test from a capsule. Request string values are redacted BY DEFAULT with
|
|
184
|
-
* a sanitize banner
|
|
184
|
+
* a sanitize banner - replace the `<redacted>` placeholders with safe, reproducing values before you
|
|
185
185
|
* commit. The test asserts the response contract via {@link assertIncidentReplays}.
|
|
186
186
|
*/
|
|
187
187
|
export function generateRegressionTest(capsule, options = {}) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/testing
|
|
2
|
+
* @nifrajs/testing - helpers for testing nifra apps in-process. `@nifrajs/client`'s `testClient` is the
|
|
3
3
|
* typed, no-network request client (the supertest/inject equivalent); this adds what it doesn't: a
|
|
4
4
|
* {@link cookieJar} and a cookie-persisting {@link testSession}, so stateful auth/session flows are as
|
|
5
5
|
* easy to test as a single request.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/testing
|
|
2
|
+
* @nifrajs/testing - helpers for testing nifra apps in-process. `@nifrajs/client`'s `testClient` is the
|
|
3
3
|
* typed, no-network request client (the supertest/inject equivalent); this adds what it doesn't: a
|
|
4
4
|
* {@link cookieJar} and a cookie-persisting {@link testSession}, so stateful auth/session flows are as
|
|
5
5
|
* easy to test as a single request.
|
package/dist/session.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A stateful, cookie-aware in-process test client. `@nifrajs/client`'s `testClient` already drives an
|
|
3
|
-
* app's `fetch` with end-to-end types (no server/port/network)
|
|
3
|
+
* app's `fetch` with end-to-end types (no server/port/network) - this wraps it with a {@link CookieJar}
|
|
4
4
|
* that carries `Set-Cookie` across calls, so a login → authenticated-request flow just works.
|
|
5
5
|
*
|
|
6
6
|
* import { testSession } from "@nifrajs/testing"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { type ClientOptions, type Treaty } from "@nifrajs/client";
|
|
15
15
|
import { type CookieJar } from "./cookies.js";
|
|
16
|
-
/** The minimal shape a nifra `server()` app satisfies
|
|
16
|
+
/** The minimal shape a nifra `server()` app satisfies - its own `fetch`. */
|
|
17
17
|
export interface AppLike {
|
|
18
18
|
fetch(request: Request): Response | Promise<Response>;
|
|
19
19
|
}
|
|
@@ -24,9 +24,9 @@ export interface TestSessionOptions extends Omit<ClientOptions, "fetch"> {
|
|
|
24
24
|
readonly cookies?: CookieJar;
|
|
25
25
|
}
|
|
26
26
|
export interface TestSession<App> {
|
|
27
|
-
/** The end-to-end-typed in-process client
|
|
27
|
+
/** The end-to-end-typed in-process client - every call carries + captures cookies. */
|
|
28
28
|
readonly client: Treaty<App>;
|
|
29
|
-
/** The jar backing this session
|
|
29
|
+
/** The jar backing this session - inspect (`cookies.get("sid")`), seed, or `clear()` it. */
|
|
30
30
|
readonly cookies: CookieJar;
|
|
31
31
|
}
|
|
32
32
|
/** Create a cookie-persisting in-process test client for `app`. */
|
package/dist/session.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A stateful, cookie-aware in-process test client. `@nifrajs/client`'s `testClient` already drives an
|
|
3
|
-
* app's `fetch` with end-to-end types (no server/port/network)
|
|
3
|
+
* app's `fetch` with end-to-end types (no server/port/network) - this wraps it with a {@link CookieJar}
|
|
4
4
|
* that carries `Set-Cookie` across calls, so a login → authenticated-request flow just works.
|
|
5
5
|
*
|
|
6
6
|
* import { testSession } from "@nifrajs/testing"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/testing",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Contract-derived adversarial tests, runtime-matrix response conformance, and cookie-aware in-process sessions for Nifra apps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
},
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@nifrajs/client": "2.
|
|
41
|
-
"@nifrajs/core": "2.
|
|
42
|
-
"@nifrajs/mock": "2.
|
|
40
|
+
"@nifrajs/client": "2.3.0",
|
|
41
|
+
"@nifrajs/core": "2.3.0",
|
|
42
|
+
"@nifrajs/mock": "2.3.0"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
package/src/adversarial.ts
CHANGED
|
@@ -163,7 +163,7 @@ export class AdversarialContractError extends Error {
|
|
|
163
163
|
.slice(0, 5)
|
|
164
164
|
.map(
|
|
165
165
|
(failure) =>
|
|
166
|
-
`${failure.runtime}: ${failure.id}
|
|
166
|
+
`${failure.runtime}: ${failure.id} - ${failure.message} (replay seed ${report.seed})`,
|
|
167
167
|
),
|
|
168
168
|
...report.gaps
|
|
169
169
|
.slice(0, 5)
|
package/src/cookies.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A tiny cookie jar for in-process tests
|
|
2
|
+
* A tiny cookie jar for in-process tests - parses `Set-Cookie` off responses and emits a `Cookie`
|
|
3
3
|
* request header, so a login → authenticated-request flow works without threading headers by hand. It
|
|
4
4
|
* honours removal (`Max-Age=0` / a past `Expires`) so logout clears the cookie; other attributes
|
|
5
|
-
* (Domain/Path/Secure/SameSite) are ignored
|
|
5
|
+
* (Domain/Path/Secure/SameSite) are ignored - everything is same-origin in-process.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export interface CookieJar {
|
package/src/incident.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Turn a failed request into a committed regression test
|
|
2
|
+
* Turn a failed request into a committed regression test - the one thing a generic error tracker
|
|
3
3
|
* (Sentry, PostHog) can't do, because it needs the framework's contract + in-process replay. Capture
|
|
4
4
|
* the request + observed response, replay it against the CURRENT app, and assert the response contract
|
|
5
5
|
* (status, optionally shape) still reproduces. This does NOT replace error tracking or store incidents
|
|
6
|
-
*
|
|
6
|
+
* - it converts one into a permanent test.
|
|
7
7
|
*
|
|
8
8
|
* Two surfaces, deliberately separate on the PII axis:
|
|
9
|
-
* - `replayIncident` / `assertIncidentReplays` run in memory against the REAL captured inputs
|
|
9
|
+
* - `replayIncident` / `assertIncidentReplays` run in memory against the REAL captured inputs - nothing
|
|
10
10
|
* is written, so reproduction is exact and there is no leak surface.
|
|
11
11
|
* - `generateRegressionTest` emits a file you commit, so it redacts request values BY DEFAULT and
|
|
12
12
|
* bannered a sanitize step: a committed fixture must never carry raw PII/secrets.
|
|
@@ -234,7 +234,7 @@ const REDACTED = "<redacted>"
|
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
236
|
* Redact leaf string values by default (unless the dotted key path is allow-listed). Non-strings are
|
|
237
|
-
* kept
|
|
237
|
+
* kept - they carry the structure that makes the fixture reproduce - so review the emitted file. This
|
|
238
238
|
* is intentionally aggressive: a committed fixture must not leak PII/secrets.
|
|
239
239
|
*/
|
|
240
240
|
export function redactForEmission(value: unknown, allow: ReadonlySet<string>, path = ""): unknown {
|
|
@@ -264,7 +264,7 @@ export interface GenerateRegressionTestOptions {
|
|
|
264
264
|
|
|
265
265
|
/**
|
|
266
266
|
* Emit a committable regression test from a capsule. Request string values are redacted BY DEFAULT with
|
|
267
|
-
* a sanitize banner
|
|
267
|
+
* a sanitize banner - replace the `<redacted>` placeholders with safe, reproducing values before you
|
|
268
268
|
* commit. The test asserts the response contract via {@link assertIncidentReplays}.
|
|
269
269
|
*/
|
|
270
270
|
export function generateRegressionTest(
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/testing
|
|
2
|
+
* @nifrajs/testing - helpers for testing nifra apps in-process. `@nifrajs/client`'s `testClient` is the
|
|
3
3
|
* typed, no-network request client (the supertest/inject equivalent); this adds what it doesn't: a
|
|
4
4
|
* {@link cookieJar} and a cookie-persisting {@link testSession}, so stateful auth/session flows are as
|
|
5
5
|
* easy to test as a single request.
|
package/src/session.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A stateful, cookie-aware in-process test client. `@nifrajs/client`'s `testClient` already drives an
|
|
3
|
-
* app's `fetch` with end-to-end types (no server/port/network)
|
|
3
|
+
* app's `fetch` with end-to-end types (no server/port/network) - this wraps it with a {@link CookieJar}
|
|
4
4
|
* that carries `Set-Cookie` across calls, so a login → authenticated-request flow just works.
|
|
5
5
|
*
|
|
6
6
|
* import { testSession } from "@nifrajs/testing"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { type ClientOptions, client, type FetchFn, type Treaty } from "@nifrajs/client"
|
|
15
15
|
import { type CookieJar, cookieJar } from "./cookies.ts"
|
|
16
16
|
|
|
17
|
-
/** The minimal shape a nifra `server()` app satisfies
|
|
17
|
+
/** The minimal shape a nifra `server()` app satisfies - its own `fetch`. */
|
|
18
18
|
export interface AppLike {
|
|
19
19
|
fetch(request: Request): Response | Promise<Response>
|
|
20
20
|
}
|
|
@@ -27,9 +27,9 @@ export interface TestSessionOptions extends Omit<ClientOptions, "fetch"> {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface TestSession<App> {
|
|
30
|
-
/** The end-to-end-typed in-process client
|
|
30
|
+
/** The end-to-end-typed in-process client - every call carries + captures cookies. */
|
|
31
31
|
readonly client: Treaty<App>
|
|
32
|
-
/** The jar backing this session
|
|
32
|
+
/** The jar backing this session - inspect (`cookies.get("sid")`), seed, or `clear()` it. */
|
|
33
33
|
readonly cookies: CookieJar
|
|
34
34
|
}
|
|
35
35
|
|