@fuzdev/fuz_app 0.65.0 → 0.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/CLAUDE.md +65 -86
- package/dist/actions/action_codegen.d.ts +1 -1
- package/dist/actions/action_codegen.js +1 -1
- package/dist/actions/action_event_data.d.ts +1 -1
- package/dist/auth/CLAUDE.md +83 -104
- package/dist/auth/audit_log_schema.js +2 -2
- package/dist/auth/daemon_token_middleware.d.ts +15 -5
- package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
- package/dist/auth/daemon_token_middleware.js +24 -15
- package/dist/auth/invite_queries.d.ts +17 -7
- package/dist/auth/invite_queries.d.ts.map +1 -1
- package/dist/auth/invite_queries.js +19 -8
- package/dist/auth/signup_routes.d.ts +47 -1
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +103 -52
- package/dist/env/resolve.d.ts +44 -7
- package/dist/env/resolve.d.ts.map +1 -1
- package/dist/env/resolve.js +94 -27
- package/dist/http/CLAUDE.md +47 -52
- package/dist/http/jsonrpc.d.ts +23 -7
- package/dist/http/jsonrpc.d.ts.map +1 -1
- package/dist/http/jsonrpc.js +19 -3
- package/dist/http/surface.d.ts +9 -2
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/runtime/mock.d.ts +1 -1
- package/dist/runtime/mock.js +1 -1
- package/dist/testing/CLAUDE.md +659 -511
- package/dist/testing/admin_integration.d.ts +5 -5
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +95 -39
- package/dist/testing/app_server.d.ts +16 -1
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +18 -3
- package/dist/testing/audit_completeness.d.ts +7 -5
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +5 -9
- package/dist/testing/bootstrap_success.js +2 -2
- package/dist/testing/cross_backend/backend_config.d.ts +113 -0
- package/dist/testing/cross_backend/backend_config.d.ts.map +1 -0
- package/dist/testing/cross_backend/backend_config.js +1 -0
- package/dist/testing/cross_backend/bench/bench_report.d.ts +46 -0
- package/dist/testing/cross_backend/bench/bench_report.d.ts.map +1 -0
- package/dist/testing/cross_backend/bench/bench_report.js +83 -0
- package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts +44 -0
- package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts.map +1 -0
- package/dist/testing/cross_backend/bench/run_cross_impl_bench.js +38 -0
- package/dist/testing/cross_backend/bench/scenario.d.ts +57 -0
- package/dist/testing/cross_backend/bench/scenario.d.ts.map +1 -0
- package/dist/testing/cross_backend/bench/scenario.js +28 -0
- package/dist/testing/cross_backend/bootstrap_backend.d.ts +41 -0
- package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -0
- package/dist/testing/cross_backend/bootstrap_backend.js +34 -0
- package/dist/testing/cross_backend/build_test_backend_paths.d.ts +24 -0
- package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -0
- package/dist/testing/cross_backend/build_test_backend_paths.js +33 -0
- package/dist/testing/cross_backend/capabilities.d.ts +3 -2
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.d.ts +122 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -0
- package/dist/testing/cross_backend/default_backend_configs.js +111 -0
- package/dist/testing/cross_backend/default_secrets.d.ts +40 -0
- package/dist/testing/cross_backend/default_secrets.d.ts.map +1 -0
- package/dist/testing/cross_backend/default_secrets.js +39 -0
- package/dist/testing/cross_backend/default_spine_surface.d.ts +64 -0
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -0
- package/dist/testing/cross_backend/default_spine_surface.js +121 -0
- package/dist/testing/cross_backend/setup.d.ts +270 -34
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +495 -15
- package/dist/testing/cross_backend/spawn_backend.d.ts +58 -0
- package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -0
- package/dist/testing/cross_backend/spawn_backend.js +229 -0
- package/dist/testing/cross_backend/spine_stub_backend_config.d.ts +66 -0
- package/dist/testing/cross_backend/spine_stub_backend_config.d.ts.map +1 -0
- package/dist/testing/cross_backend/spine_stub_backend_config.js +49 -0
- package/dist/testing/cross_backend/sse_round_trip.d.ts +37 -0
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -0
- package/dist/testing/cross_backend/sse_round_trip.js +137 -0
- package/dist/testing/cross_backend/standard.d.ts +96 -0
- package/dist/testing/cross_backend/standard.d.ts.map +1 -0
- package/dist/testing/cross_backend/standard.js +49 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +171 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -0
- package/dist/testing/cross_backend/testing_reset_actions.js +213 -0
- package/dist/testing/cross_backend/testing_server_bun.d.ts +5 -0
- package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -0
- package/dist/testing/cross_backend/testing_server_bun.js +59 -0
- package/dist/testing/cross_backend/testing_server_core.d.ts +140 -0
- package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -0
- package/dist/testing/cross_backend/testing_server_core.js +68 -0
- package/dist/testing/cross_backend/testing_server_deno.d.ts +5 -0
- package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -0
- package/dist/testing/cross_backend/testing_server_deno.js +37 -0
- package/dist/testing/cross_backend/testing_server_node.d.ts +5 -0
- package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -0
- package/dist/testing/cross_backend/testing_server_node.js +50 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +72 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -0
- package/dist/testing/cross_backend/ts_spine_backend_config.js +112 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts +35 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_round_trip.js +113 -0
- package/dist/testing/data_exposure.d.ts +4 -6
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +1 -5
- package/dist/testing/db_entities.d.ts +18 -7
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +18 -7
- package/dist/testing/integration.d.ts +27 -6
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +93 -58
- package/dist/testing/round_trip.d.ts +4 -5
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +1 -5
- package/dist/testing/rpc_helpers.d.ts +10 -4
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +1 -1
- package/dist/testing/rpc_round_trip.d.ts +5 -5
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +1 -5
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +1 -68
- package/dist/testing/standard.d.ts +4 -5
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/stubs.d.ts +10 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +9 -2
- package/dist/testing/testing_rate_limiter.d.ts +59 -0
- package/dist/testing/testing_rate_limiter.d.ts.map +1 -0
- package/dist/testing/testing_rate_limiter.js +74 -0
- package/dist/testing/transports/bootstrap.d.ts +52 -0
- package/dist/testing/transports/bootstrap.d.ts.map +1 -0
- package/dist/testing/transports/bootstrap.js +70 -0
- package/dist/testing/transports/fetch_transport.d.ts +81 -0
- package/dist/testing/transports/fetch_transport.d.ts.map +1 -0
- package/dist/testing/transports/fetch_transport.js +74 -0
- package/dist/testing/transports/sse_frame_reader.d.ts +41 -0
- package/dist/testing/transports/sse_frame_reader.d.ts.map +1 -0
- package/dist/testing/transports/sse_frame_reader.js +84 -0
- package/dist/testing/transports/sse_transport.d.ts +54 -0
- package/dist/testing/transports/sse_transport.d.ts.map +1 -0
- package/dist/testing/transports/sse_transport.js +51 -0
- package/dist/testing/transports/ws_client.d.ts +108 -0
- package/dist/testing/transports/ws_client.d.ts.map +1 -0
- package/dist/testing/transports/ws_client.js +56 -0
- package/dist/testing/transports/ws_transport.d.ts +43 -0
- package/dist/testing/transports/ws_transport.d.ts.map +1 -0
- package/dist/testing/transports/ws_transport.js +169 -0
- package/dist/testing/ws_round_trip.d.ts +21 -103
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +42 -40
- package/dist/ui/CLAUDE.md +5 -3
- package/dist/ui/MenuLink.svelte +16 -16
- package/dist/ui/MenuLink.svelte.d.ts +13 -4
- package/dist/ui/MenuLink.svelte.d.ts.map +1 -1
- package/package.json +7 -1
- package/dist/testing/transports/surface_source.d.ts +0 -51
- package/dist/testing/transports/surface_source.d.ts.map +0 -1
- package/dist/testing/transports/surface_source.js +0 -19
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Spawn a test backend binary, wait for it to come up, and return a
|
|
4
|
+
* handle the test harness drives.
|
|
5
|
+
*
|
|
6
|
+
* Lifecycle:
|
|
7
|
+
*
|
|
8
|
+
* 1. Write the bootstrap token (`config.bootstrap.token`) to
|
|
9
|
+
* `config.bootstrap.token_path` so the binary picks it up at startup.
|
|
10
|
+
* 2. `child_process.spawn(...)` the binary with `detached: true` —
|
|
11
|
+
* creates a new process group so a `SIGTERM` to the negative PID
|
|
12
|
+
* tears down any descendants the binary spawned (PTYs, child
|
|
13
|
+
* workers). vitest worker death + Ctrl+C handlers also fire the
|
|
14
|
+
* group teardown so ports never strand.
|
|
15
|
+
* 3. Poll `{base_url}{health_path}` until it returns 2xx or
|
|
16
|
+
* `startup_timeout_ms` elapses.
|
|
17
|
+
* 4. Read `config.bootstrap.daemon_token_path` to load the binary's
|
|
18
|
+
* deterministic daemon token; thread it onto `BackendHandle` so
|
|
19
|
+
* `_testing_reset` and other keeper-credential calls can authenticate.
|
|
20
|
+
*
|
|
21
|
+
* Bootstrapping (`POST /api/account/bootstrap`) is a separate concern —
|
|
22
|
+
* the caller composes `bootstrap()` from `../transports/bootstrap.ts`
|
|
23
|
+
* against a `FetchTransport` built around `handle.config.base_url`.
|
|
24
|
+
* Splitting the two keeps `spawn_backend` consumer-agnostic — fuz_app
|
|
25
|
+
* knows nothing about specific binary contents.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import { spawn } from 'node:child_process';
|
|
30
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
31
|
+
import { dirname } from 'node:path';
|
|
32
|
+
/** Number of ms between health-probe attempts. Tuned to be cheap on busy CI runners. */
|
|
33
|
+
const HEALTH_PROBE_INTERVAL_MS = 100;
|
|
34
|
+
/**
|
|
35
|
+
* Sleep helper for the probe loop. Resolves after `ms`.
|
|
36
|
+
*/
|
|
37
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
38
|
+
/**
|
|
39
|
+
* Poll `url` until it returns a 2xx response or `timeout_ms` elapses.
|
|
40
|
+
* Network errors during the wait window are expected (binary not yet
|
|
41
|
+
* listening) — they reset the loop, not throw.
|
|
42
|
+
*/
|
|
43
|
+
const wait_for_health = async (url, timeout_ms, is_alive) => {
|
|
44
|
+
const deadline = Date.now() + timeout_ms;
|
|
45
|
+
let last_error;
|
|
46
|
+
while (Date.now() < deadline) {
|
|
47
|
+
if (!is_alive()) {
|
|
48
|
+
throw new Error(`backend process exited before becoming healthy${last_error
|
|
49
|
+
? ` (last probe error: ${last_error.message ?? String(last_error)})`
|
|
50
|
+
: ''}`);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(url);
|
|
54
|
+
if (response.ok) {
|
|
55
|
+
// Drain the body so the connection can be released back to
|
|
56
|
+
// the agent pool — unconsumed bodies keep the socket open.
|
|
57
|
+
await response.arrayBuffer().catch(() => undefined);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
last_error = new Error(`status=${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
last_error = err;
|
|
64
|
+
}
|
|
65
|
+
await sleep(HEALTH_PROBE_INTERVAL_MS);
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`health probe to ${url} timed out after ${timeout_ms}ms (last error: ${last_error ? (last_error.message ?? String(last_error)) : 'none'})`);
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Process-level cleanup registry — every `spawn_backend` registers its
|
|
71
|
+
* teardown here so vitest worker death or interactive Ctrl+C kills the
|
|
72
|
+
* binaries before they strand ports.
|
|
73
|
+
*/
|
|
74
|
+
const live_teardowns = new Set();
|
|
75
|
+
let process_handlers_installed = false;
|
|
76
|
+
const install_process_handlers = () => {
|
|
77
|
+
if (process_handlers_installed)
|
|
78
|
+
return;
|
|
79
|
+
process_handlers_installed = true;
|
|
80
|
+
const fire_all = () => {
|
|
81
|
+
for (const t of live_teardowns) {
|
|
82
|
+
try {
|
|
83
|
+
t();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Swallow — exit-time best-effort cleanup, errors here go nowhere.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
live_teardowns.clear();
|
|
90
|
+
};
|
|
91
|
+
process.on('exit', fire_all);
|
|
92
|
+
// Re-emit signals after handling so the default behaviour (process exit)
|
|
93
|
+
// still applies once we've torn down children.
|
|
94
|
+
const passthrough_signal = (signal) => {
|
|
95
|
+
fire_all();
|
|
96
|
+
// Restore default and re-raise so the process exits with the right code.
|
|
97
|
+
process.removeAllListeners(signal);
|
|
98
|
+
process.kill(process.pid, signal);
|
|
99
|
+
};
|
|
100
|
+
process.on('SIGINT', () => passthrough_signal('SIGINT'));
|
|
101
|
+
process.on('SIGTERM', () => passthrough_signal('SIGTERM'));
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Read the daemon token file. The file is `{"token": "<value>"}` JSON —
|
|
105
|
+
* single canonical shape across every consumer's test binary.
|
|
106
|
+
*
|
|
107
|
+
* Retries briefly to cover the race between the binary becoming
|
|
108
|
+
* health-probe-ready and writing the token file (some binaries write
|
|
109
|
+
* the file inside a startup task that fires shortly after the readiness
|
|
110
|
+
* signal). Bounded by `attempt_count` so a binary that never writes the
|
|
111
|
+
* file surfaces as a clean error rather than hanging.
|
|
112
|
+
*
|
|
113
|
+
* @throws Error if the file never becomes readable within the retry
|
|
114
|
+
* window, or if the parsed contents don't match `{token: string}`.
|
|
115
|
+
*/
|
|
116
|
+
const read_daemon_token = async (path) => {
|
|
117
|
+
const attempt_count = 50; // 50 × 20ms = 1s window
|
|
118
|
+
const attempt_interval_ms = 20;
|
|
119
|
+
let last_error;
|
|
120
|
+
for (let i = 0; i < attempt_count; i++) {
|
|
121
|
+
try {
|
|
122
|
+
const raw = (await readFile(path, 'utf-8')).trim();
|
|
123
|
+
if (raw.length > 0) {
|
|
124
|
+
const parsed = JSON.parse(raw);
|
|
125
|
+
if (typeof parsed !== 'object' ||
|
|
126
|
+
parsed === null ||
|
|
127
|
+
!('token' in parsed) ||
|
|
128
|
+
typeof parsed.token !== 'string') {
|
|
129
|
+
throw new Error(`expected {token: string}, got ${raw}`);
|
|
130
|
+
}
|
|
131
|
+
return parsed.token;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
last_error = err;
|
|
136
|
+
}
|
|
137
|
+
await sleep(attempt_interval_ms);
|
|
138
|
+
}
|
|
139
|
+
throw new Error(`daemon token file ${path} never became readable as {token: string} (last error: ${last_error ? (last_error.message ?? String(last_error)) : 'none'})`);
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Spawn `config.start_command` and return a handle once the binary is
|
|
143
|
+
* health-probe-ready and the daemon-token file is readable.
|
|
144
|
+
*
|
|
145
|
+
* Errors at any stage SIGTERM the child group before rethrowing — the
|
|
146
|
+
* caller never sees a half-started backend.
|
|
147
|
+
*/
|
|
148
|
+
export const spawn_backend = async (config) => {
|
|
149
|
+
if (config.start_command.length === 0) {
|
|
150
|
+
throw new Error(`spawn_backend(${config.name}): start_command is empty`);
|
|
151
|
+
}
|
|
152
|
+
// Write the bootstrap token file before spawn so the binary reads it
|
|
153
|
+
// at boot.
|
|
154
|
+
await mkdir(dirname(config.bootstrap.token_path), { recursive: true });
|
|
155
|
+
await writeFile(config.bootstrap.token_path, config.bootstrap.token, { mode: 0o600 });
|
|
156
|
+
install_process_handlers();
|
|
157
|
+
const [command, ...args] = config.start_command;
|
|
158
|
+
const child = spawn(command, args, {
|
|
159
|
+
env: { ...process.env, ...config.env },
|
|
160
|
+
// Own process group so SIGTERM to the negative PID tears down the
|
|
161
|
+
// binary's descendants too — Hono workers, PTY children, etc.
|
|
162
|
+
detached: true,
|
|
163
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
164
|
+
});
|
|
165
|
+
// Buffer stderr so a startup-time crash surfaces with context.
|
|
166
|
+
const stderr_chunks = [];
|
|
167
|
+
child.stderr?.on('data', (chunk) => {
|
|
168
|
+
stderr_chunks.push(chunk);
|
|
169
|
+
});
|
|
170
|
+
// Drain stdout — discard, but the read is mandatory. `stdio: 'pipe'`
|
|
171
|
+
// leaves the stream paused until something consumes it; an unread
|
|
172
|
+
// pipe fills its OS buffer (~64KB pipe / ~208KB AF_UNIX socketpair on
|
|
173
|
+
// Linux) and the child's next blocking write to stdout parks in the
|
|
174
|
+
// kernel. A backend that logs synchronously to stdout (the default
|
|
175
|
+
// `tracing_subscriber::fmt()` writer) then wedges its whole async
|
|
176
|
+
// runtime: the writing worker holds stdout's lock while parked, every
|
|
177
|
+
// other worker that logs blocks behind it, and even lock-free routes
|
|
178
|
+
// like `/health` (which the request-tracing layer logs) hang. The
|
|
179
|
+
// failure is volume- and time-dependent — it only surfaces after a
|
|
180
|
+
// long run pumps more than a buffer's worth of `info` logs through one
|
|
181
|
+
// long-lived binary — so it hides in short/isolated runs. We discard
|
|
182
|
+
// rather than buffer (unlike stderr): stdout carries high-volume
|
|
183
|
+
// operational logging whose unbounded retention would leak across a
|
|
184
|
+
// long suite.
|
|
185
|
+
child.stdout?.on('data', () => { });
|
|
186
|
+
let exit_info = null;
|
|
187
|
+
child.on('exit', (code, signal) => {
|
|
188
|
+
exit_info = { code, signal };
|
|
189
|
+
});
|
|
190
|
+
const is_alive = () => exit_info === null;
|
|
191
|
+
let teardown_invoked = false;
|
|
192
|
+
const teardown_sync = () => {
|
|
193
|
+
if (teardown_invoked)
|
|
194
|
+
return;
|
|
195
|
+
teardown_invoked = true;
|
|
196
|
+
if (child.pid !== undefined && exit_info === null) {
|
|
197
|
+
try {
|
|
198
|
+
// Negative pid → process group.
|
|
199
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Already dead; ignore.
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const teardown = async () => {
|
|
207
|
+
teardown_sync();
|
|
208
|
+
live_teardowns.delete(teardown_sync);
|
|
209
|
+
if (exit_info !== null)
|
|
210
|
+
return;
|
|
211
|
+
// Wait for the child to actually exit so callers can be sure the
|
|
212
|
+
// port is free.
|
|
213
|
+
await new Promise((resolve) => {
|
|
214
|
+
child.once('exit', () => resolve());
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
live_teardowns.add(teardown_sync);
|
|
218
|
+
try {
|
|
219
|
+
await wait_for_health(`${config.base_url}${config.health_path}`, config.startup_timeout_ms, is_alive);
|
|
220
|
+
const daemon_token = await read_daemon_token(config.bootstrap.daemon_token_path);
|
|
221
|
+
return { config, child, daemon_token, teardown };
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
await teardown();
|
|
225
|
+
const stderr_dump = Buffer.concat(stderr_chunks).toString('utf-8');
|
|
226
|
+
const stderr_tail = stderr_dump.length > 0 ? `\nstderr:\n${stderr_dump}` : '';
|
|
227
|
+
throw new Error(`spawn_backend(${config.name}) failed: ${err.message}${stderr_tail}`);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-process `BackendConfig` preset for the non-domain spine consumer,
|
|
4
|
+
* `testing_spine_stub` — a Rust binary that mounts only the spine surface
|
|
5
|
+
* (auth / account / admin / audit / role-grant offers) with no domain
|
|
6
|
+
* layer. fuz_app drives it from `src/test/cross_backend/*.cross.test.ts`
|
|
7
|
+
* to verify its TS spec against the Rust spine end-to-end with no domain
|
|
8
|
+
* implementation in the loop — drift becomes a fuz_app failure rather than
|
|
9
|
+
* a downstream consumer's failure with mixed signals.
|
|
10
|
+
*
|
|
11
|
+
* **Binary discovery — env-supplied, never hardcoded.** The binary lives
|
|
12
|
+
* in a sibling Rust workspace, not in fuz_app, so the preset never bakes a
|
|
13
|
+
* path in. `FUZ_TESTING_SPINE_STUB_BIN` (or the `binary_path` option) must
|
|
14
|
+
* point at a prebuilt binary; the preset throws a clear error when neither
|
|
15
|
+
* is set rather than guessing. Build once with
|
|
16
|
+
* `cargo build -p testing_spine_stub --release` and point the env var at
|
|
17
|
+
* the resulting `target/release/testing_spine_stub`; operators / CI cache
|
|
18
|
+
* the binary across runs for fast spawns.
|
|
19
|
+
*
|
|
20
|
+
* **Operator setup** — the target Postgres database must exist before the
|
|
21
|
+
* harness runs (the harness never issues `CREATE DATABASE`, to avoid
|
|
22
|
+
* forcing a `CREATEDB` grant on the test role):
|
|
23
|
+
*
|
|
24
|
+
* ```bash
|
|
25
|
+
* createdb fuz_app_test_spine_stub 2>/dev/null || true
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The binary self-wipes the auth-namespace schema on every boot
|
|
29
|
+
* (`FUZ_TESTING_RESET_DB_ON_STARTUP=true`, set by the Rust-family builder),
|
|
30
|
+
* so no manual `DROP TABLE` between sessions is needed; per-test reset is
|
|
31
|
+
* the orthogonal `_testing_reset` RPC action `default_cross_process_setup`
|
|
32
|
+
* fires.
|
|
33
|
+
*
|
|
34
|
+
* @module
|
|
35
|
+
*/
|
|
36
|
+
import type { BackendConfig } from './backend_config.js';
|
|
37
|
+
/** Env var naming the prebuilt `testing_spine_stub` binary. Required when `binary_path` is omitted. */
|
|
38
|
+
export declare const SPINE_STUB_BIN_ENV = "FUZ_TESTING_SPINE_STUB_BIN";
|
|
39
|
+
/** Default listening port — slots beside zzz's 1175/1176; matches the binary's `DEFAULT_PORT`. */
|
|
40
|
+
export declare const SPINE_STUB_DEFAULT_PORT = 1177;
|
|
41
|
+
/** Default Postgres database — real PG (PGlite isn't reachable from `tokio-postgres`). */
|
|
42
|
+
export declare const SPINE_STUB_DEFAULT_DATABASE_URL = "postgres://localhost/fuz_app_test_spine_stub";
|
|
43
|
+
export interface SpineStubBackendConfigOptions {
|
|
44
|
+
/** Listening port. Default `SPINE_STUB_DEFAULT_PORT`. */
|
|
45
|
+
readonly port?: number;
|
|
46
|
+
/** Postgres connection URL. Default `SPINE_STUB_DEFAULT_DATABASE_URL`. */
|
|
47
|
+
readonly database_url?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Prebuilt binary path. Overrides the `FUZ_TESTING_SPINE_STUB_BIN` env
|
|
50
|
+
* var. When neither is set the preset throws.
|
|
51
|
+
*/
|
|
52
|
+
readonly binary_path?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build the `BackendConfig` for `testing_spine_stub`. Resolves the binary
|
|
56
|
+
* from `options.binary_path` or `FUZ_TESTING_SPINE_STUB_BIN`; throws when
|
|
57
|
+
* neither is set so a missing build surfaces as a clear error rather than
|
|
58
|
+
* a confusing spawn failure. Reconciles the binary's env contract: port
|
|
59
|
+
* via `--port` (and `FUZ_SPINE_STUB_PORT`), daemon-token dir via
|
|
60
|
+
* `FUZ_SPINE_STUB_DIR` (anchored to `paths.root` so the written
|
|
61
|
+
* `{dir}/run/daemon_token` matches the path `spawn_backend` reads).
|
|
62
|
+
*
|
|
63
|
+
* @throws Error when no binary path is available.
|
|
64
|
+
*/
|
|
65
|
+
export declare const spine_stub_backend_config: (options?: SpineStubBackendConfigOptions) => BackendConfig;
|
|
66
|
+
//# sourceMappingURL=spine_stub_backend_config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIvD,uGAAuG;AACvG,eAAO,MAAM,kBAAkB,+BAA+B,CAAC;AAE/D,kGAAkG;AAClG,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,0FAA0F;AAC1F,eAAO,MAAM,+BAA+B,iDAAiD,CAAC;AAE9F,MAAM,WAAW,6BAA6B;IAC7C,yDAAyD;IACzD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACrC,UAAS,6BAAkC,KACzC,aAkCF,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { build_test_backend_paths } from './build_test_backend_paths.js';
|
|
3
|
+
import { make_default_rust_backend_config } from './default_backend_configs.js';
|
|
4
|
+
/** Env var naming the prebuilt `testing_spine_stub` binary. Required when `binary_path` is omitted. */
|
|
5
|
+
export const SPINE_STUB_BIN_ENV = 'FUZ_TESTING_SPINE_STUB_BIN';
|
|
6
|
+
/** Default listening port — slots beside zzz's 1175/1176; matches the binary's `DEFAULT_PORT`. */
|
|
7
|
+
export const SPINE_STUB_DEFAULT_PORT = 1177;
|
|
8
|
+
/** Default Postgres database — real PG (PGlite isn't reachable from `tokio-postgres`). */
|
|
9
|
+
export const SPINE_STUB_DEFAULT_DATABASE_URL = 'postgres://localhost/fuz_app_test_spine_stub';
|
|
10
|
+
/**
|
|
11
|
+
* Build the `BackendConfig` for `testing_spine_stub`. Resolves the binary
|
|
12
|
+
* from `options.binary_path` or `FUZ_TESTING_SPINE_STUB_BIN`; throws when
|
|
13
|
+
* neither is set so a missing build surfaces as a clear error rather than
|
|
14
|
+
* a confusing spawn failure. Reconciles the binary's env contract: port
|
|
15
|
+
* via `--port` (and `FUZ_SPINE_STUB_PORT`), daemon-token dir via
|
|
16
|
+
* `FUZ_SPINE_STUB_DIR` (anchored to `paths.root` so the written
|
|
17
|
+
* `{dir}/run/daemon_token` matches the path `spawn_backend` reads).
|
|
18
|
+
*
|
|
19
|
+
* @throws Error when no binary path is available.
|
|
20
|
+
*/
|
|
21
|
+
export const spine_stub_backend_config = (options = {}) => {
|
|
22
|
+
const { port = SPINE_STUB_DEFAULT_PORT, database_url = SPINE_STUB_DEFAULT_DATABASE_URL, binary_path = process.env[SPINE_STUB_BIN_ENV], } = options;
|
|
23
|
+
if (!binary_path) {
|
|
24
|
+
throw new Error(`spine_stub_backend_config: no binary path — set ${SPINE_STUB_BIN_ENV} to a prebuilt ` +
|
|
25
|
+
'`testing_spine_stub` binary (build it with `cargo build -p testing_spine_stub --release`) ' +
|
|
26
|
+
'or pass `binary_path`.');
|
|
27
|
+
}
|
|
28
|
+
const name = 'spine_stub';
|
|
29
|
+
const paths = build_test_backend_paths(name);
|
|
30
|
+
return make_default_rust_backend_config({
|
|
31
|
+
name,
|
|
32
|
+
port,
|
|
33
|
+
// `--port` is the binary's authoritative port input; the
|
|
34
|
+
// `FUZ_SPINE_STUB_PORT` env the builder also sets (via `port_env_var`)
|
|
35
|
+
// is the lower-precedence fallback — both carry the same value.
|
|
36
|
+
start_command: [binary_path, '--port', String(port)],
|
|
37
|
+
database_url,
|
|
38
|
+
port_env_var: 'FUZ_SPINE_STUB_PORT',
|
|
39
|
+
rust_log: 'info,testing_spine_stub=info',
|
|
40
|
+
paths,
|
|
41
|
+
extra_env: {
|
|
42
|
+
// The binary writes its daemon-token JSON to
|
|
43
|
+
// `{FUZ_SPINE_STUB_DIR}/run/daemon_token`; anchoring the dir to
|
|
44
|
+
// `paths.root` makes that equal `paths.daemon_token_path`, which
|
|
45
|
+
// `spawn_backend` reads after the health probe.
|
|
46
|
+
FUZ_SPINE_STUB_DIR: paths.root,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { type BackendCapabilities } from './capabilities.js';
|
|
3
|
+
import type { SetupTest } from './setup.js';
|
|
4
|
+
/** Configuration for {@link describe_cross_process_sse_tests}. */
|
|
5
|
+
export interface CrossProcessSseTestOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Per-test fixture producer (`default_cross_process_setup(handle)`). Each
|
|
8
|
+
* case reads the fresh-per-test keeper's session cookies from
|
|
9
|
+
* `fixture.transport.cookies()` to thread onto the stream. The keeper
|
|
10
|
+
* holds `ROLE_ADMIN` by default, so it can subscribe to the admin-gated
|
|
11
|
+
* audit stream and drive `admin_session_revoke_all`.
|
|
12
|
+
*/
|
|
13
|
+
readonly setup_test: SetupTest;
|
|
14
|
+
/** Backend capability flags; every case gates on `capabilities.sse`. */
|
|
15
|
+
readonly capabilities: BackendCapabilities;
|
|
16
|
+
/** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
|
|
17
|
+
readonly base_url: string;
|
|
18
|
+
/** SSE stream path on the backend. Defaults to `/api/admin/audit/stream`. */
|
|
19
|
+
readonly sse_path?: string;
|
|
20
|
+
/**
|
|
21
|
+
* RPC endpoint path (e.g. `/api/rpc`) used by the data-frame and
|
|
22
|
+
* close-on-revoke cases to fire `admin_session_revoke_all` /
|
|
23
|
+
* `account_session_revoke_all` over the keeper's session channel. When
|
|
24
|
+
* omitted, those cases are skipped — they depend on the standard account
|
|
25
|
+
* + admin actions being mounted on the RPC endpoint.
|
|
26
|
+
*/
|
|
27
|
+
readonly rpc_path?: string;
|
|
28
|
+
/** Origin for the stream request. Defaults to `base_url`. */
|
|
29
|
+
readonly origin?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Register the cross-process SSE round-trip suite. Up to three cases over a
|
|
33
|
+
* real streaming `fetch`: connected-comment, audit data frame, and
|
|
34
|
+
* close-on-revoke.
|
|
35
|
+
*/
|
|
36
|
+
export declare const describe_cross_process_sse_tests: (options: CrossProcessSseTestOptions) => void;
|
|
37
|
+
//# sourceMappingURL=sse_round_trip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/sse_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA8C9B,OAAO,EAAC,KAAK,mBAAmB,EAAU,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAK1C,kEAAkE;AAClE,MAAM,WAAW,0BAA0B;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AA0BD;;;;GAIG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,0BAA0B,KAAG,IAwGtF,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-process SSE round-trip suite — the cross-process counterpart to the
|
|
4
|
+
* in-process `testing/sse_round_trip.ts` harness.
|
|
5
|
+
*
|
|
6
|
+
* Where the in-process harness reads a Hono `Response.body` directly, this
|
|
7
|
+
* suite opens a **real** streaming `fetch` against a spawned backend's
|
|
8
|
+
* audit-log SSE endpoint via `create_sse_transport`, threading the
|
|
9
|
+
* fresh-per-test keeper's session cookie. It is the only coverage of the
|
|
10
|
+
* spawned binary's live SSE path — the standard cross-process bundle
|
|
11
|
+
* (`describe_standard_cross_process_tests`) omits SSE by design, so consumers
|
|
12
|
+
* call this alongside it (paralleling `describe_cross_process_ws_tests`).
|
|
13
|
+
*
|
|
14
|
+
* Three cases, mirroring the in-process SSE self-test against fuz_app's
|
|
15
|
+
* standard audit-log stream:
|
|
16
|
+
*
|
|
17
|
+
* 1. **connects** — the stream opens and emits the `: connected` comment.
|
|
18
|
+
* 2. **data frame** (gated on `rpc_path`) — a minted secondary's sessions are
|
|
19
|
+
* revoked over the keeper's admin channel (`admin_session_revoke_all`),
|
|
20
|
+
* broadcasting a `session_revoke_all` audit event as one `data:` frame to
|
|
21
|
+
* the subscribed keeper **without** closing its stream (the event targets
|
|
22
|
+
* the secondary, not the subscriber). The secondary is minted *before* the
|
|
23
|
+
* stream opens so `create_account`'s own audit events (invite / signup /
|
|
24
|
+
* login / token) don't land on it.
|
|
25
|
+
* 3. **close-on-revoke** (gated on `rpc_path`) — the subscriber's *own*
|
|
26
|
+
* sessions are revoked (`account_session_revoke_all`), so the
|
|
27
|
+
* `session_revoke_all` event targets the keeper and the audit guard drops
|
|
28
|
+
* the live stream. Asserted via `SseTransport.wait_for_close`.
|
|
29
|
+
*
|
|
30
|
+
* Gated on `capabilities.sse` — backends without an end-to-end SSE stream
|
|
31
|
+
* skip (the cases still surface as `.skip` in the report). Cross-process
|
|
32
|
+
* only: `create_sse_transport` needs a real bound socket, so wire it from a
|
|
33
|
+
* `*.cross.test.ts` file, never an in-process setup.
|
|
34
|
+
*
|
|
35
|
+
* @module
|
|
36
|
+
*/
|
|
37
|
+
import { assert, describe } from 'vitest';
|
|
38
|
+
import { account_session_revoke_all_action_spec } from '../../auth/account_action_specs.js';
|
|
39
|
+
import { admin_session_revoke_all_action_spec } from '../../auth/admin_action_specs.js';
|
|
40
|
+
import { audit_log_event_specs } from '../../realtime/sse_auth_guard.js';
|
|
41
|
+
import { SSE_CONNECTED_COMMENT } from '../../realtime/sse.js';
|
|
42
|
+
import { create_sse_transport } from '../transports/sse_transport.js';
|
|
43
|
+
import { create_rpc_post_init } from '../rpc_helpers.js';
|
|
44
|
+
import { test_if } from './capabilities.js';
|
|
45
|
+
/** Default audit-log SSE stream path — the standard fuz_app `/api/admin/audit/stream`. */
|
|
46
|
+
const DEFAULT_SSE_PATH = '/api/admin/audit/stream';
|
|
47
|
+
/**
|
|
48
|
+
* Assert a decoded SSE frame is a well-formed audit `{method, params}`
|
|
49
|
+
* payload whose `params` validate against the matching `audit_log_event_specs`
|
|
50
|
+
* entry.
|
|
51
|
+
*/
|
|
52
|
+
const assert_audit_data_frame = (frame) => {
|
|
53
|
+
const data_line = frame.split('\n').find((line) => line.startsWith('data: '));
|
|
54
|
+
assert.ok(data_line, `SSE frame has no 'data:' line: ${JSON.stringify(frame)}`);
|
|
55
|
+
const payload = JSON.parse(data_line.slice('data: '.length));
|
|
56
|
+
assert.strictEqual(typeof payload.method, 'string', 'audit data frame method must be a string');
|
|
57
|
+
const spec = audit_log_event_specs.find((s) => s.method === payload.method);
|
|
58
|
+
assert.ok(spec, `no EventSpec declared for audit method '${String(payload.method)}'`);
|
|
59
|
+
const result = spec.params.safeParse(payload.params);
|
|
60
|
+
assert.ok(result.success, `audit data frame params mismatch for '${String(payload.method)}': ${result.success ? '' : JSON.stringify(result.error.issues)}`);
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Register the cross-process SSE round-trip suite. Up to three cases over a
|
|
64
|
+
* real streaming `fetch`: connected-comment, audit data frame, and
|
|
65
|
+
* close-on-revoke.
|
|
66
|
+
*/
|
|
67
|
+
export const describe_cross_process_sse_tests = (options) => {
|
|
68
|
+
const { setup_test, capabilities, base_url, rpc_path, origin } = options;
|
|
69
|
+
const sse_path = options.sse_path ?? DEFAULT_SSE_PATH;
|
|
70
|
+
describe('cross-process sse', () => {
|
|
71
|
+
test_if(capabilities.sse, 'connects and emits the connected comment', async () => {
|
|
72
|
+
const fixture = await setup_test();
|
|
73
|
+
const sse = await create_sse_transport({
|
|
74
|
+
base_url,
|
|
75
|
+
sse_path,
|
|
76
|
+
cookies: fixture.transport.cookies(),
|
|
77
|
+
origin,
|
|
78
|
+
});
|
|
79
|
+
try {
|
|
80
|
+
const first = await sse.read_frame();
|
|
81
|
+
assert.strictEqual(first + '\n\n', SSE_CONNECTED_COMMENT, 'first frame must be the connected comment');
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await sse.close();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
// Mint the secondary BEFORE opening the stream so `create_account`'s own
|
|
88
|
+
// audit events stay off it; then revoke the secondary's sessions over the
|
|
89
|
+
// keeper's admin channel → one `session_revoke_all` data frame reaches the
|
|
90
|
+
// keeper (target ≠ subscriber, so the stream stays open).
|
|
91
|
+
test_if(capabilities.sse && rpc_path !== undefined, 'broadcasts an audit event as a data frame', async () => {
|
|
92
|
+
const fixture = await setup_test();
|
|
93
|
+
const secondary = await fixture.create_account({ username: 'sse_revoke_target', roles: [] });
|
|
94
|
+
const sse = await create_sse_transport({
|
|
95
|
+
base_url,
|
|
96
|
+
sse_path,
|
|
97
|
+
cookies: fixture.transport.cookies(),
|
|
98
|
+
origin,
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
const first = await sse.read_frame();
|
|
102
|
+
assert.strictEqual(first + '\n\n', SSE_CONNECTED_COMMENT, 'first frame must be the connected comment');
|
|
103
|
+
const res = await fixture.transport(rpc_path, create_rpc_post_init(admin_session_revoke_all_action_spec.method, {
|
|
104
|
+
account_id: secondary.account.id,
|
|
105
|
+
}));
|
|
106
|
+
assert.strictEqual(res.status, 200, `admin_session_revoke_all RPC failed (status=${res.status})`);
|
|
107
|
+
const data_frame = await sse.read_frame();
|
|
108
|
+
assert_audit_data_frame(data_frame);
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
await sse.close();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
// Revoke the subscriber's OWN sessions → `session_revoke_all` targets the
|
|
115
|
+
// keeper, so the audit guard closes the live stream.
|
|
116
|
+
test_if(capabilities.sse && rpc_path !== undefined, 'stream closes when the subscriber sessions are revoked', async () => {
|
|
117
|
+
const fixture = await setup_test();
|
|
118
|
+
const sse = await create_sse_transport({
|
|
119
|
+
base_url,
|
|
120
|
+
sse_path,
|
|
121
|
+
cookies: fixture.transport.cookies(),
|
|
122
|
+
origin,
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
const first = await sse.read_frame();
|
|
126
|
+
assert.strictEqual(first + '\n\n', SSE_CONNECTED_COMMENT, 'first frame must be the connected comment');
|
|
127
|
+
const res = await fixture.transport(rpc_path, create_rpc_post_init(account_session_revoke_all_action_spec.method));
|
|
128
|
+
assert.strictEqual(res.status, 200, `account_session_revoke_all RPC failed (status=${res.status})`);
|
|
129
|
+
const closed = await sse.wait_for_close(2000);
|
|
130
|
+
assert.ok(closed, 'stream did not close within 2s after session_revoke_all');
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
await sse.close();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-process counterpart to `describe_standard_tests`.
|
|
4
|
+
*
|
|
5
|
+
* Wires the cross-process-safe subset of the standard bundle — the five
|
|
6
|
+
* suites whose option shape is `{setup_test, surface_source, capabilities, ...}`
|
|
7
|
+
* and whose bodies fire requests through `fixture.transport` rather than
|
|
8
|
+
* touching the in-process `Backend`. Consumers wire one call against a
|
|
9
|
+
* spawned binary instead of repeating the five sibling calls per file.
|
|
10
|
+
*
|
|
11
|
+
* **Suites included** — always run:
|
|
12
|
+
*
|
|
13
|
+
* - `describe_standard_integration_tests`
|
|
14
|
+
* - `describe_round_trip_validation`
|
|
15
|
+
* - `describe_rpc_round_trip_tests`
|
|
16
|
+
* - `describe_data_exposure_tests`
|
|
17
|
+
*
|
|
18
|
+
* **Gated on `roles`** — included when the consumer supplies a
|
|
19
|
+
* `RoleSchemaResult`:
|
|
20
|
+
*
|
|
21
|
+
* - `describe_standard_admin_integration_tests`
|
|
22
|
+
*
|
|
23
|
+
* **Suites omitted** — the three that don't survive a process boundary,
|
|
24
|
+
* documented here so per-consumer files don't have to repeat the
|
|
25
|
+
* bookkeeping:
|
|
26
|
+
*
|
|
27
|
+
* - `describe_rate_limiting_tests` — builds a fresh `TestApp` per test to
|
|
28
|
+
* inject tight per-test rate-limiter overrides. That path requires
|
|
29
|
+
* in-process construction of `Backend` + rate limiter; the spawned
|
|
30
|
+
* binary has neither knob nor restart-per-test budget.
|
|
31
|
+
* - `describe_audit_completeness_tests` — reaches into FK-structural
|
|
32
|
+
* introspection that only the in-process backend exposes. Wire-level
|
|
33
|
+
* audit observability lives in the consumer's own audit `.cross.test.ts`
|
|
34
|
+
* driving `audit_log_list` / `audit_log_role_grant_history`.
|
|
35
|
+
* - `describe_bootstrap_success_tests` — bootstrap is one-shot per
|
|
36
|
+
* backend lifecycle, and the consumer's `globalSetup` already consumed
|
|
37
|
+
* it before the suite file loads. Re-running would 409.
|
|
38
|
+
*
|
|
39
|
+
* @module
|
|
40
|
+
*/
|
|
41
|
+
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
42
|
+
import type { RoleSchemaResult } from '../../auth/role_schema.js';
|
|
43
|
+
import type { AppSurfaceSpec } from '../../http/surface.js';
|
|
44
|
+
import type { RpcEndpointsSuiteOption } from '../rpc_helpers.js';
|
|
45
|
+
import type { BackendCapabilities } from './capabilities.js';
|
|
46
|
+
import type { SetupTest } from './setup.js';
|
|
47
|
+
/**
|
|
48
|
+
* Configuration for `describe_standard_cross_process_tests`.
|
|
49
|
+
*
|
|
50
|
+
* Mirrors `StandardTestOptions` minus the in-process-only knobs
|
|
51
|
+
* (`create_route_specs`, `bootstrap`, `rate_limiting_app_options`,
|
|
52
|
+
* `bootstrap_token`) — those drive the three omitted suites.
|
|
53
|
+
*/
|
|
54
|
+
export interface StandardCrossProcessTestOptions {
|
|
55
|
+
/** Per-test fixture-producing function. */
|
|
56
|
+
setup_test: SetupTest;
|
|
57
|
+
/**
|
|
58
|
+
* App surface. Constructed in TS by the consumer; same shape for
|
|
59
|
+
* in-process and cross-process tests.
|
|
60
|
+
*/
|
|
61
|
+
surface_source: AppSurfaceSpec;
|
|
62
|
+
/** Backend capability declarations. */
|
|
63
|
+
capabilities: BackendCapabilities;
|
|
64
|
+
/** Session config — needed for cookie_name + factory-form rpc_endpoints resolution. */
|
|
65
|
+
session_options: SessionOptions<string>;
|
|
66
|
+
/**
|
|
67
|
+
* RPC endpoint specs — required. The standard integration tests drive
|
|
68
|
+
* `account_verify`, `account_session_*`, `account_token_*` through the
|
|
69
|
+
* RPC surface (and admin tests, when wired, drive role_grant grant/revoke
|
|
70
|
+
* through it too).
|
|
71
|
+
*/
|
|
72
|
+
rpc_endpoints: RpcEndpointsSuiteOption;
|
|
73
|
+
/**
|
|
74
|
+
* Role schema result from `create_role_schema()`.
|
|
75
|
+
* When provided, the admin integration suite is included.
|
|
76
|
+
*/
|
|
77
|
+
roles?: RoleSchemaResult;
|
|
78
|
+
/**
|
|
79
|
+
* Path prefix where admin routes are mounted.
|
|
80
|
+
* Default `'/api/admin'`.
|
|
81
|
+
*/
|
|
82
|
+
admin_prefix?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Forwarded to `describe_standard_integration_tests` — overrides the
|
|
85
|
+
* default error-coverage threshold on the scoped REST surface. Set to
|
|
86
|
+
* `0` to skip the assertion entirely.
|
|
87
|
+
*/
|
|
88
|
+
error_coverage_min?: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Run the cross-process standard test bundle — integration, admin (when
|
|
92
|
+
* `roles` provided), round trip, RPC round trip, data exposure. See the
|
|
93
|
+
* module doc for the suites omitted from this bundle and why.
|
|
94
|
+
*/
|
|
95
|
+
export declare const describe_standard_cross_process_tests: (options: StandardCrossProcessTestOptions) => void;
|
|
96
|
+
//# sourceMappingURL=standard.d.ts.map
|
|
@@ -0,0 +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"}
|