@fuzdev/fuz_app 0.127.0 → 0.128.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/dist/actions/CLAUDE.md +35 -18
- package/dist/actions/connection_closer.d.ts +16 -4
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +20 -4
- package/dist/actions/register_action_ws.d.ts +10 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +6 -3
- package/dist/actions/register_ws_endpoint.d.ts +3 -1
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +3 -1
- package/dist/auth/CLAUDE.md +13 -9
- package/dist/auth/audit_emitter.d.ts +59 -12
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +53 -15
- package/dist/auth/middleware.d.ts +30 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +59 -0
- package/dist/realtime/sse_auth_guard.d.ts +3 -1
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/server/app_server.d.ts +39 -12
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +92 -39
- package/dist/testing/CLAUDE.md +36 -23
- package/dist/testing/app_server.d.ts +1 -1
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/attack_surface.d.ts +1 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.d.ts +4 -2
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +14 -4
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +1 -2
- package/dist/testing/cross_backend/capabilities.d.ts +3 -1
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +27 -18
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +30 -19
- package/dist/testing/cross_backend/ws_round_trip.d.ts +2 -1
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +40 -4
- package/dist/testing/stubs.d.ts +23 -6
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +39 -14
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAejC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsDpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA2J3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,
|
|
1
|
+
{"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAejC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsDpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA2J3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,iIAAiI;IACjI,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAC5D;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA4EF,CAAC"}
|
|
@@ -46,8 +46,10 @@ export interface RecordingAuditEmitter {
|
|
|
46
46
|
* forward from the input envelope. Tests asserting on role-grant-shape
|
|
47
47
|
* emissions read out of the same homogeneous `calls` array.
|
|
48
48
|
* `notify` is a no-op; `add_listener` records into a local array that
|
|
49
|
-
* `listener_count` reports
|
|
50
|
-
*
|
|
49
|
+
* `listener_count` reports, and returns a remover with the production
|
|
50
|
+
* semantics (per registration, idempotent) — registered listeners never fire,
|
|
51
|
+
* since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once:
|
|
52
|
+
* a recorded call writes nothing, so nothing is ever in flight.
|
|
51
53
|
*
|
|
52
54
|
* `emit` AND `emit_pool` both append to `calls`, so a test reads either
|
|
53
55
|
* entry point's writes off the same array.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAiB5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED
|
|
1
|
+
{"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEhF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAiB5C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBAqCF,CAAC"}
|
|
@@ -44,8 +44,10 @@ export const install_audit_drift_guard = () => {
|
|
|
44
44
|
* forward from the input envelope. Tests asserting on role-grant-shape
|
|
45
45
|
* emissions read out of the same homogeneous `calls` array.
|
|
46
46
|
* `notify` is a no-op; `add_listener` records into a local array that
|
|
47
|
-
* `listener_count` reports
|
|
48
|
-
*
|
|
47
|
+
* `listener_count` reports, and returns a remover with the production
|
|
48
|
+
* semantics (per registration, idempotent) — registered listeners never fire,
|
|
49
|
+
* since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once:
|
|
50
|
+
* a recorded call writes nothing, so nothing is ever in flight.
|
|
49
51
|
*
|
|
50
52
|
* `emit` AND `emit_pool` both append to `calls`, so a test reads either
|
|
51
53
|
* entry point's writes off the same array.
|
|
@@ -65,6 +67,7 @@ export const install_audit_drift_guard = () => {
|
|
|
65
67
|
*/
|
|
66
68
|
export const create_recording_audit_emitter = (calls_ref) => {
|
|
67
69
|
const calls = calls_ref ?? [];
|
|
70
|
+
// one entry per registration, as `create_audit_emitter` keeps them
|
|
68
71
|
const listeners = [];
|
|
69
72
|
const emitter = {
|
|
70
73
|
emit: (_ctx, input) => {
|
|
@@ -88,9 +91,16 @@ export const create_recording_audit_emitter = (calls_ref) => {
|
|
|
88
91
|
},
|
|
89
92
|
notify: () => undefined,
|
|
90
93
|
add_listener: (listener) => {
|
|
91
|
-
|
|
94
|
+
const entry = { listener };
|
|
95
|
+
listeners.push(entry);
|
|
96
|
+
return () => {
|
|
97
|
+
const index = listeners.indexOf(entry);
|
|
98
|
+
if (index !== -1)
|
|
99
|
+
listeners.splice(index, 1);
|
|
100
|
+
};
|
|
92
101
|
},
|
|
93
|
-
listener_count: () => listeners.length
|
|
102
|
+
listener_count: () => listeners.length,
|
|
103
|
+
drain_inflight: () => Promise.resolve()
|
|
94
104
|
};
|
|
95
105
|
return { emitter, calls };
|
|
96
106
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AAEvF,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,
|
|
1
|
+
{"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AAEvF,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IAoUF,CAAC"}
|
|
@@ -88,8 +88,7 @@ export const describe_account_lifecycle_cross_tests = (options) => {
|
|
|
88
88
|
const del = await cross_rpc_call(t, rpc_path, 'account_delete', { account_id: fixture.account.id }, fixture.create_session_headers());
|
|
89
89
|
assert.strictEqual(error_reason(del), ERROR_CANNOT_DELETE_KEEPER);
|
|
90
90
|
// Deterministic barrier before reading: await in-flight
|
|
91
|
-
// fire-and-forget audit writes
|
|
92
|
-
// satisfied-by-construction on the TS spine via await_pending_effects).
|
|
91
|
+
// fire-and-forget audit writes on either spine.
|
|
93
92
|
const td = fixture.fresh_transport({ origin: null });
|
|
94
93
|
const drained = await cross_rpc_call(td, rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
|
|
95
94
|
assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
|
|
@@ -183,7 +183,9 @@ export interface BackendCapabilities {
|
|
|
183
183
|
* (`account_session_revoke_all` among them), so a socket can revoke the
|
|
184
184
|
* session it is running on. Gates the self-revocation case in
|
|
185
185
|
* `describe_cross_process_ws_tests`: the socket reads the reply to its
|
|
186
|
-
* request, then is closed with `WS_CLOSE_SESSION_REVOKED`.
|
|
186
|
+
* request, then is closed with `WS_CLOSE_SESSION_REVOKED`. Also gates that
|
|
187
|
+
* suite's WS-mutation audit case (with `rpc_path`; it calls
|
|
188
|
+
* `_testing_drain_effects`).
|
|
187
189
|
*
|
|
188
190
|
* `true` on fuz_app's own spine presets — the TS spine binary mounts its
|
|
189
191
|
* full action set on `/api/ws`, and the Rust `testing_spine_stub` serves
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4C9B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,kCAAkC,EAAE,OAAO,CAAC;IACrD;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4C9B;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACnC;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,kCAAkC,EAAE,OAAO,CAAC;IACrD;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,wBAAwB,EAAE,OAAO,CAAC;IAC3C;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAsBpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAInC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
|
|
@@ -71,6 +71,7 @@ import '../assert_dev_env.js';
|
|
|
71
71
|
import { z } from 'zod';
|
|
72
72
|
import { type RpcAction } from '../../actions/action_rpc.js';
|
|
73
73
|
import type { AppDeps } from '../../auth/deps.js';
|
|
74
|
+
import type { AuditEmitter } from '../../auth/audit_emitter.js';
|
|
74
75
|
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
75
76
|
import type { DaemonTokenState } from '../../auth/daemon_token.js';
|
|
76
77
|
import type { Db } from '../../db/db.js';
|
|
@@ -162,16 +163,21 @@ export declare const testing_reset_action_spec: {
|
|
|
162
163
|
* the cross-backend conformance suite uses in place of a poll/sleep before
|
|
163
164
|
* asserting on audit rows.
|
|
164
165
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
166
|
+
* The handler awaits `AuditEmitter.drain_inflight`, which waits until no
|
|
167
|
+
* tracked `emit` write is outstanding — so the binary's emitter must be built
|
|
168
|
+
* with `track_inflight: true`, or the drain resolves at once and is no barrier.
|
|
169
|
+
* Tracking is what makes it a barrier whatever the transport: an **HTTP**
|
|
170
|
+
* mutation on a binary running `await_pending_effects: true` has its writes
|
|
171
|
+
* settled by the time it responds, but a **WS** mutation is answered before
|
|
172
|
+
* its effects flush (reply-then-close for a self-revocation), so its audit
|
|
173
|
+
* write can still be in flight when the caller reads the reply. The Rust
|
|
174
|
+
* spine does the same await in `AuditEmitter::drain_inflight`, over an
|
|
175
|
+
* emitter built with `new_with_inflight_tracking`.
|
|
176
|
+
*
|
|
177
|
+
* Covered: every `emit` that ran before the drain was dispatched, and any
|
|
178
|
+
* started while it waits. A write that starts after the drain resolves is
|
|
179
|
+
* not — nor are eager writes outside `emit` (session touch, token usage),
|
|
180
|
+
* which only the dispatching transport's own flush awaits.
|
|
175
181
|
*
|
|
176
182
|
* `auth` gates on the daemon-token credential, matching `_testing_reset`.
|
|
177
183
|
*/
|
|
@@ -243,13 +249,15 @@ export declare const testing_mint_session_action_spec: {
|
|
|
243
249
|
readonly description: string;
|
|
244
250
|
};
|
|
245
251
|
/**
|
|
246
|
-
* Build the standalone `_testing_drain_effects` action
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* suite asserts on audit rows (the spine binary bundles it via
|
|
252
|
+
* Build the standalone `_testing_drain_effects` action over `audit` — the
|
|
253
|
+
* emitter the backend's handlers emit through (`deps.audit`), built with
|
|
254
|
+
* `track_inflight: true` (see the spec doc). Mount it on any test endpoint
|
|
255
|
+
* whose suite asserts on audit rows (the spine binary bundles it via
|
|
250
256
|
* `create_testing_actions`; in-process suites mount it directly).
|
|
257
|
+
*
|
|
258
|
+
* @param audit - the backend's emitter, whose in-flight writes the action awaits
|
|
251
259
|
*/
|
|
252
|
-
export declare const create_testing_drain_effects_action: () => RpcAction;
|
|
260
|
+
export declare const create_testing_drain_effects_action: (audit: AuditEmitter) => RpcAction;
|
|
253
261
|
/**
|
|
254
262
|
* `_testing_put_fact` — seed an **embedded** fact (`fact.bytes`) for the
|
|
255
263
|
* cross-process fact-serving suite, which drives over real HTTP and has no
|
|
@@ -475,9 +483,10 @@ export interface CreateTestingActionsOptions {
|
|
|
475
483
|
/**
|
|
476
484
|
* Build the testing RPC actions for a test binary's registry.
|
|
477
485
|
*
|
|
478
|
-
* Returns `
|
|
479
|
-
*
|
|
480
|
-
*
|
|
486
|
+
* Returns the bundled `_testing_*` actions (module doc). The test binary
|
|
487
|
+
* calls this at server-assembly time and registers the result on its
|
|
488
|
+
* dispatcher. `_testing_drain_effects` drains `deps.audit`, so the binary's
|
|
489
|
+
* `audit_factory` builds the emitter with `track_inflight: true`.
|
|
481
490
|
*
|
|
482
491
|
* The reset action's table-wipe list mirrors
|
|
483
492
|
* `auth_integration_truncate_tables` from `testing/db.ts` — the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAqCzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C
|
|
1
|
+
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAKzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AAqCzC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAwBC,CAAC;AAE/C;;;;;;;;GAQG;AACH,eAAO,MAAM,mCAAmC,GAAI,OAAO,YAAY,KAAG,SAIvE,CAAC;AAEJ;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qCAAqC;;;;;;;;;;;;;;;;;;;;CAYJ,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,uCAAuC,QAAO,SAGzD,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAaF,CAAC;AAyB/C,eAAO,MAAM,qCAAqC,GACjD,SAAS,aAAa,CAAC,SAAS,CAAC,KAC/B,SAMF,CAAC;AAEF,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAsIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
|
|
@@ -168,16 +168,21 @@ export const testing_reset_action_spec = {
|
|
|
168
168
|
* the cross-backend conformance suite uses in place of a poll/sleep before
|
|
169
169
|
* asserting on audit rows.
|
|
170
170
|
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
171
|
+
* The handler awaits `AuditEmitter.drain_inflight`, which waits until no
|
|
172
|
+
* tracked `emit` write is outstanding — so the binary's emitter must be built
|
|
173
|
+
* with `track_inflight: true`, or the drain resolves at once and is no barrier.
|
|
174
|
+
* Tracking is what makes it a barrier whatever the transport: an **HTTP**
|
|
175
|
+
* mutation on a binary running `await_pending_effects: true` has its writes
|
|
176
|
+
* settled by the time it responds, but a **WS** mutation is answered before
|
|
177
|
+
* its effects flush (reply-then-close for a self-revocation), so its audit
|
|
178
|
+
* write can still be in flight when the caller reads the reply. The Rust
|
|
179
|
+
* spine does the same await in `AuditEmitter::drain_inflight`, over an
|
|
180
|
+
* emitter built with `new_with_inflight_tracking`.
|
|
181
|
+
*
|
|
182
|
+
* Covered: every `emit` that ran before the drain was dispatched, and any
|
|
183
|
+
* started while it waits. A write that starts after the drain resolves is
|
|
184
|
+
* not — nor are eager writes outside `emit` (session touch, token usage),
|
|
185
|
+
* which only the dispatching transport's own flush awaits.
|
|
181
186
|
*
|
|
182
187
|
* `auth` gates on the daemon-token credential, matching `_testing_reset`.
|
|
183
188
|
*/
|
|
@@ -246,13 +251,18 @@ export const testing_mint_session_action_spec = {
|
|
|
246
251
|
'signed cookie value (exercises the authoritative server-side DB-row expiry gate).'
|
|
247
252
|
};
|
|
248
253
|
/**
|
|
249
|
-
* Build the standalone `_testing_drain_effects` action
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* suite asserts on audit rows (the spine binary bundles it via
|
|
254
|
+
* Build the standalone `_testing_drain_effects` action over `audit` — the
|
|
255
|
+
* emitter the backend's handlers emit through (`deps.audit`), built with
|
|
256
|
+
* `track_inflight: true` (see the spec doc). Mount it on any test endpoint
|
|
257
|
+
* whose suite asserts on audit rows (the spine binary bundles it via
|
|
253
258
|
* `create_testing_actions`; in-process suites mount it directly).
|
|
259
|
+
*
|
|
260
|
+
* @param audit - the backend's emitter, whose in-flight writes the action awaits
|
|
254
261
|
*/
|
|
255
|
-
export const create_testing_drain_effects_action = () => rpc_action(testing_drain_effects_action_spec, async () =>
|
|
262
|
+
export const create_testing_drain_effects_action = (audit) => rpc_action(testing_drain_effects_action_spec, async () => {
|
|
263
|
+
await audit.drain_inflight();
|
|
264
|
+
return { ok: true };
|
|
265
|
+
});
|
|
256
266
|
/**
|
|
257
267
|
* `_testing_put_fact` — seed an **embedded** fact (`fact.bytes`) for the
|
|
258
268
|
* cross-process fact-serving suite, which drives over real HTTP and has no
|
|
@@ -405,9 +415,10 @@ export const create_testing_action_manifest_action = (mounted) => {
|
|
|
405
415
|
/**
|
|
406
416
|
* Build the testing RPC actions for a test binary's registry.
|
|
407
417
|
*
|
|
408
|
-
* Returns `
|
|
409
|
-
*
|
|
410
|
-
*
|
|
418
|
+
* Returns the bundled `_testing_*` actions (module doc). The test binary
|
|
419
|
+
* calls this at server-assembly time and registers the result on its
|
|
420
|
+
* dispatcher. `_testing_drain_effects` drains `deps.audit`, so the binary's
|
|
421
|
+
* `audit_factory` builds the emitter with `track_inflight: true`.
|
|
411
422
|
*
|
|
412
423
|
* The reset action's table-wipe list mirrors
|
|
413
424
|
* `auth_integration_truncate_tables` from `testing/db.ts` — the
|
|
@@ -533,7 +544,7 @@ export const create_testing_actions = (deps, options) => {
|
|
|
533
544
|
});
|
|
534
545
|
return { hash };
|
|
535
546
|
}),
|
|
536
|
-
create_testing_drain_effects_action(),
|
|
547
|
+
create_testing_drain_effects_action(deps.audit),
|
|
537
548
|
create_testing_schema_snapshot_action(),
|
|
538
549
|
create_testing_migration_tracker_action()
|
|
539
550
|
];
|
|
@@ -38,7 +38,8 @@ export interface CrossProcessWsTestOptions {
|
|
|
38
38
|
* oversized message closing with `WS_CLOSE_MESSAGE_TOO_BIG`, anonymous-upgrade
|
|
39
39
|
* refusal, disallowed-origin refusal, and — when `rpc_path` is supplied —
|
|
40
40
|
* session revocation and account deletion closing the live socket, plus a
|
|
41
|
-
* self-revoking socket reading its reply before its close
|
|
41
|
+
* self-revoking socket reading its reply before its close, and a WS mutation's
|
|
42
|
+
* audit row read back after `_testing_drain_effects`.
|
|
42
43
|
*/
|
|
43
44
|
export declare const describe_cross_process_ws_tests: (options: CrossProcessWsTestOptions) => void;
|
|
44
45
|
//# sourceMappingURL=ws_round_trip.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwG9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAK5C,iEAAiE;AACjE,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,yBAAyB,KAAG,IAkVpF,CAAC"}
|
|
@@ -55,7 +55,11 @@ import '../assert_dev_env.js';
|
|
|
55
55
|
* `capabilities.ws_self_revocation_reply` — reads the reply to its request
|
|
56
56
|
* first. The TS spine queues the close behind the commit and sends the reply
|
|
57
57
|
* ahead of it, so the caller is never left without an answer; the Rust spine
|
|
58
|
-
* does not answer yet, which that flag records.
|
|
58
|
+
* does not answer yet, which that flag records. Under the same gate (and
|
|
59
|
+
* `rpc_path`), a socket mints an API token over WS and the token's
|
|
60
|
+
* `token_create` audit row is read back after `_testing_drain_effects` — the
|
|
61
|
+
* barrier a WS mutation needs, since a WS reply can precede its audit write.
|
|
62
|
+
* The binary's `create_testing_actions` mount provides the drain.
|
|
59
63
|
*
|
|
60
64
|
* Gated on `capabilities.ws` — backends without an end-to-end WS transport
|
|
61
65
|
* skip (the cases still surface as `.skip` in the report). Cross-process
|
|
@@ -68,14 +72,15 @@ import { assert, describe } from 'vitest';
|
|
|
68
72
|
import { assert_rejects } from '@fuzdev/fuz_util/testing.ts';
|
|
69
73
|
import { heartbeat_action_spec } from '../../actions/heartbeat.js';
|
|
70
74
|
import { DEFAULT_WS_MAX_MESSAGE_BYTES, WS_CLOSE_MESSAGE_TOO_BIG, WS_CLOSE_SESSION_REVOKED } from '../../actions/transports.js';
|
|
71
|
-
import { account_session_revoke_all_action_spec } from '../../auth/account_action_specs.js';
|
|
72
|
-
import { account_delete_action_spec } from '../../auth/admin_action_specs.js';
|
|
75
|
+
import { account_session_revoke_all_action_spec, account_token_create_action_spec } from '../../auth/account_action_specs.js';
|
|
76
|
+
import { account_delete_action_spec, audit_log_list_action_spec, AuditLogListOutput } from '../../auth/admin_action_specs.js';
|
|
73
77
|
import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
|
|
74
78
|
import { is_response_for } from '../transports/ws_client.js';
|
|
75
79
|
import { create_ws_transport } from '../transports/ws_transport.js';
|
|
76
80
|
import { connect_raw_ws, is_raw_ws_close, RAW_WS_OPCODE_TEXT } from '../transports/ws_raw_client.js';
|
|
77
81
|
import { create_rpc_post_init } from '../rpc_helpers.js';
|
|
78
82
|
import { test_if } from './capabilities.js';
|
|
83
|
+
import { cross_rpc_call, expect_output } from './cell_cross_helpers.js';
|
|
79
84
|
/** Origin guaranteed to fail the `http://localhost:*` allowlist the test backends run with. */
|
|
80
85
|
const DISALLOWED_ORIGIN = 'http://disallowed.example';
|
|
81
86
|
/**
|
|
@@ -84,7 +89,8 @@ const DISALLOWED_ORIGIN = 'http://disallowed.example';
|
|
|
84
89
|
* oversized message closing with `WS_CLOSE_MESSAGE_TOO_BIG`, anonymous-upgrade
|
|
85
90
|
* refusal, disallowed-origin refusal, and — when `rpc_path` is supplied —
|
|
86
91
|
* session revocation and account deletion closing the live socket, plus a
|
|
87
|
-
* self-revoking socket reading its reply before its close
|
|
92
|
+
* self-revoking socket reading its reply before its close, and a WS mutation's
|
|
93
|
+
* audit row read back after `_testing_drain_effects`.
|
|
88
94
|
*/
|
|
89
95
|
export const describe_cross_process_ws_tests = (options) => {
|
|
90
96
|
const { setup_test, capabilities, base_url, ws_path, origin, rpc_path, max_message_bytes = DEFAULT_WS_MAX_MESSAGE_BYTES } = options;
|
|
@@ -308,5 +314,35 @@ export const describe_cross_process_ws_tests = (options) => {
|
|
|
308
314
|
await client.close();
|
|
309
315
|
}
|
|
310
316
|
});
|
|
317
|
+
// A WS mutation is answered before its effects flush, so its audit row
|
|
318
|
+
// can land after the reply. `_testing_drain_effects` is the barrier: once
|
|
319
|
+
// it returns, the row is visible on either spine.
|
|
320
|
+
test_if(capabilities.ws && capabilities.ws_account_actions && rpc_path !== undefined, "a WS mutation's audit row is visible after _testing_drain_effects", async () => {
|
|
321
|
+
const fixture = await setup_test();
|
|
322
|
+
const client = await create_ws_transport({
|
|
323
|
+
base_url,
|
|
324
|
+
ws_path,
|
|
325
|
+
cookies: fixture.transport.cookies(),
|
|
326
|
+
origin
|
|
327
|
+
});
|
|
328
|
+
try {
|
|
329
|
+
await client.request(1, account_token_create_action_spec.method, {
|
|
330
|
+
name: 'ws drain probe',
|
|
331
|
+
scope: { kind: 'full' },
|
|
332
|
+
lifetime: { kind: 'eternal' }
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
await client.close();
|
|
337
|
+
}
|
|
338
|
+
const drained = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
|
|
339
|
+
assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
|
|
340
|
+
// `_testing_reset` wiped audit_log at setup, so the mint is the only
|
|
341
|
+
// token_create event
|
|
342
|
+
const listed = expect_output(await cross_rpc_call(fixture.transport, rpc_path, audit_log_list_action_spec.method, { event_type: 'token_create' }, fixture.create_session_headers()), AuditLogListOutput);
|
|
343
|
+
assert.strictEqual(listed.events.length, 1, JSON.stringify(listed.events));
|
|
344
|
+
assert.strictEqual(listed.events[0].outcome, 'success');
|
|
345
|
+
assert.strictEqual(listed.events[0].account_id, fixture.account.id);
|
|
346
|
+
});
|
|
311
347
|
});
|
|
312
348
|
};
|
package/dist/testing/stubs.d.ts
CHANGED
|
@@ -62,7 +62,8 @@ export declare const stub_mw: (_c: any, next: any) => Promise<void>;
|
|
|
62
62
|
* immediately; `notify` is a no-op; `add_listener` throws, so a test that
|
|
63
63
|
* wires a listener fails loudly instead of silently never firing
|
|
64
64
|
* (`create_recording_audit_emitter` accepts listeners); `listener_count`
|
|
65
|
-
* returns 0
|
|
65
|
+
* returns 0; `drain_inflight` resolves immediately, since nothing is ever
|
|
66
|
+
* written. Tests asserting on real audit-row persistence (or on listener
|
|
66
67
|
* fan-out) build a real emitter via `create_audit_emitter` against a stub or
|
|
67
68
|
* real DB — `create_test_app` already does this on the test backend.
|
|
68
69
|
*/
|
|
@@ -84,7 +85,11 @@ export declare const stub_app_deps: AppDeps;
|
|
|
84
85
|
* Create no-op `AppDeps` for auth surface testing.
|
|
85
86
|
*/
|
|
86
87
|
export declare const create_stub_app_deps: () => AppDeps;
|
|
87
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Create the API middleware stub array matching `create_auth_middleware_specs`
|
|
90
|
+
* output. Like the real factory's, each spec gets its own pass-through handler,
|
|
91
|
+
* so `assert_middleware_stack_mounted` can tell the layers apart by identity.
|
|
92
|
+
*/
|
|
88
93
|
export declare const create_stub_api_middleware: (options?: {
|
|
89
94
|
/** Include the daemon_token middleware layer. */
|
|
90
95
|
include_daemon_token?: boolean;
|
|
@@ -136,7 +141,15 @@ export interface CreateTestAppSurfaceSpecOptions {
|
|
|
136
141
|
* the exact patterns the upgrade gate matches.
|
|
137
142
|
*/
|
|
138
143
|
allowed_origins?: ReadonlyArray<RegExp>;
|
|
139
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Transform middleware array — `AppServerOptions.transform_middleware`.
|
|
146
|
+
* Receives shallow copies of a stub `trusted_proxy` spec (at `'*'`)
|
|
147
|
+
* followed by the stub auth stack, in a fresh array, as
|
|
148
|
+
* `create_app_server` passes the real ones. Throws, like
|
|
149
|
+
* `create_app_server`, if the result doesn't keep each of those specs
|
|
150
|
+
* mounted once at its original path in its original order
|
|
151
|
+
* (`assert_middleware_stack_mounted`).
|
|
152
|
+
*/
|
|
140
153
|
transform_middleware?: (specs: Array<MiddlewareSpec>) => Array<MiddlewareSpec>;
|
|
141
154
|
/**
|
|
142
155
|
* Bootstrap config — symmetric with `AppServerOptions.bootstrap`. Discriminated
|
|
@@ -162,15 +175,19 @@ export interface CreateTestAppSurfaceSpecOptions {
|
|
|
162
175
|
* Mirrors `create_app_server`'s route assembly: consumer routes +
|
|
163
176
|
* factory-managed bootstrap routes + surface generation — including its
|
|
164
177
|
* refusal of an `rpc_endpoints` or `ws_endpoints` path outside the auth
|
|
165
|
-
* middleware's scope (`assert_endpoint_in_auth_scope`)
|
|
178
|
+
* middleware's scope (`assert_endpoint_in_auth_scope`) and of a
|
|
179
|
+
* `transform_middleware` that doesn't keep the proxy and auth stack mounted
|
|
180
|
+
* (`assert_middleware_stack_mounted`). The middleware list mirrors the real
|
|
181
|
+
* one: a stub `trusted_proxy` spec at `'*'`, then the stub auth stack. If
|
|
166
182
|
* `create_app_server` changes how it wires routes, update this helper
|
|
167
183
|
* to stay in sync (single source of truth for all consumers).
|
|
168
184
|
*
|
|
169
185
|
* @param options - surface spec options
|
|
170
186
|
* @returns the surface spec for the standard suites
|
|
171
|
-
* @throws Error when an endpoint path is outside `AUTH_MIDDLEWARE_PATH`,
|
|
187
|
+
* @throws Error when an endpoint path is outside `AUTH_MIDDLEWARE_PATH`, a
|
|
172
188
|
* `ws_endpoints` spec declares no `allowed_origins` and no `allowed_origins`
|
|
173
|
-
* option is given
|
|
189
|
+
* option is given, or `transform_middleware` moves, drops, duplicates,
|
|
190
|
+
* reorders, wraps, or replaces the stub proxy spec or a stub auth spec
|
|
174
191
|
*/
|
|
175
192
|
export declare const create_test_app_surface_spec: (options: CreateTestAppSurfaceSpecOptions) => AppSurfaceSpec;
|
|
176
193
|
//# sourceMappingURL=stubs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAa7B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAG3E,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/stubs.js
CHANGED
|
@@ -19,7 +19,7 @@ import { resolve_ws_endpoints } from '../actions/ws_endpoint_spec.js';
|
|
|
19
19
|
import { AUDIT_LOG_SSE_MAX_PER_SCOPE } from '../realtime/sse_auth_guard.js';
|
|
20
20
|
import { SubscriberRegistry } from '../realtime/subscriber_registry.js';
|
|
21
21
|
import { BaseServerEnv } from '../server/env.js';
|
|
22
|
-
import { AUTH_MIDDLEWARE_PATH, assert_endpoint_in_auth_scope } from '../auth/middleware.js';
|
|
22
|
+
import { AUTH_MIDDLEWARE_PATH, assert_endpoint_in_auth_scope, assert_middleware_stack_mounted } from '../auth/middleware.js';
|
|
23
23
|
/**
|
|
24
24
|
* Create a Proxy that throws descriptive errors on any property access or method call.
|
|
25
25
|
*
|
|
@@ -99,7 +99,8 @@ const stub_db = create_noop_stub('stub_db');
|
|
|
99
99
|
* immediately; `notify` is a no-op; `add_listener` throws, so a test that
|
|
100
100
|
* wires a listener fails loudly instead of silently never firing
|
|
101
101
|
* (`create_recording_audit_emitter` accepts listeners); `listener_count`
|
|
102
|
-
* returns 0
|
|
102
|
+
* returns 0; `drain_inflight` resolves immediately, since nothing is ever
|
|
103
|
+
* written. Tests asserting on real audit-row persistence (or on listener
|
|
103
104
|
* fan-out) build a real emitter via `create_audit_emitter` against a stub or
|
|
104
105
|
* real DB — `create_test_app` already does this on the test backend.
|
|
105
106
|
*/
|
|
@@ -111,7 +112,8 @@ export const create_test_audit_emitter = () => ({
|
|
|
111
112
|
add_listener: () => {
|
|
112
113
|
throw new Error('create_test_audit_emitter accepts no listeners — use create_recording_audit_emitter');
|
|
113
114
|
},
|
|
114
|
-
listener_count: () => 0
|
|
115
|
+
listener_count: () => 0,
|
|
116
|
+
drain_inflight: async () => { }
|
|
115
117
|
});
|
|
116
118
|
/**
|
|
117
119
|
* Build a no-op `AuditLogSse` for tests that wire `audit_sse` into the
|
|
@@ -159,16 +161,27 @@ export const create_stub_app_deps = () => ({
|
|
|
159
161
|
audit: create_test_audit_emitter(),
|
|
160
162
|
connection_closer: create_realtime_closer()
|
|
161
163
|
});
|
|
162
|
-
/**
|
|
164
|
+
/** A fresh pass-through middleware — a distinct handler per call, unlike the shared `stub_mw`. */
|
|
165
|
+
const create_stub_mw = () => async (_c, next) => next();
|
|
166
|
+
/**
|
|
167
|
+
* Create the API middleware stub array matching `create_auth_middleware_specs`
|
|
168
|
+
* output. Like the real factory's, each spec gets its own pass-through handler,
|
|
169
|
+
* so `assert_middleware_stack_mounted` can tell the layers apart by identity.
|
|
170
|
+
*/
|
|
163
171
|
export const create_stub_api_middleware = (options) => {
|
|
164
172
|
const specs = [
|
|
165
|
-
{
|
|
166
|
-
|
|
167
|
-
|
|
173
|
+
{
|
|
174
|
+
name: 'origin',
|
|
175
|
+
path: AUTH_MIDDLEWARE_PATH,
|
|
176
|
+
handler: create_stub_mw(),
|
|
177
|
+
errors: { 403: ApiError }
|
|
178
|
+
},
|
|
179
|
+
{ name: 'session', path: AUTH_MIDDLEWARE_PATH, handler: create_stub_mw() },
|
|
180
|
+
{ name: 'request_context', path: AUTH_MIDDLEWARE_PATH, handler: create_stub_mw() },
|
|
168
181
|
{
|
|
169
182
|
name: 'bearer_auth',
|
|
170
183
|
path: AUTH_MIDDLEWARE_PATH,
|
|
171
|
-
handler:
|
|
184
|
+
handler: create_stub_mw(),
|
|
172
185
|
errors: { 401: ApiError, 403: ApiError, 429: RateLimitError }
|
|
173
186
|
}
|
|
174
187
|
];
|
|
@@ -176,7 +189,7 @@ export const create_stub_api_middleware = (options) => {
|
|
|
176
189
|
specs.push({
|
|
177
190
|
name: 'daemon_token',
|
|
178
191
|
path: AUTH_MIDDLEWARE_PATH,
|
|
179
|
-
handler:
|
|
192
|
+
handler: create_stub_mw(),
|
|
180
193
|
errors: { 401: ApiError, 500: ApiError, 503: ApiError }
|
|
181
194
|
});
|
|
182
195
|
}
|
|
@@ -241,15 +254,19 @@ const to_self_resolved_ws_endpoint = (endpoint) => {
|
|
|
241
254
|
* Mirrors `create_app_server`'s route assembly: consumer routes +
|
|
242
255
|
* factory-managed bootstrap routes + surface generation — including its
|
|
243
256
|
* refusal of an `rpc_endpoints` or `ws_endpoints` path outside the auth
|
|
244
|
-
* middleware's scope (`assert_endpoint_in_auth_scope`)
|
|
257
|
+
* middleware's scope (`assert_endpoint_in_auth_scope`) and of a
|
|
258
|
+
* `transform_middleware` that doesn't keep the proxy and auth stack mounted
|
|
259
|
+
* (`assert_middleware_stack_mounted`). The middleware list mirrors the real
|
|
260
|
+
* one: a stub `trusted_proxy` spec at `'*'`, then the stub auth stack. If
|
|
245
261
|
* `create_app_server` changes how it wires routes, update this helper
|
|
246
262
|
* to stay in sync (single source of truth for all consumers).
|
|
247
263
|
*
|
|
248
264
|
* @param options - surface spec options
|
|
249
265
|
* @returns the surface spec for the standard suites
|
|
250
|
-
* @throws Error when an endpoint path is outside `AUTH_MIDDLEWARE_PATH`,
|
|
266
|
+
* @throws Error when an endpoint path is outside `AUTH_MIDDLEWARE_PATH`, a
|
|
251
267
|
* `ws_endpoints` spec declares no `allowed_origins` and no `allowed_origins`
|
|
252
|
-
* option is given
|
|
268
|
+
* option is given, or `transform_middleware` moves, drops, duplicates,
|
|
269
|
+
* reorders, wraps, or replaces the stub proxy spec or a stub auth spec
|
|
253
270
|
*/
|
|
254
271
|
export const create_test_app_surface_spec = (options) => {
|
|
255
272
|
const ctx = create_stub_app_server_context(options.session_options);
|
|
@@ -292,9 +309,17 @@ export const create_test_app_surface_spec = (options) => {
|
|
|
292
309
|
])
|
|
293
310
|
: [];
|
|
294
311
|
const route_specs = [...consumer_routes, ...rpc_route_specs, ...bootstrap_route_specs];
|
|
295
|
-
|
|
312
|
+
// mirrors `create_app_server`'s `[proxy_spec, ...auth_middleware]`
|
|
313
|
+
const stack_specs = [
|
|
314
|
+
{ name: 'trusted_proxy', path: '*', handler: create_stub_mw() },
|
|
315
|
+
...create_stub_api_middleware()
|
|
316
|
+
];
|
|
317
|
+
let middleware_specs = stack_specs;
|
|
296
318
|
if (options.transform_middleware) {
|
|
297
|
-
|
|
319
|
+
// the transform gets copies in a fresh array, so mutating what it was
|
|
320
|
+
// handed can't rewrite the originals the check matches against
|
|
321
|
+
middleware_specs = options.transform_middleware(stack_specs.map((spec) => ({ ...spec })));
|
|
322
|
+
assert_middleware_stack_mounted(stack_specs, middleware_specs);
|
|
298
323
|
}
|
|
299
324
|
return create_app_surface_spec({
|
|
300
325
|
middleware_specs,
|