@nwire/test-kit 0.8.0 → 0.9.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
@@ -44,6 +44,46 @@ describe("enrolStudent", () => {
44
44
  - `createTestApp` / `bootTestApp` — supertest agent helpers for HTTP-level tests.
45
45
  - `isReachable(url)` — startup probe for Docker-backed services.
46
46
 
47
+ ## Integration helpers
48
+
49
+ Layered on `harness()` for ergonomic integration tests. Each one is a thin proxy — the base `Harness` interface stays untouched.
50
+
51
+ ### asUser
52
+
53
+ Pin `envelope.user` on every dispatch/query in a scoped proxy. The acting user (and tenant) flows through to handlers, RBAC middleware, and audit fields automatically.
54
+
55
+ ```ts
56
+ await asUser(h, { id: "u-1", roles: ["admin"], tenant: "t-1" }).dispatch(createPost, { ... });
57
+ ```
58
+
59
+ ### simulateRequest
60
+
61
+ Run a request through a wired Koa app as if it came over HTTP — wraps supertest so test code doesn't need to know which agent helper to import. Returns `{ status, body, headers }`.
62
+
63
+ ```ts
64
+ const res = await simulateRequest(api.toKoa(), { method: "POST", path: "/posts", body: { ... } });
65
+ expect(res.status).toBe(201);
66
+ ```
67
+
68
+ ### checkPolicy
69
+
70
+ Build an ability for a user via a `@nwire/rbac`-shaped factory and run a `can(action, subject?)` check. Returns `{ can, reason? }` — `reason` carries the CASL rule message when access is denied.
71
+
72
+ ```ts
73
+ const check = await checkPolicy(buildAbility, user, "delete", subject("Post", post));
74
+ expect(check.can).toBe(false);
75
+ ```
76
+
77
+ ### onLog
78
+
79
+ Tap every `logger.{info,warn,error,debug}` call made through the runtime. Returns an unsubscribe function. Use to assert "warned about X" without swapping in a fake logger.
80
+
81
+ ```ts
82
+ const off = onLog(h, (e) => expect(e.message).not.toMatch(/PII/));
83
+ // ... drive a flow ...
84
+ off();
85
+ ```
86
+
47
87
  ## When to use
48
88
 
49
89
  In every Nwire test file. Fits every level — `harness` covers unit/integration, `dockerCompose` covers real-deps, `feature` covers BDD acceptance.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Coverage for the four harness extension helpers — one spec each:
3
+ *
4
+ * asUser — pinned envelope.user reaches the handler
5
+ * simulateRequest — runs a request through a wired Koa app
6
+ * checkPolicy — wraps a CASL-shaped ability factory
7
+ * onLog — taps every logger call
8
+ *
9
+ * Each spec is intentionally small — the helpers are thin proxies, the
10
+ * unit-tests prove the contract, not the underlying primitives.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=harness-extensions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-extensions.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/harness-extensions.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Coverage for the four harness extension helpers — one spec each:
3
+ *
4
+ * asUser — pinned envelope.user reaches the handler
5
+ * simulateRequest — runs a request through a wired Koa app
6
+ * checkPolicy — wraps a CASL-shaped ability factory
7
+ * onLog — taps every logger call
8
+ *
9
+ * Each spec is intentionally small — the helpers are thin proxies, the
10
+ * unit-tests prove the contract, not the underlying primitives.
11
+ */
12
+ import { describe, it, expect } from "vitest";
13
+ import { z } from "zod";
14
+ import * as forge from "@nwire/forge";
15
+ import { defineEvent } from "@nwire/messages";
16
+ import { httpInterface, post } from "@nwire/http";
17
+ import { harness } from "../harness.js";
18
+ import { asUser, simulateRequest, checkPolicy, onLog } from "../harness-extensions.js";
19
+ // ─── shared fixture: a one-action demo app ─────────────────────────
20
+ // Tiny app that captures envelope.user on dispatch so the asUser spec
21
+ // can assert on it. Same shape as the existing harness.test.ts demo.
22
+ const GreetInput = z.object({ who: z.string() });
23
+ const GreetedEvent = defineEvent({
24
+ name: "ext-demo.greeted",
25
+ schema: z.object({ who: z.string() }),
26
+ });
27
+ const Greeted = forge.eventFactory(GreetedEvent);
28
+ let lastUser = undefined;
29
+ const greet = forge.defineAction({
30
+ name: "ext-demo.greet",
31
+ schema: GreetInput,
32
+ emits: [GreetedEvent],
33
+ handler: async (input, ctx) => {
34
+ lastUser = ctx.envelope.user;
35
+ return Greeted({ who: input.who });
36
+ },
37
+ });
38
+ const demoModule = forge.defineModule("ext-demo", {
39
+ actions: [greet],
40
+ events: [GreetedEvent],
41
+ });
42
+ const demoApp = forge.defineApp("ext-demo", { modules: [demoModule] });
43
+ describe("harness extensions", () => {
44
+ describe("asUser", () => {
45
+ it("pins envelope.user on subsequent dispatches", async () => {
46
+ const h = await harness({ app: demoApp });
47
+ try {
48
+ lastUser = undefined;
49
+ const alex = { id: "u-alex", roles: ["admin"], tenant: "t-1" };
50
+ await asUser(h, alex).dispatch(greet, { who: "Alice" });
51
+ await h.idle();
52
+ expect(lastUser).toEqual(alex);
53
+ }
54
+ finally {
55
+ await h.stop();
56
+ }
57
+ });
58
+ });
59
+ describe("simulateRequest", () => {
60
+ it("runs a POST through a wired httpInterface", async () => {
61
+ // A standalone http() app — no Nwire app needed. simulateRequest
62
+ // doesn't depend on the harness; the api param carries the
63
+ // wired Koa instance. We pass `null` for the harness in the
64
+ // helper signature (helper ignores it for the standalone path).
65
+ const api = httpInterface().wire(post("/echo", { body: z.object({ msg: z.string() }) }), async ({ input }) => ({ echoed: input.msg }));
66
+ const koaApp = api.toKoa();
67
+ const res = await simulateRequest(koaApp, {
68
+ method: "POST",
69
+ path: "/echo",
70
+ body: { msg: "hi" },
71
+ headers: { "content-type": "application/json" },
72
+ });
73
+ expect(res.status).toBe(200);
74
+ expect(res.body).toEqual({ echoed: "hi" });
75
+ });
76
+ });
77
+ describe("checkPolicy", () => {
78
+ it("returns can/cannot for a CASL-shaped ability factory", async () => {
79
+ // Inline ability factory mimicking @nwire/rbac's defineAbility
80
+ // shape — { can(action, subj), cannot, relevantRuleFor }. Keeps
81
+ // the test self-contained without pulling in CASL.
82
+ const buildAbility = (user) => {
83
+ const isAdmin = user?.roles?.includes("admin") ?? false;
84
+ return {
85
+ can: (action) => isAdmin || action === "read",
86
+ cannot: (action) => !(isAdmin || action === "read"),
87
+ relevantRuleFor: () => ({ reason: "role required" }),
88
+ };
89
+ };
90
+ const admin = await checkPolicy(buildAbility, { id: "a", roles: ["admin"] }, "delete");
91
+ expect(admin).toEqual({ can: true });
92
+ const guest = await checkPolicy(buildAbility, { id: "g" }, "delete");
93
+ expect(guest.can).toBe(false);
94
+ expect(guest.reason).toBe("role required");
95
+ });
96
+ });
97
+ describe("onLog", () => {
98
+ it("invokes the tap for every logger call and unsubscribes cleanly", async () => {
99
+ // Inject our own captured logger so we can prove the wrapper
100
+ // forwards calls AND fires the tap.
101
+ const sink = [];
102
+ const capture = {
103
+ debug: (m) => sink.push({ level: "debug", message: m }),
104
+ info: (m) => sink.push({ level: "info", message: m }),
105
+ warn: (m) => sink.push({ level: "warn", message: m }),
106
+ error: (m) => sink.push({ level: "error", message: m }),
107
+ child: () => capture,
108
+ };
109
+ const h = await harness({ app: demoApp, providers: { logger: capture } });
110
+ try {
111
+ const tapped = [];
112
+ const off = onLog(h, (entry) => tapped.push({ level: entry.level, message: entry.message }));
113
+ // Drive logger calls directly through the runtime's logger so
114
+ // we don't have to reason about which dispatch sites log what.
115
+ const runtime = h.app.runtime;
116
+ runtime.logger.warn("watch out", { foo: 1 });
117
+ runtime.logger.info("hello");
118
+ expect(tapped).toEqual([
119
+ { level: "warn", message: "watch out" },
120
+ { level: "info", message: "hello" },
121
+ ]);
122
+ // Inner logger still received the calls.
123
+ expect(sink.find((e) => e.message === "watch out")).toBeDefined();
124
+ // After unsubscribe, the tap stops firing.
125
+ off();
126
+ runtime.logger.error("post-unsub");
127
+ expect(tapped.find((e) => e.message === "post-unsub")).toBeUndefined();
128
+ }
129
+ finally {
130
+ await h.stop();
131
+ }
132
+ });
133
+ });
134
+ });
135
+ //# sourceMappingURL=harness-extensions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-extensions.test.js","sourceRoot":"","sources":["../../src/__tests__/harness-extensions.test.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@ import { describe, it, expect } from "vitest";
6
6
  import { z } from "zod";
7
7
  import * as forge from "@nwire/forge";
8
8
  import { defineEvent } from "@nwire/messages";
9
- import { harness } from "../harness";
9
+ import { harness } from "../harness.js";
10
10
  const GreetInput = z.object({ who: z.string() });
11
11
  const GreetedEvent = defineEvent({
12
12
  name: "demo.greeted",
package/dist/bdd.d.ts CHANGED
@@ -24,7 +24,7 @@
24
24
  * The wrapper is intentionally tiny — we pass through to the real Gherkin
25
25
  * runner. `@amiceli/vitest-cucumber` is a peerDependency.
26
26
  */
27
- import type { Harness } from "./harness";
27
+ import type { Harness } from "./harness.js";
28
28
  export interface BddContext {
29
29
  /** Filled in by the user's background step. */
30
30
  harness?: Harness;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Harness extensions — four ergonomic helpers layered on `harness()` for
3
+ * integration tests:
4
+ *
5
+ * - `asUser(user)` — pins envelope.user for every dispatch/query in a
6
+ * scoped proxy. Use to test "logged in as X" flows without re-stitching
7
+ * envelopes by hand.
8
+ * - `simulateRequest(req)` — runs a request through a wired Koa app as
9
+ * if it came over HTTP. Wraps supertest so callers don't need to know
10
+ * which agent helper to import.
11
+ * - `checkPolicy(user, action, subject?)` — wraps `@nwire/rbac`'s
12
+ * `defineAbility` result for assert-style policy tests.
13
+ * - `onLog(handler)` — taps every `logger.{info,warn,error,debug}` call
14
+ * made by the runtime. Lets a test assert "warned about X" without
15
+ * swapping in a fake logger.
16
+ *
17
+ * These helpers are additive — the base `Harness` interface is unchanged.
18
+ * They live in a separate file so the in-memory `harness` core stays small
19
+ * and the optional deps (`@nwire/auth`, `@nwire/rbac`, `koa`) stay opt-in.
20
+ */
21
+ import type { ActionDefinition } from "@nwire/forge";
22
+ import type { ZodTypeAny } from "@nwire/messages";
23
+ import type { z } from "zod";
24
+ import type { Harness } from "./harness.js";
25
+ export interface UserLike {
26
+ readonly id: string;
27
+ readonly email?: string;
28
+ readonly name?: string;
29
+ readonly roles?: readonly string[];
30
+ readonly scopes?: readonly string[];
31
+ readonly tenant?: string;
32
+ }
33
+ /**
34
+ * Subset of `Harness` returned by `asUser()`. Same `dispatch` / `query`
35
+ * shape, but with the pinned user threaded through every call. Other
36
+ * surface (telemetry, idle, stop) stays on the original harness — the
37
+ * scoped proxy is intentionally narrow.
38
+ */
39
+ export interface ScopedHarness {
40
+ dispatch<TSchema extends ZodTypeAny>(action: ActionDefinition<TSchema>, input: z.input<TSchema>, envelope?: {
41
+ tenant?: string;
42
+ correlationId?: string;
43
+ }): Promise<unknown>;
44
+ query<TResult = unknown>(queryDef: {
45
+ readonly name: string;
46
+ }, input: unknown, tenant?: string): Promise<TResult>;
47
+ }
48
+ /**
49
+ * Return a thin proxy that pins `envelope.user` (and `envelope.userId`,
50
+ * `envelope.tenant` derived from the user) on every dispatch/query.
51
+ * Pass `undefined` to clear — the returned proxy is then anonymous.
52
+ *
53
+ * await harness.asUser({ id: "u1", roles: ["admin"] }).dispatch(...)
54
+ */
55
+ export declare function asUser(harness: Harness, user: UserLike | undefined): ScopedHarness;
56
+ export interface SimulatedRequest {
57
+ readonly method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
58
+ readonly path: string;
59
+ readonly body?: unknown;
60
+ readonly headers?: Record<string, string>;
61
+ }
62
+ export interface SimulatedResponse {
63
+ readonly status: number;
64
+ readonly body: unknown;
65
+ readonly headers: Record<string, string>;
66
+ }
67
+ /**
68
+ * Run a request through a Koa app as if it came over HTTP. The harness
69
+ * doesn't boot a Koa app on its own (the in-process runtime has no HTTP
70
+ * surface), so callers pass the wired Koa instance explicitly. Returns a
71
+ * minimal `{ status, body, headers }` triple so test code doesn't have to
72
+ * know the supertest API.
73
+ */
74
+ export declare function simulateRequest(app: any, req: SimulatedRequest): Promise<SimulatedResponse>;
75
+ export interface PolicyCheck {
76
+ readonly can: boolean;
77
+ readonly reason?: string;
78
+ }
79
+ /**
80
+ * Build a CASL Ability for `user` via the supplied `buildAbility` factory
81
+ * and run a `can(action, subject?)` check. Returns `{ can, reason? }` —
82
+ * `reason` carries the CASL rule message when access is denied.
83
+ *
84
+ * Requires `@nwire/rbac` to be available. We `await import()` it instead
85
+ * of declaring a hard dep so test-kit doesn't force a CASL install on
86
+ * users who don't run RBAC tests.
87
+ */
88
+ export declare function checkPolicy(buildAbility: (user: UserLike | null) => {
89
+ can(action: string, subject?: unknown): boolean;
90
+ cannot(action: string, subject?: unknown): boolean;
91
+ relevantRuleFor?(action: string, subject?: unknown): {
92
+ reason?: string;
93
+ } | null;
94
+ }, user: UserLike | null, action: string, subject?: unknown): Promise<PolicyCheck>;
95
+ export type LogLevel = "debug" | "info" | "warn" | "error";
96
+ export interface LogEntry {
97
+ readonly level: LogLevel;
98
+ readonly message: string;
99
+ readonly fields?: Record<string, unknown>;
100
+ }
101
+ /**
102
+ * Tap every log call made through the harness's logger. Returns an
103
+ * unsubscribe function. The tap wraps `harness.app.runtime.logger` —
104
+ * `child()` calls inherit the tap so envelope-scoped loggers fire it too.
105
+ *
106
+ * This mutates the runtime's logger reference in place (test-only). Call
107
+ * the returned function to restore the original logger.
108
+ */
109
+ export declare function onLog(harness: Harness, handler: (entry: LogEntry) => void): () => void;
110
+ //# sourceMappingURL=harness-extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-extensions.d.ts","sourceRoot":"","sources":["../src/harness-extensions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Harness extensions — four ergonomic helpers layered on `harness()` for
3
+ * integration tests:
4
+ *
5
+ * - `asUser(user)` — pins envelope.user for every dispatch/query in a
6
+ * scoped proxy. Use to test "logged in as X" flows without re-stitching
7
+ * envelopes by hand.
8
+ * - `simulateRequest(req)` — runs a request through a wired Koa app as
9
+ * if it came over HTTP. Wraps supertest so callers don't need to know
10
+ * which agent helper to import.
11
+ * - `checkPolicy(user, action, subject?)` — wraps `@nwire/rbac`'s
12
+ * `defineAbility` result for assert-style policy tests.
13
+ * - `onLog(handler)` — taps every `logger.{info,warn,error,debug}` call
14
+ * made by the runtime. Lets a test assert "warned about X" without
15
+ * swapping in a fake logger.
16
+ *
17
+ * These helpers are additive — the base `Harness` interface is unchanged.
18
+ * They live in a separate file so the in-memory `harness` core stays small
19
+ * and the optional deps (`@nwire/auth`, `@nwire/rbac`, `koa`) stay opt-in.
20
+ */
21
+ import supertest from "supertest";
22
+ /**
23
+ * Return a thin proxy that pins `envelope.user` (and `envelope.userId`,
24
+ * `envelope.tenant` derived from the user) on every dispatch/query.
25
+ * Pass `undefined` to clear — the returned proxy is then anonymous.
26
+ *
27
+ * await harness.asUser({ id: "u1", roles: ["admin"] }).dispatch(...)
28
+ */
29
+ export function asUser(harness, user) {
30
+ // Direct runtime access — the public Harness.dispatch path only accepts
31
+ // tenant/userId/correlationId on its envelope arg. We need to seed
32
+ // `user` too, which is on MessageEnvelope but not on the public dispatch
33
+ // overload. Going through runtime.dispatch with a hand-seeded envelope
34
+ // keeps the type contract honest without widening the public Harness API.
35
+ const runtime = harness.app.runtime;
36
+ return {
37
+ async dispatch(action, input, envelope) {
38
+ if (!user)
39
+ return runtime.dispatch(action, input);
40
+ const { seedEnvelope } = await import("@nwire/envelope");
41
+ const seeded = seedEnvelope({
42
+ tenant: envelope?.tenant ?? user.tenant,
43
+ userId: user.id,
44
+ user,
45
+ correlationId: envelope?.correlationId,
46
+ });
47
+ return runtime.dispatch(action, input, seeded);
48
+ },
49
+ async query(queryDef, input, tenant) {
50
+ // Queries don't carry an envelope today — pass the user's tenant if
51
+ // the caller didn't override. Once queries gain envelope support,
52
+ // user pinning flows through here automatically.
53
+ return runtime.query(queryDef.name, input, tenant ?? user?.tenant ?? "");
54
+ },
55
+ };
56
+ }
57
+ /**
58
+ * Run a request through a Koa app as if it came over HTTP. The harness
59
+ * doesn't boot a Koa app on its own (the in-process runtime has no HTTP
60
+ * surface), so callers pass the wired Koa instance explicitly. Returns a
61
+ * minimal `{ status, body, headers }` triple so test code doesn't have to
62
+ * know the supertest API.
63
+ */
64
+ export async function simulateRequest(
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ app, req) {
67
+ if (!app) {
68
+ throw new Error("simulateRequest: pass a wired Koa app instance as the first argument — " +
69
+ "the harness's in-process runtime has no HTTP surface.");
70
+ }
71
+ // Accept either a raw Koa instance (has .callback) or a pre-built agent.
72
+ const agent = typeof app.callback === "function"
73
+ ? supertest(app.callback())
74
+ : app;
75
+ const method = req.method.toLowerCase();
76
+ let request = agent[method](req.path);
77
+ for (const [key, value] of Object.entries(req.headers ?? {})) {
78
+ request = request.set(key, value);
79
+ }
80
+ if (req.body !== undefined)
81
+ request = request.send(req.body);
82
+ const res = await request;
83
+ return {
84
+ status: res.status,
85
+ body: res.body,
86
+ headers: res.headers,
87
+ };
88
+ }
89
+ /**
90
+ * Build a CASL Ability for `user` via the supplied `buildAbility` factory
91
+ * and run a `can(action, subject?)` check. Returns `{ can, reason? }` —
92
+ * `reason` carries the CASL rule message when access is denied.
93
+ *
94
+ * Requires `@nwire/rbac` to be available. We `await import()` it instead
95
+ * of declaring a hard dep so test-kit doesn't force a CASL install on
96
+ * users who don't run RBAC tests.
97
+ */
98
+ export async function checkPolicy(buildAbility, user, action, subject) {
99
+ try {
100
+ const ability = buildAbility(user);
101
+ const can = subject !== undefined ? ability.can(action, subject) : ability.can(action);
102
+ if (can)
103
+ return { can: true };
104
+ const rule = typeof ability.relevantRuleFor === "function"
105
+ ? ability.relevantRuleFor(action, subject)
106
+ : null;
107
+ return { can: false, reason: rule?.reason ?? `denied: ${action}` };
108
+ }
109
+ catch (err) {
110
+ throw new Error(`checkPolicy: buildAbility threw — ensure @nwire/rbac is installed and the factory is valid (${err.message})`);
111
+ }
112
+ }
113
+ /**
114
+ * Tap every log call made through the harness's logger. Returns an
115
+ * unsubscribe function. The tap wraps `harness.app.runtime.logger` —
116
+ * `child()` calls inherit the tap so envelope-scoped loggers fire it too.
117
+ *
118
+ * This mutates the runtime's logger reference in place (test-only). Call
119
+ * the returned function to restore the original logger.
120
+ */
121
+ export function onLog(harness, handler) {
122
+ // We monkey-patch the logger on the runtime so every existing reference
123
+ // (envelope-scoped child loggers already handed out) sees the tap too.
124
+ // The wrapper preserves the original behavior and forwards calls.
125
+ const runtime = harness.app.runtime;
126
+ const original = runtime.logger;
127
+ const wrapped = wrapLogger(original, handler);
128
+ runtime.logger = wrapped;
129
+ return () => {
130
+ runtime.logger = original;
131
+ };
132
+ }
133
+ function wrapLogger(inner, handler) {
134
+ const wrap = (level) => function (message, fields) {
135
+ handler({ level, message, fields });
136
+ inner[level](message, fields);
137
+ };
138
+ return {
139
+ debug: wrap("debug"),
140
+ info: wrap("info"),
141
+ warn: wrap("warn"),
142
+ error: wrap("error"),
143
+ child(bindings) {
144
+ return wrapLogger(inner.child(bindings), handler);
145
+ },
146
+ };
147
+ }
148
+ //# sourceMappingURL=harness-extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-extensions.js","sourceRoot":"","sources":["../src/harness-extensions.ts"],"names":[],"mappings":"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"}
package/dist/harness.d.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  import type { AppDefinition, ActionDefinition, CreateAppOptions } from "@nwire/forge";
15
15
  import type { z } from "zod";
16
16
  import type { ZodTypeAny } from "@nwire/messages";
17
- import { TelemetryProbe } from "./telemetry-probe";
17
+ import { TelemetryProbe } from "./telemetry-probe.js";
18
18
  export interface HarnessOptions {
19
19
  /** AppDefinition to boot. */
20
20
  readonly app: AppDefinition;
package/dist/harness.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * Uses in-memory adapters by default. Pass `providers` to swap in real
12
12
  * stores / buses / queues for integration testing.
13
13
  */
14
- import { TelemetryProbe } from "./telemetry-probe";
14
+ import { TelemetryProbe } from "./telemetry-probe.js";
15
15
  export async function harness(options) {
16
16
  const app = options.app.create(options.providers ?? {});
17
17
  const probe = new TelemetryProbe();
@@ -1 +1 @@
1
- {"version":3,"file":"harness.js","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA6CnD,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAuB;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,cAAc,EAAE,CAAC;IACnC,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,GAAc,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACnF,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAElB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE;QAC3B,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,GAAG;QACH,SAAS,EAAE,KAAK;QAChB,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ;YACpC,OAAO,EAAE,CAAC;YACV,IAAI,CAAC;gBACH,uEAAuE;gBACvE,iEAAiE;gBACjE,wEAAwE;gBACxE,6DAA6D;gBAC7D,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;oBACzD,MAAM,MAAM,GAAG,YAAY,CAAC;wBAC1B,MAAM,EAAE,QAAQ,CAAC,MAAM;wBACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;wBACvB,aAAa,EAAE,QAAQ,CAAC,aAAa;qBACtC,CAAC,CAAC;oBACH,OAAO,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;gBAC3D,CAAC;gBACD,OAAO,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACnD,CAAC;oBAAS,CAAC;gBACT,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE;YACtC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACzD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,EAAE,EAAE,SAAS,GAAG,IAAI;YACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;gBAC7B,IAAI,OAAO,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,GAAG,OAAO;oBAAE,OAAO;gBACjE,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,CAAC;YACD,MAAM,IAAI,KAAK,CACb,gDAAgD,SAAS,eAAe,OAAO,GAAG,CACnF,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI;YACR,WAAW,EAAE,CAAC;YACd,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA6CnD,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAuB;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,cAAc,EAAE,CAAC;IACnC,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,GAAc,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACnF,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IAElB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9B,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE;QAC3B,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,GAAG;QACH,SAAS,EAAE,KAAK;QAChB,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ;YACpC,OAAO,EAAE,CAAC;YACV,IAAI,CAAC;gBACH,uEAAuE;gBACvE,iEAAiE;gBACjE,wEAAwE;gBACxE,6DAA6D;gBAC7D,IAAI,QAAQ,EAAE,CAAC;oBACb,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;oBACzD,MAAM,MAAM,GAAG,YAAY,CAAC;wBAC1B,MAAM,EAAE,QAAQ,CAAC,MAAM;wBACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;wBACvB,aAAa,EAAE,QAAQ,CAAC,aAAa;qBACtC,CAAC,CAAC;oBACH,OAAO,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAc,EAAE,MAAM,CAAC,CAAC;gBACpE,CAAC;gBACD,OAAO,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAc,CAAC,CAAC;YAC5D,CAAC;oBAAS,CAAC;gBACT,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QACD,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE;YACtC,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACzD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,EAAE,EAAE,SAAS,GAAG,IAAI;YACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;gBAC7B,IAAI,OAAO,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,GAAG,OAAO;oBAAE,OAAO;gBACjE,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;YAClB,CAAC;YACD,MAAM,IAAI,KAAK,CACb,gDAAgD,SAAS,eAAe,OAAO,GAAG,CACnF,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI;YACR,WAAW,EAAE,CAAC;YACd,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AACrD,CAAC"}
@@ -15,11 +15,12 @@
15
15
  *
16
16
  * Plus existing helpers: zod fixture factory, supertest agent.
17
17
  */
18
- export { harness, type Harness, type HarnessOptions } from "./harness";
19
- export { TelemetryProbe, type TelemetryFilter } from "./telemetry-probe";
20
- export { dockerCompose, PRESETS as DOCKER_COMPOSE_PRESETS, type ComposeStack, type ComposePreset, } from "./docker-compose";
21
- export { isReachable } from "./is-reachable";
22
- export { feature, type BddContext } from "./bdd";
23
- export { factory, sequence, type Factory, type SequenceCounter } from "./zod-fixture-factory";
24
- export { createTestApp, bootTestApp, type BootedTestApp } from "./supertest-app-helper";
18
+ export { harness, type Harness, type HarnessOptions } from "./harness.js";
19
+ export { asUser, simulateRequest, checkPolicy, onLog, type UserLike, type ScopedHarness, type SimulatedRequest, type SimulatedResponse, type PolicyCheck, type LogLevel, type LogEntry, } from "./harness-extensions.js";
20
+ export { TelemetryProbe, type TelemetryFilter } from "./telemetry-probe.js";
21
+ export { dockerCompose, PRESETS as DOCKER_COMPOSE_PRESETS, type ComposeStack, type ComposePreset, } from "./docker-compose.js";
22
+ export { isReachable } from "./is-reachable.js";
23
+ export { feature, type BddContext } from "./bdd.js";
24
+ export { factory, sequence, type Factory, type SequenceCounter } from "./zod-fixture-factory.js";
25
+ export { createTestApp, bootTestApp, type BootedTestApp } from "./supertest-app-helper.js";
25
26
  //# sourceMappingURL=test-kit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-kit.d.ts","sourceRoot":"","sources":["../src/test-kit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,aAAa,EACb,OAAO,IAAI,sBAAsB,EACjC,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAGjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"test-kit.d.ts","sourceRoot":"","sources":["../src/test-kit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,MAAM,EACN,eAAe,EACf,WAAW,EACX,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,QAAQ,GACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,aAAa,EACb,OAAO,IAAI,sBAAsB,EACjC,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC;AAGjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/test-kit.js CHANGED
@@ -15,12 +15,13 @@
15
15
  *
16
16
  * Plus existing helpers: zod fixture factory, supertest agent.
17
17
  */
18
- export { harness } from "./harness";
19
- export { TelemetryProbe } from "./telemetry-probe";
20
- export { dockerCompose, PRESETS as DOCKER_COMPOSE_PRESETS, } from "./docker-compose";
21
- export { isReachable } from "./is-reachable";
22
- export { feature } from "./bdd";
18
+ export { harness } from "./harness.js";
19
+ export { asUser, simulateRequest, checkPolicy, onLog, } from "./harness-extensions.js";
20
+ export { TelemetryProbe } from "./telemetry-probe.js";
21
+ export { dockerCompose, PRESETS as DOCKER_COMPOSE_PRESETS, } from "./docker-compose.js";
22
+ export { isReachable } from "./is-reachable.js";
23
+ export { feature } from "./bdd.js";
23
24
  // Pre-existing helpers
24
- export { factory, sequence } from "./zod-fixture-factory";
25
- export { createTestApp, bootTestApp } from "./supertest-app-helper";
25
+ export { factory, sequence } from "./zod-fixture-factory.js";
26
+ export { createTestApp, bootTestApp } from "./supertest-app-helper.js";
26
27
  //# sourceMappingURL=test-kit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-kit.js","sourceRoot":"","sources":["../src/test-kit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAqC,MAAM,WAAW,CAAC;AACvE,OAAO,EAAE,cAAc,EAAwB,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,aAAa,EACb,OAAO,IAAI,sBAAsB,GAGlC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAmB,MAAM,OAAO,CAAC;AAEjD,uBAAuB;AACvB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAsC,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,WAAW,EAAsB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"test-kit.js","sourceRoot":"","sources":["../src/test-kit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAqC,MAAM,WAAW,CAAC;AACvE,OAAO,EACL,MAAM,EACN,eAAe,EACf,WAAW,EACX,KAAK,GAQN,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAwB,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,aAAa,EACb,OAAO,IAAI,sBAAsB,GAGlC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAmB,MAAM,OAAO,CAAC;AAEjD,uBAAuB;AACvB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAsC,MAAM,uBAAuB,CAAC;AAC9F,OAAO,EAAE,aAAa,EAAE,WAAW,EAAsB,MAAM,wBAAwB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nwire/test-kit",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Shared test helpers and zod-driven fixture factories",
5
5
  "keywords": [
6
6
  "fixtures",
@@ -28,15 +28,17 @@
28
28
  "dependencies": {
29
29
  "supertest": "^7.2.2",
30
30
  "zod": "^4.0.0",
31
- "@nwire/envelope": "0.8.0",
32
- "@nwire/forge": "0.8.0",
33
- "@nwire/messages": "0.8.0"
31
+ "@nwire/forge": "0.9.0",
32
+ "@nwire/messages": "0.8.17",
33
+ "@nwire/envelope": "0.8.17",
34
+ "@nwire/logger": "0.8.17"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/koa": "^2.15.0",
37
38
  "@types/node": "^22.19.9",
38
39
  "@types/supertest": "^6.0.3",
39
- "typescript": "^5.9.3"
40
+ "typescript": "^5.9.3",
41
+ "@nwire/http": "0.9.0"
40
42
  },
41
43
  "peerDependencies": {
42
44
  "@amiceli/vitest-cucumber": "^4.0.0",
@@ -51,7 +53,7 @@
51
53
  }
52
54
  },
53
55
  "scripts": {
54
- "build": "tsc",
56
+ "build": "tsc && node ../../scripts/fix-dist-extensions.mjs dist",
55
57
  "dev": "tsc --watch",
56
58
  "typecheck": "tsc --noEmit"
57
59
  }