@fuzdev/fuz_app 0.69.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.
@@ -219,15 +219,16 @@ test-only by construction.
219
219
  - `assert_schema_snapshots_equal(a, b, labels?)` — throws on drift with a fully-formatted diff message.
220
220
  - `SchemaDiff` — tagged-union per drift kind: `schema_version_only_in`, `schema_version_sequence_differs`, `table_only_in`, `column_only_in`, `column_field_differs`, `index_only_in`, `index_definition_differs`, `constraint_only_in`, `constraint_differs`, `sequence_only_in`, `sequence_data_type_differs`.
221
221
 
222
- **Cross-impl gate pattern** — consumers running two backends against a
223
- shared schema (zzz's `--backend=both`, fuz_app's cross-backend suite)
224
- bootstrap each impl against an isolated DB, snapshot, then compare:
222
+ **Cross-impl gate pattern** — a dual-impl consumer running two backends
223
+ (a TS Hono server and a Rust spine server) against a shared schema, plus
224
+ fuz_app's own cross-backend suite, bootstrap each impl against an isolated
225
+ DB, snapshot, then compare:
225
226
 
226
227
  ```ts
227
- await drop_recreate_db('zzz_test');
228
+ await drop_recreate_db('app_test');
228
229
  await spawn_backend(deno_config);
229
230
  const snapshot_deno = await query_schema_snapshot(db);
230
- await drop_recreate_db('zzz_test');
231
+ await drop_recreate_db('app_test');
231
232
  await spawn_backend(rust_config);
232
233
  const snapshot_rust = await query_schema_snapshot(db);
233
234
  assert_schema_snapshots_equal(snapshot_deno, snapshot_rust, {a: 'deno', b: 'rust'});
@@ -789,8 +790,8 @@ points:
789
790
  The standard test suites take a unified
790
791
  `{setup_test, surface_source, capabilities}` shape so the same suite bodies
791
792
  run against an in-process Hono harness today and against a spawned backend
792
- over real HTTP — either the Rust spine (`zzz_server`, `fuz_forge_server`, or
793
- the non-domain `testing_spine_stub`) or a **TS** spine binary built on the
793
+ over real HTTP — either the Rust spine (`zzz_server`, another consumer's
794
+ spine server, or the non-domain `testing_spine_stub`) or a **TS** spine binary built on the
794
795
  test-server core below (fuz_app's own domain-free `testing_spine_server`, run
795
796
  on Node + Deno + Bun). In-process is the fast feedback path; cross-process is the
796
797
  source of truth for wire-shape conformance.
@@ -1094,7 +1095,11 @@ in-process legs (plain `gro test`) are `src/test/auth/cell_crud_parity.db.test.t
1094
1095
  same bootstrap-equivalent step (the only path for roles like
1095
1096
  `ROLE_KEEPER` whose `grant_paths` is bootstrap-only), refreshes
1096
1097
  `DaemonTokenState.keeper_account_id` to the new row, then fires the
1097
- consumer-supplied `reset_state` callback for domain-state reset. Auth
1098
+ consumer-supplied `reset_state(db)` callback for domain-state reset
1099
+ passed the **transactional** `Db` the auth wipe ran on, so DB-domain
1100
+ consumers (e.g. fuz_forge truncating its cell / fact / file tables) reset
1101
+ on the same connection rather than a separately-pooled one that would
1102
+ deadlock against this open transaction under PGlite. Auth
1098
1103
  gates on `credential_types: ['daemon_token']` — effectively keeper-only
1099
1104
  without forcing the `actor: 'required'` ⟺ `acting?: ActingActor`
1100
1105
  biconditional. No free-form runtime grant action exists — see the
@@ -0,0 +1,57 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Generic vitest `globalSetup` factory for cross-backend integration suites.
4
+ *
5
+ * Pairs with `make_cross_backend_project`: each cross-backend vitest project
6
+ * sets its own `test.name`, and this factory derives the backend name from
7
+ * that project name (vitest 4 passes the `TestProject` to globalSetup), picks
8
+ * the matching `BackendConfig`, spawns + bootstraps it via `bootstrap_backend`,
9
+ * and `provide`s a serializable handle that `*.cross.test.ts` files
10
+ * `inject` and rebuild with `reconstruct_bootstrapped_handle`.
11
+ *
12
+ * A consumer's `global_setup.ts` collapses to:
13
+ *
14
+ * ```ts
15
+ * import {create_cross_backend_global_setup} from
16
+ * '@fuzdev/fuz_app/testing/cross_backend/create_cross_backend_global_setup.js';
17
+ * import {deno_backend_config, rust_backend_config} from './my_backend_config.js';
18
+ * import './cross_test_types.js'; // augments inject('backend_handle')
19
+ *
20
+ * export default create_cross_backend_global_setup({
21
+ * configs: {deno: deno_backend_config, rust: rust_backend_config},
22
+ * });
23
+ * ```
24
+ *
25
+ * vitest 4's `provide` hard-rejects non-serializable values, so the live
26
+ * `child` / `teardown` / `keeper_transport` are stripped via
27
+ * `serialize_bootstrapped_handle`; the teardown closure stays in the
28
+ * globalSetup process and is returned for vitest to fire after the suite.
29
+ *
30
+ * @module
31
+ */
32
+ import type { TestProject } from 'vitest/node';
33
+ import type { BackendConfig } from './backend_config.js';
34
+ export interface CrossBackendGlobalSetupOptions {
35
+ /**
36
+ * Map of derived backend name → `BackendConfig` factory. The derived
37
+ * name (see `derive_name`) selects the factory; unknown names throw with
38
+ * the full supported list so a misnamed project surfaces clearly.
39
+ */
40
+ readonly configs: Readonly<Record<string, () => BackendConfig>>;
41
+ /**
42
+ * Derive the backend name from the vitest project name. Default strips
43
+ * `cross_backend_(ts_)?`.
44
+ */
45
+ readonly derive_name?: (project_name: string) => string;
46
+ /**
47
+ * Key passed to `project.provide` (and read by `inject` in test files).
48
+ * Default `'backend_handle'`. Augment vitest's `ProvidedContext` for it.
49
+ */
50
+ readonly provide_key?: string;
51
+ }
52
+ /**
53
+ * Build a vitest `globalSetup` default export. Returns the
54
+ * `(project) => teardown` function vitest 4 expects.
55
+ */
56
+ export declare const create_cross_backend_global_setup: ({ configs, derive_name, provide_key, }: CrossBackendGlobalSetupOptions) => ((project: TestProject) => Promise<() => Promise<void>>);
57
+ //# sourceMappingURL=create_cross_backend_global_setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create_cross_backend_global_setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/create_cross_backend_global_setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAYvD,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,aAAa,CAAC,CAAC,CAAC;IAChE;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,eAAO,MAAM,iCAAiC,GAAI,wCAI/C,8BAA8B,KAAG,CAAC,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAqB1F,CAAC"}
@@ -0,0 +1,31 @@
1
+ import '../assert_dev_env.js';
2
+ import { bootstrap_backend } from './bootstrap_backend.js';
3
+ import { serialize_bootstrapped_handle } from './setup.js';
4
+ /**
5
+ * Default project-name → backend-name reduction: strips the
6
+ * `cross_backend_` prefix plus an optional `ts_` discriminator (the TS
7
+ * canonical backends carry it to distinguish JS runtimes). So
8
+ * `cross_backend_ts_deno` → `deno` and `cross_backend_rust` → `rust`.
9
+ */
10
+ const DEFAULT_PROJECT_NAME_PREFIX = /^cross_backend_(?:ts_)?/;
11
+ /**
12
+ * Build a vitest `globalSetup` default export. Returns the
13
+ * `(project) => teardown` function vitest 4 expects.
14
+ */
15
+ export const create_cross_backend_global_setup = ({ configs, derive_name = (project_name) => project_name.replace(DEFAULT_PROJECT_NAME_PREFIX, ''), provide_key = 'backend_handle', }) => {
16
+ return async (project) => {
17
+ const name = derive_name(project.name);
18
+ const factory = configs[name];
19
+ if (!factory) {
20
+ throw new Error(`Could not derive backend config from vitest project '${project.name}' ` +
21
+ `(derived name '${name}') — expected one of: ${Object.keys(configs).join(', ')}`);
22
+ }
23
+ const bootstrapped = await bootstrap_backend(factory());
24
+ // vitest's `provide` is keyed on the consumer-augmented `ProvidedContext`;
25
+ // the generic key is a plain string, so cast past the keyof constraint.
26
+ project.provide(provide_key, serialize_bootstrapped_handle(bootstrapped));
27
+ return async () => {
28
+ await bootstrapped.teardown();
29
+ };
30
+ };
31
+ };
@@ -70,6 +70,14 @@ export interface MakeDefaultTsBackendConfigOptions {
70
70
  * override.
71
71
  */
72
72
  readonly port_env_var?: string;
73
+ /**
74
+ * Session cookie name the binary's `create_session_config` uses.
75
+ * Defaults to `'fuz_session'`. Must match the consumer's session config
76
+ * — the harness threads the `_testing_reset`-returned keeper cookie into
77
+ * its jar under this name, so a mismatch surfaces as 401s on the
78
+ * `create_account` path (e.g. fuz_forge uses `'fuz_forge_session'`).
79
+ */
80
+ readonly cookie_name?: string;
73
81
  }
74
82
  /**
75
83
  * Shared builder for TS-family backends (Deno + Node). Owns the common
@@ -111,6 +119,11 @@ export interface MakeDefaultRustBackendConfigOptions {
111
119
  * (e.g. `'info,zzz_server=info,testing_zzz_server=info'`).
112
120
  */
113
121
  readonly rust_log?: string;
122
+ /**
123
+ * Session cookie name the binary uses. Defaults to `'fuz_session'`.
124
+ * Must match the consumer's session config (see the TS builder's note).
125
+ */
126
+ readonly cookie_name?: string;
114
127
  }
115
128
  /**
116
129
  * Shared builder for Rust-family backends. Owns the common env baseline
@@ -1 +1 @@
1
- {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBASpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAStC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAmCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA2CF,CAAC"}
1
+ {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBASpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAStC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAoCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA4CF,CAAC"}
@@ -49,7 +49,7 @@ const build_default_bootstrap = (paths, overrides) => ({
49
49
  * genuinely differs.
50
50
  */
51
51
  export const make_default_ts_backend_config = (opts) => {
52
- const { name, port, start_command, database_url = 'memory://', extra_env, capabilities = ts_default_capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', } = opts;
52
+ const { name, port, start_command, database_url = 'memory://', extra_env, capabilities = ts_default_capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', cookie_name = 'fuz_session', } = opts;
53
53
  return {
54
54
  name,
55
55
  start_command,
@@ -58,7 +58,7 @@ export const make_default_ts_backend_config = (opts) => {
58
58
  ws_path: '/api/ws',
59
59
  health_path: '/health',
60
60
  bootstrap_path: '/api/account/bootstrap',
61
- cookie_name: 'fuz_session',
61
+ cookie_name,
62
62
  startup_timeout_ms: 30_000,
63
63
  env: {
64
64
  NODE_ENV: 'development',
@@ -81,7 +81,7 @@ export const make_default_ts_backend_config = (opts) => {
81
81
  * the 120s startup window for cargo's first-run build cost.
82
82
  */
83
83
  export const make_default_rust_backend_config = (opts) => {
84
- const { name, port, start_command, database_url, extra_env, capabilities = rust_default_capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', rust_log = 'info', } = opts;
84
+ const { name, port, start_command, database_url, extra_env, capabilities = rust_default_capabilities, paths = build_test_backend_paths(name), bootstrap_overrides, port_env_var = 'PORT', rust_log = 'info', cookie_name = 'fuz_session', } = opts;
85
85
  return {
86
86
  name,
87
87
  start_command,
@@ -90,7 +90,7 @@ export const make_default_rust_backend_config = (opts) => {
90
90
  ws_path: '/api/ws',
91
91
  health_path: '/health',
92
92
  bootstrap_path: '/api/account/bootstrap',
93
- cookie_name: 'fuz_session',
93
+ cookie_name,
94
94
  startup_timeout_ms: 120_000,
95
95
  env: {
96
96
  RUST_LOG: rust_log,
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Generic vitest project factory for cross-backend integration suites.
3
+ *
4
+ * One vitest project per spawned backend; each runs the consumer's shared
5
+ * `*.cross.test.ts` files against its own bootstrapped binary. The paired
6
+ * `create_cross_backend_global_setup` (in `global_setup.ts`) reads the
7
+ * project's `name` to pick which `BackendConfig` to spawn, so the project
8
+ * name is the single source of truth for backend selection.
9
+ *
10
+ * Consumers compose these into their `vite.config.ts`:
11
+ *
12
+ * ```ts
13
+ * const cross_backend_projects = process.env.FUZ_TEST_CROSS_BACKEND
14
+ * ? [
15
+ * make_cross_backend_project({name: 'cross_backend_ts_deno', global_setup: GLOBAL_SETUP}),
16
+ * make_cross_backend_project({name: 'cross_backend_rust', global_setup: GLOBAL_SETUP}),
17
+ * ]
18
+ * : [];
19
+ * ```
20
+ *
21
+ * where `GLOBAL_SETUP = './src/test/cross_backend/global_setup.ts'`.
22
+ *
23
+ * This module is intentionally dependency-free and `assert_dev_env`-free:
24
+ * it runs at vite **config** time (including production builds, where the
25
+ * consumer gates the projects behind an env flag), so it must not pull in
26
+ * the DEV-only test runtime.
27
+ *
28
+ * @module
29
+ */
30
+ export interface CrossBackendProjectOptions {
31
+ /**
32
+ * vitest project name. `create_cross_backend_global_setup` derives the
33
+ * backend name from it (by default stripping a `cross_backend_(ts_)?`
34
+ * prefix), so name projects `cross_backend_<backend>` (e.g.
35
+ * `cross_backend_rust`, `cross_backend_ts_deno`).
36
+ */
37
+ readonly name: string;
38
+ /**
39
+ * Path to the consumer's vitest `globalSetup` module, relative to the
40
+ * consumer repo root (e.g. `'./src/test/cross_backend/global_setup.ts'`).
41
+ * That module is expected to export a `create_cross_backend_global_setup`
42
+ * result as its default.
43
+ */
44
+ readonly global_setup: string;
45
+ /** Test-file globs. Default: `['src/test/cross_backend/*.cross.test.ts']`. */
46
+ readonly include?: ReadonlyArray<string>;
47
+ /** Globs to exclude from `include` (e.g. a backend-specific variant file). Default: `[]`. */
48
+ readonly exclude?: ReadonlyArray<string>;
49
+ /** vitest `sequence.groupOrder`. Default: `3` (runs after unit + db). */
50
+ readonly group_order?: number;
51
+ }
52
+ /**
53
+ * Build a single cross-backend vitest project config. Spread the results
54
+ * into `test.projects` in the consumer's `vite.config.ts`. `isolate: false`
55
+ * + `fileParallelism: false` because a project shares one spawned backend
56
+ * across its files.
57
+ */
58
+ export declare const make_cross_backend_project: ({ name, global_setup, include, exclude, group_order, }: CrossBackendProjectOptions) => {
59
+ extends: true;
60
+ test: {
61
+ name: string;
62
+ include: Array<string>;
63
+ exclude: Array<string>;
64
+ globalSetup: Array<string>;
65
+ isolate: false;
66
+ fileParallelism: false;
67
+ sequence: {
68
+ groupOrder: number;
69
+ };
70
+ };
71
+ };
72
+ //# sourceMappingURL=make_cross_backend_project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make_cross_backend_project.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/make_cross_backend_project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAQH,MAAM,WAAW,0BAA0B;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACzC,6FAA6F;IAC7F,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACzC,yEAAyE;IACzE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,GAAI,wDAMxC,0BAA0B,KAAG;IAC/B,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACvB,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,OAAO,EAAE,KAAK,CAAC;QACf,eAAe,EAAE,KAAK,CAAC;QACvB,QAAQ,EAAE;YAAC,UAAU,EAAE,MAAM,CAAA;SAAC,CAAC;KAC/B,CAAC;CAYD,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Generic vitest project factory for cross-backend integration suites.
3
+ *
4
+ * One vitest project per spawned backend; each runs the consumer's shared
5
+ * `*.cross.test.ts` files against its own bootstrapped binary. The paired
6
+ * `create_cross_backend_global_setup` (in `global_setup.ts`) reads the
7
+ * project's `name` to pick which `BackendConfig` to spawn, so the project
8
+ * name is the single source of truth for backend selection.
9
+ *
10
+ * Consumers compose these into their `vite.config.ts`:
11
+ *
12
+ * ```ts
13
+ * const cross_backend_projects = process.env.FUZ_TEST_CROSS_BACKEND
14
+ * ? [
15
+ * make_cross_backend_project({name: 'cross_backend_ts_deno', global_setup: GLOBAL_SETUP}),
16
+ * make_cross_backend_project({name: 'cross_backend_rust', global_setup: GLOBAL_SETUP}),
17
+ * ]
18
+ * : [];
19
+ * ```
20
+ *
21
+ * where `GLOBAL_SETUP = './src/test/cross_backend/global_setup.ts'`.
22
+ *
23
+ * This module is intentionally dependency-free and `assert_dev_env`-free:
24
+ * it runs at vite **config** time (including production builds, where the
25
+ * consumer gates the projects behind an env flag), so it must not pull in
26
+ * the DEV-only test runtime.
27
+ *
28
+ * @module
29
+ */
30
+ /** Default test-file globs — the convention is `src/test/cross_backend/*.cross.test.ts`. */
31
+ const DEFAULT_INCLUDE = ['src/test/cross_backend/*.cross.test.ts'];
32
+ /** vitest `sequence.groupOrder` for cross-backend projects — after unit (1) + db (2). */
33
+ const DEFAULT_GROUP_ORDER = 3;
34
+ /**
35
+ * Build a single cross-backend vitest project config. Spread the results
36
+ * into `test.projects` in the consumer's `vite.config.ts`. `isolate: false`
37
+ * + `fileParallelism: false` because a project shares one spawned backend
38
+ * across its files.
39
+ */
40
+ export const make_cross_backend_project = ({ name, global_setup, include = DEFAULT_INCLUDE, exclude = [], group_order = DEFAULT_GROUP_ORDER, }) => ({
41
+ extends: true,
42
+ test: {
43
+ name,
44
+ include: [...include],
45
+ exclude: [...exclude],
46
+ globalSetup: [global_setup],
47
+ isolate: false,
48
+ fileParallelism: false,
49
+ sequence: { groupOrder: group_order },
50
+ },
51
+ });
@@ -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;CAC5B;AAED;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,GACjD,SAAS,+BAA+B,KACtC,IAqCF,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,
@@ -62,6 +62,7 @@ import { type RpcAction } from '../../actions/action_rpc.js';
62
62
  import type { AppDeps } from '../../auth/deps.js';
63
63
  import type { SessionOptions } from '../../auth/session_cookie.js';
64
64
  import type { DaemonTokenState } from '../../auth/daemon_token.js';
65
+ import type { Db } from '../../db/db.js';
65
66
  /**
66
67
  * The `_testing_reset` action spec.
67
68
  *
@@ -229,15 +230,18 @@ export interface CreateTestingActionsOptions {
229
230
  */
230
231
  readonly daemon_token_state: DaemonTokenState;
231
232
  /**
232
- * Consumer-supplied callback invoked after the auth-table reset.
233
- * `testing_zzz_server` clears workspace registry + terminals + the
234
- * scoped FS scratch dir here; `testing_spine_stub` has no domain
235
- * layer and passes a no-op (or omits the option). Runs inside the
236
- * same RPC dispatch as the auth-table writes, so a throw surfaces
237
- * to the caller as a JSON-RPC error and the per-test fixture
238
- * short-circuits.
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.
239
243
  */
240
- readonly reset_state?: () => Promise<void> | void;
244
+ readonly reset_state?: (db: Db) => Promise<void> | void;
241
245
  }
242
246
  /**
243
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
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"}
@@ -279,9 +279,12 @@ export const create_testing_actions = (deps, options) => {
279
279
  // of stale-id-then-refresh on the next call.
280
280
  daemon_token_state.keeper_account_id = keeper.account.id;
281
281
  // 7. Fire domain-state reset (zzz workspaces/terminals/scratch,
282
- // 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.
283
286
  if (reset_state)
284
- await reset_state();
287
+ await reset_state(ctx.db);
285
288
  return { ...keeper, extra_accounts: extras };
286
289
  }),
287
290
  rpc_action(testing_mint_session_action_spec, async (input, ctx) => {
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAM9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAiB1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAC,KAAK,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAyzCF,CAAC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAM9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAiB1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAC,KAAK,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IA60CF,CAAC"}
@@ -605,13 +605,30 @@ export const describe_standard_integration_tests = (options) => {
605
605
  test('non-admin cannot access admin routes', async () => {
606
606
  const fixture = await options.setup_test();
607
607
  // admin routes are optional in the base suite — admin-specific coverage
608
- // lives in describe_standard_admin_integration_tests
609
- const admin_route = route_specs.find((s) => s.auth.roles?.includes('admin') ?? false);
608
+ // lives in describe_standard_admin_integration_tests.
609
+ //
610
+ // Pick an admin REST route where a clean role-denial 403 is
611
+ // reachable: account-grain (`actor: 'none'`, so the authorization
612
+ // phase doesn't first demand an acting actor → `actor_required`)
613
+ // and input/param/query-free (so input validation doesn't 400
614
+ // first, per the 401 → 400 → 403 phase order). Admin surfaces are
615
+ // RPC-first now; a consumer may expose no such REST route (fuz_app
616
+ // itself, zzz) — then this REST-era check is a no-op and RPC denial
617
+ // is covered by `describe_rpc_attack_surface_tests`.
618
+ const admin_route = route_specs.find((s) => (s.auth.roles?.includes('admin') ?? false) &&
619
+ s.auth.actor === 'none' &&
620
+ !s.input &&
621
+ !s.params &&
622
+ !s.query);
610
623
  if (!admin_route)
611
624
  return;
625
+ // Probe with a freshly-created account — it holds no admin role
626
+ // (the keeper fixture behind `create_session_headers` does, so it
627
+ // would pass the gate).
628
+ const non_admin = await fixture.create_account({ username: 'non_admin_admin_probe' });
612
629
  const res = await fixture.transport(admin_route.path, {
613
630
  method: admin_route.method,
614
- headers: fixture.create_session_headers(),
631
+ headers: { cookie: `${cookie_name}=${non_admin.session_cookie}` },
615
632
  });
616
633
  assert.strictEqual(res.status, 403);
617
634
  const body = await res.json();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.69.0",
3
+ "version": "0.70.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",