@fuzdev/fuz_app 0.118.0 → 0.119.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 +301 -73
- package/dist/actions/action_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +16 -1
- package/dist/actions/connection_closer.d.ts +136 -51
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +133 -34
- package/dist/actions/register_action_ws.d.ts +176 -24
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +581 -241
- package/dist/actions/register_ws_endpoint.d.ts +1 -0
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +1 -0
- package/dist/actions/socket.svelte.d.ts +43 -9
- package/dist/actions/socket.svelte.d.ts.map +1 -1
- package/dist/actions/socket.svelte.js +59 -8
- package/dist/actions/transports.d.ts +55 -1
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +55 -1
- package/dist/actions/transports_ws_auth_guard.d.ts +53 -67
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +87 -111
- package/dist/actions/transports_ws_backend.d.ts +212 -30
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +327 -70
- package/dist/actions/ws_endpoint_spec.d.ts +26 -13
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
- package/dist/auth/CLAUDE.md +113 -32
- package/dist/auth/account_actions.d.ts +12 -18
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +47 -33
- package/dist/auth/account_routes.d.ts +5 -11
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +40 -36
- package/dist/auth/account_schema.d.ts +27 -0
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +22 -0
- package/dist/auth/admin_actions.d.ts +8 -17
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +33 -21
- package/dist/auth/api_token_queries.d.ts +23 -2
- package/dist/auth/api_token_queries.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +39 -3
- package/dist/auth/audit_emitter.d.ts +61 -38
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +57 -14
- package/dist/auth/audit_log_route_schema.js +7 -7
- package/dist/auth/audit_log_routes.d.ts +31 -6
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +85 -11
- package/dist/auth/audit_log_schema.d.ts +48 -0
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +58 -0
- package/dist/auth/cleanup.d.ts +203 -28
- package/dist/auth/cleanup.d.ts.map +1 -1
- package/dist/auth/cleanup.js +222 -26
- package/dist/auth/deps.d.ts +28 -0
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/resolved_auth.d.ts +105 -0
- package/dist/auth/resolved_auth.d.ts.map +1 -0
- package/dist/auth/resolved_auth.js +124 -0
- package/dist/auth/session_middleware.d.ts +8 -1
- package/dist/auth/session_middleware.d.ts.map +1 -1
- package/dist/auth/session_middleware.js +9 -3
- package/dist/auth/session_queries.d.ts +31 -6
- package/dist/auth/session_queries.d.ts.map +1 -1
- package/dist/auth/session_queries.js +31 -6
- package/dist/auth/standard_rpc_actions.d.ts +9 -9
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +3 -3
- package/dist/db/CLAUDE.md +7 -1
- package/dist/db/status.d.ts +58 -1
- package/dist/db/status.d.ts.map +1 -1
- package/dist/db/status.js +205 -77
- package/dist/http/CLAUDE.md +7 -3
- package/dist/http/db_routes.d.ts +6 -2
- package/dist/http/db_routes.d.ts.map +1 -1
- package/dist/http/db_routes.js +2 -2
- package/dist/periodic.d.ts +80 -0
- package/dist/periodic.d.ts.map +1 -0
- package/dist/periodic.js +122 -0
- package/dist/realtime/sse_auth_guard.d.ts +79 -49
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +103 -73
- package/dist/realtime/subscriber_registry.d.ts +119 -8
- package/dist/realtime/subscriber_registry.d.ts.map +1 -1
- package/dist/realtime/subscriber_registry.js +184 -25
- package/dist/server/app_backend.d.ts +12 -1
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +5 -2
- package/dist/server/app_server.d.ts +55 -15
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +41 -13
- package/dist/testing/CLAUDE.md +140 -48
- package/dist/testing/app_server.d.ts +2 -2
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +11 -2
- package/dist/testing/audit_drift_guard.d.ts +1 -48
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +0 -42
- package/dist/testing/connection_closer_helpers.d.ts +9 -22
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
- package/dist/testing/connection_closer_helpers.js +11 -17
- package/dist/testing/cross_backend/capabilities.d.ts +67 -13
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +27 -12
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +21 -0
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +13 -4
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +4 -1
- package/dist/testing/cross_backend/session_cap.d.ts +13 -0
- package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
- package/dist/testing/cross_backend/session_cap.js +55 -19
- package/dist/testing/cross_backend/sse_round_trip.d.ts +10 -2
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/sse_round_trip.js +93 -26
- package/dist/testing/cross_backend/testing_server_bun.js +4 -4
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +10 -2
- package/dist/testing/cross_backend/ws_connection_cap.d.ts +35 -0
- package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_connection_cap.js +131 -0
- 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 +156 -19
- package/dist/testing/sse_round_trip.d.ts +6 -4
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/stubs.d.ts +1 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/testing/transports/sse_transport.d.ts +1 -1
- package/dist/testing/transports/ws_client.d.ts +7 -0
- package/dist/testing/transports/ws_client.d.ts.map +1 -1
- package/dist/testing/transports/ws_raw_client.d.ts +51 -0
- package/dist/testing/transports/ws_raw_client.d.ts.map +1 -0
- package/dist/testing/transports/ws_raw_client.js +156 -0
- package/dist/testing/transports/ws_transport.d.ts +28 -0
- package/dist/testing/transports/ws_transport.d.ts.map +1 -1
- package/dist/testing/transports/ws_transport.js +45 -1
- package/dist/testing/ws_round_trip.d.ts +8 -3
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +19 -4
- package/dist/ui/AdminAccounts.svelte +4 -3
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminOverview.svelte +3 -1
- package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +22 -6
- package/dist/ui/admin_accounts_state.svelte.d.ts +15 -1
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +16 -0
- package/dist/ui/auth_state.svelte.d.ts +25 -1
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +28 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE
|
|
1
|
+
{"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IACxC,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,QAAO,eAiB1C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,aAAa,GAAG,SAAS,EAC/B,QAAQ,SAAS,GAAG,OAAO,GAAG,SAAS,EACvC,IAAI,MAAM,KACR,IAIF,CAAC"}
|
|
@@ -2,40 +2,34 @@ import './assert_dev_env.js';
|
|
|
2
2
|
import { assert } from 'vitest';
|
|
3
3
|
/**
|
|
4
4
|
* Build a `ConnectionCloser` that records every call into `calls` rather
|
|
5
|
-
* than touching real transports. Each method returns 1 ("one
|
|
6
|
-
* closed") regardless of whether a real
|
|
7
|
-
*
|
|
5
|
+
* than touching real transports. Each method returns 1 ("one connection
|
|
6
|
+
* closed") regardless of whether a real connection exists — handlers
|
|
7
|
+
* ignore the return value.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
*
|
|
12
|
-
* uses a fresh internal counter — `at: N` values within a single test
|
|
13
|
-
* are meaningful, but cannot be compared against audit emit ordering.
|
|
9
|
+
* To see a handler's closes, add the closer to the backend's
|
|
10
|
+
* `deps.connection_closer` (`RealtimeCloser.add`), or build the action
|
|
11
|
+
* factory's deps with it.
|
|
14
12
|
*/
|
|
15
|
-
export const create_recording_closer = (
|
|
13
|
+
export const create_recording_closer = () => {
|
|
16
14
|
const calls = [];
|
|
17
|
-
const seq = seq_ref ?? { value: 0 };
|
|
18
15
|
const closer = {
|
|
19
16
|
close_sockets_for_session: (id) => {
|
|
20
|
-
calls.push({ method: 'session', id
|
|
17
|
+
calls.push({ method: 'session', id });
|
|
21
18
|
return 1;
|
|
22
19
|
},
|
|
23
20
|
close_sockets_for_token: (id) => {
|
|
24
|
-
calls.push({ method: 'token', id
|
|
21
|
+
calls.push({ method: 'token', id });
|
|
25
22
|
return 1;
|
|
26
23
|
},
|
|
27
24
|
close_sockets_for_account: (id) => {
|
|
28
|
-
calls.push({ method: 'account', id
|
|
25
|
+
calls.push({ method: 'account', id });
|
|
29
26
|
return 1;
|
|
30
27
|
}
|
|
31
28
|
};
|
|
32
29
|
return { closer, calls };
|
|
33
30
|
};
|
|
34
31
|
/**
|
|
35
|
-
* Pin `{method, id}` on a single recorded close call
|
|
36
|
-
* the `at: N` sequence number. Use at every "did the closer fire?"
|
|
37
|
-
* assertion site; the sequence number is only meaningful for dedicated
|
|
38
|
-
* ordering tests (paired with `create_emit_ordering_audit_factory`).
|
|
32
|
+
* Pin `{method, id}` on a single recorded close call.
|
|
39
33
|
*
|
|
40
34
|
* Throws via `assert.ok` if `call` is `undefined` — index a recorded
|
|
41
35
|
* `calls` array directly (`calls[0]`) and let this helper handle the
|
|
@@ -2,9 +2,9 @@ import '../assert_dev_env.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Backend wiring facts recorded for documentation — **not** gating flags.
|
|
4
4
|
*
|
|
5
|
-
* The companion to `BackendCapabilities`: where each capability flag
|
|
6
|
-
* `test_if(capabilities.X, ...)`
|
|
7
|
-
*
|
|
5
|
+
* The companion to `BackendCapabilities`: where each capability flag is read
|
|
6
|
+
* by a suite (a `test_if(capabilities.X, ...)` gate, or a fork of one case's
|
|
7
|
+
* assertion), nothing reads these. They record middleware / limiter wiring that
|
|
8
8
|
* differs between the TS and Rust families (a backend-shape record) but gates
|
|
9
9
|
* no cross test today. They live in their own type precisely so
|
|
10
10
|
* `BackendCapabilities` stops claiming gating power it doesn't have — fold a
|
|
@@ -42,15 +42,17 @@ export interface BackendShapeNotes {
|
|
|
42
42
|
export interface BackendCapabilities {
|
|
43
43
|
/**
|
|
44
44
|
* WebSocket transport is reachable end-to-end. Gates the cross-process
|
|
45
|
-
* WS round-trip suite
|
|
46
|
-
*
|
|
45
|
+
* WS round-trip suite and the per-account connection-cap suite
|
|
46
|
+
* (`describe_ws_connection_cap_cross_tests`); the in-process
|
|
47
|
+
* `describe_ws_round_trip_tests` runs against `register_action_ws`
|
|
48
|
+
* directly and ignores this flag.
|
|
47
49
|
*/
|
|
48
50
|
readonly ws: boolean;
|
|
49
51
|
/**
|
|
50
52
|
* SSE transport is reachable end-to-end. Gates the cross-process SSE
|
|
51
53
|
* suite (`describe_cross_process_sse_tests` — connect, audit data frame,
|
|
52
|
-
* close-on-revoke); in-process SSE uses the
|
|
53
|
-
* ignores this flag.
|
|
54
|
+
* close-on-revoke, the per-session stream cap); in-process SSE uses the
|
|
55
|
+
* `on_audit_event` hook and ignores this flag.
|
|
54
56
|
*/
|
|
55
57
|
readonly sse: boolean;
|
|
56
58
|
/**
|
|
@@ -159,6 +161,55 @@ export interface BackendCapabilities {
|
|
|
159
161
|
* coverage.
|
|
160
162
|
*/
|
|
161
163
|
readonly peer_request: boolean;
|
|
164
|
+
/**
|
|
165
|
+
* The backend's HTTP server accepts WebSocket frames written in the same
|
|
166
|
+
* TCP write as the upgrade request, handing them to the socket once the
|
|
167
|
+
* handshake completes. Gates the `frames sent with the handshake` case in
|
|
168
|
+
* `describe_cross_process_ws_tests` — the deterministic form of "frames
|
|
169
|
+
* that arrive before the connection is admitted wait for admission", since
|
|
170
|
+
* those frames are in the backend's hands before it can have admitted
|
|
171
|
+
* anything.
|
|
172
|
+
*
|
|
173
|
+
* `true` for Node (`ws`), Deno, and the Rust spine (hyper hands the
|
|
174
|
+
* leftover bytes to the upgraded stream); `false` for Bun, whose HTTP
|
|
175
|
+
* parser answers such a request `400` before any handler runs. A backend
|
|
176
|
+
* without it still runs the suite's client-library case (requests sent the
|
|
177
|
+
* moment the socket opens), which pins the same contract without forcing
|
|
178
|
+
* the ordering.
|
|
179
|
+
*/
|
|
180
|
+
readonly ws_handshake_pipelining: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* The backend's WebSocket endpoint mounts the self-service account actions
|
|
183
|
+
* (`account_session_revoke_all` among them), so a socket can revoke the
|
|
184
|
+
* session it is running on. Gates the self-revocation case in
|
|
185
|
+
* `describe_cross_process_ws_tests`: the socket reads the reply to its
|
|
186
|
+
* request, then is closed with `WS_CLOSE_SESSION_REVOKED`.
|
|
187
|
+
*
|
|
188
|
+
* `true` on fuz_app's own spine presets — the TS spine binary mounts
|
|
189
|
+
* `create_account_actions` on `/api/ws`, and the Rust `testing_spine_stub`
|
|
190
|
+
* serves one action registry on RPC and WS. `false` in the family defaults:
|
|
191
|
+
* a consumer's WS endpoint carries whatever its domain mounts, so it opts in
|
|
192
|
+
* once the account actions are there.
|
|
193
|
+
*/
|
|
194
|
+
readonly ws_account_actions: boolean;
|
|
195
|
+
/**
|
|
196
|
+
* A socket that revokes the credential it is running on reads the reply to
|
|
197
|
+
* that request **before** the revocation's close. Read by the
|
|
198
|
+
* self-revocation case in `describe_cross_process_ws_tests` (which
|
|
199
|
+
* `ws_account_actions` gates): with it `true` the case requires the reply
|
|
200
|
+
* frame ahead of the `WS_CLOSE_SESSION_REVOKED` close; with it `false` the
|
|
201
|
+
* case requires the close alone.
|
|
202
|
+
*
|
|
203
|
+
* `true` for the TS family — `register_action_ws` sends the response and
|
|
204
|
+
* only then flushes the request's post-commit queue, where the close is.
|
|
205
|
+
* `false` for the Rust family: its dispatch runs the post-commit queue
|
|
206
|
+
* before handing the response to the socket loop, and the loop, finding the
|
|
207
|
+
* connection already closed, writes the close frame and drops the response.
|
|
208
|
+
* The caller's revocation still took effect; it is not told so. The flag
|
|
209
|
+
* records that divergence so the suite stays green on both spines without
|
|
210
|
+
* hiding it — flip it once the Rust spine answers first.
|
|
211
|
+
*/
|
|
212
|
+
readonly ws_self_revocation_reply: boolean;
|
|
162
213
|
/**
|
|
163
214
|
* A test `CellCreateAuthorize` policy is live-mounted on the backend's cell
|
|
164
215
|
* layer — creating a `kind: 'gated'` cell requires the `participant` role
|
|
@@ -177,12 +228,15 @@ export interface BackendCapabilities {
|
|
|
177
228
|
/**
|
|
178
229
|
* Capability declarations for the in-process Hono transport. Nearly every
|
|
179
230
|
* flag is `true` because in-process testing exercises the full backend
|
|
180
|
-
* with no missing optional behaviors. The
|
|
181
|
-
*
|
|
182
|
-
* socket with an `on_request` responder via
|
|
183
|
-
* in-process driver never runs it — the
|
|
184
|
-
* server-initiated requests
|
|
185
|
-
*
|
|
231
|
+
* with no missing optional behaviors. The exceptions are the flags whose
|
|
232
|
+
* cases are cross-process-only: `peer_request` (`describe_peer_ping_ws_tests`
|
|
233
|
+
* needs a real bound socket with an `on_request` responder via
|
|
234
|
+
* `create_ws_transport`, so the in-process driver never runs it — the
|
|
235
|
+
* transport itself supports server-initiated requests),
|
|
236
|
+
* `ws_handshake_pipelining` (raw bytes on a real socket),
|
|
237
|
+
* `ws_account_actions` (a real socket's reply-then-close ordering), and
|
|
238
|
+
* `cell_gated_create`. Cross-process consumers declare each flag explicitly
|
|
239
|
+
* per backend.
|
|
186
240
|
*/
|
|
187
241
|
export declare const in_process_capabilities: BackendCapabilities;
|
|
188
242
|
/**
|
|
@@ -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;
|
|
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;;;;;;;;;;;;OAYG;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"}
|
|
@@ -7,25 +7,29 @@ import '../assert_dev_env.js';
|
|
|
7
7
|
* implement. No `if (config.name === 'rust')` branches anywhere — name-
|
|
8
8
|
* checking is a code smell that says capability vocabulary is missing.
|
|
9
9
|
*
|
|
10
|
-
* In-process Hono via `default_in_process_setup` declares every
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* In-process Hono via `default_in_process_setup` declares every capability
|
|
11
|
+
* its transport can exercise (see `in_process_capabilities` — the
|
|
12
|
+
* cross-process-only flags stay `false`). Cross-process backends opt in
|
|
13
|
+
* per-flag on their `BackendConfig`.
|
|
13
14
|
*
|
|
14
15
|
* **Where the per-backend declarations live** (this file owns only the
|
|
15
16
|
* vocabulary + the in-process preset):
|
|
16
17
|
*
|
|
17
|
-
* - `in_process_capabilities` — here; every flag `true
|
|
18
|
+
* - `in_process_capabilities` — here; every flag `true` except the
|
|
19
|
+
* cross-process-only ones.
|
|
18
20
|
* - `ts_default_capabilities` / `rust_default_capabilities` — consumer-facing
|
|
19
21
|
* family defaults, in `default_backend_configs.ts` (full literals, so adding
|
|
20
22
|
* a capability is a compile error until each family declares it).
|
|
21
23
|
* - `ts_spine_capabilities` / `ts_spine_bun_capabilities` — fuz_app's own TS
|
|
22
24
|
* spine presets, in `ts_spine_backend_config.ts` (deltas off the family
|
|
23
|
-
* default; Bun flips `oversized_reject_closes_connection`
|
|
25
|
+
* default; Bun flips `oversized_reject_closes_connection` and
|
|
26
|
+
* `ws_handshake_pipelining`).
|
|
24
27
|
* - `rust_spine_stub_capabilities` — fuz_app's Rust spine-stub preset, in
|
|
25
28
|
* `rust_spine_stub_backend_config.ts` (delta off the rust family default).
|
|
26
29
|
*
|
|
27
30
|
* **Gating flags vs shape notes.** `BackendCapabilities` holds only flags a
|
|
28
|
-
* suite actually gates on (each has a `test_if(capabilities.X, ...)` reader
|
|
31
|
+
* suite actually gates on (each has a `test_if(capabilities.X, ...)` reader,
|
|
32
|
+
* or — `ws_self_revocation_reply` — forks one case's assertion).
|
|
29
33
|
* Wiring facts that gate nothing — `bearer_auth` / `trusted_proxy` /
|
|
30
34
|
* `login_rate_limit` — live in the parallel `BackendShapeNotes` record
|
|
31
35
|
* (`in_process_shape_notes` here, `ts_default_shape_notes` /
|
|
@@ -38,12 +42,15 @@ import { test } from 'vitest';
|
|
|
38
42
|
/**
|
|
39
43
|
* Capability declarations for the in-process Hono transport. Nearly every
|
|
40
44
|
* flag is `true` because in-process testing exercises the full backend
|
|
41
|
-
* with no missing optional behaviors. The
|
|
42
|
-
*
|
|
43
|
-
* socket with an `on_request` responder via
|
|
44
|
-
* in-process driver never runs it — the
|
|
45
|
-
* server-initiated requests
|
|
46
|
-
*
|
|
45
|
+
* with no missing optional behaviors. The exceptions are the flags whose
|
|
46
|
+
* cases are cross-process-only: `peer_request` (`describe_peer_ping_ws_tests`
|
|
47
|
+
* needs a real bound socket with an `on_request` responder via
|
|
48
|
+
* `create_ws_transport`, so the in-process driver never runs it — the
|
|
49
|
+
* transport itself supports server-initiated requests),
|
|
50
|
+
* `ws_handshake_pipelining` (raw bytes on a real socket),
|
|
51
|
+
* `ws_account_actions` (a real socket's reply-then-close ordering), and
|
|
52
|
+
* `cell_gated_create`. Cross-process consumers declare each flag explicitly
|
|
53
|
+
* per backend.
|
|
47
54
|
*/
|
|
48
55
|
export const in_process_capabilities = Object.freeze({
|
|
49
56
|
ws: true,
|
|
@@ -56,6 +63,14 @@ export const in_process_capabilities = Object.freeze({
|
|
|
56
63
|
account_status: true,
|
|
57
64
|
oversized_reject_closes_connection: true,
|
|
58
65
|
peer_request: false,
|
|
66
|
+
// Cross-process-only, like `peer_request`: the case needs a real bound socket
|
|
67
|
+
// to write raw bytes to.
|
|
68
|
+
ws_handshake_pipelining: false,
|
|
69
|
+
// Cross-process-only: the case reads a real socket's frames in wire order.
|
|
70
|
+
ws_account_actions: false,
|
|
71
|
+
// Read only by the case `ws_account_actions` gates; the in-process
|
|
72
|
+
// dispatcher does answer first.
|
|
73
|
+
ws_self_revocation_reply: true,
|
|
59
74
|
// Cross-process-only: the test `CellCreateAuthorize` policy is mounted on the
|
|
60
75
|
// spine binaries' full mount + the Rust stub, not the in-process default app.
|
|
61
76
|
cell_gated_create: false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAQhG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAE3E;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAQhG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAE3E;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAsCpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAgCtC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,iBAInC,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAIrC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAoCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA4CF,CAAC"}
|
|
@@ -41,6 +41,17 @@ export const ts_default_capabilities = Object.freeze({
|
|
|
41
41
|
// the HTTP RPC endpoint (the no-transport case). The TS spine config opts in;
|
|
42
42
|
// a generic consumer enables it once it wires that mount.
|
|
43
43
|
peer_request: false,
|
|
44
|
+
// Node (`ws`) and Deno hand frames pipelined with the upgrade request to the
|
|
45
|
+
// socket. A Bun-served consumer overrides to `false` (see the bun spine
|
|
46
|
+
// config) — `Bun.serve` answers such a request `400`.
|
|
47
|
+
ws_handshake_pipelining: true,
|
|
48
|
+
// Off by default: a consumer's WS endpoint mounts its own domain's actions.
|
|
49
|
+
// fuz_app's spine configs opt in (the spine binary mounts the account
|
|
50
|
+
// actions on `/api/ws`); a consumer does once its endpoint carries them.
|
|
51
|
+
ws_account_actions: false,
|
|
52
|
+
// `register_action_ws` sends a request's response before it flushes the
|
|
53
|
+
// post-commit queue a self-revocation's close is on.
|
|
54
|
+
ws_self_revocation_reply: true,
|
|
44
55
|
// Off by default — the `cell_gated_create` test policy is a fuz_app test
|
|
45
56
|
// fixture, mounted only on the spine binaries (`ts_spine_*`), not generic
|
|
46
57
|
// consumers.
|
|
@@ -70,6 +81,16 @@ export const rust_default_capabilities = Object.freeze({
|
|
|
70
81
|
// Server-initiated requests landed Rust-first canonical — the Rust spine
|
|
71
82
|
// drives the `peer/ping` round-trip, so the family opts into the suite.
|
|
72
83
|
peer_request: true,
|
|
84
|
+
// hyper hands bytes pipelined after the upgrade request to the upgraded
|
|
85
|
+
// stream, where they wait for the read loop that starts at admission.
|
|
86
|
+
ws_handshake_pipelining: true,
|
|
87
|
+
// Off by default, as on the TS family: a consumer opts in once its WS
|
|
88
|
+
// endpoint mounts the account actions. The `testing_spine_stub` preset does.
|
|
89
|
+
ws_account_actions: false,
|
|
90
|
+
// The Rust dispatch runs the post-commit queue — the self-revocation's
|
|
91
|
+
// close — before the socket loop writes the response, and the loop then
|
|
92
|
+
// drops it. A divergence from the TS spine; see the flag's doc.
|
|
93
|
+
ws_self_revocation_reply: false,
|
|
73
94
|
// Off by default — the `testing_spine_stub` preset opts in (it mounts the
|
|
74
95
|
// `TestCellGatedCreateAuthorize` policy); a generic Rust consumer doesn't.
|
|
75
96
|
cell_gated_create: false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role_grant_offer_notification_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_offer_notification_ws.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAmC,MAAM,YAAY,CAAC;AAK7E,iFAAiF;AACjF,MAAM,WAAW,uCAAuC;IACvD,8EAA8E;IAC9E,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;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+CAA+C,GAC3D,SAAS,uCAAuC,KAC9C,
|
|
1
|
+
{"version":3,"file":"role_grant_offer_notification_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/role_grant_offer_notification_ws.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAmC,MAAM,YAAY,CAAC;AAK7E,iFAAiF;AACjF,MAAM,WAAW,uCAAuC;IACvD,8EAA8E;IAC9E,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;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,+CAA+C,GAC3D,SAAS,uCAAuC,KAC9C,IAuWF,CAAC"}
|
|
@@ -38,7 +38,7 @@ import '../assert_dev_env.js';
|
|
|
38
38
|
* guard), and accept stays idempotent on the role_grant while still superseding
|
|
39
39
|
* pending siblings. Gated on `capabilities.ws`.
|
|
40
40
|
*
|
|
41
|
-
* Cross-process only: `
|
|
41
|
+
* Cross-process only: `create_admitted_ws_transport` needs a real bound socket, so wire
|
|
42
42
|
* it from a `*.cross.test.ts` file, never an in-process setup. Authed cookies
|
|
43
43
|
* come from the per-account session minted by `fixture.create_account` /
|
|
44
44
|
* `fixture.create_session_headers`.
|
|
@@ -47,7 +47,7 @@ import '../assert_dev_env.js';
|
|
|
47
47
|
*/
|
|
48
48
|
import { assert, describe } from 'vitest';
|
|
49
49
|
import { rpc_call } from '../rpc_helpers.js';
|
|
50
|
-
import {
|
|
50
|
+
import { create_admitted_ws_transport } from '../transports/ws_transport.js';
|
|
51
51
|
import { is_notification_with } from '../transports/ws_client.js';
|
|
52
52
|
import { ROLE_ADMIN } from '../../auth/role_schema.js';
|
|
53
53
|
import { ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_ACCEPTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_DECLINED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_RETRACTED_NOTIFICATION_METHOD, ROLE_GRANT_OFFER_SUPERSEDE_NOTIFICATION_METHOD, ROLE_GRANT_REVOKE_NOTIFICATION_METHOD, RoleGrantOfferReceivedParams, RoleGrantOfferAcceptedParams, RoleGrantOfferDeclinedParams, RoleGrantOfferRetractedParams, RoleGrantOfferSupersedeParams, RoleGrantRevokeParams } from '../../auth/role_grant_offer_notifications.js';
|
|
@@ -65,10 +65,19 @@ const RPC_PATH = '/api/rpc';
|
|
|
65
65
|
export const describe_role_grant_offer_notification_ws_tests = (options) => {
|
|
66
66
|
const { setup_test, capabilities, base_url, ws_path } = options;
|
|
67
67
|
// -- shared helpers -------------------------------------------------------
|
|
68
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Open a WS transport for a single session cookie (`<name>=<value>`),
|
|
70
|
+
* admitted before it resolves: a targeted notification is delivered only to
|
|
71
|
+
* an admitted connection, and the handshake alone doesn't say it is one.
|
|
72
|
+
*/
|
|
69
73
|
const open_ws = (cookie) => {
|
|
70
74
|
assert.ok(cookie, 'expected a session cookie for the WS upgrade');
|
|
71
|
-
return
|
|
75
|
+
return create_admitted_ws_transport({
|
|
76
|
+
base_url,
|
|
77
|
+
ws_path,
|
|
78
|
+
cookies: [cookie],
|
|
79
|
+
origin: base_url
|
|
80
|
+
});
|
|
72
81
|
};
|
|
73
82
|
/** Drive a JSON-RPC call over the fixture's HTTP transport. */
|
|
74
83
|
const rpc = (fixture, method, params, headers) => rpc_call({ app: fixture.transport, path: RPC_PATH, method, params, headers });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;
|
|
1
|
+
{"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAqBnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAkDF,CAAC"}
|
|
@@ -61,7 +61,9 @@ export const RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV = 'FUZ_RUST_SPINE_STUB_EXP
|
|
|
61
61
|
* Capabilities for the Rust `testing_spine_stub` — `rust_default_capabilities`
|
|
62
62
|
* plus `sse` (the stub serves `GET /api/admin/audit/stream` over the spine
|
|
63
63
|
* `fuz_realtime::SseRegistry` + audit listener), `ready` (it live-mounts
|
|
64
|
-
* `/ready` over the env-supplied fixture path),
|
|
64
|
+
* `/ready` over the env-supplied fixture path), `ws_account_actions` (it
|
|
65
|
+
* serves one action registry on `/api/rpc` and `/api/ws`), and
|
|
66
|
+
* `cell_gated_create` (it
|
|
65
67
|
* mounts the `TestCellGatedCreateAuthorize` policy on its cell layer). Named
|
|
66
68
|
* (not inline) so every spine preset is greppable, mirroring
|
|
67
69
|
* `ts_spine_capabilities`.
|
|
@@ -70,6 +72,7 @@ const rust_spine_stub_capabilities = Object.freeze({
|
|
|
70
72
|
...rust_default_capabilities,
|
|
71
73
|
sse: true,
|
|
72
74
|
ready: true,
|
|
75
|
+
ws_account_actions: true,
|
|
73
76
|
cell_gated_create: true
|
|
74
77
|
});
|
|
75
78
|
/** Default listening port — slots beside zzz's 1175/1176; matches the binary's `DEFAULT_PORT`. */
|
|
@@ -14,6 +14,19 @@ export interface SessionCapCrossTestOptions {
|
|
|
14
14
|
readonly login_path?: string;
|
|
15
15
|
/** REST account-status route path. Default `/api/account/status`. */
|
|
16
16
|
readonly status_path?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Where the backend's WebSocket endpoint is, for the case that an evicted
|
|
19
|
+
* session's socket is closed. Pass it when the backend has a WS transport
|
|
20
|
+
* (`capabilities.ws`); omitted, that case is skipped.
|
|
21
|
+
*/
|
|
22
|
+
readonly ws?: {
|
|
23
|
+
/** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
|
|
24
|
+
readonly base_url: string;
|
|
25
|
+
/** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
|
|
26
|
+
readonly ws_path: string;
|
|
27
|
+
/** Origin for the upgrade. Defaults to `base_url`. */
|
|
28
|
+
readonly origin?: string;
|
|
29
|
+
};
|
|
17
30
|
}
|
|
18
31
|
export declare const describe_session_cap_cross_tests: (options: SessionCapCrossTestOptions) => void;
|
|
19
32
|
//# sourceMappingURL=session_cap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session_cap.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/session_cap.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"session_cap.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/session_cap.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6D9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,gDAAgD;AAChD,MAAM,WAAW,0BAA0B;IAC1C,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE;QACb,2EAA2E;QAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,+DAA+D;QAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,sDAAsD;QACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACF;AAED,eAAO,MAAM,gCAAgC,GAAI,SAAS,0BAA0B,KAAG,IA4FtF,CAAC"}
|
|
@@ -3,15 +3,11 @@ import '../assert_dev_env.js';
|
|
|
3
3
|
* Cross-backend parity suite for the **per-account concurrent-session cap**
|
|
4
4
|
* over real HTTP.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* (`session_middleware.lifecycle.db.test.ts`, `session_token_limits.integration.db.test.ts`)
|
|
12
|
-
* whose Rust counterparts simply didn't exist to fail. This suite is the pin
|
|
13
|
-
* that runs on *both*, so the next spine to lose the cap fails here rather than
|
|
14
|
-
* in production. Two properties:
|
|
6
|
+
* Both spines cap concurrent sessions per account (the TS
|
|
7
|
+
* `query_session_enforce_limit` / `DEFAULT_MAX_SESSIONS`, the Rust
|
|
8
|
+
* `create_capped_session`). Each spine's own tests pin its half in-process;
|
|
9
|
+
* nothing in them crosses the wire, so a spine that lost the cap would fail
|
|
10
|
+
* no shared test. This suite is the pin that runs on *both*. It pins:
|
|
15
11
|
*
|
|
16
12
|
* - **the cap bounds concurrent sessions, by eviction not refusal** — logging in
|
|
17
13
|
* `max_sessions + 1` times succeeds every time (a login is never denied for
|
|
@@ -19,6 +15,11 @@ import '../assert_dev_env.js';
|
|
|
19
15
|
* - **the evicted session is the oldest** — the first login's cookie no longer
|
|
20
16
|
* authenticates once the cap is exceeded. A spine with no cap authenticates it
|
|
21
17
|
* fine, which is exactly the defect this catches.
|
|
18
|
+
* - **an evicted session's socket is closed** (when `ws` is supplied) — the cap
|
|
19
|
+
* deletes the session row, and a WebSocket never re-reads its session, so
|
|
20
|
+
* the login that evicts it must close the socket it opened
|
|
21
|
+
* (`WS_CLOSE_SESSION_REVOKED`), once the eviction has committed. A spine
|
|
22
|
+
* that only deletes the row leaves the socket running on a dead session.
|
|
22
23
|
*
|
|
23
24
|
* **Why `max_sessions + 1` logins and not `max_sessions`.** The starting session
|
|
24
25
|
* count isn't zero and isn't guaranteed equal across impls — `create_account`
|
|
@@ -48,16 +49,32 @@ import '../assert_dev_env.js';
|
|
|
48
49
|
* @module
|
|
49
50
|
*/
|
|
50
51
|
import { describe, test, assert } from 'vitest';
|
|
52
|
+
import { heartbeat_action_spec } from '../../actions/heartbeat.js';
|
|
53
|
+
import { WS_CLOSE_SESSION_REVOKED } from '../../actions/transports.js';
|
|
51
54
|
import { DEFAULT_MAX_SESSIONS } from '../../auth/account_route_schema.js';
|
|
52
55
|
import { DEFAULT_TEST_PASSWORD } from '../test_credentials.js';
|
|
56
|
+
import { create_ws_transport } from '../transports/ws_transport.js';
|
|
57
|
+
import { test_if } from './capabilities.js';
|
|
53
58
|
export const describe_session_cap_cross_tests = (options) => {
|
|
54
|
-
const { setup_test } = options;
|
|
59
|
+
const { setup_test, ws } = options;
|
|
55
60
|
const max_sessions = options.max_sessions ?? DEFAULT_MAX_SESSIONS;
|
|
56
61
|
const login_path = options.login_path ?? '/api/account/login';
|
|
57
62
|
const status_path = options.status_path ?? '/api/account/status';
|
|
58
63
|
// Fresh-keeper-per-test wipes the DB between tests, so a literal username
|
|
59
64
|
// never collides (see `setup.ts`).
|
|
60
65
|
const username = 'session_cap_user';
|
|
66
|
+
/** Log `username` in on a fresh transport — one login, one session, one jar. */
|
|
67
|
+
const login = async (fixture, label) => {
|
|
68
|
+
const transport = fixture.fresh_transport();
|
|
69
|
+
const res = await transport(login_path, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'content-type': 'application/json' },
|
|
72
|
+
body: JSON.stringify({ username, password: DEFAULT_TEST_PASSWORD })
|
|
73
|
+
});
|
|
74
|
+
assert.strictEqual(res.status, 200, `${label} must succeed — the cap evicts, it never refuses`);
|
|
75
|
+
assert.ok(transport.cookies().length > 0, `${label} must set a session cookie on its jar`);
|
|
76
|
+
return transport;
|
|
77
|
+
};
|
|
61
78
|
describe('per-account session cap parity', () => {
|
|
62
79
|
test(`logging in ${max_sessions + 1} times evicts the oldest session (newest cookie resolves, first does not)`, async () => {
|
|
63
80
|
const fixture = await setup_test();
|
|
@@ -69,15 +86,7 @@ export const describe_session_cap_cross_tests = (options) => {
|
|
|
69
86
|
// look alike. Held oldest-first.
|
|
70
87
|
const sessions = [];
|
|
71
88
|
for (let i = 0; i < max_sessions + 1; i++) {
|
|
72
|
-
|
|
73
|
-
const res = await transport(login_path, {
|
|
74
|
-
method: 'POST',
|
|
75
|
-
headers: { 'content-type': 'application/json' },
|
|
76
|
-
body: JSON.stringify({ username, password: DEFAULT_TEST_PASSWORD })
|
|
77
|
-
});
|
|
78
|
-
assert.strictEqual(res.status, 200, `login ${i + 1}/${max_sessions + 1} must succeed — the cap evicts, it never refuses`);
|
|
79
|
-
assert.ok(transport.cookies().length > 0, `login ${i + 1} must set a session cookie on its jar`);
|
|
80
|
-
sessions.push(transport);
|
|
89
|
+
sessions.push(await login(fixture, `login ${i + 1}/${max_sessions + 1}`));
|
|
81
90
|
}
|
|
82
91
|
/** `GET /status` on one login's transport: 200 authenticated, 401 not. */
|
|
83
92
|
const status_on = async (transport) => {
|
|
@@ -89,5 +98,32 @@ export const describe_session_cap_cross_tests = (options) => {
|
|
|
89
98
|
assert.strictEqual(await status_on(sessions[0]), 401, `the oldest session must be evicted once the account exceeds ${max_sessions} sessions ` +
|
|
90
99
|
'(a spine with no cap authenticates this cookie)');
|
|
91
100
|
});
|
|
101
|
+
// `max_sessions` further logins leave the watched session outside the
|
|
102
|
+
// newest `max_sessions`, so it is evicted whatever sessions the account
|
|
103
|
+
// held before it (`create_account` seeds one on some cradles).
|
|
104
|
+
test_if(ws !== undefined, 'a session the cap evicts has its open socket closed', async () => {
|
|
105
|
+
const fixture = await setup_test();
|
|
106
|
+
await fixture.create_account({ username, password_value: DEFAULT_TEST_PASSWORD });
|
|
107
|
+
const victim = await login(fixture, 'the login whose socket is watched');
|
|
108
|
+
const client = await create_ws_transport({
|
|
109
|
+
base_url: ws.base_url,
|
|
110
|
+
ws_path: ws.ws_path,
|
|
111
|
+
cookies: victim.cookies(),
|
|
112
|
+
origin: ws.origin
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
// admitted and dispatching before the evicting logins
|
|
116
|
+
await client.request(1, heartbeat_action_spec.method, {});
|
|
117
|
+
for (let i = 0; i < max_sessions; i++) {
|
|
118
|
+
await login(fixture, `evicting login ${i + 1}/${max_sessions}`);
|
|
119
|
+
}
|
|
120
|
+
const closed = await client.wait_for_close(2000);
|
|
121
|
+
assert.ok(closed, `the socket of a session evicted past ${max_sessions} sessions did not close within 2s`);
|
|
122
|
+
assert.strictEqual(client.close_code, WS_CLOSE_SESSION_REVOKED);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
await client.close();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
92
128
|
});
|
|
93
129
|
};
|
|
@@ -27,11 +27,19 @@ export interface CrossProcessSseTestOptions {
|
|
|
27
27
|
readonly rpc_path?: string;
|
|
28
28
|
/** Origin for the stream request. Defaults to `base_url`. */
|
|
29
29
|
readonly origin?: string;
|
|
30
|
+
/**
|
|
31
|
+
* The backend's per-session audit-stream cap, which the cap case opens one
|
|
32
|
+
* stream past. Defaults to `AUDIT_LOG_SSE_MAX_PER_SCOPE`, the cap both
|
|
33
|
+
* spines apply unless overridden; `null` (a backend with the cap disabled)
|
|
34
|
+
* skips the case.
|
|
35
|
+
*/
|
|
36
|
+
readonly max_per_scope?: number | null;
|
|
30
37
|
}
|
|
31
38
|
/**
|
|
32
|
-
* Register the cross-process SSE round-trip suite.
|
|
39
|
+
* Register the cross-process SSE round-trip suite. Its cases run over a
|
|
33
40
|
* real streaming `fetch`: connected-comment, audit data frame, account-wide
|
|
34
|
-
* close-on-revoke,
|
|
41
|
+
* close-on-revoke, session-scoped close-on-revoke, close on account delete,
|
|
42
|
+
* and the per-session cap.
|
|
35
43
|
*/
|
|
36
44
|
export declare const describe_cross_process_sse_tests: (options: CrossProcessSseTestOptions) => void;
|
|
37
45
|
//# sourceMappingURL=sse_round_trip.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/sse_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"sse_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/sse_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8E9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAK5C,kEAAkE;AAClE,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AA0BD;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,0BAA0B,KAAG,IA2PtF,CAAC"}
|