@fuzdev/fuz_app 0.68.0 → 0.70.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/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +10 -3
- package/dist/auth/admin_action_specs.d.ts +2 -3
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +2 -3
- package/dist/auth/admin_actions.d.ts +4 -14
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +28 -36
- package/dist/auth/signup_routes.d.ts +0 -3
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +9 -3
- package/dist/auth/standard_rpc_actions.d.ts +5 -5
- package/dist/auth/standard_rpc_actions.js +4 -4
- package/dist/server/app_server.d.ts +1 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +1 -5
- package/dist/testing/CLAUDE.md +98 -10
- package/dist/testing/app_server.d.ts +34 -0
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +31 -6
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
- package/dist/testing/cross_backend/account_lifecycle.js +69 -1
- package/dist/testing/cross_backend/actor_lookup.d.ts +10 -0
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -0
- package/dist/testing/cross_backend/actor_lookup.js +83 -0
- package/dist/testing/cross_backend/actor_search.d.ts +6 -0
- package/dist/testing/cross_backend/actor_search.d.ts.map +1 -0
- package/dist/testing/cross_backend/actor_search.js +92 -0
- package/dist/testing/cross_backend/app_settings.d.ts +6 -0
- package/dist/testing/cross_backend/app_settings.d.ts.map +1 -0
- package/dist/testing/cross_backend/app_settings.js +95 -0
- package/dist/testing/cross_backend/backend_config.d.ts +1 -1
- package/dist/testing/cross_backend/capabilities.d.ts +0 -9
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +0 -1
- package/dist/testing/cross_backend/cell_grant_role.d.ts +8 -0
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_grant_role.js +102 -0
- package/dist/testing/cross_backend/conformance_case.d.ts +144 -0
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -0
- package/dist/testing/cross_backend/conformance_case.js +132 -0
- package/dist/testing/cross_backend/conformance_table.d.ts +46 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -0
- package/dist/testing/cross_backend/conformance_table.js +199 -0
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +57 -0
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/create_cross_backend_global_setup.js +31 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts +13 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +4 -6
- package/dist/testing/cross_backend/default_spine_surface.d.ts +17 -9
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +20 -12
- package/dist/testing/cross_backend/make_cross_backend_project.d.ts +72 -0
- package/dist/testing/cross_backend/make_cross_backend_project.d.ts.map +1 -0
- package/dist/testing/cross_backend/make_cross_backend_project.js +51 -0
- package/dist/testing/cross_backend/origin.d.ts +10 -0
- package/dist/testing/cross_backend/origin.d.ts.map +1 -0
- package/dist/testing/cross_backend/origin.js +73 -0
- package/dist/testing/cross_backend/setup.d.ts +22 -40
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +34 -5
- package/dist/testing/cross_backend/standard.d.ts +8 -0
- package/dist/testing/cross_backend/standard.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.js +1 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +102 -10
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +96 -5
- package/dist/testing/cross_backend/xfail.d.ts +15 -0
- package/dist/testing/cross_backend/xfail.d.ts.map +1 -0
- package/dist/testing/cross_backend/xfail.js +37 -0
- package/dist/testing/integration.d.ts +2 -3
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +40 -88
- package/dist/testing/rate_limiting.d.ts +1 -1
- package/dist/testing/rpc_helpers.d.ts +3 -3
- package/dist/testing/sse_round_trip.d.ts +1 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +0 -1
- package/dist/ui/AdminAccounts.svelte +74 -83
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +21 -23
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +17 -26
- package/dist/ui/OpenSignupToggle.svelte +2 -5
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +9 -10
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +7 -17
- package/dist/ui/admin_accounts_state.svelte.d.ts +12 -19
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +10 -24
- package/dist/ui/admin_invites_state.svelte.d.ts +8 -11
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +7 -16
- package/dist/ui/admin_sessions_state.svelte.d.ts +6 -10
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +4 -14
- package/dist/ui/app_settings_state.svelte.d.ts +8 -12
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +6 -16
- package/dist/ui/audit_log_state.svelte.d.ts +9 -8
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +8 -20
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-backend parity suite for Origin verification.
|
|
4
|
+
*
|
|
5
|
+
* Origin checking is middleware that runs *before* the RPC dispatcher and
|
|
6
|
+
* returns a flat REST `{error}` body — not a JSON-RPC envelope — so it
|
|
7
|
+
* doesn't fit the envelope-shaped conformance-table runner. This dedicated
|
|
8
|
+
* imperative suite drives raw transport calls instead, mirroring how the
|
|
9
|
+
* in-process origin tests were already hand-rolled. Two cases:
|
|
10
|
+
*
|
|
11
|
+
* - **disallowed `Origin` → 403** `forbidden_origin`, refused before any
|
|
12
|
+
* handler runs (the allowlist rejects the cross-origin request even with
|
|
13
|
+
* a valid session cookie attached).
|
|
14
|
+
* - **absent `Origin` → request passes** — non-browser / direct-access
|
|
15
|
+
* clients (curl, CLI, server-to-server) carry no `Origin` and must not be
|
|
16
|
+
* blocked; token auth is the control for those callers.
|
|
17
|
+
*
|
|
18
|
+
* Runs both legs via the shared `{setup_test, capabilities}` protocol: the
|
|
19
|
+
* in-process leg (`auth/origin_parity.db.test.ts`, plain `gro test`) and the
|
|
20
|
+
* cross-process leg (`cross_backend/origin.cross.test.ts`, the TS spine
|
|
21
|
+
* binaries + Rust `testing_spine_stub` over real HTTP). Origin middleware is
|
|
22
|
+
* on every spine, so the suite is ungated.
|
|
23
|
+
*
|
|
24
|
+
* `$lib`-free by contract (relative specifiers only), like the sibling
|
|
25
|
+
* cross-backend suites.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import { describe, test, assert } from 'vitest';
|
|
30
|
+
import { account_verify_action_spec } from '../../auth/account_action_specs.js';
|
|
31
|
+
import { ERROR_FORBIDDEN_ORIGIN } from '../../http/error_schemas.js';
|
|
32
|
+
import { SPINE_RPC_PATH } from './default_spine_surface.js';
|
|
33
|
+
/** Build the JSON-RPC envelope body for a nullary `account_verify` call. */
|
|
34
|
+
const verify_envelope = (id) => JSON.stringify({ jsonrpc: '2.0', method: account_verify_action_spec.method, id });
|
|
35
|
+
export const describe_origin_cross_tests = (options) => {
|
|
36
|
+
const { setup_test } = options;
|
|
37
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
38
|
+
describe('origin verification parity', () => {
|
|
39
|
+
test('disallowed Origin → 403 forbidden_origin (refused before dispatch)', async () => {
|
|
40
|
+
const fixture = await setup_test();
|
|
41
|
+
// Keeper session cookie attached + a rogue Origin header (overrides
|
|
42
|
+
// the transport's default allowed Origin). The allowlist must reject
|
|
43
|
+
// before the dispatcher, returning a flat REST error body.
|
|
44
|
+
const res = await fixture.transport(rpc_path, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: {
|
|
47
|
+
...fixture.create_session_headers(),
|
|
48
|
+
origin: 'http://evil.com',
|
|
49
|
+
'content-type': 'application/json',
|
|
50
|
+
},
|
|
51
|
+
body: verify_envelope('evil-origin'),
|
|
52
|
+
});
|
|
53
|
+
assert.strictEqual(res.status, 403, 'disallowed Origin must be rejected with 403');
|
|
54
|
+
const body = (await res.json().catch(() => undefined));
|
|
55
|
+
assert.strictEqual(body?.error, ERROR_FORBIDDEN_ORIGIN);
|
|
56
|
+
});
|
|
57
|
+
test('absent Origin → request passes (non-browser direct access)', async () => {
|
|
58
|
+
const fixture = await setup_test();
|
|
59
|
+
// `origin: null` so no Origin header is sent at all (a header omission
|
|
60
|
+
// alone wouldn't suffice cross-process — the jar auto-adds the default
|
|
61
|
+
// allowed Origin). The keeper cookie rides via an explicit header.
|
|
62
|
+
const res = await fixture.fresh_transport({ origin: null })(rpc_path, {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
...fixture.create_session_headers(),
|
|
66
|
+
'content-type': 'application/json',
|
|
67
|
+
},
|
|
68
|
+
body: verify_envelope('no-origin'),
|
|
69
|
+
});
|
|
70
|
+
assert.strictEqual(res.status, 200, 'absent Origin with a valid session must pass');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
2
|
import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
3
|
-
import type { Keyring } from '../../auth/keyring.js';
|
|
4
3
|
import type { RouteSpec } from '../../http/route_spec.js';
|
|
5
4
|
import type { AppServerContext, BootstrapServerOptions } from '../../server/app_server.js';
|
|
6
5
|
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
7
|
-
import { type CreateTestAppOptions, type SuiteAppOptions, type TestAccount
|
|
6
|
+
import { type CreateTestAppOptions, type SuiteAppOptions, type TestAccount } from '../app_server.js';
|
|
8
7
|
import { type RpcEndpointsSuiteOption } from '../rpc_helpers.js';
|
|
9
8
|
import { type BackendCapabilities } from './capabilities.js';
|
|
10
9
|
import type { AppSurfaceSpec } from '../../http/surface.js';
|
|
@@ -140,49 +139,32 @@ export interface TestFixtureBase {
|
|
|
140
139
|
* for suites that don't declare any.
|
|
141
140
|
*/
|
|
142
141
|
readonly extra_accounts: Readonly<Record<string, ExtraAccountFixture>>;
|
|
142
|
+
/**
|
|
143
|
+
* Forge an *expired server-side session* for the keeper account and
|
|
144
|
+
* return the ready-to-send `Cookie` header value (`name=value`). The
|
|
145
|
+
* minted `auth_session` row is backdated while the signed cookie payload
|
|
146
|
+
* stays valid — so resolution clears the cookie-payload gate
|
|
147
|
+
* (`parse_session`) and is refused at the authoritative DB-row gate
|
|
148
|
+
* (`query_session_get_valid` — `WHERE expires_at > NOW()`). Backs the
|
|
149
|
+
* `expired_session` conformance principal. In-process mints directly via
|
|
150
|
+
* `mint_test_session`; cross-process drives the `_testing_mint_session`
|
|
151
|
+
* RPC over the keeper's daemon-token channel (the driver has no keyring).
|
|
152
|
+
*/
|
|
153
|
+
readonly mint_expired_session: () => Promise<string>;
|
|
143
154
|
}
|
|
144
155
|
/**
|
|
145
156
|
* The per-test bundle returned by `SetupTest`. Every Tier 1 suite body
|
|
146
|
-
* reads exclusively from this shape — no `test_app.backend.*` reads
|
|
147
|
-
*
|
|
157
|
+
* reads exclusively from this shape — no `test_app.backend.*` reads remain
|
|
158
|
+
* in the suite bodies.
|
|
148
159
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* (
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* same producer (`default_in_process_setup` vs. cross-process variant).
|
|
156
|
-
*
|
|
157
|
-
* Suite bodies narrow with `assert(fixture.in_process)` after
|
|
158
|
-
* `setup_test()`; sites that reach for `keyring` or `backend_internals`
|
|
159
|
-
* are in-process-only by structure and the assertion surfaces a clear
|
|
160
|
-
* failure if a future cross-process consumer reaches them without a
|
|
161
|
-
* `test_if` gate.
|
|
160
|
+
* Transport-agnostic: in-process and cross-process producers return the
|
|
161
|
+
* same shape. Behaviors that once needed raw backend access (keyring for
|
|
162
|
+
* forging cookies) are reached through wire-shaped seams instead —
|
|
163
|
+
* `mint_expired_session()` mints over the `_testing_mint_session` channel
|
|
164
|
+
* cross-process and directly in-process, so suite bodies never branch on
|
|
165
|
+
* the transport.
|
|
162
166
|
*/
|
|
163
|
-
export type TestFixture =
|
|
164
|
-
readonly in_process: true;
|
|
165
|
-
/**
|
|
166
|
-
* Test-only keyring access — in-process only. Used for
|
|
167
|
-
* expired-cookie generation in `describe_standard_integration_tests`.
|
|
168
|
-
*/
|
|
169
|
-
readonly keyring: Keyring;
|
|
170
|
-
/**
|
|
171
|
-
* Raw backend access (`deps.db`, etc.) — in-process only. Used
|
|
172
|
-
* by the origin-verification cookie-composition sites in
|
|
173
|
-
* `describe_standard_integration_tests`. Tests that need a
|
|
174
|
-
* direct DB role_grant seed at the in-process layer reach for
|
|
175
|
-
* `create_test_role_grant_direct` from `db_entities.ts`; that
|
|
176
|
-
* helper bypasses the consent flow on purpose for query-level
|
|
177
|
-
* (`*.db.test.ts`) tests and has no cross-process analog.
|
|
178
|
-
* Cross-process tests grant additional roles via
|
|
179
|
-
* `fixture.create_account({roles})` (offer/accept) or
|
|
180
|
-
* `extra_accounts` (bootstrap-time bypass).
|
|
181
|
-
*/
|
|
182
|
-
readonly backend_internals: TestAppServer;
|
|
183
|
-
}) | (TestFixtureBase & {
|
|
184
|
-
readonly in_process: false;
|
|
185
|
-
});
|
|
167
|
+
export type TestFixture = TestFixtureBase;
|
|
186
168
|
/**
|
|
187
169
|
* Per-test fixture-producing function. Invoked once inside every
|
|
188
170
|
* `test()` body. The implementation captures factory inputs (in-process)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuB9B,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE5C,OAAO,KAAK,EAAC,
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuB9B,OAAO,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAE5C,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAC,gBAAgB,EAAE,sBAAsB,EAAC,MAAM,4BAA4B,CAAC;AACzF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAIjE,OAAO,EAKN,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAGN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA0B,KAAK,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AACpF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAyB,KAAK,cAAc,EAAC,MAAM,kCAAkC,CAAC;AAC7F,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAE7C;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACtC;AAED,sEAAsE;AACtE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,OAAO,EAAE;QAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACjE,QAAQ,CAAC,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5F,QAAQ,CAAC,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3F;AAoCD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,CAAC,EAAE;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAC,KAAK,cAAc,CAAC;IAC1F,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE;QAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACjE,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5F,4DAA4D;IAC5D,QAAQ,CAAC,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3F,iEAAiE;IACjE,QAAQ,CAAC,2BAA2B,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjG;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE,wBAAwB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC7F;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;IACvE;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,oBAAoB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CACrD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;AAenD;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IAClE;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS,qBAAqB,KAAG,SAsDjC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC/D,iEAAiE;IACjE,QAAQ,CAAC,gBAAgB,EAAE,cAAc,CAAC;IAC1C,2DAA2D;IAC3D,QAAQ,CAAC,cAAc,EAAE;QAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACxE,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE;QAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IAC3C,wGAAwG;IACxG,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC/C;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,sCAAsC,GAAG,IAAI,CACxD,yBAAyB,EACzB,OAAO,GAAG,UAAU,CACpB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,qCAAqC;IACrD,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACzC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACrD,QAAQ,CAAC,cAAc,EAAE,yBAAyB,CAAC,gBAAgB,CAAC,CAAC;IACrE,QAAQ,CAAC,YAAY,EAAE,yBAAyB,CAAC,cAAc,CAAC,CAAC;IACjE,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,QAAQ,yBAAyB,KAC/B,qCAMD,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC3C,YAAY,qCAAqC,KAC/C,sCAUD,CAAC;AAEH,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACxC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACpD;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;CAC1D;AAwXD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,2BAA2B,GACvC,QAAQ,sCAAsC,EAC9C,UAAU,wBAAwB,KAChC,SA+HF,CAAC;AAEF;;;;;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;;;;;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;CA0BjC,CAAC"}
|
|
@@ -23,7 +23,7 @@ import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
|
23
23
|
import { ROLE_KEEPER } from '../../auth/role_schema.js';
|
|
24
24
|
import { DAEMON_TOKEN_HEADER } from '../../auth/daemon_token.js';
|
|
25
25
|
import { USERNAME_LENGTH_MAX } from '../../primitive_schemas.js';
|
|
26
|
-
import { create_test_app, create_test_account_with_credentials, DEFAULT_TEST_PASSWORD, } from '../app_server.js';
|
|
26
|
+
import { create_test_app, create_test_account_with_credentials, mint_test_session, DEFAULT_TEST_PASSWORD, } from '../app_server.js';
|
|
27
27
|
import { create_test_app_surface_spec } from '../stubs.js';
|
|
28
28
|
import { http_transport, } from '../rpc_helpers.js';
|
|
29
29
|
import { in_process_capabilities } from './capabilities.js';
|
|
@@ -110,7 +110,6 @@ export const default_in_process_setup = (options) => async () => {
|
|
|
110
110
|
extra_accounts[spec.username] = build_extra_account_fixture(seeded, cookie_name);
|
|
111
111
|
}
|
|
112
112
|
return {
|
|
113
|
-
in_process: true,
|
|
114
113
|
transport: in_process_fetch_transport(test_app.app),
|
|
115
114
|
// In-process the wrapper is stateless and never auto-adds Origin —
|
|
116
115
|
// `options` is accepted for API symmetry with cross-process but
|
|
@@ -123,8 +122,18 @@ export const default_in_process_setup = (options) => async () => {
|
|
|
123
122
|
create_daemon_token_headers: test_app.create_daemon_token_headers,
|
|
124
123
|
create_account: test_app.create_account,
|
|
125
124
|
extra_accounts,
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
// Forge directly against the live backend's DB + keyring — no wire
|
|
126
|
+
// hop needed in-process.
|
|
127
|
+
mint_expired_session: async () => {
|
|
128
|
+
const { session_cookie } = await mint_test_session({
|
|
129
|
+
db: test_app.backend.deps.db,
|
|
130
|
+
keyring: test_app.backend.keyring,
|
|
131
|
+
session_options: options.session_options,
|
|
132
|
+
account_id: test_app.backend.account.id,
|
|
133
|
+
expires_in_seconds: EXPIRED_SESSION_OFFSET_SECONDS,
|
|
134
|
+
});
|
|
135
|
+
return `${cookie_name}=${session_cookie}`;
|
|
136
|
+
},
|
|
128
137
|
};
|
|
129
138
|
};
|
|
130
139
|
/**
|
|
@@ -224,6 +233,15 @@ const rpc_via_transport = async (transport, rpc_path, method, params, backend_na
|
|
|
224
233
|
}
|
|
225
234
|
return raw.result;
|
|
226
235
|
};
|
|
236
|
+
/**
|
|
237
|
+
* Backdating offset (seconds) the `mint_expired_session` seam passes to
|
|
238
|
+
* `mint_test_session` / `_testing_mint_session`. A minute in the past is
|
|
239
|
+
* comfortably past `NOW()` for the DB-row expiry gate without depending on
|
|
240
|
+
* clock precision.
|
|
241
|
+
*/
|
|
242
|
+
const EXPIRED_SESSION_OFFSET_SECONDS = -60;
|
|
243
|
+
/** Structural subset of `_testing_mint_session`'s output. */
|
|
244
|
+
const MintSessionResponseShape = z.object({ session_cookie: z.string() });
|
|
227
245
|
/** Structural subset of `_testing_reset`'s output. */
|
|
228
246
|
const TestingResetResponseShape = z.object({
|
|
229
247
|
account: z.object({ id: Uuid, username: z.string() }),
|
|
@@ -504,7 +522,6 @@ export const default_cross_process_setup = (handle, options) => {
|
|
|
504
522
|
initial_cookies: [`${cookie_name}=${keeper.session_cookie}`],
|
|
505
523
|
});
|
|
506
524
|
return {
|
|
507
|
-
in_process: false,
|
|
508
525
|
transport,
|
|
509
526
|
fresh_transport: (fresh_options) => create_fetch_transport({
|
|
510
527
|
base_url: handle.config.base_url,
|
|
@@ -517,6 +534,18 @@ export const default_cross_process_setup = (handle, options) => {
|
|
|
517
534
|
create_daemon_token_headers,
|
|
518
535
|
create_account,
|
|
519
536
|
extra_accounts,
|
|
537
|
+
// Forge over the wire — the cross-process driver has no keyring,
|
|
538
|
+
// so `_testing_mint_session` mints the backdated row + signs the
|
|
539
|
+
// cookie server-side over the keeper's daemon-token channel.
|
|
540
|
+
mint_expired_session: async () => {
|
|
541
|
+
const raw = await rpc_via_transport(refreshed_handle.keeper_transport, handle.config.rpc_path, '_testing_mint_session', { account_id: keeper.account.id, expires_in_seconds: EXPIRED_SESSION_OFFSET_SECONDS }, handle.config.name, { [DAEMON_TOKEN_HEADER]: handle.daemon_token });
|
|
542
|
+
const parsed = MintSessionResponseShape.safeParse(raw);
|
|
543
|
+
if (!parsed.success) {
|
|
544
|
+
throw new Error(`_testing_mint_session(${handle.config.name}) returned unexpected result: ` +
|
|
545
|
+
`${JSON.stringify(raw)} (${parsed.error.message})`);
|
|
546
|
+
}
|
|
547
|
+
return `${cookie_name}=${parsed.data.session_cookie}`;
|
|
548
|
+
},
|
|
520
549
|
};
|
|
521
550
|
};
|
|
522
551
|
};
|
|
@@ -86,6 +86,14 @@ export interface StandardCrossProcessTestOptions {
|
|
|
86
86
|
* `0` to skip the assertion entirely.
|
|
87
87
|
*/
|
|
88
88
|
error_coverage_min?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Forwarded to `describe_round_trip_validation` as `skip_routes`
|
|
91
|
+
* (`'METHOD /path'` keys). For consumer REST routes whose responses
|
|
92
|
+
* aren't JSON-with-an-output-schema and so can't be round-tripped — e.g.
|
|
93
|
+
* fuz_forge's git smart-HTTP routes (`git-upload-pack` / `git-receive-pack`
|
|
94
|
+
* / `info/refs`) which stream git protocol bytes.
|
|
95
|
+
*/
|
|
96
|
+
round_trip_skip_routes?: Array<string>;
|
|
89
97
|
}
|
|
90
98
|
/**
|
|
91
99
|
* Run the cross-process standard test bundle — integration, admin (when
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standard.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/standard.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAM1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,WAAW,+BAA+B;IAC/C,2CAA2C;IAC3C,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"standard.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/standard.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAM1D,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,WAAW,+BAA+B;IAC/C,2CAA2C;IAC3C,UAAU,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACvC;AAED;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAsCF,CAAC"}
|
|
@@ -22,6 +22,7 @@ export const describe_standard_cross_process_tests = (options) => {
|
|
|
22
22
|
setup_test: options.setup_test,
|
|
23
23
|
surface_source: options.surface_source,
|
|
24
24
|
capabilities: options.capabilities,
|
|
25
|
+
skip_routes: options.round_trip_skip_routes,
|
|
25
26
|
});
|
|
26
27
|
describe_rpc_round_trip_tests({
|
|
27
28
|
setup_test: options.setup_test,
|
|
@@ -2,8 +2,14 @@ import '../assert_dev_env.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Test-binary RPC actions for cross-process integration tests.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* wipe + keeper re-seed
|
|
5
|
+
* Three daemon-token-authed actions, bundled by `create_testing_actions`:
|
|
6
|
+
* **`_testing_reset`** (DB wipe + keeper re-seed), **`_testing_drain_effects`**
|
|
7
|
+
* (audit barrier), and **`_testing_mint_session`** (forge an
|
|
8
|
+
* expired-by-construction server-side session for the expiry conformance
|
|
9
|
+
* cases).
|
|
10
|
+
*
|
|
11
|
+
* `_testing_reset` — full DB wipe + keeper re-seed + optional
|
|
12
|
+
* secondary-account seeding. The
|
|
7
13
|
* handler wipes every auth-namespace row (no keeper-preserve filter),
|
|
8
14
|
* flips `bootstrap_lock` back to its post-bootstrap shape, seeds a
|
|
9
15
|
* fresh keeper account inline (reusing `create_test_account_with_credentials`
|
|
@@ -56,6 +62,7 @@ import { type RpcAction } from '../../actions/action_rpc.js';
|
|
|
56
62
|
import type { AppDeps } from '../../auth/deps.js';
|
|
57
63
|
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
58
64
|
import type { DaemonTokenState } from '../../auth/daemon_token.js';
|
|
65
|
+
import type { Db } from '../../db/db.js';
|
|
59
66
|
/**
|
|
60
67
|
* The `_testing_reset` action spec.
|
|
61
68
|
*
|
|
@@ -124,6 +131,88 @@ export declare const testing_reset_action_spec: {
|
|
|
124
131
|
readonly async: true;
|
|
125
132
|
readonly description: "Test-binary only — wipe auth tables, re-bootstrap a fresh keeper (+ optional extras), fire the domain-state reset.";
|
|
126
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* `_testing_drain_effects` — await in-flight fire-and-forget audit writes so
|
|
136
|
+
* a following `audit_log_list` is authoritative. The deterministic barrier
|
|
137
|
+
* the cross-backend conformance suite uses in place of a poll/sleep before
|
|
138
|
+
* asserting on audit rows.
|
|
139
|
+
*
|
|
140
|
+
* On the TS spine the barrier is **satisfied by construction**: the test
|
|
141
|
+
* binary runs `await_pending_effects: true`, so every mutation's fire-and-
|
|
142
|
+
* forget audit emits are awaited before its response returns — by the time
|
|
143
|
+
* a later drain call runs, prior emits are already durable. The action still
|
|
144
|
+
* exists so the cross-backend test body calls the same method on every
|
|
145
|
+
* backend; the Rust spine (whose audit writes are detached tokio tasks)
|
|
146
|
+
* does the real await in `AuditEmitter::drain_inflight`.
|
|
147
|
+
*
|
|
148
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset`.
|
|
149
|
+
*/
|
|
150
|
+
export declare const testing_drain_effects_action_spec: {
|
|
151
|
+
readonly method: "_testing_drain_effects";
|
|
152
|
+
readonly kind: "request_response";
|
|
153
|
+
readonly initiator: "frontend";
|
|
154
|
+
readonly auth: {
|
|
155
|
+
readonly account: "required";
|
|
156
|
+
readonly actor: "none";
|
|
157
|
+
readonly credential_types: readonly ["daemon_token"];
|
|
158
|
+
};
|
|
159
|
+
readonly side_effects: false;
|
|
160
|
+
readonly input: z.ZodVoid;
|
|
161
|
+
readonly output: z.ZodObject<{
|
|
162
|
+
ok: z.ZodBoolean;
|
|
163
|
+
}, z.core.$strict>;
|
|
164
|
+
readonly async: true;
|
|
165
|
+
readonly description: "Test-binary only — await in-flight fire-and-forget audit writes so a following audit_log_list read is authoritative.";
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* `_testing_mint_session` — mint an expired-by-construction server-side
|
|
169
|
+
* session for an existing account and return its signed cookie value.
|
|
170
|
+
*
|
|
171
|
+
* The minted `auth_session` row's `expires_at` is backdated (negative
|
|
172
|
+
* `expires_in_seconds`) while the returned cookie's own signed payload
|
|
173
|
+
* stays valid (future). Cross-process auth resolution therefore passes the
|
|
174
|
+
* cookie-payload gate (`parse_session`) and is refused by the authoritative
|
|
175
|
+
* DB-row gate (`query_session_get_valid` — `WHERE expires_at > NOW()`) —
|
|
176
|
+
* the gate the in-process payload-expiry tests never reach and the one that
|
|
177
|
+
* structurally needs a server-side mint (the cross-process driver has no
|
|
178
|
+
* keyring / DB access). The `expired_session` conformance principal drives
|
|
179
|
+
* this.
|
|
180
|
+
*
|
|
181
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset` —
|
|
182
|
+
* effectively keeper-only. Like its siblings the action is internally
|
|
183
|
+
* privileged (a direct `auth_session` insert the production wire never
|
|
184
|
+
* exposes); daemon-token auth is the structural fence and the module's
|
|
185
|
+
* `assert_dev_env` import (TS) plus the Rust `cargo xtask check-release`
|
|
186
|
+
* dep-graph audit keep the `_testing_` surface out of every shipped build.
|
|
187
|
+
*/
|
|
188
|
+
export declare const testing_mint_session_action_spec: {
|
|
189
|
+
readonly method: "_testing_mint_session";
|
|
190
|
+
readonly kind: "request_response";
|
|
191
|
+
readonly initiator: "frontend";
|
|
192
|
+
readonly auth: {
|
|
193
|
+
readonly account: "required";
|
|
194
|
+
readonly actor: "none";
|
|
195
|
+
readonly credential_types: readonly ["daemon_token"];
|
|
196
|
+
};
|
|
197
|
+
readonly side_effects: true;
|
|
198
|
+
readonly input: z.ZodObject<{
|
|
199
|
+
account_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
200
|
+
expires_in_seconds: z.ZodNumber;
|
|
201
|
+
}, z.core.$strict>;
|
|
202
|
+
readonly output: z.ZodObject<{
|
|
203
|
+
session_cookie: z.ZodString;
|
|
204
|
+
}, z.core.$strict>;
|
|
205
|
+
readonly async: true;
|
|
206
|
+
readonly description: string;
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Build the standalone `_testing_drain_effects` action. No deps — on TS the
|
|
210
|
+
* barrier is satisfied by `await_pending_effects` (see the spec doc), so the
|
|
211
|
+
* handler just returns `{ok: true}`. Mount it on any test endpoint whose
|
|
212
|
+
* suite asserts on audit rows (the spine binary bundles it via
|
|
213
|
+
* `create_testing_actions`; in-process suites mount it directly).
|
|
214
|
+
*/
|
|
215
|
+
export declare const create_testing_drain_effects_action: () => RpcAction;
|
|
127
216
|
/** Options for `create_testing_actions`. */
|
|
128
217
|
export interface CreateTestingActionsOptions {
|
|
129
218
|
/**
|
|
@@ -141,15 +230,18 @@ export interface CreateTestingActionsOptions {
|
|
|
141
230
|
*/
|
|
142
231
|
readonly daemon_token_state: DaemonTokenState;
|
|
143
232
|
/**
|
|
144
|
-
* Consumer-supplied callback invoked after the auth-table reset
|
|
145
|
-
* `
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
233
|
+
* Consumer-supplied callback invoked after the auth-table reset, passed
|
|
234
|
+
* the same transactional `Db` the auth wipes ran on. DB-domain consumers
|
|
235
|
+
* (e.g. fuz_forge truncating its cell / fact / file tables) MUST use this
|
|
236
|
+
* `db` rather than a separately-pooled connection — under PGlite's single
|
|
237
|
+
* connection a second connection deadlocks against this still-open
|
|
238
|
+
* transaction. `testing_zzz_server` clears in-memory workspace registry +
|
|
239
|
+
* terminals + scoped-FS scratch (ignores `db`); `testing_spine_stub` has
|
|
240
|
+
* no domain layer and omits the option. Runs inside the same RPC dispatch
|
|
241
|
+
* as the auth-table writes, so a throw surfaces to the caller as a
|
|
242
|
+
* JSON-RPC error and the per-test fixture short-circuits.
|
|
151
243
|
*/
|
|
152
|
-
readonly reset_state?: () => Promise<void> | void;
|
|
244
|
+
readonly reset_state?: (db: Db) => Promise<void> | void;
|
|
153
245
|
}
|
|
154
246
|
/**
|
|
155
247
|
* Build the testing RPC actions for a test binary's registry.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B
|
|
1
|
+
{"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAEvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAiBvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwBQ,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAeC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CA0GjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
|
|
@@ -2,8 +2,14 @@ import '../assert_dev_env.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Test-binary RPC actions for cross-process integration tests.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* wipe + keeper re-seed
|
|
5
|
+
* Three daemon-token-authed actions, bundled by `create_testing_actions`:
|
|
6
|
+
* **`_testing_reset`** (DB wipe + keeper re-seed), **`_testing_drain_effects`**
|
|
7
|
+
* (audit barrier), and **`_testing_mint_session`** (forge an
|
|
8
|
+
* expired-by-construction server-side session for the expiry conformance
|
|
9
|
+
* cases).
|
|
10
|
+
*
|
|
11
|
+
* `_testing_reset` — full DB wipe + keeper re-seed + optional
|
|
12
|
+
* secondary-account seeding. The
|
|
7
13
|
* handler wipes every auth-namespace row (no keeper-preserve filter),
|
|
8
14
|
* flips `bootstrap_lock` back to its post-bootstrap shape, seeds a
|
|
9
15
|
* fresh keeper account inline (reusing `create_test_account_with_credentials`
|
|
@@ -56,7 +62,7 @@ import { Uuid } from '@fuzdev/fuz_util/id.js';
|
|
|
56
62
|
import { rpc_action } from '../../actions/action_rpc.js';
|
|
57
63
|
import { ROLE_ADMIN, ROLE_KEEPER } from '../../auth/role_schema.js';
|
|
58
64
|
import { auth_integration_truncate_tables } from '../db.js';
|
|
59
|
-
import { create_test_account_with_credentials, DEFAULT_TEST_PASSWORD } from '../app_server.js';
|
|
65
|
+
import { create_test_account_with_credentials, mint_test_session, DEFAULT_TEST_PASSWORD, } from '../app_server.js';
|
|
60
66
|
/** Output shape for an individual seeded account (keeper or extra). */
|
|
61
67
|
const SeededAccountShape = z.strictObject({
|
|
62
68
|
account: z.strictObject({ id: Uuid, username: z.string() }),
|
|
@@ -111,6 +117,77 @@ export const testing_reset_action_spec = {
|
|
|
111
117
|
async: true,
|
|
112
118
|
description: 'Test-binary only — wipe auth tables, re-bootstrap a fresh keeper (+ optional extras), fire the domain-state reset.',
|
|
113
119
|
};
|
|
120
|
+
/**
|
|
121
|
+
* `_testing_drain_effects` — await in-flight fire-and-forget audit writes so
|
|
122
|
+
* a following `audit_log_list` is authoritative. The deterministic barrier
|
|
123
|
+
* the cross-backend conformance suite uses in place of a poll/sleep before
|
|
124
|
+
* asserting on audit rows.
|
|
125
|
+
*
|
|
126
|
+
* On the TS spine the barrier is **satisfied by construction**: the test
|
|
127
|
+
* binary runs `await_pending_effects: true`, so every mutation's fire-and-
|
|
128
|
+
* forget audit emits are awaited before its response returns — by the time
|
|
129
|
+
* a later drain call runs, prior emits are already durable. The action still
|
|
130
|
+
* exists so the cross-backend test body calls the same method on every
|
|
131
|
+
* backend; the Rust spine (whose audit writes are detached tokio tasks)
|
|
132
|
+
* does the real await in `AuditEmitter::drain_inflight`.
|
|
133
|
+
*
|
|
134
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset`.
|
|
135
|
+
*/
|
|
136
|
+
export const testing_drain_effects_action_spec = {
|
|
137
|
+
method: '_testing_drain_effects',
|
|
138
|
+
kind: 'request_response',
|
|
139
|
+
initiator: 'frontend',
|
|
140
|
+
auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
|
|
141
|
+
side_effects: false,
|
|
142
|
+
input: z.void(),
|
|
143
|
+
output: z.strictObject({ ok: z.boolean() }),
|
|
144
|
+
async: true,
|
|
145
|
+
description: 'Test-binary only — await in-flight fire-and-forget audit writes so a following audit_log_list read is authoritative.',
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* `_testing_mint_session` — mint an expired-by-construction server-side
|
|
149
|
+
* session for an existing account and return its signed cookie value.
|
|
150
|
+
*
|
|
151
|
+
* The minted `auth_session` row's `expires_at` is backdated (negative
|
|
152
|
+
* `expires_in_seconds`) while the returned cookie's own signed payload
|
|
153
|
+
* stays valid (future). Cross-process auth resolution therefore passes the
|
|
154
|
+
* cookie-payload gate (`parse_session`) and is refused by the authoritative
|
|
155
|
+
* DB-row gate (`query_session_get_valid` — `WHERE expires_at > NOW()`) —
|
|
156
|
+
* the gate the in-process payload-expiry tests never reach and the one that
|
|
157
|
+
* structurally needs a server-side mint (the cross-process driver has no
|
|
158
|
+
* keyring / DB access). The `expired_session` conformance principal drives
|
|
159
|
+
* this.
|
|
160
|
+
*
|
|
161
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset` —
|
|
162
|
+
* effectively keeper-only. Like its siblings the action is internally
|
|
163
|
+
* privileged (a direct `auth_session` insert the production wire never
|
|
164
|
+
* exposes); daemon-token auth is the structural fence and the module's
|
|
165
|
+
* `assert_dev_env` import (TS) plus the Rust `cargo xtask check-release`
|
|
166
|
+
* dep-graph audit keep the `_testing_` surface out of every shipped build.
|
|
167
|
+
*/
|
|
168
|
+
export const testing_mint_session_action_spec = {
|
|
169
|
+
method: '_testing_mint_session',
|
|
170
|
+
kind: 'request_response',
|
|
171
|
+
initiator: 'frontend',
|
|
172
|
+
auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
|
|
173
|
+
side_effects: true,
|
|
174
|
+
input: z.strictObject({
|
|
175
|
+
account_id: Uuid,
|
|
176
|
+
expires_in_seconds: z.number().int(),
|
|
177
|
+
}),
|
|
178
|
+
output: z.strictObject({ session_cookie: z.string() }),
|
|
179
|
+
async: true,
|
|
180
|
+
description: 'Test-binary only — mint a backdated-expiry auth_session row for an account and return its ' +
|
|
181
|
+
'signed cookie value (exercises the authoritative server-side DB-row expiry gate).',
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Build the standalone `_testing_drain_effects` action. No deps — on TS the
|
|
185
|
+
* barrier is satisfied by `await_pending_effects` (see the spec doc), so the
|
|
186
|
+
* handler just returns `{ok: true}`. Mount it on any test endpoint whose
|
|
187
|
+
* suite asserts on audit rows (the spine binary bundles it via
|
|
188
|
+
* `create_testing_actions`; in-process suites mount it directly).
|
|
189
|
+
*/
|
|
190
|
+
export const create_testing_drain_effects_action = () => rpc_action(testing_drain_effects_action_spec, async () => ({ ok: true }));
|
|
114
191
|
/**
|
|
115
192
|
* Build the testing RPC actions for a test binary's registry.
|
|
116
193
|
*
|
|
@@ -202,11 +279,25 @@ export const create_testing_actions = (deps, options) => {
|
|
|
202
279
|
// of stale-id-then-refresh on the next call.
|
|
203
280
|
daemon_token_state.keeper_account_id = keeper.account.id;
|
|
204
281
|
// 7. Fire domain-state reset (zzz workspaces/terminals/scratch,
|
|
205
|
-
// or no-op for spine_stub).
|
|
282
|
+
// fuz_forge cell/fact/file truncation, or no-op for spine_stub).
|
|
283
|
+
// Pass the transactional `ctx.db` so DB-domain truncation runs
|
|
284
|
+
// on the same connection — a separate pool connection deadlocks
|
|
285
|
+
// against this open transaction under PGlite.
|
|
206
286
|
if (reset_state)
|
|
207
|
-
await reset_state();
|
|
287
|
+
await reset_state(ctx.db);
|
|
208
288
|
return { ...keeper, extra_accounts: extras };
|
|
209
289
|
}),
|
|
290
|
+
rpc_action(testing_mint_session_action_spec, async (input, ctx) => {
|
|
291
|
+
const { session_cookie } = await mint_test_session({
|
|
292
|
+
db: ctx.db,
|
|
293
|
+
keyring: deps.keyring,
|
|
294
|
+
session_options,
|
|
295
|
+
account_id: input.account_id,
|
|
296
|
+
expires_in_seconds: input.expires_in_seconds,
|
|
297
|
+
});
|
|
298
|
+
return { session_cookie };
|
|
299
|
+
}),
|
|
300
|
+
create_testing_drain_effects_action(),
|
|
210
301
|
];
|
|
211
302
|
};
|
|
212
303
|
/** Set of auth-namespace tables `_testing_reset` wipes. Mirrored by the coverage test. */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Register `fn` as an expected-failure test. Passes while `fn` throws /
|
|
4
|
+
* rejects; **fails** once `fn` succeeds (signalling the gap closed and the
|
|
5
|
+
* marker should be removed).
|
|
6
|
+
*
|
|
7
|
+
* @param tracking_id - descriptive id of the tracked gap (e.g.
|
|
8
|
+
* `'audit-log-sse-rust-spine'`) — a feature/behavior name, not a
|
|
9
|
+
* process/milestone label.
|
|
10
|
+
* @param reason - why the case is deferred-by-design.
|
|
11
|
+
* @param name - the assertion / test label.
|
|
12
|
+
* @param fn - the test body (expected to throw/reject until the gap closes).
|
|
13
|
+
*/
|
|
14
|
+
export declare const xfail_until: (tracking_id: string, reason: string, name: string, fn: () => void | Promise<void>) => void;
|
|
15
|
+
//# sourceMappingURL=xfail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xfail.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/xfail.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAyB9B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,GACvB,aAAa,MAAM,EACnB,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAC5B,IAEF,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* `xfail_until` — mark a deferred-by-design gap as an expected failure.
|
|
4
|
+
*
|
|
5
|
+
* A thin wrapper over vitest's `test.fails` that bakes a tracking id +
|
|
6
|
+
* reason into the test label. Two properties make it the right tool for
|
|
7
|
+
* declared gaps (distinct from in-scope gaps, which fail loud as a normal
|
|
8
|
+
* red `test`):
|
|
9
|
+
*
|
|
10
|
+
* - **Visible** — the case shows in the report as a distinct expected
|
|
11
|
+
* failure, not a silent `.skip`, so a deferred gap never disappears from
|
|
12
|
+
* view.
|
|
13
|
+
* - **Self-cleaning** — `test.fails` turns **red** the moment the body
|
|
14
|
+
* stops throwing (i.e. the impl starts passing), forcing whoever closed
|
|
15
|
+
* the gap to delete the marker. A `.skip` would rot silently; this can't.
|
|
16
|
+
*
|
|
17
|
+
* Sibling to `test_if` in `capabilities.ts`. No taxonomy — a one-line
|
|
18
|
+
* marker with a tracking id and a reason, nothing more.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { test } from 'vitest';
|
|
23
|
+
/**
|
|
24
|
+
* Register `fn` as an expected-failure test. Passes while `fn` throws /
|
|
25
|
+
* rejects; **fails** once `fn` succeeds (signalling the gap closed and the
|
|
26
|
+
* marker should be removed).
|
|
27
|
+
*
|
|
28
|
+
* @param tracking_id - descriptive id of the tracked gap (e.g.
|
|
29
|
+
* `'audit-log-sse-rust-spine'`) — a feature/behavior name, not a
|
|
30
|
+
* process/milestone label.
|
|
31
|
+
* @param reason - why the case is deferred-by-design.
|
|
32
|
+
* @param name - the assertion / test label.
|
|
33
|
+
* @param fn - the test body (expected to throw/reject until the gap closes).
|
|
34
|
+
*/
|
|
35
|
+
export const xfail_until = (tracking_id, reason, name, fn) => {
|
|
36
|
+
test.fails(`${name} [xfail_until ${tracking_id}: ${reason}]`, fn);
|
|
37
|
+
};
|