@fuzdev/fuz_app 0.86.0 → 0.88.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 +146 -0
- package/dist/auth/account_route_schema.d.ts.map +1 -0
- package/dist/auth/account_route_schema.js +141 -0
- package/dist/auth/account_routes.d.ts +0 -79
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +15 -110
- 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 +6 -3
- 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/account_lifecycle.d.ts +5 -5
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +1 -1
- package/dist/testing/cross_backend/actor_lookup.d.ts +5 -5
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
- package/dist/testing/cross_backend/actor_search.d.ts +3 -3
- package/dist/testing/cross_backend/actor_search.d.ts.map +1 -1
- package/dist/testing/cross_backend/app_settings.d.ts +3 -3
- package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
- package/dist/testing/cross_backend/body_size.d.ts +10 -0
- package/dist/testing/cross_backend/body_size.d.ts.map +1 -0
- package/dist/testing/cross_backend/body_size.js +137 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts +10 -0
- package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -0
- package/dist/testing/cross_backend/body_size_smuggling.js +138 -0
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts +0 -11
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.d.ts +2 -2
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +1 -1
- package/dist/testing/cross_backend/cell_grant_role.d.ts +2 -2
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_grant_role.js +1 -1
- package/dist/testing/cross_backend/cell_relations.d.ts +2 -2
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_relations.js +1 -1
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
- package/dist/testing/cross_backend/conformance_table.js +8 -6
- package/dist/testing/cross_backend/fact_serving.d.ts +2 -3
- package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
- 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/origin.d.ts +5 -5
- package/dist/testing/cross_backend/origin.d.ts.map +1 -1
- package/dist/testing/cross_backend/ready.d.ts +2 -7
- package/dist/testing/cross_backend/ready.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.d.ts +54 -135
- 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_backdoor.d.ts +2 -2
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +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/integration.js +2 -2
- 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 +2 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-backend request-smuggling probe for the body-size limit's connection
|
|
4
|
+
* handling — the security sibling of `body_size.ts`.
|
|
5
|
+
*
|
|
6
|
+
* When the server caps the request body it answers `413` on the
|
|
7
|
+
* `Content-Length` header and closes the connection *without reading the
|
|
8
|
+
* oversized body*. That close is load-bearing: HTTP/1.1 forbids reusing a
|
|
9
|
+
* keep-alive connection whose request body wasn't consumed, because the unread
|
|
10
|
+
* body bytes would otherwise be parsed as the start of the next request — a
|
|
11
|
+
* classic request-smuggling vector. This suite proves the mitigation holds end
|
|
12
|
+
* to end by **pipelining**: it opens a raw TCP socket and sends, in one write,
|
|
13
|
+
* an oversized `POST` immediately followed by a second `GET` request. A correct
|
|
14
|
+
* server rejects the POST with `413` and never processes the trailing GET (the
|
|
15
|
+
* connection closes with the GET bytes unconsumed); a vulnerable one would
|
|
16
|
+
* drain past the body and answer the smuggled GET too. The assertion is
|
|
17
|
+
* therefore "**at most one** HTTP response comes back" — a *second* response is
|
|
18
|
+
* the smuggle. It's `<= 1` rather than "exactly the 413" because the impls close
|
|
19
|
+
* differently at the TCP level (node-server graceful close delivers the 413
|
|
20
|
+
* first; hyper's RST can drop the in-flight 413 before the client reads it), so
|
|
21
|
+
* demanding a cleanly-read 413 would be flaky; the 413-ness itself is pinned
|
|
22
|
+
* reliably over `fetch` by `describe_body_size_cross_tests`. A **positive
|
|
23
|
+
* control** (two pipelined requests → >= 2 responses) proves a second response
|
|
24
|
+
* *would* be seen if the trailing request were processed — without it the
|
|
25
|
+
* `<= 1` assertion would be vacuous on a server that never reuses connections,
|
|
26
|
+
* and it also proves the response counter isn't undercounting.
|
|
27
|
+
*
|
|
28
|
+
* Raw-socket by necessity (the `FetchTransport` can't pipeline two requests on
|
|
29
|
+
* one connection), so — unlike `body_size.ts` — this is **cross-process only**
|
|
30
|
+
* (no in-process leg; there is no socket in-process) and ungated (the limit is
|
|
31
|
+
* on every spine). Robust by construction: it counts responses until the
|
|
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.
|
|
35
|
+
*
|
|
36
|
+
* Cited property: `docs/security.md` §"Body Size Limiting" (connection close on
|
|
37
|
+
* oversized reject).
|
|
38
|
+
*
|
|
39
|
+
* `$lib`-free by contract (relative + `node:` specifiers only).
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
*/
|
|
43
|
+
import { connect } from 'node:net';
|
|
44
|
+
import { describe, test, assert } from 'vitest';
|
|
45
|
+
import { SPINE_RPC_PATH } from './default_spine_surface.js';
|
|
46
|
+
/** The shared 1 MiB cap (see `body_size.ts` for why it's a local constant). */
|
|
47
|
+
const BODY_LIMIT_DEFAULT_BYTES = 1024 * 1024;
|
|
48
|
+
/**
|
|
49
|
+
* Open a raw TCP socket to `base_url`, write `request_bytes` once, and collect
|
|
50
|
+
* everything the server sends back until it closes the connection or
|
|
51
|
+
* `read_timeout_ms` elapses. Write errors are swallowed: the server closing
|
|
52
|
+
* mid-upload (the correct response to an oversized body) surfaces as
|
|
53
|
+
* `EPIPE`/`ECONNRESET` on our unfinished write, which is exactly the behavior
|
|
54
|
+
* under test — what matters is what we *read back*.
|
|
55
|
+
*/
|
|
56
|
+
const send_raw = (base_url, request_bytes, read_timeout_ms) => new Promise((resolve) => {
|
|
57
|
+
const url = new URL(base_url);
|
|
58
|
+
const port = Number(url.port) || (url.protocol === 'https:' ? 443 : 80);
|
|
59
|
+
const socket = connect({ host: url.hostname, port });
|
|
60
|
+
let received = '';
|
|
61
|
+
let settled = false;
|
|
62
|
+
const finish = () => {
|
|
63
|
+
if (settled)
|
|
64
|
+
return;
|
|
65
|
+
settled = true;
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
socket.destroy();
|
|
68
|
+
resolve(received);
|
|
69
|
+
};
|
|
70
|
+
const timer = setTimeout(finish, read_timeout_ms);
|
|
71
|
+
socket.setEncoding('latin1');
|
|
72
|
+
socket.on('connect', () => socket.write(request_bytes));
|
|
73
|
+
socket.on('data', (chunk) => {
|
|
74
|
+
received += chunk;
|
|
75
|
+
});
|
|
76
|
+
socket.on('error', () => { }); // EPIPE/ECONNRESET on mid-write close is expected
|
|
77
|
+
socket.on('close', finish);
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Count HTTP response status lines in a raw byte stream. Deliberately
|
|
81
|
+
* **unanchored** (no `^`/`m`): a second pipelined response is concatenated
|
|
82
|
+
* straight after the first response's body, which carries no trailing newline,
|
|
83
|
+
* so a line-anchored match would miss it — and missing a smuggled second
|
|
84
|
+
* response is a silent false negative. No response header or JSON error body
|
|
85
|
+
* contains the literal `HTTP/1.x NNN`, so an unanchored match counts exactly
|
|
86
|
+
* the status lines. The positive control below proves this counts 2 when the
|
|
87
|
+
* server genuinely returns 2.
|
|
88
|
+
*/
|
|
89
|
+
const count_responses = (raw) => (raw.match(/HTTP\/1\.[01] \d{3}/g) ?? []).length;
|
|
90
|
+
export const describe_body_size_smuggling_cross_tests = (options) => {
|
|
91
|
+
const { base_url } = options;
|
|
92
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
93
|
+
const host = new URL(base_url).host;
|
|
94
|
+
describe('body-size limit — request-smuggling resistance', () => {
|
|
95
|
+
// Positive control: prove the server returns >1 response on a single
|
|
96
|
+
// connection. Without this the smuggling assertion below would be
|
|
97
|
+
// vacuously green on a server that simply never reuses connections — and
|
|
98
|
+
// it also validates `count_responses` actually counts a second response.
|
|
99
|
+
test('control: two pipelined requests → ≥2 responses (connection reuse is real)', async () => {
|
|
100
|
+
const two_requests = `GET ${rpc_path} HTTP/1.1\r\nHost: ${host}\r\n\r\n` +
|
|
101
|
+
`GET ${rpc_path} HTTP/1.1\r\nHost: ${host}\r\nConnection: close\r\n\r\n`;
|
|
102
|
+
const response = await send_raw(base_url, two_requests, 2000);
|
|
103
|
+
const n = count_responses(response);
|
|
104
|
+
assert.ok(n >= 2, `expected ≥2 responses on one keep-alive connection (got ${n}); without ` +
|
|
105
|
+
`connection reuse — or with an undercounting matcher — the smuggling ` +
|
|
106
|
+
`assertion below is not a real signal. Raw head: ${response.slice(0, 120)}`);
|
|
107
|
+
});
|
|
108
|
+
test('oversized POST + pipelined GET → smuggled request not processed', async () => {
|
|
109
|
+
const oversized_len = BODY_LIMIT_DEFAULT_BYTES + 1024;
|
|
110
|
+
// One write: an over-cap POST (rejected on Content-Length, body never
|
|
111
|
+
// read) immediately followed by a GET. If the server wrongly drained
|
|
112
|
+
// the unread body it would reach + answer this GET — a smuggle.
|
|
113
|
+
const payload = `POST ${rpc_path} HTTP/1.1\r\n` +
|
|
114
|
+
`Host: ${host}\r\n` +
|
|
115
|
+
`Content-Type: application/json\r\n` +
|
|
116
|
+
`Content-Length: ${oversized_len}\r\n` +
|
|
117
|
+
`\r\n` +
|
|
118
|
+
'x'.repeat(oversized_len) +
|
|
119
|
+
`GET ${rpc_path} HTTP/1.1\r\nHost: ${host}\r\n\r\n`;
|
|
120
|
+
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
|
+
const n = count_responses(response);
|
|
134
|
+
assert.ok(n <= 1, `expected at most one response (the GET must not be smuggled in off the ` +
|
|
135
|
+
`unread body); a second response means it was. Saw ${n}. Raw head: ${response.slice(0, 120)}`);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
};
|
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
import type { z } from 'zod';
|
|
3
3
|
import type { FetchTransport } from '../transports/fetch_transport.js';
|
|
4
|
-
import type { BackendCapabilities } from './capabilities.js';
|
|
5
|
-
import type { SetupTest } from './setup.js';
|
|
6
|
-
/** Shared options for the cell cross-backend parity suites. */
|
|
7
|
-
export interface CellCrossTestOptions {
|
|
8
|
-
/** Per-test fixture-producing function (fresh keeper + db per call). */
|
|
9
|
-
readonly setup_test: SetupTest;
|
|
10
|
-
/** Backend capability declarations — each suite gates on its own flag. */
|
|
11
|
-
readonly capabilities: BackendCapabilities;
|
|
12
|
-
/** RPC endpoint path the cell verbs are mounted on. Default `/api/rpc`. */
|
|
13
|
-
readonly rpc_path?: string;
|
|
14
|
-
}
|
|
15
4
|
/** Minimal JSON-RPC envelope shape the suites read off responses. */
|
|
16
5
|
export interface RpcResult {
|
|
17
6
|
readonly ok: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_cross_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_cross_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_cross_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_cross_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kCAAkC,CAAC;AAErE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAC,CAAC;CAC5F;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAMnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAQrE,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import {
|
|
3
|
-
export declare const describe_cell_crud_cross_tests: (options:
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
|
+
export declare const describe_cell_crud_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
|
|
4
4
|
//# sourceMappingURL=cell_crud.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsD9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IAwSlF,CAAC"}
|
|
@@ -42,7 +42,7 @@ import '../assert_dev_env.js';
|
|
|
42
42
|
import { describe, assert } from 'vitest';
|
|
43
43
|
import { CellCreateOutput, CellDeleteOutput, CellGetOutput, CellListOutput, CellUpdateOutput, } from '../../auth/cell_action_specs.js';
|
|
44
44
|
import { test_if } from './capabilities.js';
|
|
45
|
-
import { cross_rpc_call, error_reason, expect_output
|
|
45
|
+
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
46
46
|
import { SPINE_RPC_PATH } from './default_spine_surface.js';
|
|
47
47
|
export const describe_cell_crud_cross_tests = (options) => {
|
|
48
48
|
const { setup_test, capabilities } = options;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
3
|
/** App role the holder is seeded with; matches the spine's registered role. */
|
|
4
4
|
export declare const CELL_EDITOR_ROLE = "cell_editor";
|
|
5
5
|
/** Username the fixture seeds (via `extra_accounts`) holding `CELL_EDITOR_ROLE`. */
|
|
6
6
|
export declare const CELL_ROLE_HOLDER_USERNAME = "cell_role_holder";
|
|
7
|
-
export declare const describe_cell_grant_role_cross_tests: (options:
|
|
7
|
+
export declare const describe_cell_grant_role_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
|
|
8
8
|
//# sourceMappingURL=cell_grant_role.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_grant_role.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_grant_role.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_grant_role.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_grant_role.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8C9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,gBAAyB,CAAC;AAEvD,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAK5D,eAAO,MAAM,oCAAoC,GAAI,SAAS,wBAAwB,KAAG,IAuIxF,CAAC"}
|
|
@@ -37,7 +37,7 @@ import { describe, assert } from 'vitest';
|
|
|
37
37
|
import { CellCreateOutput, CellGetOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
|
|
38
38
|
import { CellGrantCreateOutput, ERROR_CELL_GRANT_UNKNOWN_ROLE, } from '../../auth/cell_grant_action_specs.js';
|
|
39
39
|
import { test_if } from './capabilities.js';
|
|
40
|
-
import { cross_rpc_call, error_reason, expect_output
|
|
40
|
+
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
41
41
|
import { SPINE_CELL_EDITOR_ROLE, SPINE_RPC_PATH } from './default_spine_surface.js';
|
|
42
42
|
/** App role the holder is seeded with; matches the spine's registered role. */
|
|
43
43
|
export const CELL_EDITOR_ROLE = SPINE_CELL_EDITOR_ROLE;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import {
|
|
3
|
-
export declare const describe_cell_relations_cross_tests: (options:
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
|
+
export declare const describe_cell_relations_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
|
|
4
4
|
//# sourceMappingURL=cell_relations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqF9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAsFzD,eAAO,MAAM,mCAAmC,GAAI,SAAS,wBAAwB,KAAG,IAyvBvF,CAAC"}
|
|
@@ -60,7 +60,7 @@ import { CellFieldDeleteOutput, CellFieldListOutput, CellFieldSetOutput, } from
|
|
|
60
60
|
import { CellItemDeleteOutput, CellItemInsertOutput, CellItemListOutput, CellItemMoveOutput, } from '../../auth/cell_item_action_specs.js';
|
|
61
61
|
import { CellAuditListOutput } from '../../auth/cell_audit_action_specs.js';
|
|
62
62
|
import { test_if } from './capabilities.js';
|
|
63
|
-
import { cross_rpc_call, error_reason, expect_output
|
|
63
|
+
import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
|
|
64
64
|
import { SPINE_RPC_PATH } from './default_spine_surface.js';
|
|
65
65
|
/** Create a cell over the wire and return its id (the parity gate parses the output). */
|
|
66
66
|
const create_cell = async (t, rpc_path, h, params) => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', params, h), CellCreateOutput).cell
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwB9B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,KAAK,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,YAAY,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;
|
|
1
|
+
{"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwB9B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,KAAK,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,YAAY,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAkOD;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,uBAAuB,KAAG,IAgBnF,CAAC"}
|
|
@@ -133,13 +133,15 @@ const run_rpc_case = async (c, transport, headers, suppress_default_origin, reso
|
|
|
133
133
|
assert.ok(!res.ok, `${c.name}: expected error status ${c.expect.status} but got success`);
|
|
134
134
|
assert.strictEqual(res.status, c.expect.status, `${c.name}: error status`);
|
|
135
135
|
if (c.expect.error_reason !== undefined) {
|
|
136
|
+
// A row that *declares* a reason must carry it — present AND equal —
|
|
137
|
+
// mirroring the REST branch's unconditional `body.error` assertion. The
|
|
138
|
+
// earlier skip-if-absent form let a backend that dropped the reason pass
|
|
139
|
+
// a row declaring one, blessing a reason/forensic-parity divergence (the
|
|
140
|
+
// IDOR-mask / privilege reason is exactly the distinguishing bit). Rows
|
|
141
|
+
// whose denial genuinely has no reason (the bare `unauthenticated()` 401)
|
|
142
|
+
// simply omit `error_reason` and are pinned by `status` above.
|
|
136
143
|
const reason = res.error.data?.reason;
|
|
137
|
-
|
|
138
|
-
// 401 now); a denial that genuinely omits it falls back to the status
|
|
139
|
-
// assertion above to pin the denial class.
|
|
140
|
-
if (reason !== undefined) {
|
|
141
|
-
assert.strictEqual(reason, c.expect.error_reason, `${c.name}: error.data.reason`);
|
|
142
|
-
}
|
|
144
|
+
assert.strictEqual(reason, c.expect.error_reason, `${c.name}: error.data.reason`);
|
|
143
145
|
}
|
|
144
146
|
if (c.expect.fields)
|
|
145
147
|
assert_fields(res.error.data, c.expect.fields, c.name);
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import {
|
|
3
|
-
import type { SetupTest } from './setup.js';
|
|
2
|
+
import type { RpcPathCrossSuiteOptions, SetupTest } from './setup.js';
|
|
4
3
|
/**
|
|
5
4
|
* The fact suite adds one optional knob to the shared cell options: a setup
|
|
6
5
|
* variant whose keeper carries a **second actor**. Only the multi-actor case
|
|
7
6
|
* needs it; the rest of the suite runs single-actor, so wiring it is opt-in.
|
|
8
7
|
* Omit it and the multi-actor case silently skips.
|
|
9
8
|
*/
|
|
10
|
-
export interface FactServingCrossTestOptions extends
|
|
9
|
+
export interface FactServingCrossTestOptions extends RpcPathCrossSuiteOptions {
|
|
11
10
|
readonly setup_test_multi_actor?: SetupTest;
|
|
12
11
|
}
|
|
13
12
|
export declare const describe_fact_serving_cross_tests: (options: FactServingCrossTestOptions) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAC,wBAAwB,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB;IAC5E,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AA8BD,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8NxF,CAAC"}
|
|
@@ -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,10 +1,10 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
3
|
/**
|
|
4
|
-
* Options for the origin parity suite.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Options for the origin parity suite. The standard RPC-dispatched
|
|
5
|
+
* cross-suite shape (`setup_test` / `capabilities` / `rpc_path`); aliases
|
|
6
|
+
* the shared `RpcPathCrossSuiteOptions` rather than minting a duplicate.
|
|
7
7
|
*/
|
|
8
|
-
export type OriginCrossTestOptions =
|
|
8
|
+
export type OriginCrossTestOptions = RpcPathCrossSuiteOptions;
|
|
9
9
|
export declare const describe_origin_cross_tests: (options: OriginCrossTestOptions) => void;
|
|
10
10
|
//# sourceMappingURL=origin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/origin.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/origin.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAM9D,eAAO,MAAM,2BAA2B,GAAI,SAAS,sBAAsB,KAAG,IAwC7E,CAAC"}
|