@fuzdev/fuz_app 0.67.1 → 0.69.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/CLAUDE.md +99 -5
- package/dist/auth/account_queries.d.ts +87 -4
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +107 -17
- package/dist/auth/account_schema.d.ts +19 -0
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +8 -0
- package/dist/auth/admin_action_specs.d.ts +170 -3
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +148 -4
- 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 +246 -40
- package/dist/auth/audit_log_ddl.d.ts +10 -1
- package/dist/auth/audit_log_ddl.d.ts.map +1 -1
- package/dist/auth/audit_log_ddl.js +13 -4
- package/dist/auth/audit_log_schema.d.ts +34 -1
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +73 -0
- package/dist/auth/auth_ddl.d.ts +2 -2
- package/dist/auth/auth_ddl.d.ts.map +1 -1
- package/dist/auth/auth_ddl.js +10 -2
- package/dist/auth/cell_action_specs.d.ts +1295 -0
- package/dist/auth/cell_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_action_specs.js +397 -0
- package/dist/auth/cell_actions.d.ts +63 -0
- package/dist/auth/cell_actions.d.ts.map +1 -0
- package/dist/auth/cell_actions.js +546 -0
- package/dist/auth/cell_audit_action_specs.d.ts +131 -0
- package/dist/auth/cell_audit_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_audit_action_specs.js +70 -0
- package/dist/auth/cell_audit_actions.d.ts +18 -0
- package/dist/auth/cell_audit_actions.d.ts.map +1 -0
- package/dist/auth/cell_audit_actions.js +59 -0
- package/dist/auth/cell_audit_events.d.ts +28 -0
- package/dist/auth/cell_audit_events.d.ts.map +1 -0
- package/dist/auth/cell_audit_events.js +42 -0
- package/dist/auth/cell_audit_metadata.d.ts +48 -0
- package/dist/auth/cell_audit_metadata.d.ts.map +1 -0
- package/dist/auth/cell_audit_metadata.js +46 -0
- package/dist/auth/cell_authorize.d.ts +88 -0
- package/dist/auth/cell_authorize.d.ts.map +1 -0
- package/dist/auth/cell_authorize.js +172 -0
- package/dist/auth/cell_data_schema.d.ts +44 -0
- package/dist/auth/cell_data_schema.d.ts.map +1 -0
- package/dist/auth/cell_data_schema.js +42 -0
- package/dist/auth/cell_field_action_specs.d.ts +244 -0
- package/dist/auth/cell_field_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_field_action_specs.js +136 -0
- package/dist/auth/cell_field_actions.d.ts +34 -0
- package/dist/auth/cell_field_actions.d.ts.map +1 -0
- package/dist/auth/cell_field_actions.js +153 -0
- package/dist/auth/cell_field_audit_metadata.d.ts +30 -0
- package/dist/auth/cell_field_audit_metadata.d.ts.map +1 -0
- package/dist/auth/cell_field_audit_metadata.js +28 -0
- package/dist/auth/cell_grant_action_specs.d.ts +333 -0
- package/dist/auth/cell_grant_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_grant_action_specs.js +148 -0
- package/dist/auth/cell_grant_actions.d.ts +50 -0
- package/dist/auth/cell_grant_actions.d.ts.map +1 -0
- package/dist/auth/cell_grant_actions.js +208 -0
- package/dist/auth/cell_grant_audit_metadata.d.ts +75 -0
- package/dist/auth/cell_grant_audit_metadata.d.ts.map +1 -0
- package/dist/auth/cell_grant_audit_metadata.js +54 -0
- package/dist/auth/cell_item_action_specs.d.ts +331 -0
- package/dist/auth/cell_item_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_item_action_specs.js +182 -0
- package/dist/auth/cell_item_actions.d.ts +37 -0
- package/dist/auth/cell_item_actions.d.ts.map +1 -0
- package/dist/auth/cell_item_actions.js +204 -0
- package/dist/auth/cell_item_audit_metadata.d.ts +35 -0
- package/dist/auth/cell_item_audit_metadata.d.ts.map +1 -0
- package/dist/auth/cell_item_audit_metadata.js +32 -0
- package/dist/auth/cell_relation_visibility.d.ts +32 -0
- package/dist/auth/cell_relation_visibility.d.ts.map +1 -0
- package/dist/auth/cell_relation_visibility.js +57 -0
- package/dist/auth/deps.d.ts +9 -0
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.d.ts +30 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +54 -0
- 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/db/CLAUDE.md +118 -0
- package/dist/db/cell_audit_queries.d.ts +26 -0
- package/dist/db/cell_audit_queries.d.ts.map +1 -0
- package/dist/db/cell_audit_queries.js +53 -0
- package/dist/db/cell_ddl.d.ts +151 -0
- package/dist/db/cell_ddl.d.ts.map +1 -0
- package/dist/db/cell_ddl.js +247 -0
- package/dist/db/cell_field_queries.d.ts +105 -0
- package/dist/db/cell_field_queries.d.ts.map +1 -0
- package/dist/db/cell_field_queries.js +113 -0
- package/dist/db/cell_grant_queries.d.ts +132 -0
- package/dist/db/cell_grant_queries.d.ts.map +1 -0
- package/dist/db/cell_grant_queries.js +145 -0
- package/dist/db/cell_history_ddl.d.ts +38 -0
- package/dist/db/cell_history_ddl.d.ts.map +1 -0
- package/dist/db/cell_history_ddl.js +61 -0
- package/dist/db/cell_item_queries.d.ts +107 -0
- package/dist/db/cell_item_queries.d.ts.map +1 -0
- package/dist/db/cell_item_queries.js +119 -0
- package/dist/db/cell_queries.d.ts +327 -0
- package/dist/db/cell_queries.d.ts.map +1 -0
- package/dist/db/cell_queries.js +431 -0
- package/dist/db/fact_ddl.d.ts +38 -0
- package/dist/db/fact_ddl.d.ts.map +1 -0
- package/dist/db/fact_ddl.js +71 -0
- package/dist/db/fact_queries.d.ts +140 -0
- package/dist/db/fact_queries.d.ts.map +1 -0
- package/dist/db/fact_queries.js +161 -0
- package/dist/db/fact_store.d.ts +112 -0
- package/dist/db/fact_store.d.ts.map +1 -0
- package/dist/db/fact_store.js +225 -0
- 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/server/env.d.ts +2 -0
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +6 -0
- package/dist/server/fact_write.d.ts +32 -0
- package/dist/server/fact_write.d.ts.map +1 -0
- package/dist/server/fact_write.js +56 -0
- package/dist/server/file_fact_fetcher.d.ts +42 -0
- package/dist/server/file_fact_fetcher.d.ts.map +1 -0
- package/dist/server/file_fact_fetcher.js +60 -0
- package/dist/server/file_fact_url.d.ts +53 -0
- package/dist/server/file_fact_url.d.ts.map +1 -0
- package/dist/server/file_fact_url.js +52 -0
- package/dist/server/serve_fact_route.d.ts +78 -0
- package/dist/server/serve_fact_route.d.ts.map +1 -0
- package/dist/server/serve_fact_route.js +205 -0
- package/dist/testing/CLAUDE.md +142 -6
- package/dist/testing/app_server.d.ts +46 -0
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +67 -8
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +67 -1
- package/dist/testing/cross_backend/account_lifecycle.d.ts +10 -0
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -0
- package/dist/testing/cross_backend/account_lifecycle.js +144 -0
- 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 +29 -7
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +3 -1
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts +39 -0
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_cross_helpers.js +45 -0
- package/dist/testing/cross_backend/cell_crud.d.ts +4 -0
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_crud.js +168 -0
- 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/cell_relations.d.ts +4 -0
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_relations.js +229 -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/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +6 -2
- 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/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 +39 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +90 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +91 -3
- 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/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +4 -0
- package/dist/testing/integration.d.ts +2 -3
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +20 -85
- 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/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +4 -0
- package/dist/ui/AdminAccounts.svelte +84 -35
- 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 +41 -20
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +52 -22
- 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_rpc_adapters.d.ts +6 -2
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +5 -1
- 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 +2 -2
|
@@ -37,12 +37,23 @@ import { create_test_app_surface_spec } from '../stubs.js';
|
|
|
37
37
|
*/
|
|
38
38
|
export const spine_session_options = create_session_config('fuz_session');
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
40
|
+
* App role the role-shaped-`cell_grant` cross suite exercises. Registered
|
|
41
|
+
* with no grant path (`grant_paths: []`) so it stays a valid registry member
|
|
42
|
+
* without entering the admin / self-service grant flows — holders are seeded
|
|
43
|
+
* directly via `extra_accounts`. Must match the `cell_editor` entry in the
|
|
44
|
+
* Rust `testing_spine_stub`'s `known_roles` (cross-language test contract).
|
|
44
45
|
*/
|
|
45
|
-
export const
|
|
46
|
+
export const SPINE_CELL_EDITOR_ROLE = 'cell_editor';
|
|
47
|
+
/**
|
|
48
|
+
* The spine's closed role registry: built-ins plus `SPINE_CELL_EDITOR_ROLE`.
|
|
49
|
+
* Threaded into the cell spec set's role-validity gate; the Rust stub mirrors
|
|
50
|
+
* the same membership. When the spine grows additional grantable roles,
|
|
51
|
+
* thread their registry through `create_role_schema` here so the admin suite
|
|
52
|
+
* picks up grant-path coverage.
|
|
53
|
+
*/
|
|
54
|
+
export const spine_roles = create_role_schema([
|
|
55
|
+
{ name: SPINE_CELL_EDITOR_ROLE, grant_paths: [] },
|
|
56
|
+
]);
|
|
46
57
|
/** RPC endpoint mount path — matches the binary's `/api/rpc`. */
|
|
47
58
|
export const SPINE_RPC_PATH = '/api/rpc';
|
|
48
59
|
/**
|
|
@@ -54,11 +65,10 @@ export const SPINE_RPC_PATH = '/api/rpc';
|
|
|
54
65
|
*/
|
|
55
66
|
export const SPINE_SSE_PATH = '/api/admin/audit/stream';
|
|
56
67
|
/**
|
|
57
|
-
* Factory-form RPC endpoints
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* never called across the process boundary.
|
|
68
|
+
* Factory-form RPC endpoints over the per-test `ctx.deps`. `create_app_server`
|
|
69
|
+
* (in the binary) owns live dispatch; the surface builder invokes the factory
|
|
70
|
+
* once with a stub ctx for setup-time path/method lookup, so the handler
|
|
71
|
+
* closures are never called across the process boundary.
|
|
62
72
|
*
|
|
63
73
|
* Test binaries append their own `_testing_reset` action to this endpoint's
|
|
64
74
|
* `actions` (see `testing_reset_actions.ts`); it is intentionally excluded
|
|
@@ -69,7 +79,6 @@ export const spine_rpc_endpoints = (ctx) => [
|
|
|
69
79
|
{
|
|
70
80
|
path: SPINE_RPC_PATH,
|
|
71
81
|
actions: create_standard_rpc_actions(ctx.deps, {
|
|
72
|
-
app_settings: ctx.app_settings,
|
|
73
82
|
roles: spine_roles,
|
|
74
83
|
}),
|
|
75
84
|
},
|
|
@@ -97,7 +106,6 @@ export const create_spine_route_specs = (ctx) => [
|
|
|
97
106
|
session_options: spine_session_options,
|
|
98
107
|
ip_rate_limiter: null,
|
|
99
108
|
signup_account_rate_limiter: null,
|
|
100
|
-
app_settings: ctx.app_settings,
|
|
101
109
|
}),
|
|
102
110
|
]),
|
|
103
111
|
...(ctx.audit_sse
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { CellCrossTestOptions } from './cell_cross_helpers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for the origin parity suite. Shares the shape of the cell /
|
|
5
|
+
* account-lifecycle suites (`setup_test` / `capabilities` / `rpc_path`);
|
|
6
|
+
* reuses `CellCrossTestOptions` rather than minting a structural duplicate.
|
|
7
|
+
*/
|
|
8
|
+
export type OriginCrossTestOptions = CellCrossTestOptions;
|
|
9
|
+
export declare const describe_origin_cross_tests: (options: OriginCrossTestOptions) => void;
|
|
10
|
+
//# sourceMappingURL=origin.d.ts.map
|
|
@@ -0,0 +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,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAGlE;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAM1D,eAAO,MAAM,2BAA2B,GAAI,SAAS,sBAAsB,KAAG,IAwC7E,CAAC"}
|
|
@@ -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":"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"}
|
|
@@ -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';
|
|
@@ -85,6 +85,11 @@ const in_process_fetch_transport = (app) => {
|
|
|
85
85
|
* beyond what `create_test_app` already does.
|
|
86
86
|
*/
|
|
87
87
|
export const default_in_process_setup = (options) => async () => {
|
|
88
|
+
// Per-test fresh db. When `options.migration_namespaces` is set,
|
|
89
|
+
// `create_test_app` provisions an auth+extras PGlite (e.g. the cell
|
|
90
|
+
// layer); otherwise the auth-only default. Either way the factory
|
|
91
|
+
// resets + re-migrates on each `create`, so the per-test keeper
|
|
92
|
+
// bootstrap below lands on a clean DB.
|
|
88
93
|
const test_app = await create_test_app(options);
|
|
89
94
|
// Seed bootstrap-time secondaries against the same DB the keeper
|
|
90
95
|
// just landed on. Direct-insert is the only path for roles whose
|
|
@@ -105,7 +110,6 @@ export const default_in_process_setup = (options) => async () => {
|
|
|
105
110
|
extra_accounts[spec.username] = build_extra_account_fixture(seeded, cookie_name);
|
|
106
111
|
}
|
|
107
112
|
return {
|
|
108
|
-
in_process: true,
|
|
109
113
|
transport: in_process_fetch_transport(test_app.app),
|
|
110
114
|
// In-process the wrapper is stateless and never auto-adds Origin —
|
|
111
115
|
// `options` is accepted for API symmetry with cross-process but
|
|
@@ -118,8 +122,18 @@ export const default_in_process_setup = (options) => async () => {
|
|
|
118
122
|
create_daemon_token_headers: test_app.create_daemon_token_headers,
|
|
119
123
|
create_account: test_app.create_account,
|
|
120
124
|
extra_accounts,
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
},
|
|
123
137
|
};
|
|
124
138
|
};
|
|
125
139
|
/**
|
|
@@ -219,6 +233,15 @@ const rpc_via_transport = async (transport, rpc_path, method, params, backend_na
|
|
|
219
233
|
}
|
|
220
234
|
return raw.result;
|
|
221
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() });
|
|
222
245
|
/** Structural subset of `_testing_reset`'s output. */
|
|
223
246
|
const TestingResetResponseShape = z.object({
|
|
224
247
|
account: z.object({ id: Uuid, username: z.string() }),
|
|
@@ -499,7 +522,6 @@ export const default_cross_process_setup = (handle, options) => {
|
|
|
499
522
|
initial_cookies: [`${cookie_name}=${keeper.session_cookie}`],
|
|
500
523
|
});
|
|
501
524
|
return {
|
|
502
|
-
in_process: false,
|
|
503
525
|
transport,
|
|
504
526
|
fresh_transport: (fresh_options) => create_fetch_transport({
|
|
505
527
|
base_url: handle.config.base_url,
|
|
@@ -512,6 +534,18 @@ export const default_cross_process_setup = (handle, options) => {
|
|
|
512
534
|
create_daemon_token_headers,
|
|
513
535
|
create_account,
|
|
514
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
|
+
},
|
|
515
549
|
};
|
|
516
550
|
};
|
|
517
551
|
};
|
|
@@ -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`
|
|
@@ -124,6 +130,88 @@ export declare const testing_reset_action_spec: {
|
|
|
124
130
|
readonly async: true;
|
|
125
131
|
readonly description: "Test-binary only — wipe auth tables, re-bootstrap a fresh keeper (+ optional extras), fire the domain-state reset.";
|
|
126
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* `_testing_drain_effects` — await in-flight fire-and-forget audit writes so
|
|
135
|
+
* a following `audit_log_list` is authoritative. The deterministic barrier
|
|
136
|
+
* the cross-backend conformance suite uses in place of a poll/sleep before
|
|
137
|
+
* asserting on audit rows.
|
|
138
|
+
*
|
|
139
|
+
* On the TS spine the barrier is **satisfied by construction**: the test
|
|
140
|
+
* binary runs `await_pending_effects: true`, so every mutation's fire-and-
|
|
141
|
+
* forget audit emits are awaited before its response returns — by the time
|
|
142
|
+
* a later drain call runs, prior emits are already durable. The action still
|
|
143
|
+
* exists so the cross-backend test body calls the same method on every
|
|
144
|
+
* backend; the Rust spine (whose audit writes are detached tokio tasks)
|
|
145
|
+
* does the real await in `AuditEmitter::drain_inflight`.
|
|
146
|
+
*
|
|
147
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset`.
|
|
148
|
+
*/
|
|
149
|
+
export declare const testing_drain_effects_action_spec: {
|
|
150
|
+
readonly method: "_testing_drain_effects";
|
|
151
|
+
readonly kind: "request_response";
|
|
152
|
+
readonly initiator: "frontend";
|
|
153
|
+
readonly auth: {
|
|
154
|
+
readonly account: "required";
|
|
155
|
+
readonly actor: "none";
|
|
156
|
+
readonly credential_types: readonly ["daemon_token"];
|
|
157
|
+
};
|
|
158
|
+
readonly side_effects: false;
|
|
159
|
+
readonly input: z.ZodVoid;
|
|
160
|
+
readonly output: z.ZodObject<{
|
|
161
|
+
ok: z.ZodBoolean;
|
|
162
|
+
}, z.core.$strict>;
|
|
163
|
+
readonly async: true;
|
|
164
|
+
readonly description: "Test-binary only — await in-flight fire-and-forget audit writes so a following audit_log_list read is authoritative.";
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* `_testing_mint_session` — mint an expired-by-construction server-side
|
|
168
|
+
* session for an existing account and return its signed cookie value.
|
|
169
|
+
*
|
|
170
|
+
* The minted `auth_session` row's `expires_at` is backdated (negative
|
|
171
|
+
* `expires_in_seconds`) while the returned cookie's own signed payload
|
|
172
|
+
* stays valid (future). Cross-process auth resolution therefore passes the
|
|
173
|
+
* cookie-payload gate (`parse_session`) and is refused by the authoritative
|
|
174
|
+
* DB-row gate (`query_session_get_valid` — `WHERE expires_at > NOW()`) —
|
|
175
|
+
* the gate the in-process payload-expiry tests never reach and the one that
|
|
176
|
+
* structurally needs a server-side mint (the cross-process driver has no
|
|
177
|
+
* keyring / DB access). The `expired_session` conformance principal drives
|
|
178
|
+
* this.
|
|
179
|
+
*
|
|
180
|
+
* `auth` gates on the daemon-token credential, matching `_testing_reset` —
|
|
181
|
+
* effectively keeper-only. Like its siblings the action is internally
|
|
182
|
+
* privileged (a direct `auth_session` insert the production wire never
|
|
183
|
+
* exposes); daemon-token auth is the structural fence and the module's
|
|
184
|
+
* `assert_dev_env` import (TS) plus the Rust `cargo xtask check-release`
|
|
185
|
+
* dep-graph audit keep the `_testing_` surface out of every shipped build.
|
|
186
|
+
*/
|
|
187
|
+
export declare const testing_mint_session_action_spec: {
|
|
188
|
+
readonly method: "_testing_mint_session";
|
|
189
|
+
readonly kind: "request_response";
|
|
190
|
+
readonly initiator: "frontend";
|
|
191
|
+
readonly auth: {
|
|
192
|
+
readonly account: "required";
|
|
193
|
+
readonly actor: "none";
|
|
194
|
+
readonly credential_types: readonly ["daemon_token"];
|
|
195
|
+
};
|
|
196
|
+
readonly side_effects: true;
|
|
197
|
+
readonly input: z.ZodObject<{
|
|
198
|
+
account_id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
|
|
199
|
+
expires_in_seconds: z.ZodNumber;
|
|
200
|
+
}, z.core.$strict>;
|
|
201
|
+
readonly output: z.ZodObject<{
|
|
202
|
+
session_cookie: z.ZodString;
|
|
203
|
+
}, z.core.$strict>;
|
|
204
|
+
readonly async: true;
|
|
205
|
+
readonly description: string;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Build the standalone `_testing_drain_effects` action. No deps — on TS the
|
|
209
|
+
* barrier is satisfied by `await_pending_effects` (see the spec doc), so the
|
|
210
|
+
* handler just returns `{ok: true}`. Mount it on any test endpoint whose
|
|
211
|
+
* suite asserts on audit rows (the spine binary bundles it via
|
|
212
|
+
* `create_testing_actions`; in-process suites mount it directly).
|
|
213
|
+
*/
|
|
214
|
+
export declare const create_testing_drain_effects_action: () => RpcAction;
|
|
127
215
|
/** Options for `create_testing_actions`. */
|
|
128
216
|
export interface CreateTestingActionsOptions {
|
|
129
217
|
/**
|
|
@@ -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;AAiBjE;;;;;;;;;;;;;;;;;;;;;;;;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;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAuGjB,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
|
*
|
|
@@ -207,6 +284,17 @@ export const create_testing_actions = (deps, options) => {
|
|
|
207
284
|
await reset_state();
|
|
208
285
|
return { ...keeper, extra_accounts: extras };
|
|
209
286
|
}),
|
|
287
|
+
rpc_action(testing_mint_session_action_spec, async (input, ctx) => {
|
|
288
|
+
const { session_cookie } = await mint_test_session({
|
|
289
|
+
db: ctx.db,
|
|
290
|
+
keyring: deps.keyring,
|
|
291
|
+
session_options,
|
|
292
|
+
account_id: input.account_id,
|
|
293
|
+
expires_in_seconds: input.expires_in_seconds,
|
|
294
|
+
});
|
|
295
|
+
return { session_cookie };
|
|
296
|
+
}),
|
|
297
|
+
create_testing_drain_effects_action(),
|
|
210
298
|
];
|
|
211
299
|
};
|
|
212
300
|
/** 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
|
+
};
|