@fuzdev/fuz_app 0.64.0 → 0.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +513 -928
- package/dist/actions/broadcast_api.d.ts +1 -1
- package/dist/actions/broadcast_api.js +1 -1
- package/dist/actions/cancel.d.ts +2 -2
- package/dist/actions/cancel.js +3 -3
- package/dist/actions/connection_closer.d.ts +1 -4
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +1 -4
- package/dist/actions/register_action_ws.d.ts +2 -2
- package/dist/actions/register_ws_endpoint.d.ts +1 -1
- package/dist/actions/transports_ws_auth_guard.d.ts +1 -2
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +1 -2
- package/dist/auth/CLAUDE.md +591 -1871
- package/dist/auth/account_schema.d.ts +1 -1
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +1 -1
- package/dist/auth/audit_log_ddl.d.ts +1 -1
- package/dist/auth/audit_log_ddl.d.ts.map +1 -1
- package/dist/auth/audit_log_ddl.js +1 -1
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +1 -5
- package/dist/auth/bootstrap_routes.d.ts +7 -1
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +15 -11
- package/dist/auth/keyring.d.ts +6 -6
- package/dist/auth/keyring.js +8 -8
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +4 -2
- package/dist/db/create_db.d.ts.map +1 -1
- package/dist/db/create_db.js +13 -0
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.js +3 -3
- package/dist/http/CLAUDE.md +224 -498
- package/dist/http/error_schemas.d.ts +0 -4
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +0 -4
- package/dist/http/ip_canonical.d.ts +5 -4
- package/dist/http/ip_canonical.d.ts.map +1 -1
- package/dist/http/ip_canonical.js +8 -4
- package/dist/http/origin.d.ts +1 -1
- package/dist/http/origin.js +1 -1
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_server.d.ts +41 -10
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +10 -4
- package/dist/server/env.d.ts +7 -7
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +14 -14
- package/dist/server/static.d.ts +4 -4
- package/dist/server/static.js +7 -7
- package/dist/testing/CLAUDE.md +220 -46
- package/dist/testing/admin_integration.d.ts +18 -23
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +159 -201
- package/dist/testing/app_server.d.ts +125 -38
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +140 -42
- package/dist/testing/audit_completeness.d.ts +23 -22
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +199 -156
- package/dist/testing/bootstrap_success.d.ts +28 -0
- package/dist/testing/bootstrap_success.d.ts.map +1 -0
- package/dist/testing/bootstrap_success.js +144 -0
- package/dist/testing/cross_backend/capabilities.d.ts +64 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
- package/dist/testing/cross_backend/capabilities.js +47 -0
- package/dist/testing/cross_backend/setup.d.ts +215 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/setup.js +101 -0
- package/dist/testing/data_exposure.d.ts +14 -15
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +127 -146
- package/dist/testing/db_entities.d.ts +11 -1
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +13 -1
- package/dist/testing/integration.d.ts +35 -21
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +231 -291
- package/dist/testing/integration_helpers.d.ts +16 -6
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +7 -7
- package/dist/testing/mock_fs.d.ts.map +1 -1
- package/dist/testing/mock_fs.js +0 -2
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +9 -0
- package/dist/testing/role_grant_helpers.d.ts +31 -0
- package/dist/testing/role_grant_helpers.d.ts.map +1 -0
- package/dist/testing/role_grant_helpers.js +46 -0
- package/dist/testing/round_trip.d.ts +21 -16
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +65 -86
- package/dist/testing/rpc_round_trip.d.ts +24 -21
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +91 -104
- package/dist/testing/schema_introspect.d.ts +106 -0
- package/dist/testing/schema_introspect.d.ts.map +1 -0
- package/dist/testing/schema_introspect.js +123 -0
- package/dist/testing/schema_parity.d.ts +144 -0
- package/dist/testing/schema_parity.d.ts.map +1 -0
- package/dist/testing/schema_parity.js +233 -0
- package/dist/testing/standard.d.ts +57 -25
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +62 -5
- package/dist/testing/stubs.d.ts +11 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +24 -21
- package/dist/testing/transports/surface_source.d.ts +51 -0
- package/dist/testing/transports/surface_source.d.ts.map +1 -0
- package/dist/testing/transports/surface_source.js +19 -0
- package/package.json +4 -4
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
import type { SessionOptions } from '../auth/session_cookie.js';
|
|
3
|
+
import type { AppServerContext, BootstrapLiveOptions } from '../server/app_server.js';
|
|
4
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
5
|
+
import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
|
|
6
|
+
/** Options for `describe_bootstrap_success_tests`. */
|
|
7
|
+
export interface BootstrapSuccessTestOptions {
|
|
8
|
+
session_options: SessionOptions<string>;
|
|
9
|
+
/** Same factory the consumer's production server uses. */
|
|
10
|
+
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
11
|
+
/** RPC endpoints — passed through to `create_app_server` for shape parity. */
|
|
12
|
+
rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
13
|
+
/**
|
|
14
|
+
* Live bootstrap config — the suite drives `POST /bootstrap` against
|
|
15
|
+
* `bootstrap.token_path`. The suite does NOT assert on `on_bootstrap`
|
|
16
|
+
* callback invocation (Hono-coupled signature is in-process only);
|
|
17
|
+
* assertions land on observable DB state.
|
|
18
|
+
*/
|
|
19
|
+
bootstrap: BootstrapLiveOptions;
|
|
20
|
+
/** Override the synthetic token text. Default deterministic. */
|
|
21
|
+
bootstrap_token?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run the bootstrap success-path test suite against the consumer's
|
|
25
|
+
* production-shaped wiring.
|
|
26
|
+
*/
|
|
27
|
+
export declare const describe_bootstrap_success_tests: (options: BootstrapSuccessTestOptions) => void;
|
|
28
|
+
//# sourceMappingURL=bootstrap_success.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap_success.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/bootstrap_success.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AACpF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,kBAAkB,CAAC;AAM9D,sDAAsD;AACtD,MAAM,WAAW,2BAA2B;IAC3C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,0DAA0D;IAC1D,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,8EAA8E;IAC9E,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;OAKG;IACH,SAAS,EAAE,oBAAoB,CAAC;IAChC,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,2BAA2B,KAAG,IAyIvF,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Bootstrap success-path suite for consumer projects.
|
|
4
|
+
*
|
|
5
|
+
* Exercises `POST /bootstrap` against an empty DB (no pre-keeper, lock
|
|
6
|
+
* unflipped) through the real `bootstrap_account` flow. Asserts on
|
|
7
|
+
* observable state — account exists, `bootstrap_lock.bootstrapped` is
|
|
8
|
+
* true, audit row emitted, response body shape — rather than
|
|
9
|
+
* `on_bootstrap` callback invocation, so the suite stays cross-impl
|
|
10
|
+
* friendly when Phase 3 cross-process testing wires it against a
|
|
11
|
+
* spawned Rust backend.
|
|
12
|
+
*
|
|
13
|
+
* Folded into `describe_standard_tests` with a `bootstrap.mode === 'live'`
|
|
14
|
+
* silent-skip gate; consumers wiring live bootstrap pick up success-path
|
|
15
|
+
* coverage by default.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { describe, test, assert } from 'vitest';
|
|
20
|
+
import { ERROR_ALREADY_BOOTSTRAPPED, ERROR_INVALID_TOKEN } from '../http/error_schemas.js';
|
|
21
|
+
import { create_test_app_for_bootstrap } from './app_server.js';
|
|
22
|
+
const DEFAULT_TEST_TOKEN = 'test-bootstrap-token-value-deterministic';
|
|
23
|
+
const TEST_USERNAME = 'keeper';
|
|
24
|
+
const TEST_PASSWORD = 'test-password-with-min-12-chars';
|
|
25
|
+
/**
|
|
26
|
+
* Run the bootstrap success-path test suite against the consumer's
|
|
27
|
+
* production-shaped wiring.
|
|
28
|
+
*/
|
|
29
|
+
export const describe_bootstrap_success_tests = (options) => {
|
|
30
|
+
const token = options.bootstrap_token ?? DEFAULT_TEST_TOKEN;
|
|
31
|
+
const route_prefix = options.bootstrap.route_prefix ?? '/api/account';
|
|
32
|
+
const bootstrap_path = `${route_prefix}/bootstrap`;
|
|
33
|
+
describe('bootstrap success path', () => {
|
|
34
|
+
test('POST /bootstrap with valid token creates the keeper account and flips the lock', async () => {
|
|
35
|
+
const test_app = await create_test_app_for_bootstrap({
|
|
36
|
+
session_options: options.session_options,
|
|
37
|
+
create_route_specs: options.create_route_specs,
|
|
38
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
39
|
+
bootstrap: options.bootstrap,
|
|
40
|
+
bootstrap_token: token,
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
const response = await test_app.app.request(bootstrap_path, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
token,
|
|
48
|
+
username: TEST_USERNAME,
|
|
49
|
+
password: TEST_PASSWORD,
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
// Response shape
|
|
53
|
+
assert.strictEqual(response.status, 200);
|
|
54
|
+
const body = (await response.json());
|
|
55
|
+
assert.strictEqual(body.ok, true);
|
|
56
|
+
assert.strictEqual(body.account.username, TEST_USERNAME);
|
|
57
|
+
assert.ok(body.account.id);
|
|
58
|
+
assert.ok(body.actor.id);
|
|
59
|
+
// Observable state: account exists in DB
|
|
60
|
+
const account = await test_app.backend.deps.db.query_one('SELECT username FROM account WHERE username = $1', [TEST_USERNAME]);
|
|
61
|
+
assert.ok(account);
|
|
62
|
+
// Observable state: bootstrap_lock flipped to true
|
|
63
|
+
const lock = await test_app.backend.deps.db.query_one('SELECT bootstrapped FROM bootstrap_lock WHERE id = 1');
|
|
64
|
+
assert.ok(lock);
|
|
65
|
+
assert.strictEqual(lock.bootstrapped, true);
|
|
66
|
+
// Observable state: audit row emitted
|
|
67
|
+
const audit_row = await test_app.backend.deps.db.query_one("SELECT event_type, account_id FROM audit_log WHERE event_type = 'bootstrap' AND outcome = 'success' LIMIT 1");
|
|
68
|
+
assert.ok(audit_row);
|
|
69
|
+
assert.strictEqual(audit_row.account_id, body.account.id);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await test_app.cleanup();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
test('second POST /bootstrap returns 403 ALREADY_BOOTSTRAPPED', async () => {
|
|
76
|
+
const test_app = await create_test_app_for_bootstrap({
|
|
77
|
+
session_options: options.session_options,
|
|
78
|
+
create_route_specs: options.create_route_specs,
|
|
79
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
80
|
+
bootstrap: options.bootstrap,
|
|
81
|
+
bootstrap_token: token,
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
// First bootstrap succeeds
|
|
85
|
+
const first = await test_app.app.request(bootstrap_path, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
token,
|
|
90
|
+
username: TEST_USERNAME,
|
|
91
|
+
password: TEST_PASSWORD,
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
assert.strictEqual(first.status, 200);
|
|
95
|
+
// Second attempt blocked by lock
|
|
96
|
+
const second = await test_app.app.request(bootstrap_path, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
token,
|
|
101
|
+
username: 'another_user',
|
|
102
|
+
password: TEST_PASSWORD,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
assert.strictEqual(second.status, 403);
|
|
106
|
+
const body = (await second.json());
|
|
107
|
+
assert.strictEqual(body.error, ERROR_ALREADY_BOOTSTRAPPED);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
await test_app.cleanup();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
test('POST /bootstrap with wrong token returns 401 INVALID_TOKEN', async () => {
|
|
114
|
+
const test_app = await create_test_app_for_bootstrap({
|
|
115
|
+
session_options: options.session_options,
|
|
116
|
+
create_route_specs: options.create_route_specs,
|
|
117
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
118
|
+
bootstrap: options.bootstrap,
|
|
119
|
+
bootstrap_token: token,
|
|
120
|
+
});
|
|
121
|
+
try {
|
|
122
|
+
const response = await test_app.app.request(bootstrap_path, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
token: 'wrong-token-value-that-does-not-match',
|
|
127
|
+
username: TEST_USERNAME,
|
|
128
|
+
password: TEST_PASSWORD,
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
assert.strictEqual(response.status, 401);
|
|
132
|
+
const body = (await response.json());
|
|
133
|
+
assert.strictEqual(body.error, ERROR_INVALID_TOKEN);
|
|
134
|
+
// Observable state: lock NOT flipped (transaction rolled back on auth failure)
|
|
135
|
+
const lock = await test_app.backend.deps.db.query_one('SELECT bootstrapped FROM bootstrap_lock WHERE id = 1');
|
|
136
|
+
assert.ok(lock);
|
|
137
|
+
assert.strictEqual(lock.bootstrapped, false);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await test_app.cleanup();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Optional behaviors a backend may support. Each flag's TSDoc names the
|
|
4
|
+
* tests that gate on it; add a new flag here before referencing it from
|
|
5
|
+
* a suite body, and document the gating tests inline.
|
|
6
|
+
*/
|
|
7
|
+
export interface BackendCapabilities {
|
|
8
|
+
/**
|
|
9
|
+
* Bearer token auth (`Authorization: Bearer <token>`) is wired through
|
|
10
|
+
* the backend's middleware stack. Gates the bearer-token cases in
|
|
11
|
+
* `describe_standard_integration_tests` and `describe_rate_limiting_tests`.
|
|
12
|
+
*/
|
|
13
|
+
readonly bearer_auth: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Trusted-proxy XFF parsing is wired (`X-Forwarded-For` etc.). Gates
|
|
16
|
+
* the proxy-resolution cases in `describe_standard_integration_tests`
|
|
17
|
+
* and the future cross-process proxy integration suite.
|
|
18
|
+
*/
|
|
19
|
+
readonly trusted_proxy: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Per-account login rate limiting is wired. Gates the per-account
|
|
22
|
+
* rate-limit cases in `describe_rate_limiting_tests`.
|
|
23
|
+
*/
|
|
24
|
+
readonly login_rate_limit: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* WebSocket transport is reachable end-to-end. Gates the cross-process
|
|
27
|
+
* WS round-trip suite; the in-process `describe_ws_round_trip_tests`
|
|
28
|
+
* runs against `register_action_ws` directly and ignores this flag.
|
|
29
|
+
*/
|
|
30
|
+
readonly ws: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* SSE transport is reachable end-to-end. Gates the cross-process SSE
|
|
33
|
+
* close-detection cases; in-process SSE uses the
|
|
34
|
+
* `on_audit_event` hook and ignores this flag.
|
|
35
|
+
*/
|
|
36
|
+
readonly sse: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Test has direct access to backend-internal state (keyring for
|
|
39
|
+
* signing cookies, DB pool for FK-structural raw queries). Always
|
|
40
|
+
* `true` for in-process Hono via `default_in_process_setup`; always
|
|
41
|
+
* `false` cross-process. Gates the 3 keyring reads in
|
|
42
|
+
* `describe_standard_integration_tests` (expired-cookie generation)
|
|
43
|
+
* and the FK-structural raw query in `describe_audit_completeness_tests`.
|
|
44
|
+
*/
|
|
45
|
+
readonly in_process_only: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Capability declarations for the in-process Hono transport. Every flag
|
|
49
|
+
* is `true` because in-process testing exercises the full backend with
|
|
50
|
+
* no missing optional behaviors. Cross-process consumers
|
|
51
|
+
* declare each flag explicitly per backend.
|
|
52
|
+
*/
|
|
53
|
+
export declare const in_process_capabilities: BackendCapabilities;
|
|
54
|
+
/**
|
|
55
|
+
* Conditional `test()` wrapper — registers a vitest case only when
|
|
56
|
+
* `cond` is true; otherwise registers it as `.skip` so the run still
|
|
57
|
+
* surfaces the gated coverage in the report.
|
|
58
|
+
*
|
|
59
|
+
* Thin wrapper around vitest's `test.skipIf(!cond)` with the argument
|
|
60
|
+
* order flipped to match the more readable `test_if(capabilities.bearer_auth, ...)`
|
|
61
|
+
* call pattern.
|
|
62
|
+
*/
|
|
63
|
+
export declare const test_if: (cond: boolean, name: string, fn: () => void | Promise<void>) => void;
|
|
64
|
+
//# sourceMappingURL=capabilities.d.ts.map
|
|
@@ -0,0 +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;AAmB9B;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAOpC,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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Capability vocabulary for cross-backend integration testing.
|
|
4
|
+
*
|
|
5
|
+
* Backends declare which optional behaviors they support; suite bodies
|
|
6
|
+
* call `test_if(capabilities.X, ...)` to skip cases the backend doesn't
|
|
7
|
+
* implement. No `if (config.name === 'rust')` branches anywhere — name-
|
|
8
|
+
* checking is a code smell that says capability vocabulary is missing.
|
|
9
|
+
*
|
|
10
|
+
* In-process Hono via `default_in_process_setup` declares every
|
|
11
|
+
* capability `true` (see `in_process_capabilities`). Cross-process
|
|
12
|
+
* backends opt in per-flag on their `BackendConfig`.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { test } from 'vitest';
|
|
17
|
+
/**
|
|
18
|
+
* Capability declarations for the in-process Hono transport. Every flag
|
|
19
|
+
* is `true` because in-process testing exercises the full backend with
|
|
20
|
+
* no missing optional behaviors. Cross-process consumers
|
|
21
|
+
* declare each flag explicitly per backend.
|
|
22
|
+
*/
|
|
23
|
+
export const in_process_capabilities = Object.freeze({
|
|
24
|
+
bearer_auth: true,
|
|
25
|
+
trusted_proxy: true,
|
|
26
|
+
login_rate_limit: true,
|
|
27
|
+
ws: true,
|
|
28
|
+
sse: true,
|
|
29
|
+
in_process_only: true,
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Conditional `test()` wrapper — registers a vitest case only when
|
|
33
|
+
* `cond` is true; otherwise registers it as `.skip` so the run still
|
|
34
|
+
* surfaces the gated coverage in the report.
|
|
35
|
+
*
|
|
36
|
+
* Thin wrapper around vitest's `test.skipIf(!cond)` with the argument
|
|
37
|
+
* order flipped to match the more readable `test_if(capabilities.bearer_auth, ...)`
|
|
38
|
+
* call pattern.
|
|
39
|
+
*/
|
|
40
|
+
export const test_if = (cond, name, fn) => {
|
|
41
|
+
if (cond) {
|
|
42
|
+
test(name, fn);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
test.skip(name, fn);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-test fixture protocol shared by in-process and cross-process
|
|
4
|
+
* transports.
|
|
5
|
+
*
|
|
6
|
+
* Each standard suite body takes a required
|
|
7
|
+
* `setup_test: () => Promise<TestFixture>` callback and invokes it once
|
|
8
|
+
* per test. The fixture carries everything a test needs to fire requests
|
|
9
|
+
* and assert on a single bootstrapped keeper account — transport,
|
|
10
|
+
* account / actor identity, three header builders, a multi-account mint
|
|
11
|
+
* factory, and (in-process only) the in-memory keyring + raw backend.
|
|
12
|
+
*
|
|
13
|
+
* `default_in_process_setup(options)` wraps `create_test_app` into the
|
|
14
|
+
* `SetupTest` contract. The cross-process sibling
|
|
15
|
+
* (`default_cross_process_setup`) lands alongside the spawn-a-backend
|
|
16
|
+
* transport plumbing — it implements the same contract by spawning a
|
|
17
|
+
* binary and bootstrapping over real HTTP.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import type { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
22
|
+
import type { Keyring } from '../../auth/keyring.js';
|
|
23
|
+
import type { RouteSpec } from '../../http/route_spec.js';
|
|
24
|
+
import type { AppServerContext, BootstrapServerOptions } from '../../server/app_server.js';
|
|
25
|
+
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
26
|
+
import { type CreateTestAppOptions, type SuiteAppOptions, type TestAccount, type TestAppServer } from '../app_server.js';
|
|
27
|
+
import { type RpcTestTransport, type RpcEndpointsSuiteOption } from '../rpc_helpers.js';
|
|
28
|
+
import { type BackendCapabilities } from './capabilities.js';
|
|
29
|
+
import type { SurfaceSource } from '../transports/surface_source.js';
|
|
30
|
+
/**
|
|
31
|
+
* Options for `TestFixture.create_account` — mints an additional
|
|
32
|
+
* bootstrapped account alongside the keeper. Matches the existing
|
|
33
|
+
* `TestApp.create_account` signature so the migration to fixture-style
|
|
34
|
+
* reads is a one-site call rewrite per use.
|
|
35
|
+
*/
|
|
36
|
+
export interface CreateTestAccountOptions {
|
|
37
|
+
readonly username?: string;
|
|
38
|
+
readonly password_value?: string;
|
|
39
|
+
readonly roles?: Array<string>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Shape returned by `TestFixture.create_account`. Aliased to the
|
|
43
|
+
* existing `TestAccount` interface from `app_server.ts` — same shape,
|
|
44
|
+
* stable name on the cross-backend testing surface so call sites read
|
|
45
|
+
* `fixture.create_account(...)` returning `TestAccountFixture` without
|
|
46
|
+
* crossing module boundaries.
|
|
47
|
+
*/
|
|
48
|
+
export type TestAccountFixture = TestAccount;
|
|
49
|
+
/**
|
|
50
|
+
* Fields shared by every `TestFixture` regardless of transport. The
|
|
51
|
+
* discriminated union below adds in-process-only fields conditionally.
|
|
52
|
+
*/
|
|
53
|
+
export interface TestFixtureBase {
|
|
54
|
+
/** Transport for this test's HTTP requests (cookie-threaded cross-process). */
|
|
55
|
+
readonly transport: RpcTestTransport;
|
|
56
|
+
/** The freshly-bootstrapped keeper account. */
|
|
57
|
+
readonly account: {
|
|
58
|
+
readonly id: Uuid;
|
|
59
|
+
readonly username: string;
|
|
60
|
+
};
|
|
61
|
+
/** The actor linked to the keeper account. */
|
|
62
|
+
readonly actor: {
|
|
63
|
+
readonly id: Uuid;
|
|
64
|
+
};
|
|
65
|
+
/** Build request headers with the keeper's session cookie. */
|
|
66
|
+
readonly create_session_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
67
|
+
/** Build request headers with the keeper's bearer token. */
|
|
68
|
+
readonly create_bearer_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
69
|
+
/** Build request headers with the daemon token (keeper auth). */
|
|
70
|
+
readonly create_daemon_token_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
71
|
+
/**
|
|
72
|
+
* Mint an additional bootstrapped account for cross-account / multi-user
|
|
73
|
+
* tests. In-process: re-uses `create_test_account_with_credentials` against the same DB;
|
|
74
|
+
* cross-process: goes through the consumer-supplied DB-admin
|
|
75
|
+
* channel.
|
|
76
|
+
*/
|
|
77
|
+
readonly create_account: (options?: CreateTestAccountOptions) => Promise<TestAccountFixture>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The per-test bundle returned by `SetupTest`. Every Tier 1 suite body
|
|
81
|
+
* reads exclusively from this shape — no `test_app.backend.*` reads
|
|
82
|
+
* remain in the suite bodies.
|
|
83
|
+
*
|
|
84
|
+
* Discriminated by `in_process`: when `true`, `keyring` and
|
|
85
|
+
* `backend_internals` are present (compile-time narrowed); when `false`,
|
|
86
|
+
* they're absent and the suite body must either run via the public wire
|
|
87
|
+
* (cross-process) or gate the test with
|
|
88
|
+
* `test_if(capabilities.in_process_only, ...)`. The discriminant value
|
|
89
|
+
* mirrors `BackendCapabilities.in_process_only` — both source from the
|
|
90
|
+
* same producer (`default_in_process_setup` vs. cross-process variant).
|
|
91
|
+
*
|
|
92
|
+
* Suite bodies narrow with `assert(fixture.in_process)` after
|
|
93
|
+
* `setup_test()`; sites that reach for `keyring` or `backend_internals`
|
|
94
|
+
* are in-process-only by structure and the assertion surfaces a clear
|
|
95
|
+
* failure if a future cross-process consumer reaches them without a
|
|
96
|
+
* `test_if` gate.
|
|
97
|
+
*/
|
|
98
|
+
export type TestFixture = (TestFixtureBase & {
|
|
99
|
+
readonly in_process: true;
|
|
100
|
+
/**
|
|
101
|
+
* Test-only keyring access — in-process only. Used for
|
|
102
|
+
* expired-cookie generation in `describe_standard_integration_tests`.
|
|
103
|
+
*/
|
|
104
|
+
readonly keyring: Keyring;
|
|
105
|
+
/**
|
|
106
|
+
* Raw backend access (`deps.db`, etc.) — in-process only. Used by
|
|
107
|
+
* `create_test_role_grant_direct` seed sites in
|
|
108
|
+
* `describe_standard_admin_integration_tests` and the
|
|
109
|
+
* origin-verification cookie-composition sites in
|
|
110
|
+
* `describe_standard_integration_tests`.
|
|
111
|
+
*/
|
|
112
|
+
readonly backend_internals: TestAppServer;
|
|
113
|
+
}) | (TestFixtureBase & {
|
|
114
|
+
readonly in_process: false;
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* Per-test fixture-producing function. Invoked once inside every
|
|
118
|
+
* `test()` body. The implementation captures factory inputs (in-process)
|
|
119
|
+
* or a long-running backend handle (cross-process) and creates
|
|
120
|
+
* a fresh per-test bundle on each call.
|
|
121
|
+
*/
|
|
122
|
+
export type SetupTest = () => Promise<TestFixture>;
|
|
123
|
+
/**
|
|
124
|
+
* Build a `SetupTest` that creates a fresh `TestApp` per call via
|
|
125
|
+
* `create_test_app` and projects it into the `TestFixture` shape.
|
|
126
|
+
*
|
|
127
|
+
* Same factory inputs `create_test_app` already takes — this helper
|
|
128
|
+
* is a projection layer, not a new lifecycle. fuz_app's own `src/test/`
|
|
129
|
+
* and consumer suites pass `default_in_process_setup({...factory_inputs})`
|
|
130
|
+
* in place of the old per-suite factory-input bundle.
|
|
131
|
+
*
|
|
132
|
+
* The describe-level `auth_integration_truncate_tables` / pglite WASM
|
|
133
|
+
* cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
|
|
134
|
+
* (`testing/db.js`) — `default_in_process_setup` doesn't manage db state
|
|
135
|
+
* beyond what `create_test_app` already does.
|
|
136
|
+
*/
|
|
137
|
+
export declare const default_in_process_setup: (options: CreateTestAppOptions) => SetupTest;
|
|
138
|
+
/**
|
|
139
|
+
* Consumer-facing options for `default_in_process_suite_options` — the
|
|
140
|
+
* minimal factory inputs both `default_in_process_setup` and
|
|
141
|
+
* `create_test_app_surface_spec` consume to produce the
|
|
142
|
+
* `{setup_test, surface_source, capabilities}` bundle.
|
|
143
|
+
*/
|
|
144
|
+
export interface DefaultInProcessSuiteOptions {
|
|
145
|
+
session_options: SessionOptions<string>;
|
|
146
|
+
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
147
|
+
rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
148
|
+
/**
|
|
149
|
+
* Bootstrap config — top-level slot, single source of truth for both
|
|
150
|
+
* surface generation and live dispatch. Same precedent as
|
|
151
|
+
* `rpc_endpoints`. Discriminated by `mode`; omit for the default (no
|
|
152
|
+
* bootstrap route mounted).
|
|
153
|
+
*/
|
|
154
|
+
bootstrap?: BootstrapServerOptions;
|
|
155
|
+
app_options?: SuiteAppOptions;
|
|
156
|
+
/**
|
|
157
|
+
* Additional roles to grant the bootstrapped keeper alongside
|
|
158
|
+
* `ROLE_KEEPER` — additive, never replaces. The keeper account
|
|
159
|
+
* always holds `ROLE_KEEPER` (otherwise daemon-token auth breaks);
|
|
160
|
+
* pass extras here for suites that need additional role coverage.
|
|
161
|
+
*
|
|
162
|
+
* Admin-suite consumers pass `[ROLE_ADMIN]` so the default keeper
|
|
163
|
+
* can hit admin-gated RPC methods.
|
|
164
|
+
* `describe_standard_admin_integration_tests` and
|
|
165
|
+
* `describe_audit_completeness_tests` need this.
|
|
166
|
+
*/
|
|
167
|
+
extra_keeper_roles?: Array<string>;
|
|
168
|
+
/**
|
|
169
|
+
* Pre-built `SurfaceSource` — overrides the default which calls
|
|
170
|
+
* `create_test_app_surface_spec` against the same factory inputs.
|
|
171
|
+
* Pass when surface assembly needs fields outside the shared subset
|
|
172
|
+
* (e.g. `env_schema`, `event_specs`, `ws_endpoints`, `transform_middleware`).
|
|
173
|
+
*/
|
|
174
|
+
surface_source?: SurfaceSource;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Build the full in-process suite bundle in a single helper invocation.
|
|
178
|
+
* Output covers `{setup_test, surface_source, capabilities}` plus every
|
|
179
|
+
* factory input the Tier 1 suites read at their top level
|
|
180
|
+
* (`session_options`, `create_route_specs`, `rpc_endpoints`) — so the
|
|
181
|
+
* call site spreads once and adds only suite-specific extras
|
|
182
|
+
* (`roles`, `skip_routes`, `input_overrides`, `db_factories`, ...).
|
|
183
|
+
*
|
|
184
|
+
* ```ts
|
|
185
|
+
* // Suite-extras-free call: helper output is the entire options bag.
|
|
186
|
+
* describe_round_trip_validation(default_in_process_suite_options({
|
|
187
|
+
* session_options,
|
|
188
|
+
* create_route_specs,
|
|
189
|
+
* rpc_endpoints: [rpc_endpoint_spec],
|
|
190
|
+
* }));
|
|
191
|
+
*
|
|
192
|
+
* // With suite-specific extras: spread and add.
|
|
193
|
+
* describe_standard_admin_integration_tests({
|
|
194
|
+
* ...default_in_process_suite_options({
|
|
195
|
+
* session_options, create_route_specs, rpc_endpoints,
|
|
196
|
+
* extra_keeper_roles: [ROLE_ADMIN],
|
|
197
|
+
* }),
|
|
198
|
+
* roles,
|
|
199
|
+
* });
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
202
|
+
* Suites that don't read `session_options` / `rpc_endpoints` at their
|
|
203
|
+
* top level (`round_trip`, `data_exposure`) accept the spread anyway —
|
|
204
|
+
* excess properties on spread sources aren't checked by TS, and the
|
|
205
|
+
* uniform shape keeps consumer call sites mechanical.
|
|
206
|
+
*/
|
|
207
|
+
export declare const default_in_process_suite_options: <const O extends DefaultInProcessSuiteOptions>(options: O) => {
|
|
208
|
+
setup_test: SetupTest;
|
|
209
|
+
surface_source: SurfaceSource;
|
|
210
|
+
capabilities: BackendCapabilities;
|
|
211
|
+
session_options: O["session_options"];
|
|
212
|
+
create_route_specs: O["create_route_specs"];
|
|
213
|
+
rpc_endpoints: O["rpc_endpoints"];
|
|
214
|
+
};
|
|
215
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { ROLE_KEEPER } from '../../auth/role_schema.js';
|
|
3
|
+
import { create_test_app, } from '../app_server.js';
|
|
4
|
+
import { create_test_app_surface_spec } from '../stubs.js';
|
|
5
|
+
import { http_transport, } from '../rpc_helpers.js';
|
|
6
|
+
import { in_process_capabilities } from './capabilities.js';
|
|
7
|
+
/**
|
|
8
|
+
* Build a `SetupTest` that creates a fresh `TestApp` per call via
|
|
9
|
+
* `create_test_app` and projects it into the `TestFixture` shape.
|
|
10
|
+
*
|
|
11
|
+
* Same factory inputs `create_test_app` already takes — this helper
|
|
12
|
+
* is a projection layer, not a new lifecycle. fuz_app's own `src/test/`
|
|
13
|
+
* and consumer suites pass `default_in_process_setup({...factory_inputs})`
|
|
14
|
+
* in place of the old per-suite factory-input bundle.
|
|
15
|
+
*
|
|
16
|
+
* The describe-level `auth_integration_truncate_tables` / pglite WASM
|
|
17
|
+
* cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
|
|
18
|
+
* (`testing/db.js`) — `default_in_process_setup` doesn't manage db state
|
|
19
|
+
* beyond what `create_test_app` already does.
|
|
20
|
+
*/
|
|
21
|
+
export const default_in_process_setup = (options) => async () => {
|
|
22
|
+
const test_app = await create_test_app(options);
|
|
23
|
+
return {
|
|
24
|
+
in_process: true,
|
|
25
|
+
transport: http_transport(test_app.app),
|
|
26
|
+
account: test_app.backend.account,
|
|
27
|
+
actor: test_app.backend.actor,
|
|
28
|
+
create_session_headers: test_app.create_session_headers,
|
|
29
|
+
create_bearer_headers: test_app.create_bearer_headers,
|
|
30
|
+
create_daemon_token_headers: test_app.create_daemon_token_headers,
|
|
31
|
+
create_account: test_app.create_account,
|
|
32
|
+
keyring: test_app.backend.keyring,
|
|
33
|
+
backend_internals: test_app.backend,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
// NOTE: bootstrap config is read from `options.bootstrap` — top-level slot,
|
|
37
|
+
// single source of truth for both the surface spec (so the route appears in
|
|
38
|
+
// `expected_public_routes` / attack-surface iteration) AND the live
|
|
39
|
+
// `create_test_app` (so the route exists at dispatch time and returns 403
|
|
40
|
+
// `ERROR_ALREADY_BOOTSTRAPPED` matching its declared 403 schema). Same
|
|
41
|
+
// precedent as `rpc_endpoints`. Discriminated union shape (`mode: 'disabled'`
|
|
42
|
+
// | `'surface_only'` | `'live'`) replaces the old `token_path: string | null`
|
|
43
|
+
// overload that conflated three deployment intents on one channel.
|
|
44
|
+
/**
|
|
45
|
+
* Build the full in-process suite bundle in a single helper invocation.
|
|
46
|
+
* Output covers `{setup_test, surface_source, capabilities}` plus every
|
|
47
|
+
* factory input the Tier 1 suites read at their top level
|
|
48
|
+
* (`session_options`, `create_route_specs`, `rpc_endpoints`) — so the
|
|
49
|
+
* call site spreads once and adds only suite-specific extras
|
|
50
|
+
* (`roles`, `skip_routes`, `input_overrides`, `db_factories`, ...).
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* // Suite-extras-free call: helper output is the entire options bag.
|
|
54
|
+
* describe_round_trip_validation(default_in_process_suite_options({
|
|
55
|
+
* session_options,
|
|
56
|
+
* create_route_specs,
|
|
57
|
+
* rpc_endpoints: [rpc_endpoint_spec],
|
|
58
|
+
* }));
|
|
59
|
+
*
|
|
60
|
+
* // With suite-specific extras: spread and add.
|
|
61
|
+
* describe_standard_admin_integration_tests({
|
|
62
|
+
* ...default_in_process_suite_options({
|
|
63
|
+
* session_options, create_route_specs, rpc_endpoints,
|
|
64
|
+
* extra_keeper_roles: [ROLE_ADMIN],
|
|
65
|
+
* }),
|
|
66
|
+
* roles,
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* Suites that don't read `session_options` / `rpc_endpoints` at their
|
|
71
|
+
* top level (`round_trip`, `data_exposure`) accept the spread anyway —
|
|
72
|
+
* excess properties on spread sources aren't checked by TS, and the
|
|
73
|
+
* uniform shape keeps consumer call sites mechanical.
|
|
74
|
+
*/
|
|
75
|
+
export const default_in_process_suite_options = (options) => ({
|
|
76
|
+
setup_test: default_in_process_setup({
|
|
77
|
+
session_options: options.session_options,
|
|
78
|
+
create_route_specs: options.create_route_specs,
|
|
79
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
80
|
+
bootstrap: options.bootstrap,
|
|
81
|
+
app_options: options.app_options,
|
|
82
|
+
roles: [ROLE_KEEPER, ...(options.extra_keeper_roles ?? [])],
|
|
83
|
+
}),
|
|
84
|
+
surface_source: options.surface_source ??
|
|
85
|
+
{
|
|
86
|
+
kind: 'inline',
|
|
87
|
+
spec: create_test_app_surface_spec({
|
|
88
|
+
session_options: options.session_options,
|
|
89
|
+
create_route_specs: options.create_route_specs,
|
|
90
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
91
|
+
// Mirror what `create_test_app` → `create_app_server` will mount.
|
|
92
|
+
// Both helpers read from the top-level `bootstrap` slot so surface
|
|
93
|
+
// and live app stay in sync by construction.
|
|
94
|
+
bootstrap: options.bootstrap,
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
capabilities: in_process_capabilities,
|
|
98
|
+
session_options: options.session_options,
|
|
99
|
+
create_route_specs: options.create_route_specs,
|
|
100
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
101
|
+
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
|
-
import type { AppSurface
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
import { type DbFactory } from './db.js';
|
|
2
|
+
import type { AppSurface } from '../http/surface.js';
|
|
3
|
+
import type { BackendCapabilities } from './cross_backend/capabilities.js';
|
|
4
|
+
import type { SetupTest } from './cross_backend/setup.js';
|
|
5
|
+
import type { SurfaceSource } from './transports/surface_source.js';
|
|
7
6
|
/**
|
|
8
7
|
* Recursively collect all property names from a JSON Schema.
|
|
9
8
|
*
|
|
@@ -21,20 +20,20 @@ export declare const assert_output_schemas_no_sensitive_fields: (surface: AppSur
|
|
|
21
20
|
export declare const assert_non_admin_schemas_no_admin_fields: (surface: AppSurface, admin_only_fields?: ReadonlyArray<string>) => void;
|
|
22
21
|
/** Options for `describe_data_exposure_tests`. */
|
|
23
22
|
export interface DataExposureTestOptions {
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
/** Per-test fixture-producing function (per-describe cadence). */
|
|
24
|
+
setup_test: SetupTest;
|
|
25
|
+
/**
|
|
26
|
+
* Source of the app surface for schema-level + route-iteration checks.
|
|
27
|
+
* Currently requires `kind: 'inline'` — the cross-process snapshot
|
|
28
|
+
* variant lands alongside the spawned-backend transport plumbing.
|
|
29
|
+
*/
|
|
30
|
+
surface_source: SurfaceSource;
|
|
31
|
+
/** Backend capability declarations. */
|
|
32
|
+
capabilities: BackendCapabilities;
|
|
30
33
|
/** Fields that must never appear in any response. Default: `sensitive_field_blocklist`. */
|
|
31
34
|
sensitive_fields?: ReadonlyArray<string>;
|
|
32
35
|
/** Fields that must not appear in non-admin responses. Default: `admin_only_field_blocklist`. */
|
|
33
36
|
admin_only_fields?: ReadonlyArray<string>;
|
|
34
|
-
/** Optional overrides for `AppServerOptions`. */
|
|
35
|
-
app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
|
|
36
|
-
/** Database factories to run tests against. Default: pglite only. */
|
|
37
|
-
db_factories?: Array<DbFactory>;
|
|
38
37
|
/** Routes to skip, in `'METHOD /path'` format. */
|
|
39
38
|
skip_routes?: Array<string>;
|
|
40
39
|
}
|