@fuzdev/fuz_app 0.87.0 → 0.89.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/action_rpc.js +1 -1
- package/dist/actions/register_action_ws.js +1 -1
- package/dist/auth/CLAUDE.md +15 -0
- package/dist/auth/account_actions.js +1 -1
- package/dist/auth/account_route_schema.d.ts +152 -0
- package/dist/auth/account_route_schema.d.ts.map +1 -0
- package/dist/auth/account_route_schema.js +147 -0
- package/dist/auth/account_routes.d.ts +18 -83
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +28 -115
- package/dist/auth/audit_log_route_schema.d.ts +32 -0
- package/dist/auth/audit_log_route_schema.d.ts.map +1 -0
- package/dist/auth/audit_log_route_schema.js +36 -0
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +2 -12
- package/dist/auth/bearer_auth.js +1 -1
- package/dist/auth/bootstrap_route_schema.d.ts +85 -0
- package/dist/auth/bootstrap_route_schema.d.ts.map +1 -0
- package/dist/auth/bootstrap_route_schema.js +56 -0
- package/dist/auth/bootstrap_routes.d.ts +0 -20
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +4 -35
- package/dist/auth/signup_route_schema.d.ts +53 -0
- package/dist/auth/signup_route_schema.d.ts.map +1 -0
- package/dist/auth/signup_route_schema.js +59 -0
- package/dist/auth/signup_routes.d.ts +0 -26
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +8 -40
- package/dist/http/CLAUDE.md +2 -1
- package/dist/http/client_ip.d.ts +15 -0
- package/dist/http/client_ip.d.ts.map +1 -0
- package/dist/http/client_ip.js +13 -0
- package/dist/http/proxy.d.ts +0 -7
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +0 -7
- package/dist/realtime/sse.d.ts +0 -2
- package/dist/realtime/sse.d.ts.map +1 -1
- package/dist/realtime/sse.js +1 -2
- package/dist/realtime/sse_constants.d.ts +17 -0
- package/dist/realtime/sse_constants.d.ts.map +1 -0
- package/dist/realtime/sse_constants.js +16 -0
- package/dist/testing/CLAUDE.md +9 -1
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +1 -1
- package/dist/testing/app_server.d.ts +0 -15
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +1 -15
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +12 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size_smuggling.js +68 -41
- package/dist/testing/cross_backend/capabilities.d.ts +29 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +15 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +13 -0
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +1 -0
- package/dist/testing/cross_backend/in_process_setup.d.ts +143 -0
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/in_process_setup.js +166 -0
- 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 +13 -6
- package/dist/testing/cross_backend/setup.d.ts +31 -140
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +11 -171
- package/dist/testing/cross_backend/sse_round_trip.js +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +2 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +16 -1
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +15 -18
- package/dist/testing/middleware.d.ts.map +1 -1
- package/dist/testing/middleware.js +2 -1
- package/dist/testing/sse_round_trip.d.ts +1 -1
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +1 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +7 -9
- package/dist/testing/test_credentials.d.ts +23 -0
- package/dist/testing/test_credentials.d.ts.map +1 -0
- package/dist/testing/test_credentials.js +22 -0
- package/package.json +4 -4
|
@@ -4,37 +4,45 @@ import '../assert_dev_env.js';
|
|
|
4
4
|
* handling — the security sibling of `body_size.ts`.
|
|
5
5
|
*
|
|
6
6
|
* When the server caps the request body it answers `413` on the
|
|
7
|
-
* `Content-Length` header
|
|
8
|
-
*
|
|
9
|
-
* keep-alive connection whose request body wasn't consumed, because
|
|
10
|
-
* body bytes would
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
7
|
+
* `Content-Length` header. The strong (defense-in-depth) posture is to close
|
|
8
|
+
* the connection *without reading the oversized body*: HTTP/1.1 forbids reusing
|
|
9
|
+
* a keep-alive connection whose request body wasn't consumed, because unread
|
|
10
|
+
* body bytes would be parsed as the start of the next request — a classic
|
|
11
|
+
* request-smuggling vector. This suite probes the boundary by **pipelining**:
|
|
12
|
+
* it opens a raw TCP socket and sends, in one write, an oversized `POST`
|
|
13
|
+
* immediately followed by a second `GET`. The assertion forks on the backend's
|
|
14
|
+
* declared `oversized_reject_closes_connection` capability:
|
|
15
|
+
*
|
|
16
|
+
* - **Closes (Node / Deno / hyper)** — the reject closes the socket with the
|
|
17
|
+
* GET bytes unconsumed, so **at most one** response comes back. `<= 1` rather
|
|
18
|
+
* than "exactly the 413" because the impls close differently at the TCP level
|
|
19
|
+
* (node-server graceful close delivers the 413 first; hyper's RST can drop the
|
|
20
|
+
* in-flight 413 before the client reads it), so demanding a cleanly-read 413
|
|
21
|
+
* would be flaky.
|
|
22
|
+
* - **Drains + keepalives (Bun)** — `Bun.serve` reads the full declared
|
|
23
|
+
* `Content-Length` body and answers the *correctly-framed* pipelined GET, so
|
|
24
|
+
* **two** responses come back. This is **not** a smuggle: the GET is delimited
|
|
25
|
+
* by the body's `Content-Length`, not the unread body reinterpreted as a
|
|
26
|
+
* request — Bun answers it with a clean `400` (`missing method`), not the `x`
|
|
27
|
+
* body bytes reparsed. The security property asserted here is **no desync**
|
|
28
|
+
* (`<= 2`): a real desync would reframe the 1 MiB of `x` into bogus request
|
|
29
|
+
* lines and push the count past two.
|
|
30
|
+
*
|
|
31
|
+
* Either way the oversized body is rejected *with* a 413 — pinned reliably over
|
|
32
|
+
* `fetch` by `describe_body_size_cross_tests`; this test owns only the
|
|
33
|
+
* connection-handling half. A **positive control** (two pipelined requests →
|
|
34
|
+
* `>= 2` responses) proves a second response *would* be seen if a trailing
|
|
35
|
+
* request were processed — without it the close-posture `<= 1` would be vacuous
|
|
36
|
+
* on a server that never reuses connections — and that the counter isn't
|
|
37
|
+
* undercounting.
|
|
27
38
|
*
|
|
28
39
|
* Raw-socket by necessity (the `FetchTransport` can't pipeline two requests on
|
|
29
40
|
* one connection), so — unlike `body_size.ts` — this is **cross-process only**
|
|
30
|
-
* (no in-process leg; there is no socket in-process)
|
|
31
|
-
*
|
|
32
|
-
* socket closes or a short read timeout, so a server that closes (the expected
|
|
33
|
-
* path) and one that merely holds the connection open without smuggling both
|
|
34
|
-
* read as one response; only an actual second response fails.
|
|
41
|
+
* (no in-process leg; there is no socket in-process). The connection-close half
|
|
42
|
+
* is capability-gated; the no-desync half holds on every spine.
|
|
35
43
|
*
|
|
36
|
-
* Cited property: `docs/security.md` §"Body Size Limiting" (connection
|
|
37
|
-
* oversized reject).
|
|
44
|
+
* Cited property: `docs/security.md` §"Body Size Limiting" (connection handling
|
|
45
|
+
* on oversized reject).
|
|
38
46
|
*
|
|
39
47
|
* `$lib`-free by contract (relative + `node:` specifiers only).
|
|
40
48
|
*
|
|
@@ -90,6 +98,7 @@ const count_responses = (raw) => (raw.match(/HTTP\/1\.[01] \d{3}/g) ?? []).lengt
|
|
|
90
98
|
export const describe_body_size_smuggling_cross_tests = (options) => {
|
|
91
99
|
const { base_url } = options;
|
|
92
100
|
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
101
|
+
const closes_connection = options.closes_connection ?? true;
|
|
93
102
|
const host = new URL(base_url).host;
|
|
94
103
|
describe('body-size limit — request-smuggling resistance', () => {
|
|
95
104
|
// Positive control: prove the server returns >1 response on a single
|
|
@@ -118,21 +127,39 @@ export const describe_body_size_smuggling_cross_tests = (options) => {
|
|
|
118
127
|
'x'.repeat(oversized_len) +
|
|
119
128
|
`GET ${rpc_path} HTTP/1.1\r\nHost: ${host}\r\n\r\n`;
|
|
120
129
|
const response = await send_raw(base_url, payload, 2000);
|
|
121
|
-
// The security property is "the pipelined GET is not processed", i.e.
|
|
122
|
-
// **at most one** response comes back (the 413, or none if the close
|
|
123
|
-
// raced the read). A *second* response is the smuggle. We assert `<= 1`
|
|
124
|
-
// rather than "exactly the 413" because the two impls close the
|
|
125
|
-
// connection differently at the TCP level — node-server closes
|
|
126
|
-
// gracefully (the 413 is delivered first), hyper sends an RST that can
|
|
127
|
-
// drop the in-flight 413 before the client reads it — and demanding a
|
|
128
|
-
// cleanly-read 413 here would be flaky. That the oversized body is
|
|
129
|
-
// rejected *with* a 413 is pinned reliably (over `fetch`) by
|
|
130
|
-
// `describe_body_size_cross_tests`; this test owns only the no-smuggle
|
|
131
|
-
// half. The control above proves a second response *would* be seen if
|
|
132
|
-
// the GET were processed, so `<= 1` is a real signal, not a vacuous one.
|
|
133
130
|
const n = count_responses(response);
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
if (closes_connection) {
|
|
132
|
+
// Strong posture (Node / Deno / hyper): the reject closes the
|
|
133
|
+
// connection *without reading the body*, so the pipelined GET is
|
|
134
|
+
// never reached — **at most one** response comes back (the 413, or
|
|
135
|
+
// none if the close raced the read). A *second* response would mean
|
|
136
|
+
// the body was drained and the GET processed. We assert `<= 1` rather
|
|
137
|
+
// than "exactly the 413" because the impls close differently at the
|
|
138
|
+
// TCP level — node-server closes gracefully (413 delivered first),
|
|
139
|
+
// hyper sends an RST that can drop the in-flight 413 before the client
|
|
140
|
+
// reads it — and demanding a cleanly-read 413 would be flaky. The
|
|
141
|
+
// 413-ness itself is pinned reliably (over `fetch`) by
|
|
142
|
+
// `describe_body_size_cross_tests`. The control above proves a second
|
|
143
|
+
// response *would* be seen if the GET were processed, so `<= 1` is a
|
|
144
|
+
// real signal, not vacuous.
|
|
145
|
+
assert.ok(n <= 1, `expected at most one response (oversized reject closes the connection; the ` +
|
|
146
|
+
`pipelined GET must not be reached). Saw ${n}. Raw head: ${response.slice(0, 120)}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Drain-and-keepalive posture (Bun): `Bun.serve` reads the full
|
|
150
|
+
// declared `Content-Length` body and keeps the socket alive, so it
|
|
151
|
+
// answers the *correctly-framed* pipelined GET — **two** responses
|
|
152
|
+
// (the 413 + a well-formed reply to the GET). This is not a smuggle:
|
|
153
|
+
// the GET is delimited by the body's `Content-Length`, not the unread
|
|
154
|
+
// body reinterpreted as request bytes. The security property here is
|
|
155
|
+
// **no desync** — the body bytes are never reparsed as request(s). A
|
|
156
|
+
// real desync would reframe the 1 MiB of `x` into bogus request
|
|
157
|
+
// lines, pushing the count past two; `<= 2` is the no-desync bound,
|
|
158
|
+
// and the control above proves the counter isn't undercounting.
|
|
159
|
+
assert.ok(n <= 2, `expected at most two responses (the 413 + one framed reply to the ` +
|
|
160
|
+
`pipelined GET); more means the oversized body was reparsed as requests ` +
|
|
161
|
+
`(a desync). Saw ${n}. Raw head: ${response.slice(0, 120)}`);
|
|
162
|
+
}
|
|
136
163
|
});
|
|
137
164
|
});
|
|
138
165
|
};
|
|
@@ -99,6 +99,35 @@ export interface BackendCapabilities {
|
|
|
99
99
|
* readiness parity coverage. The drift → `503` path stays per-impl unit tests.
|
|
100
100
|
*/
|
|
101
101
|
readonly ready: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* The account surface serves `GET /api/account/status` (account info +
|
|
104
|
+
* `bootstrap_available` flag). Bundled into `create_account_route_specs`, so
|
|
105
|
+
* any backend mounting the account routes serves it — `true` for every
|
|
106
|
+
* spine. Gates the `account status response body` case in
|
|
107
|
+
* `describe_standard_integration_tests`: when `true` the case asserts the
|
|
108
|
+
* route is present (fail-loud on 404, no silent skip); when `false` it skips
|
|
109
|
+
* explicitly (a backend that deliberately omits the route).
|
|
110
|
+
*/
|
|
111
|
+
readonly account_status: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* On an oversized-body `413` reject the backend **closes the connection
|
|
114
|
+
* without reading the body** (the defense-in-depth posture), rather than
|
|
115
|
+
* draining the declared `Content-Length` and keeping the socket alive.
|
|
116
|
+
* Gates the strong half of `describe_body_size_smuggling_cross_tests`: when
|
|
117
|
+
* `true`, the pipelined GET is never reached (at most one response); when
|
|
118
|
+
* `false`, the suite instead asserts the weaker but still-load-bearing
|
|
119
|
+
* no-desync property (the body is framed on `Content-Length`, not reparsed
|
|
120
|
+
* as request bytes).
|
|
121
|
+
*
|
|
122
|
+
* `true` for the Node / Deno (`@hono/node-server` graceful close) and Rust
|
|
123
|
+
* (hyper RST) backends; `false` for Bun — `Bun.serve` reads the full body
|
|
124
|
+
* and processes the correctly-framed pipelined request even when the `413`
|
|
125
|
+
* carries `Connection: close`. Bun is not insecure (no desync — it answers
|
|
126
|
+
* the cleanly-delimited GET with a proper `400`); the flag records the
|
|
127
|
+
* connection-handling divergence so the suite stays green without losing the
|
|
128
|
+
* smuggle detector. See `docs/security.md` §"Body Size Limiting".
|
|
129
|
+
*/
|
|
130
|
+
readonly oversized_reject_closes_connection: boolean;
|
|
102
131
|
}
|
|
103
132
|
/**
|
|
104
133
|
* Capability declarations for the in-process Hono transport. Every flag
|
|
@@ -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;AAgC9B;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;;;;OASG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;;;;;OAQG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;OAIG;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;;;;;;;;OAQG;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;CACrD;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAapC,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"}
|
|
@@ -11,6 +11,19 @@ import '../assert_dev_env.js';
|
|
|
11
11
|
* capability `true` (see `in_process_capabilities`). Cross-process
|
|
12
12
|
* backends opt in per-flag on their `BackendConfig`.
|
|
13
13
|
*
|
|
14
|
+
* **Where the per-backend declarations live** (this file owns only the
|
|
15
|
+
* vocabulary + the in-process preset):
|
|
16
|
+
*
|
|
17
|
+
* - `in_process_capabilities` — here; every flag `true`.
|
|
18
|
+
* - `ts_default_capabilities` / `rust_default_capabilities` — consumer-facing
|
|
19
|
+
* family defaults, in `default_backend_configs.ts` (full literals, so adding
|
|
20
|
+
* a capability is a compile error until each family declares it).
|
|
21
|
+
* - `ts_spine_capabilities` / `ts_spine_bun_capabilities` — fuz_app's own TS
|
|
22
|
+
* spine presets, in `ts_spine_backend_config.ts` (deltas off the family
|
|
23
|
+
* default; Bun flips `oversized_reject_closes_connection`).
|
|
24
|
+
* - `rust_spine_stub_capabilities` — fuz_app's Rust spine-stub preset, in
|
|
25
|
+
* `rust_spine_stub_backend_config.ts` (delta off the rust family default).
|
|
26
|
+
*
|
|
14
27
|
* @module
|
|
15
28
|
*/
|
|
16
29
|
import { test } from 'vitest';
|
|
@@ -31,6 +44,8 @@ export const in_process_capabilities = Object.freeze({
|
|
|
31
44
|
account_lifecycle: true,
|
|
32
45
|
fact_serving: true,
|
|
33
46
|
ready: true,
|
|
47
|
+
account_status: true,
|
|
48
|
+
oversized_reject_closes_connection: true,
|
|
34
49
|
});
|
|
35
50
|
/**
|
|
36
51
|
* Conditional `test()` wrapper — registers a vitest case only when
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;GAKG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAoBpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAmBtC,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"}
|
|
@@ -20,6 +20,13 @@ export const ts_default_capabilities = Object.freeze({
|
|
|
20
20
|
// Off by default like `sse` — a generic TS consumer backend may not mount
|
|
21
21
|
// `/ready`. fuz_app's own spine configs (`ts_spine_*`) opt in.
|
|
22
22
|
ready: false,
|
|
23
|
+
// `GET /api/account/status` is bundled into `create_account_route_specs`, so
|
|
24
|
+
// every TS backend mounting account routes serves it.
|
|
25
|
+
account_status: true,
|
|
26
|
+
// Node/Deno close the socket on an oversized-body reject (the default
|
|
27
|
+
// posture). A Bun-served consumer overrides to `false` (see the bun spine
|
|
28
|
+
// config) — fail-loud rather than silently skipping the smuggle detector.
|
|
29
|
+
oversized_reject_closes_connection: true,
|
|
23
30
|
});
|
|
24
31
|
/**
|
|
25
32
|
* Capabilities for the Rust family. Adds `trusted_proxy: true` (the
|
|
@@ -40,6 +47,12 @@ export const rust_default_capabilities = Object.freeze({
|
|
|
40
47
|
// Off by default like `sse`; the spine-stub preset opts in (it mounts
|
|
41
48
|
// `/ready` over the env-supplied fixture path).
|
|
42
49
|
ready: false,
|
|
50
|
+
// The Rust `account_router` bundles `/status` into the account routes, so
|
|
51
|
+
// every Rust spine serving the account surface serves it.
|
|
52
|
+
account_status: true,
|
|
53
|
+
// hyper sends an RST on the oversized-body reject — the connection closes
|
|
54
|
+
// and the pipelined request is never reached.
|
|
55
|
+
oversized_reject_closes_connection: true,
|
|
43
56
|
});
|
|
44
57
|
/** Bootstrap block built from the default secrets + supplied paths. */
|
|
45
58
|
const build_default_bootstrap = (paths, overrides) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAIpD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,8CAA8C,CAAC;AACrF,OAAO,EAAqB,KAAK,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAwB,KAAK,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAIxF,OAAO,EAAqB,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EAAC,cAAc,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oCAAoC,CAAC;AAGzE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,gBAExB,CAAC;AAEH,iEAAiE;AACjE,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,4BAA4B,CAAC;AAExD,+CAA+C;AAC/C,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,gBAAgB,EACrB,UAAU,wBAAwB,KAChC,KAAK,CAAC,eAAe,CAQvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAIpD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,8CAA8C,CAAC;AACrF,OAAO,EAAqB,KAAK,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAwB,KAAK,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAIxF,OAAO,EAAqB,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EAAC,cAAc,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oCAAoC,CAAC;AAGzE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,gBAExB,CAAC;AAEH,iEAAiE;AACjE,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,4BAA4B,CAAC;AAExD,+CAA+C;AAC/C,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAC/B,KAAK,gBAAgB,EACrB,UAAU,wBAAwB,KAChC,KAAK,CAAC,eAAe,CAQvB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,SAAS,CAkB/E,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,EAAE,GAAwD,CAAC;AAEjG;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,GAAI,MAAM,MAAM,KAAG,SAC6B,CAAC;AAE3F;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAO,cAM1C,CAAC"}
|
|
@@ -80,6 +80,7 @@ export const create_spine_route_specs = (ctx) => [
|
|
|
80
80
|
ip_rate_limiter: null,
|
|
81
81
|
login_account_rate_limiter: null,
|
|
82
82
|
login_fail_floor_ms: 0,
|
|
83
|
+
bootstrap_status: ctx.bootstrap_status,
|
|
83
84
|
}),
|
|
84
85
|
...create_signup_route_specs(ctx.deps, {
|
|
85
86
|
session_options: spine_session_options,
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { RouteSpec } from '../../http/route_spec.js';
|
|
3
|
+
import type { AppSurfaceSpec } from '../../http/surface.js';
|
|
4
|
+
import type { BootstrapServerOptions } from '../../server/app_server.js';
|
|
5
|
+
import type { AppServerContext } from '../../server/app_server_context.js';
|
|
6
|
+
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
7
|
+
import { type CreateTestAppOptions, type SuiteAppOptions } from '../app_server.js';
|
|
8
|
+
import { type RpcEndpointsSuiteOption } from '../rpc_helpers.js';
|
|
9
|
+
import { type BackendCapabilities } from './capabilities.js';
|
|
10
|
+
import { type SetupTest, type ExtraAccountSpec } from './setup.js';
|
|
11
|
+
/**
|
|
12
|
+
* Options for `default_in_process_setup`. Extends `CreateTestAppOptions`
|
|
13
|
+
* with the same `extra_accounts` slot the cross-process variant accepts
|
|
14
|
+
* — both transports observe the same bootstrap-time secondary set so
|
|
15
|
+
* suite bodies can read `fixture.extra_accounts[username]` uniformly.
|
|
16
|
+
*/
|
|
17
|
+
export interface InProcessSetupOptions extends CreateTestAppOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Additional accounts seeded at this transport's bootstrap-equivalent
|
|
20
|
+
* step. See `ExtraAccountSpec` for the cradle-only-bypass rationale.
|
|
21
|
+
* Most suites pass `undefined` / `[]`; the `ROLE_KEEPER` probe (in
|
|
22
|
+
* `describe_standard_admin_integration_tests`) is the primary user.
|
|
23
|
+
*/
|
|
24
|
+
readonly extra_accounts?: ReadonlyArray<ExtraAccountSpec>;
|
|
25
|
+
/**
|
|
26
|
+
* Additional actor names to seed on the bootstrapped keeper — exposed on
|
|
27
|
+
* `fixture.extra_actors`. See `CrossProcessSetupOptions.extra_actors` /
|
|
28
|
+
* `TestFixtureBase.extra_actors`. Seeded directly against the live backend
|
|
29
|
+
* DB (in-process has no wire hop).
|
|
30
|
+
*/
|
|
31
|
+
readonly extra_actors?: ReadonlyArray<string>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a `SetupTest` that creates a fresh `TestApp` per call via
|
|
35
|
+
* `create_test_app` and projects it into the `TestFixture` shape.
|
|
36
|
+
*
|
|
37
|
+
* Same factory inputs `create_test_app` already takes — this helper
|
|
38
|
+
* is a projection layer, not a new lifecycle. fuz_app's own `src/test/`
|
|
39
|
+
* and consumer suites pass `default_in_process_setup({...factory_inputs})`
|
|
40
|
+
* in place of the old per-suite factory-input bundle. The `extra_accounts`
|
|
41
|
+
* slot (see `InProcessSetupOptions`) seeds bootstrap-time secondaries
|
|
42
|
+
* directly via `create_test_account_with_credentials` against the same
|
|
43
|
+
* DB the keeper just landed on — mirrors the cross-process
|
|
44
|
+
* `_testing_reset` cradle so suite bodies read
|
|
45
|
+
* `fixture.extra_accounts[username]` uniformly regardless of transport.
|
|
46
|
+
*
|
|
47
|
+
* The describe-level `auth_integration_truncate_tables` / pglite WASM
|
|
48
|
+
* cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
|
|
49
|
+
* (`testing/db.ts`) — `default_in_process_setup` doesn't manage db state
|
|
50
|
+
* beyond what `create_test_app` already does.
|
|
51
|
+
*/
|
|
52
|
+
export declare const default_in_process_setup: (options: InProcessSetupOptions) => SetupTest;
|
|
53
|
+
/**
|
|
54
|
+
* Consumer-facing options for `default_in_process_suite_options` — the
|
|
55
|
+
* minimal factory inputs both `default_in_process_setup` and
|
|
56
|
+
* `create_test_app_surface_spec` consume to produce the
|
|
57
|
+
* `{setup_test, surface_source, capabilities}` bundle.
|
|
58
|
+
*/
|
|
59
|
+
export interface DefaultInProcessSuiteOptions {
|
|
60
|
+
session_options: SessionOptions<string>;
|
|
61
|
+
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
62
|
+
rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
63
|
+
/**
|
|
64
|
+
* Bootstrap config — top-level slot, single source of truth for both
|
|
65
|
+
* surface generation and live dispatch. Same precedent as
|
|
66
|
+
* `rpc_endpoints`. Discriminated by `mode`; omit for the default (no
|
|
67
|
+
* bootstrap route mounted).
|
|
68
|
+
*/
|
|
69
|
+
bootstrap?: BootstrapServerOptions;
|
|
70
|
+
app_options?: SuiteAppOptions;
|
|
71
|
+
/**
|
|
72
|
+
* Additional roles to grant the bootstrapped keeper alongside
|
|
73
|
+
* `ROLE_KEEPER` — additive, never replaces. The keeper account
|
|
74
|
+
* always holds `ROLE_KEEPER` (otherwise daemon-token auth breaks);
|
|
75
|
+
* pass extras here for suites that need additional role coverage.
|
|
76
|
+
*
|
|
77
|
+
* Admin-suite consumers pass `[ROLE_ADMIN]` so the default keeper
|
|
78
|
+
* can hit admin-gated RPC methods.
|
|
79
|
+
* `describe_standard_admin_integration_tests` and
|
|
80
|
+
* `describe_audit_completeness_tests` need this.
|
|
81
|
+
*/
|
|
82
|
+
extra_keeper_roles?: Array<string>;
|
|
83
|
+
/**
|
|
84
|
+
* Bootstrap-time secondary accounts seeded alongside the keeper. See
|
|
85
|
+
* `ExtraAccountSpec` for the cradle-only-bypass rationale. Same shape
|
|
86
|
+
* as the cross-process `extra_accounts` option — suites read seeded
|
|
87
|
+
* accounts from `fixture.extra_accounts[username]` regardless of
|
|
88
|
+
* transport.
|
|
89
|
+
*/
|
|
90
|
+
extra_accounts?: ReadonlyArray<ExtraAccountSpec>;
|
|
91
|
+
/**
|
|
92
|
+
* Additional actor names to seed on the bootstrapped keeper — exposed on
|
|
93
|
+
* `fixture.extra_actors`. See `TestFixtureBase.extra_actors`.
|
|
94
|
+
*/
|
|
95
|
+
extra_actors?: ReadonlyArray<string>;
|
|
96
|
+
/**
|
|
97
|
+
* Pre-built `AppSurfaceSpec` — overrides the default which calls
|
|
98
|
+
* `create_test_app_surface_spec` against the same factory inputs.
|
|
99
|
+
* Pass when surface assembly needs fields outside the shared subset
|
|
100
|
+
* (e.g. `env_schema`, `event_specs`, `ws_endpoints`, `transform_middleware`).
|
|
101
|
+
*/
|
|
102
|
+
surface_source?: AppSurfaceSpec;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Build the full in-process suite bundle in a single helper invocation.
|
|
106
|
+
* Output covers `{setup_test, surface_source, capabilities}` plus every
|
|
107
|
+
* factory input the Tier 1 suites read at their top level
|
|
108
|
+
* (`session_options`, `create_route_specs`, `rpc_endpoints`) — so the
|
|
109
|
+
* call site spreads once and adds only suite-specific extras
|
|
110
|
+
* (`roles`, `skip_routes`, `input_overrides`, `db_factories`, ...).
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* // Suite-extras-free call: helper output is the entire options bag.
|
|
114
|
+
* describe_round_trip_validation(default_in_process_suite_options({
|
|
115
|
+
* session_options,
|
|
116
|
+
* create_route_specs,
|
|
117
|
+
* rpc_endpoints: [rpc_endpoint_spec],
|
|
118
|
+
* }));
|
|
119
|
+
*
|
|
120
|
+
* // With suite-specific extras: spread and add.
|
|
121
|
+
* describe_standard_admin_integration_tests({
|
|
122
|
+
* ...default_in_process_suite_options({
|
|
123
|
+
* session_options, create_route_specs, rpc_endpoints,
|
|
124
|
+
* extra_keeper_roles: [ROLE_ADMIN],
|
|
125
|
+
* }),
|
|
126
|
+
* roles,
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* Suites that don't read `session_options` / `rpc_endpoints` at their
|
|
131
|
+
* top level (`round_trip`, `data_exposure`) accept the spread anyway —
|
|
132
|
+
* excess properties on spread sources aren't checked by TS, and the
|
|
133
|
+
* uniform shape keeps consumer call sites mechanical.
|
|
134
|
+
*/
|
|
135
|
+
export declare const default_in_process_suite_options: <const O extends DefaultInProcessSuiteOptions>(options: O) => {
|
|
136
|
+
setup_test: SetupTest;
|
|
137
|
+
surface_source: AppSurfaceSpec;
|
|
138
|
+
capabilities: BackendCapabilities;
|
|
139
|
+
session_options: O["session_options"];
|
|
140
|
+
create_route_specs: O["create_route_specs"];
|
|
141
|
+
rpc_endpoints: O["rpc_endpoints"];
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=in_process_setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in_process_setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/in_process_setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqB9B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EAIN,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAGN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA0B,KAAK,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAEpF,OAAO,EAGN,KAAK,SAAS,EAEd,KAAK,gBAAgB,EACrB,MAAM,YAAY,CAAC;AAepB;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IAClE;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC1D;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS,qBAAqB,KAAG,SAqEjC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,WAAW,4BAA4B;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACnC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACjD;;;OAGG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CAChC;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,gCAAgC,GAAI,KAAK,CAAC,CAAC,SAAS,4BAA4B,EAC5F,SAAS,CAAC,KACR;IACF,UAAU,EAAE,SAAS,CAAC;IACtB,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,mBAAmB,CAAC;IAClC,eAAe,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC;IACtC,kBAAkB,EAAE,CAAC,CAAC,oBAAoB,CAAC,CAAC;IAC5C,aAAa,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC;CA2BjC,CAAC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { ROLE_KEEPER } from '../../auth/role_schema.js';
|
|
3
|
+
import { query_create_actor } from '../../auth/account_queries.js';
|
|
4
|
+
import { create_test_app, create_test_account_with_credentials, mint_test_session, } from '../app_server.js';
|
|
5
|
+
import { create_test_app_surface_spec } from '../stubs.js';
|
|
6
|
+
import { http_transport, } from '../rpc_helpers.js';
|
|
7
|
+
import { in_process_capabilities } from './capabilities.js';
|
|
8
|
+
import { build_extra_account_fixture, EXPIRED_SESSION_OFFSET_SECONDS, } from './setup.js';
|
|
9
|
+
/**
|
|
10
|
+
* Wrap a Hono-style app into a `FetchTransport`-shaped object so the
|
|
11
|
+
* shared `TestFixtureBase.transport` type holds for both in-process and
|
|
12
|
+
* cross-process setups. In-process has no real cookie jar — the no-op
|
|
13
|
+
* `cookies()` returns `[]`; in-process tests build cookies via
|
|
14
|
+
* `fixture.create_session_headers()` instead.
|
|
15
|
+
*/
|
|
16
|
+
const in_process_fetch_transport = (app) => {
|
|
17
|
+
const call = http_transport(app);
|
|
18
|
+
const transport = ((url, init) => call(url, init));
|
|
19
|
+
return Object.assign(transport, { cookies: () => [] });
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Build a `SetupTest` that creates a fresh `TestApp` per call via
|
|
23
|
+
* `create_test_app` and projects it into the `TestFixture` shape.
|
|
24
|
+
*
|
|
25
|
+
* Same factory inputs `create_test_app` already takes — this helper
|
|
26
|
+
* is a projection layer, not a new lifecycle. fuz_app's own `src/test/`
|
|
27
|
+
* and consumer suites pass `default_in_process_setup({...factory_inputs})`
|
|
28
|
+
* in place of the old per-suite factory-input bundle. The `extra_accounts`
|
|
29
|
+
* slot (see `InProcessSetupOptions`) seeds bootstrap-time secondaries
|
|
30
|
+
* directly via `create_test_account_with_credentials` against the same
|
|
31
|
+
* DB the keeper just landed on — mirrors the cross-process
|
|
32
|
+
* `_testing_reset` cradle so suite bodies read
|
|
33
|
+
* `fixture.extra_accounts[username]` uniformly regardless of transport.
|
|
34
|
+
*
|
|
35
|
+
* The describe-level `auth_integration_truncate_tables` / pglite WASM
|
|
36
|
+
* cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
|
|
37
|
+
* (`testing/db.ts`) — `default_in_process_setup` doesn't manage db state
|
|
38
|
+
* beyond what `create_test_app` already does.
|
|
39
|
+
*/
|
|
40
|
+
export const default_in_process_setup = (options) => async () => {
|
|
41
|
+
// Per-test fresh db. When `options.migration_namespaces` is set,
|
|
42
|
+
// `create_test_app` provisions an auth+extras PGlite (e.g. the cell
|
|
43
|
+
// layer); otherwise the auth-only default. Either way the factory
|
|
44
|
+
// resets + re-migrates on each `create`, so the per-test keeper
|
|
45
|
+
// bootstrap below lands on a clean DB.
|
|
46
|
+
const test_app = await create_test_app(options);
|
|
47
|
+
// Seed bootstrap-time secondaries against the same DB the keeper
|
|
48
|
+
// just landed on. Direct-insert is the only path for roles whose
|
|
49
|
+
// `grant_paths` excludes `'admin'` (e.g. `ROLE_KEEPER`) — see
|
|
50
|
+
// `ExtraAccountSpec` for why this bypass is bootstrap-cradle-only.
|
|
51
|
+
const extra_accounts = {};
|
|
52
|
+
const { cookie_name } = options.session_options;
|
|
53
|
+
for (const spec of options.extra_accounts ?? []) {
|
|
54
|
+
const seeded = await create_test_account_with_credentials({
|
|
55
|
+
db: test_app.backend.deps.db,
|
|
56
|
+
keyring: test_app.backend.keyring,
|
|
57
|
+
session_options: options.session_options,
|
|
58
|
+
password: test_app.backend.deps.password,
|
|
59
|
+
username: spec.username,
|
|
60
|
+
password_value: spec.password_value,
|
|
61
|
+
roles: [...spec.roles],
|
|
62
|
+
});
|
|
63
|
+
extra_accounts[spec.username] = build_extra_account_fixture(seeded, cookie_name);
|
|
64
|
+
}
|
|
65
|
+
// Seed additional keeper actors directly against the same DB. Mirrors
|
|
66
|
+
// the cross-process `_testing_reset` `extra_actors` path; no production
|
|
67
|
+
// wire mints a second actor, so this bootstrap-cradle insert is the
|
|
68
|
+
// only way into a multi-actor keeper state.
|
|
69
|
+
const extra_actors = [];
|
|
70
|
+
for (const name of options.extra_actors ?? []) {
|
|
71
|
+
const seeded_actor = await query_create_actor({ db: test_app.backend.deps.db }, test_app.backend.account.id, name);
|
|
72
|
+
extra_actors.push({ id: seeded_actor.id, name: seeded_actor.name });
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
transport: in_process_fetch_transport(test_app.app),
|
|
76
|
+
// In-process the wrapper is stateless and never auto-adds Origin —
|
|
77
|
+
// `options` is accepted for API symmetry with cross-process but
|
|
78
|
+
// has no observable effect.
|
|
79
|
+
fresh_transport: () => in_process_fetch_transport(test_app.app),
|
|
80
|
+
account: test_app.backend.account,
|
|
81
|
+
actor: test_app.backend.actor,
|
|
82
|
+
create_session_headers: test_app.create_session_headers,
|
|
83
|
+
create_bearer_headers: test_app.create_bearer_headers,
|
|
84
|
+
create_daemon_token_headers: test_app.create_daemon_token_headers,
|
|
85
|
+
create_account: test_app.create_account,
|
|
86
|
+
extra_accounts,
|
|
87
|
+
extra_actors,
|
|
88
|
+
// Forge directly against the live backend's DB + keyring — no wire
|
|
89
|
+
// hop needed in-process.
|
|
90
|
+
mint_expired_session: async () => {
|
|
91
|
+
const { session_cookie } = await mint_test_session({
|
|
92
|
+
db: test_app.backend.deps.db,
|
|
93
|
+
keyring: test_app.backend.keyring,
|
|
94
|
+
session_options: options.session_options,
|
|
95
|
+
account_id: test_app.backend.account.id,
|
|
96
|
+
expires_in_seconds: EXPIRED_SESSION_OFFSET_SECONDS,
|
|
97
|
+
});
|
|
98
|
+
return `${cookie_name}=${session_cookie}`;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
// NOTE: bootstrap config is read from `options.bootstrap` — top-level slot,
|
|
103
|
+
// single source of truth for both the surface spec (so the route appears in
|
|
104
|
+
// `expected_public_routes` / attack-surface iteration) AND the live
|
|
105
|
+
// `create_test_app` (so the route exists at dispatch time and returns 403
|
|
106
|
+
// `ERROR_ALREADY_BOOTSTRAPPED` matching its declared 403 schema). Same
|
|
107
|
+
// precedent as `rpc_endpoints`. Discriminated union shape (`mode: 'disabled'`
|
|
108
|
+
// | `'surface_only'` | `'live'`) replaces the old `token_path: string | null`
|
|
109
|
+
// overload that conflated three deployment intents on one channel.
|
|
110
|
+
/**
|
|
111
|
+
* Build the full in-process suite bundle in a single helper invocation.
|
|
112
|
+
* Output covers `{setup_test, surface_source, capabilities}` plus every
|
|
113
|
+
* factory input the Tier 1 suites read at their top level
|
|
114
|
+
* (`session_options`, `create_route_specs`, `rpc_endpoints`) — so the
|
|
115
|
+
* call site spreads once and adds only suite-specific extras
|
|
116
|
+
* (`roles`, `skip_routes`, `input_overrides`, `db_factories`, ...).
|
|
117
|
+
*
|
|
118
|
+
* ```ts
|
|
119
|
+
* // Suite-extras-free call: helper output is the entire options bag.
|
|
120
|
+
* describe_round_trip_validation(default_in_process_suite_options({
|
|
121
|
+
* session_options,
|
|
122
|
+
* create_route_specs,
|
|
123
|
+
* rpc_endpoints: [rpc_endpoint_spec],
|
|
124
|
+
* }));
|
|
125
|
+
*
|
|
126
|
+
* // With suite-specific extras: spread and add.
|
|
127
|
+
* describe_standard_admin_integration_tests({
|
|
128
|
+
* ...default_in_process_suite_options({
|
|
129
|
+
* session_options, create_route_specs, rpc_endpoints,
|
|
130
|
+
* extra_keeper_roles: [ROLE_ADMIN],
|
|
131
|
+
* }),
|
|
132
|
+
* roles,
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* Suites that don't read `session_options` / `rpc_endpoints` at their
|
|
137
|
+
* top level (`round_trip`, `data_exposure`) accept the spread anyway —
|
|
138
|
+
* excess properties on spread sources aren't checked by TS, and the
|
|
139
|
+
* uniform shape keeps consumer call sites mechanical.
|
|
140
|
+
*/
|
|
141
|
+
export const default_in_process_suite_options = (options) => ({
|
|
142
|
+
setup_test: default_in_process_setup({
|
|
143
|
+
session_options: options.session_options,
|
|
144
|
+
create_route_specs: options.create_route_specs,
|
|
145
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
146
|
+
bootstrap: options.bootstrap,
|
|
147
|
+
app_options: options.app_options,
|
|
148
|
+
roles: [ROLE_KEEPER, ...(options.extra_keeper_roles ?? [])],
|
|
149
|
+
extra_accounts: options.extra_accounts,
|
|
150
|
+
extra_actors: options.extra_actors,
|
|
151
|
+
}),
|
|
152
|
+
surface_source: options.surface_source ??
|
|
153
|
+
create_test_app_surface_spec({
|
|
154
|
+
session_options: options.session_options,
|
|
155
|
+
create_route_specs: options.create_route_specs,
|
|
156
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
157
|
+
// Mirror what `create_test_app` → `create_app_server` will mount.
|
|
158
|
+
// Both helpers read from the top-level `bootstrap` slot so surface
|
|
159
|
+
// and live app stay in sync by construction.
|
|
160
|
+
bootstrap: options.bootstrap,
|
|
161
|
+
}),
|
|
162
|
+
capabilities: in_process_capabilities,
|
|
163
|
+
session_options: options.session_options,
|
|
164
|
+
create_route_specs: options.create_route_specs,
|
|
165
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
166
|
+
});
|
|
@@ -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;AAuC9B,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAQvD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;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;AAuC9B,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAQvD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAenG,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,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAwCF,CAAC"}
|
|
@@ -46,6 +46,18 @@ export const RUST_SPINE_STUB_BIN_ENV = 'FUZ_TESTING_RUST_SPINE_STUB_BIN';
|
|
|
46
46
|
* column-presence is engine-portable, so one file is the cross-impl contract.
|
|
47
47
|
*/
|
|
48
48
|
export const RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV = 'FUZ_RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH';
|
|
49
|
+
/**
|
|
50
|
+
* Capabilities for the Rust `testing_spine_stub` — `rust_default_capabilities`
|
|
51
|
+
* plus `sse` (the stub serves `GET /api/admin/audit/stream` over the spine
|
|
52
|
+
* `fuz_realtime::SseRegistry` + audit listener) and `ready` (it live-mounts
|
|
53
|
+
* `/ready` over the env-supplied fixture path). Named (not inline) so every
|
|
54
|
+
* spine preset is greppable, mirroring `ts_spine_capabilities`.
|
|
55
|
+
*/
|
|
56
|
+
const rust_spine_stub_capabilities = Object.freeze({
|
|
57
|
+
...rust_default_capabilities,
|
|
58
|
+
sse: true,
|
|
59
|
+
ready: true,
|
|
60
|
+
});
|
|
49
61
|
/** Default listening port — slots beside zzz's 1175/1176; matches the binary's `DEFAULT_PORT`. */
|
|
50
62
|
export const RUST_SPINE_STUB_DEFAULT_PORT = 1177;
|
|
51
63
|
/** Default Postgres database — real PG (PGlite isn't reachable from `tokio-postgres`). */
|
|
@@ -78,12 +90,7 @@ export const rust_spine_stub_backend_config = (options = {}) => {
|
|
|
78
90
|
// is the lower-precedence fallback — both carry the same value.
|
|
79
91
|
start_command: [binary_path, '--port', String(port)],
|
|
80
92
|
database_url,
|
|
81
|
-
|
|
82
|
-
// `fuz_realtime::SseRegistry` + audit listener), so it advertises `sse`
|
|
83
|
-
// like the TS spines — the cross-process SSE suite's three cases run. It
|
|
84
|
-
// also live-mounts `/ready` over the env-supplied fixture path, so it
|
|
85
|
-
// advertises `ready` for `describe_ready_cross_tests`.
|
|
86
|
-
capabilities: { ...rust_default_capabilities, sse: true, ready: true },
|
|
93
|
+
capabilities: rust_spine_stub_capabilities,
|
|
87
94
|
port_env_var: 'FUZ_RUST_SPINE_STUB_PORT',
|
|
88
95
|
rust_log: 'info,testing_spine_stub=info',
|
|
89
96
|
paths,
|