@fuzdev/fuz_app 0.64.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 +510 -946
- 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/actions/broadcast_api.d.ts +1 -1
- package/dist/actions/broadcast_api.js +1 -1
- package/dist/actions/cancel.d.ts +2 -2
- package/dist/actions/cancel.js +3 -3
- package/dist/actions/connection_closer.d.ts +1 -4
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/connection_closer.js +1 -4
- package/dist/actions/register_action_ws.d.ts +2 -2
- package/dist/actions/register_ws_endpoint.d.ts +1 -1
- package/dist/actions/transports_ws_auth_guard.d.ts +1 -2
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +1 -2
- package/dist/auth/CLAUDE.md +570 -1871
- package/dist/auth/account_schema.d.ts +1 -1
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/api_token_queries.js +1 -1
- package/dist/auth/audit_log_ddl.d.ts +1 -1
- package/dist/auth/audit_log_ddl.d.ts.map +1 -1
- package/dist/auth/audit_log_ddl.js +1 -1
- package/dist/auth/audit_log_schema.js +2 -2
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +1 -5
- package/dist/auth/bootstrap_routes.d.ts +7 -1
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +15 -11
- package/dist/auth/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/keyring.d.ts +6 -6
- package/dist/auth/keyring.js +8 -8
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +4 -2
- package/dist/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/db/create_db.d.ts.map +1 -1
- package/dist/db/create_db.js +13 -0
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.js +3 -3
- package/dist/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 +243 -522
- package/dist/http/error_schemas.d.ts +0 -4
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +0 -4
- package/dist/http/ip_canonical.d.ts +5 -4
- package/dist/http/ip_canonical.d.ts.map +1 -1
- package/dist/http/ip_canonical.js +8 -4
- package/dist/http/jsonrpc.d.ts +23 -7
- package/dist/http/jsonrpc.d.ts.map +1 -1
- package/dist/http/jsonrpc.js +19 -3
- package/dist/http/origin.d.ts +1 -1
- package/dist/http/origin.js +1 -1
- 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 +2 -2
- package/dist/server/app_server.d.ts +41 -10
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +10 -4
- package/dist/server/env.d.ts +7 -7
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +14 -14
- package/dist/server/static.d.ts +4 -4
- package/dist/server/static.js +7 -7
- package/dist/testing/CLAUDE.md +740 -418
- package/dist/testing/admin_integration.d.ts +18 -23
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +230 -216
- package/dist/testing/app_server.d.ts +141 -39
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +157 -44
- package/dist/testing/audit_completeness.d.ts +25 -22
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +198 -159
- package/dist/testing/bootstrap_success.d.ts +28 -0
- package/dist/testing/bootstrap_success.d.ts.map +1 -0
- package/dist/testing/bootstrap_success.js +144 -0
- package/dist/testing/cross_backend/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 +65 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
- package/dist/testing/cross_backend/capabilities.js +47 -0
- package/dist/testing/cross_backend/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 +451 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/setup.js +581 -0
- 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 +11 -14
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +123 -146
- package/dist/testing/db_entities.d.ts +22 -1
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +24 -1
- package/dist/testing/integration.d.ts +56 -21
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +294 -319
- package/dist/testing/integration_helpers.d.ts +16 -6
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +7 -7
- package/dist/testing/mock_fs.d.ts.map +1 -1
- package/dist/testing/mock_fs.js +0 -2
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +9 -0
- package/dist/testing/role_grant_helpers.d.ts +31 -0
- package/dist/testing/role_grant_helpers.d.ts.map +1 -0
- package/dist/testing/role_grant_helpers.js +46 -0
- package/dist/testing/round_trip.d.ts +20 -16
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +61 -86
- 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 +24 -21
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +87 -104
- package/dist/testing/schema_introspect.d.ts +106 -0
- package/dist/testing/schema_introspect.d.ts.map +1 -0
- package/dist/testing/schema_introspect.js +123 -0
- package/dist/testing/schema_parity.d.ts +144 -0
- package/dist/testing/schema_parity.d.ts.map +1 -0
- package/dist/testing/schema_parity.js +233 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +1 -68
- package/dist/testing/standard.d.ts +56 -25
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +62 -5
- package/dist/testing/stubs.d.ts +21 -6
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +33 -23
- 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 +10 -4
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
import type { SessionOptions } from '../auth/session_cookie.js';
|
|
3
|
+
import type { AppServerContext, BootstrapLiveOptions } from '../server/app_server.js';
|
|
4
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
5
|
+
import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
|
|
6
|
+
/** Options for `describe_bootstrap_success_tests`. */
|
|
7
|
+
export interface BootstrapSuccessTestOptions {
|
|
8
|
+
session_options: SessionOptions<string>;
|
|
9
|
+
/** Same factory the consumer's production server uses. */
|
|
10
|
+
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
11
|
+
/** RPC endpoints — passed through to `create_app_server` for shape parity. */
|
|
12
|
+
rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
13
|
+
/**
|
|
14
|
+
* Live bootstrap config — the suite drives `POST /bootstrap` against
|
|
15
|
+
* `bootstrap.token_path`. The suite does NOT assert on `on_bootstrap`
|
|
16
|
+
* callback invocation (Hono-coupled signature is in-process only);
|
|
17
|
+
* assertions land on observable DB state.
|
|
18
|
+
*/
|
|
19
|
+
bootstrap: BootstrapLiveOptions;
|
|
20
|
+
/** Override the synthetic token text. Default deterministic. */
|
|
21
|
+
bootstrap_token?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Run the bootstrap success-path test suite against the consumer's
|
|
25
|
+
* production-shaped wiring.
|
|
26
|
+
*/
|
|
27
|
+
export declare const describe_bootstrap_success_tests: (options: BootstrapSuccessTestOptions) => void;
|
|
28
|
+
//# sourceMappingURL=bootstrap_success.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap_success.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/bootstrap_success.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AACpF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,kBAAkB,CAAC;AAM9D,sDAAsD;AACtD,MAAM,WAAW,2BAA2B;IAC3C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,0DAA0D;IAC1D,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,8EAA8E;IAC9E,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;OAKG;IACH,SAAS,EAAE,oBAAoB,CAAC;IAChC,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,2BAA2B,KAAG,IAyIvF,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Bootstrap success-path suite for consumer projects.
|
|
4
|
+
*
|
|
5
|
+
* Exercises `POST /bootstrap` against an empty DB (no pre-keeper, lock
|
|
6
|
+
* unflipped) through the real `bootstrap_account` flow. Asserts on
|
|
7
|
+
* observable state — account exists, `bootstrap_lock.bootstrapped` is
|
|
8
|
+
* true, audit row emitted, response body shape — rather than
|
|
9
|
+
* `on_bootstrap` callback invocation, so the suite stays cross-impl
|
|
10
|
+
* friendly when cross-process testing wires it against a spawned
|
|
11
|
+
* Rust backend.
|
|
12
|
+
*
|
|
13
|
+
* Folded into `describe_standard_tests` with a `bootstrap.mode === 'live'`
|
|
14
|
+
* silent-skip gate; consumers wiring live bootstrap pick up success-path
|
|
15
|
+
* coverage by default.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { describe, test, assert } from 'vitest';
|
|
20
|
+
import { ERROR_ALREADY_BOOTSTRAPPED, ERROR_INVALID_TOKEN } from '../http/error_schemas.js';
|
|
21
|
+
import { create_test_app_for_bootstrap } from './app_server.js';
|
|
22
|
+
const DEFAULT_TEST_TOKEN = 'test-bootstrap-token-value-deterministic';
|
|
23
|
+
const TEST_USERNAME = 'keeper';
|
|
24
|
+
const TEST_PASSWORD = 'test-password-with-min-12-chars';
|
|
25
|
+
/**
|
|
26
|
+
* Run the bootstrap success-path test suite against the consumer's
|
|
27
|
+
* production-shaped wiring.
|
|
28
|
+
*/
|
|
29
|
+
export const describe_bootstrap_success_tests = (options) => {
|
|
30
|
+
const token = options.bootstrap_token ?? DEFAULT_TEST_TOKEN;
|
|
31
|
+
const route_prefix = options.bootstrap.route_prefix ?? '/api/account';
|
|
32
|
+
const bootstrap_path = `${route_prefix}/bootstrap`;
|
|
33
|
+
describe('bootstrap success path', () => {
|
|
34
|
+
test('POST /bootstrap with valid token creates the keeper account and flips the lock', async () => {
|
|
35
|
+
const test_app = await create_test_app_for_bootstrap({
|
|
36
|
+
session_options: options.session_options,
|
|
37
|
+
create_route_specs: options.create_route_specs,
|
|
38
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
39
|
+
bootstrap: options.bootstrap,
|
|
40
|
+
bootstrap_token: token,
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
const response = await test_app.app.request(bootstrap_path, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
|
|
46
|
+
body: JSON.stringify({
|
|
47
|
+
token,
|
|
48
|
+
username: TEST_USERNAME,
|
|
49
|
+
password: TEST_PASSWORD,
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
// Response shape
|
|
53
|
+
assert.strictEqual(response.status, 200);
|
|
54
|
+
const body = (await response.json());
|
|
55
|
+
assert.strictEqual(body.ok, true);
|
|
56
|
+
assert.strictEqual(body.account.username, TEST_USERNAME);
|
|
57
|
+
assert.ok(body.account.id);
|
|
58
|
+
assert.ok(body.actor.id);
|
|
59
|
+
// Observable state: account exists in DB
|
|
60
|
+
const account = await test_app.backend.deps.db.query_one('SELECT username FROM account WHERE username = $1', [TEST_USERNAME]);
|
|
61
|
+
assert.ok(account);
|
|
62
|
+
// Observable state: bootstrap_lock flipped to true
|
|
63
|
+
const lock = await test_app.backend.deps.db.query_one('SELECT bootstrapped FROM bootstrap_lock WHERE id = 1');
|
|
64
|
+
assert.ok(lock);
|
|
65
|
+
assert.strictEqual(lock.bootstrapped, true);
|
|
66
|
+
// Observable state: audit row emitted
|
|
67
|
+
const audit_row = await test_app.backend.deps.db.query_one("SELECT event_type, account_id FROM audit_log WHERE event_type = 'bootstrap' AND outcome = 'success' LIMIT 1");
|
|
68
|
+
assert.ok(audit_row);
|
|
69
|
+
assert.strictEqual(audit_row.account_id, body.account.id);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await test_app.cleanup();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
test('second POST /bootstrap returns 403 ALREADY_BOOTSTRAPPED', async () => {
|
|
76
|
+
const test_app = await create_test_app_for_bootstrap({
|
|
77
|
+
session_options: options.session_options,
|
|
78
|
+
create_route_specs: options.create_route_specs,
|
|
79
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
80
|
+
bootstrap: options.bootstrap,
|
|
81
|
+
bootstrap_token: token,
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
// First bootstrap succeeds
|
|
85
|
+
const first = await test_app.app.request(bootstrap_path, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
token,
|
|
90
|
+
username: TEST_USERNAME,
|
|
91
|
+
password: TEST_PASSWORD,
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
assert.strictEqual(first.status, 200);
|
|
95
|
+
// Second attempt blocked by lock
|
|
96
|
+
const second = await test_app.app.request(bootstrap_path, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
token,
|
|
101
|
+
username: 'another_user',
|
|
102
|
+
password: TEST_PASSWORD,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
assert.strictEqual(second.status, 403);
|
|
106
|
+
const body = (await second.json());
|
|
107
|
+
assert.strictEqual(body.error, ERROR_ALREADY_BOOTSTRAPPED);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
await test_app.cleanup();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
test('POST /bootstrap with wrong token returns 401 INVALID_TOKEN', async () => {
|
|
114
|
+
const test_app = await create_test_app_for_bootstrap({
|
|
115
|
+
session_options: options.session_options,
|
|
116
|
+
create_route_specs: options.create_route_specs,
|
|
117
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
118
|
+
bootstrap: options.bootstrap,
|
|
119
|
+
bootstrap_token: token,
|
|
120
|
+
});
|
|
121
|
+
try {
|
|
122
|
+
const response = await test_app.app.request(bootstrap_path, {
|
|
123
|
+
method: 'POST',
|
|
124
|
+
headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
token: 'wrong-token-value-that-does-not-match',
|
|
127
|
+
username: TEST_USERNAME,
|
|
128
|
+
password: TEST_PASSWORD,
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
assert.strictEqual(response.status, 401);
|
|
132
|
+
const body = (await response.json());
|
|
133
|
+
assert.strictEqual(body.error, ERROR_INVALID_TOKEN);
|
|
134
|
+
// Observable state: lock NOT flipped (transaction rolled back on auth failure)
|
|
135
|
+
const lock = await test_app.backend.deps.db.query_one('SELECT bootstrapped FROM bootstrap_lock WHERE id = 1');
|
|
136
|
+
assert.ok(lock);
|
|
137
|
+
assert.strictEqual(lock.bootstrapped, false);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await test_app.cleanup();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-process backend configuration.
|
|
4
|
+
*
|
|
5
|
+
* `BackendConfig` describes a spawnable test binary — argv, mount paths,
|
|
6
|
+
* env vars, bootstrap credentials, daemon-token discovery path, declared
|
|
7
|
+
* capabilities. Consumer projects ship per-backend factories
|
|
8
|
+
* (`deno_backend_config()`, `rust_backend_config()`,
|
|
9
|
+
* `spine_stub_backend_config()`) that produce this shape; `spawn_backend`
|
|
10
|
+
* consumes it.
|
|
11
|
+
*
|
|
12
|
+
* fuz_app ships `spine_stub_backend_config()` as a convenience preset
|
|
13
|
+
* (operational dep on `testing_spine_stub` — path-based discovery, no
|
|
14
|
+
* `package.json` coupling to the stub's source package). Otherwise backend-specific
|
|
15
|
+
* knowledge (binary paths, port choices, env vars) is a consumer
|
|
16
|
+
* concern; fuz_app's testing library knows nothing about Deno, Cargo, or
|
|
17
|
+
* any specific runtime beyond that preset.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import type { BackendCapabilities } from './capabilities.js';
|
|
22
|
+
/**
|
|
23
|
+
* Auth-bootstrap configuration for a spawnable test binary. The runner
|
|
24
|
+
* writes `token` to `token_path` before launching the child, then POSTs
|
|
25
|
+
* `bootstrap_path` (default `/api/account/bootstrap`) with the token plus
|
|
26
|
+
* the `username` / `password` to mint the keeper account and capture the
|
|
27
|
+
* session cookie. After health-probe, the runner reads
|
|
28
|
+
* `daemon_token_path` to load the binary's deterministic daemon token,
|
|
29
|
+
* which `default_cross_process_setup` threads onto the per-test
|
|
30
|
+
* `TestFixture` for `_testing_reset` calls and other keeper-credential
|
|
31
|
+
* operations.
|
|
32
|
+
*/
|
|
33
|
+
export interface BackendBootstrapConfig {
|
|
34
|
+
/** Path the binary reads for the bootstrap token (env: `*_BOOTSTRAP_TOKEN_PATH`). */
|
|
35
|
+
readonly token_path: string;
|
|
36
|
+
/** Token text written to `token_path` before spawn. */
|
|
37
|
+
readonly token: string;
|
|
38
|
+
/** Username for the bootstrapped keeper. */
|
|
39
|
+
readonly username: string;
|
|
40
|
+
/** Password for the bootstrapped keeper. */
|
|
41
|
+
readonly password: string;
|
|
42
|
+
/**
|
|
43
|
+
* Path the test binary writes its daemon-token JSON to on boot
|
|
44
|
+
* (env: `*_DAEMON_TOKEN_PATH`). `spawn_backend` reads this file once
|
|
45
|
+
* after the health probe succeeds and threads the token onto
|
|
46
|
+
* `BackendHandle.daemon_token` for `_testing_reset` calls plus any
|
|
47
|
+
* other admin/keeper-gated cross-process tests.
|
|
48
|
+
*/
|
|
49
|
+
readonly daemon_token_path: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Configuration for one spawnable test backend. Consumer factories
|
|
53
|
+
* (`deno_backend_config()`, `rust_backend_config()`) produce these and
|
|
54
|
+
* the runner consumes them through `spawn_backend`.
|
|
55
|
+
*
|
|
56
|
+
* Path defaults match the standard fuz_app surface — Deno + Rust spine
|
|
57
|
+
* (`zzz_server`, `fuz_forge_server`, `testing_spine_stub`) all converge on
|
|
58
|
+
* `/api/account/{bootstrap,login,logout,password}`,
|
|
59
|
+
* `/api/rpc`, `/api/ws`, `/health`. Override only when a backend
|
|
60
|
+
* deliberately diverges (which it shouldn't, per the contract).
|
|
61
|
+
*/
|
|
62
|
+
export interface BackendConfig {
|
|
63
|
+
/** Diagnostic label (`"deno"`, `"rust"`, `"spine_stub"`). Surfaces in test output. */
|
|
64
|
+
readonly name: string;
|
|
65
|
+
/** argv passed to the spawn. The first entry is the binary path. */
|
|
66
|
+
readonly start_command: ReadonlyArray<string>;
|
|
67
|
+
/** Base URL for HTTP requests, including port (e.g. `http://localhost:8788`). */
|
|
68
|
+
readonly base_url: string;
|
|
69
|
+
/** JSON-RPC endpoint mount point. Default `/api/rpc`. */
|
|
70
|
+
readonly rpc_path: string;
|
|
71
|
+
/** WebSocket endpoint mount point. Default `/api/ws`. */
|
|
72
|
+
readonly ws_path: string;
|
|
73
|
+
/**
|
|
74
|
+
* SSE stream mount point — drives the cross-process SSE suite's stream
|
|
75
|
+
* path. Optional: only backends advertising `capabilities.sse` serve a
|
|
76
|
+
* stream, and the suite defaults to `/api/admin/audit/stream` (the
|
|
77
|
+
* standard fuz_app audit-log stream) when omitted. Set it only when a
|
|
78
|
+
* backend mounts its stream elsewhere.
|
|
79
|
+
*/
|
|
80
|
+
readonly sse_path?: string;
|
|
81
|
+
/** Readiness probe path. Default `/health`. */
|
|
82
|
+
readonly health_path: string;
|
|
83
|
+
/** Bootstrap POST path. Default `/api/account/bootstrap`. */
|
|
84
|
+
readonly bootstrap_path: string;
|
|
85
|
+
/**
|
|
86
|
+
* Session cookie name the backend issues. Default `fuz_session` per
|
|
87
|
+
* the ecosystem convergence; consumers using a custom session name
|
|
88
|
+
* (legacy `zzz_session`, etc.) override. `default_cross_process_setup`
|
|
89
|
+
* extracts the per-account session value from the transport jar by
|
|
90
|
+
* this name so the cross-process `TestAccount.session_cookie` matches
|
|
91
|
+
* the in-process shape.
|
|
92
|
+
*/
|
|
93
|
+
readonly cookie_name: string;
|
|
94
|
+
/** How long to wait for the health probe (ms) before giving up. */
|
|
95
|
+
readonly startup_timeout_ms: number;
|
|
96
|
+
/**
|
|
97
|
+
* Env vars merged into the child process. Must include the binary's
|
|
98
|
+
* `*_BOOTSTRAP_TOKEN_PATH` + `*_DAEMON_TOKEN_PATH` env var names so
|
|
99
|
+
* the binary reads/writes the right files. Also must include the
|
|
100
|
+
* binary's `*_ALLOWED_ORIGINS` (typically
|
|
101
|
+
* `'http://localhost:*'` for cross-process tests).
|
|
102
|
+
*/
|
|
103
|
+
readonly env: Readonly<Record<string, string>>;
|
|
104
|
+
/** Auth bootstrap details — see `BackendBootstrapConfig`. */
|
|
105
|
+
readonly bootstrap: BackendBootstrapConfig;
|
|
106
|
+
/**
|
|
107
|
+
* Capabilities this backend supports — drives `test_if(capabilities.X, ...)`
|
|
108
|
+
* gating in suite bodies. See `capabilities.ts` for the vocabulary and
|
|
109
|
+
* existing flags. Cross-process backends set `in_process_only: false`.
|
|
110
|
+
*/
|
|
111
|
+
readonly capabilities: BackendCapabilities;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=backend_config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACtC,qFAAqF;IACrF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC3C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import '../../assert_dev_env.js';
|
|
2
|
+
import { type BenchmarkComparison } from '@fuzdev/fuz_util/benchmark_stats.js';
|
|
3
|
+
import type { CrossImplBenchResult } from './run_cross_impl_bench.js';
|
|
4
|
+
/**
|
|
5
|
+
* Reporting adapters over a `CrossImplBenchResult` — all built on fuz_util's
|
|
6
|
+
* formatters + Welch comparison. Markdown for human eyeballs, a per-scenario
|
|
7
|
+
* TS-vs-reference significance verdict, and a self-describing JSON artifact.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
/** Per-scenario markdown table (one row per backend), each under an `###` heading. */
|
|
12
|
+
export declare const format_cross_impl_markdown: (result: CrossImplBenchResult) => string;
|
|
13
|
+
/** One backend's result compared against the reference backend, per scenario. */
|
|
14
|
+
export interface CrossImplComparisonEntry {
|
|
15
|
+
readonly scenario: string;
|
|
16
|
+
/** The reference backend (`a` side of the comparison). */
|
|
17
|
+
readonly reference: string;
|
|
18
|
+
/** The compared backend (`b` side). */
|
|
19
|
+
readonly backend: string;
|
|
20
|
+
readonly comparison: BenchmarkComparison;
|
|
21
|
+
}
|
|
22
|
+
export interface CompareCrossImplOptions {
|
|
23
|
+
/** Backend to compare every other backend against. Defaults to `result.backends[0]`. */
|
|
24
|
+
readonly reference?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Welch-test verdict for every non-reference backend vs the reference, per
|
|
28
|
+
* scenario. With deno + node + rust and `reference: 'deno'` you get
|
|
29
|
+
* `node vs deno` and `rust vs deno` for each scenario.
|
|
30
|
+
*/
|
|
31
|
+
export declare const compare_cross_impl: (result: CrossImplBenchResult, options?: CompareCrossImplOptions) => Array<CrossImplComparisonEntry>;
|
|
32
|
+
/**
|
|
33
|
+
* Render the comparison entries as one line each. The prefix lists the
|
|
34
|
+
* reference first to match `benchmark_stats_compare`'s "First" (= the `a`
|
|
35
|
+
* arg = reference) / "Second" (= the `b` arg = backend) in the
|
|
36
|
+
* recommendation text — `compare_cross_impl` passes `(reference, backend)`.
|
|
37
|
+
*/
|
|
38
|
+
export declare const format_cross_impl_comparison: (entries: ReadonlyArray<CrossImplComparisonEntry>) => string;
|
|
39
|
+
/**
|
|
40
|
+
* Self-describing JSON artifact: one entry per backend × scenario with the
|
|
41
|
+
* percentiles (raw-sample tail), the resolved budget, and iteration count.
|
|
42
|
+
* Diffable and reviewable without a TS runtime; the seed for the deferred
|
|
43
|
+
* static-docs comparison surface.
|
|
44
|
+
*/
|
|
45
|
+
export declare const format_cross_impl_json: (result: CrossImplBenchResult) => string;
|
|
46
|
+
//# sourceMappingURL=bench_report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bench_report.d.ts","sourceRoot":"../src/lib/","sources":["../../../../src/lib/testing/cross_backend/bench/bench_report.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAC;AAGjC,OAAO,EAEN,KAAK,mBAAmB,EACxB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,2BAA2B,CAAC;AAEpE;;;;;;GAMG;AAEH,sFAAsF;AACtF,eAAO,MAAM,0BAA0B,GAAI,QAAQ,oBAAoB,KAAG,MAM3D,CAAC;AAEhB,iFAAiF;AACjF,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;CACzC;AAED,MAAM,WAAW,uBAAuB;IACvC,wFAAwF;IACxF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC9B,QAAQ,oBAAoB,EAC5B,UAAU,uBAAuB,KAC/B,KAAK,CAAC,wBAAwB,CAoBhC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,aAAa,CAAC,wBAAwB,CAAC,KAC9C,MAGU,CAAC;AAEd;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,oBAAoB,KAAG,MA4BpE,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import '../../assert_dev_env.js';
|
|
2
|
+
import { benchmark_format_markdown } from '@fuzdev/fuz_util/benchmark_format.js';
|
|
3
|
+
import { benchmark_stats_compare, } from '@fuzdev/fuz_util/benchmark_stats.js';
|
|
4
|
+
/**
|
|
5
|
+
* Reporting adapters over a `CrossImplBenchResult` — all built on fuz_util's
|
|
6
|
+
* formatters + Welch comparison. Markdown for human eyeballs, a per-scenario
|
|
7
|
+
* TS-vs-reference significance verdict, and a self-describing JSON artifact.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
/** Per-scenario markdown table (one row per backend), each under an `###` heading. */
|
|
12
|
+
export const format_cross_impl_markdown = (result) => result.scenarios
|
|
13
|
+
.map((scenario) => {
|
|
14
|
+
const results = result.entries.filter((e) => e.scenario === scenario).map((e) => e.result);
|
|
15
|
+
return `### ${scenario}\n\n${benchmark_format_markdown(results)}`;
|
|
16
|
+
})
|
|
17
|
+
.join('\n\n');
|
|
18
|
+
/**
|
|
19
|
+
* Welch-test verdict for every non-reference backend vs the reference, per
|
|
20
|
+
* scenario. With deno + node + rust and `reference: 'deno'` you get
|
|
21
|
+
* `node vs deno` and `rust vs deno` for each scenario.
|
|
22
|
+
*/
|
|
23
|
+
export const compare_cross_impl = (result, options) => {
|
|
24
|
+
const reference = options?.reference ?? result.backends[0];
|
|
25
|
+
if (reference === undefined)
|
|
26
|
+
return [];
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const scenario of result.scenarios) {
|
|
29
|
+
const ref_entry = result.entries.find((e) => e.scenario === scenario && e.backend === reference);
|
|
30
|
+
if (!ref_entry)
|
|
31
|
+
continue;
|
|
32
|
+
for (const e of result.entries) {
|
|
33
|
+
if (e.scenario !== scenario || e.backend === reference)
|
|
34
|
+
continue;
|
|
35
|
+
out.push({
|
|
36
|
+
scenario,
|
|
37
|
+
reference,
|
|
38
|
+
backend: e.backend,
|
|
39
|
+
comparison: benchmark_stats_compare(ref_entry.result.stats, e.result.stats),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Render the comparison entries as one line each. The prefix lists the
|
|
47
|
+
* reference first to match `benchmark_stats_compare`'s "First" (= the `a`
|
|
48
|
+
* arg = reference) / "Second" (= the `b` arg = backend) in the
|
|
49
|
+
* recommendation text — `compare_cross_impl` passes `(reference, backend)`.
|
|
50
|
+
*/
|
|
51
|
+
export const format_cross_impl_comparison = (entries) => entries
|
|
52
|
+
.map((e) => `${e.scenario}: ${e.reference} vs ${e.backend} — ${e.comparison.recommendation}`)
|
|
53
|
+
.join('\n');
|
|
54
|
+
/**
|
|
55
|
+
* Self-describing JSON artifact: one entry per backend × scenario with the
|
|
56
|
+
* percentiles (raw-sample tail), the resolved budget, and iteration count.
|
|
57
|
+
* Diffable and reviewable without a TS runtime; the seed for the deferred
|
|
58
|
+
* static-docs comparison surface.
|
|
59
|
+
*/
|
|
60
|
+
export const format_cross_impl_json = (result) => JSON.stringify({
|
|
61
|
+
generated_at: new Date().toISOString(),
|
|
62
|
+
backends: result.backends,
|
|
63
|
+
scenarios: result.scenarios,
|
|
64
|
+
entries: result.entries.map((e) => ({
|
|
65
|
+
backend: e.backend,
|
|
66
|
+
scenario: e.scenario,
|
|
67
|
+
iterations: e.result.iterations,
|
|
68
|
+
budget: e.result.budget,
|
|
69
|
+
stats: {
|
|
70
|
+
mean_ns: e.result.stats.mean_ns,
|
|
71
|
+
p50_ns: e.result.stats.p50_ns,
|
|
72
|
+
p90_ns: e.result.stats.p90_ns,
|
|
73
|
+
p95_ns: e.result.stats.p95_ns,
|
|
74
|
+
p99_ns: e.result.stats.p99_ns,
|
|
75
|
+
min_ns: e.result.stats.min_ns,
|
|
76
|
+
max_ns: e.result.stats.max_ns,
|
|
77
|
+
std_dev_ns: e.result.stats.std_dev_ns,
|
|
78
|
+
ops_per_second: e.result.stats.ops_per_second,
|
|
79
|
+
outlier_ratio: e.result.stats.outlier_ratio,
|
|
80
|
+
sample_size: e.result.stats.sample_size,
|
|
81
|
+
},
|
|
82
|
+
})),
|
|
83
|
+
}, null, 2);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import '../../assert_dev_env.js';
|
|
2
|
+
import type { BenchmarkConfig, BenchmarkResult } from '@fuzdev/fuz_util/benchmark_types.js';
|
|
3
|
+
import type { BootstrappedBackendHandle } from '../setup.js';
|
|
4
|
+
import type { BenchScenario } from './scenario.js';
|
|
5
|
+
/**
|
|
6
|
+
* Drive identical wire scenarios across several spawned backends and time each
|
|
7
|
+
* round trip, so a TS impl and a Rust impl can be compared apples-to-apples
|
|
8
|
+
* (both cross-process over real HTTP). The reusable cross-impl measurement
|
|
9
|
+
* primitive.
|
|
10
|
+
*
|
|
11
|
+
* fuz_util's benchmark library is the engine — `Benchmark` runs each scenario
|
|
12
|
+
* as a task and `BenchmarkResult.stats` carries the percentiles; this module
|
|
13
|
+
* is the thin scenario→task→tagged-result adapter. Reporting (markdown,
|
|
14
|
+
* TS-vs-Rust verdict, JSON artifact) lives in `bench_report.ts`.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
/** One backend × one scenario, with its timing result. */
|
|
19
|
+
export interface CrossImplBenchEntry {
|
|
20
|
+
readonly backend: string;
|
|
21
|
+
readonly scenario: string;
|
|
22
|
+
readonly result: BenchmarkResult;
|
|
23
|
+
}
|
|
24
|
+
/** Full sweep across the supplied backends and scenarios. */
|
|
25
|
+
export interface CrossImplBenchResult {
|
|
26
|
+
/** Backend names, in the order they were run. */
|
|
27
|
+
readonly backends: ReadonlyArray<string>;
|
|
28
|
+
/** Scenario names that ran on at least one backend. */
|
|
29
|
+
readonly scenarios: ReadonlyArray<string>;
|
|
30
|
+
readonly entries: ReadonlyArray<CrossImplBenchEntry>;
|
|
31
|
+
}
|
|
32
|
+
export interface RunCrossImplBenchOptions {
|
|
33
|
+
/**
|
|
34
|
+
* Already-bootstrapped backends to benchmark. Each one's `keeper_transport`
|
|
35
|
+
* is the pre-authed transport scenarios fire against — bootstrap once, then
|
|
36
|
+
* hammer; no per-iteration reset.
|
|
37
|
+
*/
|
|
38
|
+
readonly handles: ReadonlyArray<BootstrappedBackendHandle>;
|
|
39
|
+
readonly scenarios: ReadonlyArray<BenchScenario>;
|
|
40
|
+
/** Overrides merged over the network-tuned defaults below. */
|
|
41
|
+
readonly config?: Partial<BenchmarkConfig>;
|
|
42
|
+
}
|
|
43
|
+
export declare const run_cross_impl_bench: (options: RunCrossImplBenchOptions) => Promise<CrossImplBenchResult>;
|
|
44
|
+
//# sourceMappingURL=run_cross_impl_bench.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run_cross_impl_bench.d.ts","sourceRoot":"../src/lib/","sources":["../../../../src/lib/testing/cross_backend/bench/run_cross_impl_bench.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAC,eAAe,EAAE,eAAe,EAAC,MAAM,qCAAqC,CAAC;AAE1F,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAC,aAAa,EAAuB,MAAM,eAAe,CAAC;AAEvE;;;;;;;;;;;;GAYG;AAEH,0DAA0D;AAC1D,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;CACjC;AAED,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB;IACpC,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACzC,uDAAuD;IACvD,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,wBAAwB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,yBAAyB,CAAC,CAAC;IAC3D,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;IACjD,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;CAC3C;AAeD,eAAO,MAAM,oBAAoB,GAChC,SAAS,wBAAwB,KAC/B,OAAO,CAAC,oBAAoB,CAwB9B,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import '../../assert_dev_env.js';
|
|
2
|
+
import { Benchmark } from '@fuzdev/fuz_util/benchmark.js';
|
|
3
|
+
/**
|
|
4
|
+
* Network-tuned defaults — fuz_util's micro-benchmark defaults (warmup 10,
|
|
5
|
+
* min 30, duration 1000ms) are sized for sub-microsecond functions. RPC round
|
|
6
|
+
* trips are millisecond-scale and IO-bound, so warm the connection more and
|
|
7
|
+
* collect a higher sample floor for stable tail percentiles.
|
|
8
|
+
*/
|
|
9
|
+
const DEFAULT_BENCH_CONFIG = {
|
|
10
|
+
warmup_iterations: 20,
|
|
11
|
+
min_iterations: 100,
|
|
12
|
+
duration_ms: 3000,
|
|
13
|
+
cooldown_ms: 100,
|
|
14
|
+
};
|
|
15
|
+
export const run_cross_impl_bench = async (options) => {
|
|
16
|
+
const config = { ...DEFAULT_BENCH_CONFIG, ...options.config };
|
|
17
|
+
const entries = [];
|
|
18
|
+
for (const handle of options.handles) {
|
|
19
|
+
const ctx = {
|
|
20
|
+
transport: handle.keeper_transport,
|
|
21
|
+
rpc_path: handle.config.rpc_path,
|
|
22
|
+
capabilities: handle.config.capabilities,
|
|
23
|
+
};
|
|
24
|
+
for (const scenario of options.scenarios) {
|
|
25
|
+
if (scenario.requires && !scenario.requires(handle.config.capabilities))
|
|
26
|
+
continue;
|
|
27
|
+
// One task per Benchmark, named by the backend so the report tables
|
|
28
|
+
// read as backend rows under a per-scenario heading.
|
|
29
|
+
const bench = new Benchmark(config);
|
|
30
|
+
bench.add({ name: handle.config.name, fn: () => scenario.run(ctx), async: true });
|
|
31
|
+
const [result] = await bench.run();
|
|
32
|
+
entries.push({ backend: handle.config.name, scenario: scenario.name, result: result });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const backends = options.handles.map((h) => h.config.name);
|
|
36
|
+
const scenarios = [...new Set(entries.map((e) => e.scenario))];
|
|
37
|
+
return { backends, scenarios, entries };
|
|
38
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import '../../assert_dev_env.js';
|
|
2
|
+
import type { FetchTransport } from '../../transports/fetch_transport.js';
|
|
3
|
+
import type { BackendCapabilities } from '../capabilities.js';
|
|
4
|
+
/**
|
|
5
|
+
* Context handed to a `BenchScenario.run`. Carries a ready, pre-authed
|
|
6
|
+
* transport (the bootstrapped keeper's, by default) plus the resolved RPC
|
|
7
|
+
* path and the backend's declared capabilities. A scenario fires one round
|
|
8
|
+
* trip (or a small fixed *idempotent* sequence) against it — no per-call
|
|
9
|
+
* `_testing_reset`, which is the correctness-test model and would dominate
|
|
10
|
+
* the timing.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
export interface BenchScenarioContext {
|
|
15
|
+
/** Pre-authed transport — the bootstrapped keeper's session cookie jar. */
|
|
16
|
+
readonly transport: FetchTransport;
|
|
17
|
+
/** RPC endpoint path, e.g. `'/api/rpc'`. */
|
|
18
|
+
readonly rpc_path: string;
|
|
19
|
+
/** Declared capabilities of the backend this context targets. */
|
|
20
|
+
readonly capabilities: BackendCapabilities;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* One benchmarkable wire scenario. The `run` body is the `Benchmark` task fn:
|
|
24
|
+
* it must `throw` on a non-success response so the benchmark records a failed
|
|
25
|
+
* iteration rather than timing an error path as if it succeeded.
|
|
26
|
+
*
|
|
27
|
+
* Scenarios should be **idempotent** — they run thousands of times against a
|
|
28
|
+
* single bootstrapped backend with no reset between iterations. Prefer reads;
|
|
29
|
+
* a mutating scenario must not accumulate unbounded state.
|
|
30
|
+
*/
|
|
31
|
+
export interface BenchScenario {
|
|
32
|
+
/** Scenario name (groups the per-backend results in the report). */
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/**
|
|
35
|
+
* Optional capability gate — return `false` to skip this scenario on a
|
|
36
|
+
* backend that can't serve it (e.g. a WS scenario needs `capabilities.ws`).
|
|
37
|
+
*/
|
|
38
|
+
readonly requires?: (capabilities: BackendCapabilities) => boolean;
|
|
39
|
+
/** The timed body. Throws on a non-success response. */
|
|
40
|
+
readonly run: (ctx: BenchScenarioContext) => Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Starter cross-impl scenarios — all on the standard spine surface, so they
|
|
44
|
+
* run on every backend (TS Hono, Rust spine), and all reads, so they're safe
|
|
45
|
+
* to repeat against one bootstrapped keeper without state accumulation.
|
|
46
|
+
*
|
|
47
|
+
* - `account_verify` — the dispatch + auth-resolve floor (no real query work).
|
|
48
|
+
* - `account_session_list` — an authed DB read.
|
|
49
|
+
* - `audit_log_list` — an admin paginated read (the keeper holds `ROLE_ADMIN`).
|
|
50
|
+
*
|
|
51
|
+
* `login` is deliberately omitted: the cross-process test binaries wire a
|
|
52
|
+
* fast `TestingArgon2idHasher`, so a login scenario would measure dispatch
|
|
53
|
+
* rather than real Argon2 cost — misleading without its own clearly-labeled
|
|
54
|
+
* tier.
|
|
55
|
+
*/
|
|
56
|
+
export declare const default_bench_scenarios: ReadonlyArray<BenchScenario>;
|
|
57
|
+
//# sourceMappingURL=scenario.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario.d.ts","sourceRoot":"../src/lib/","sources":["../../../../src/lib/testing/cross_backend/bench/scenario.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qCAAqC,CAAC;AACxE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,oBAAoB,CAAC;AAE5D;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC3C;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,mBAAmB,KAAK,OAAO,CAAC;IACnE,wDAAwD;IACxD,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D;AAcD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC,aAAa,CAIhE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import '../../assert_dev_env.js';
|
|
2
|
+
import { rpc_call } from '../../rpc_helpers.js';
|
|
3
|
+
/** Fire one authed JSON-RPC call; throw on a non-success envelope. */
|
|
4
|
+
const rpc_scenario = (method, params) => async (ctx) => {
|
|
5
|
+
const result = await rpc_call({ app: ctx.transport, path: ctx.rpc_path, method, params });
|
|
6
|
+
if (!result.ok) {
|
|
7
|
+
throw new Error(`bench scenario '${method}' failed: ${result.error.code} ${result.error.message}`);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Starter cross-impl scenarios — all on the standard spine surface, so they
|
|
12
|
+
* run on every backend (TS Hono, Rust spine), and all reads, so they're safe
|
|
13
|
+
* to repeat against one bootstrapped keeper without state accumulation.
|
|
14
|
+
*
|
|
15
|
+
* - `account_verify` — the dispatch + auth-resolve floor (no real query work).
|
|
16
|
+
* - `account_session_list` — an authed DB read.
|
|
17
|
+
* - `audit_log_list` — an admin paginated read (the keeper holds `ROLE_ADMIN`).
|
|
18
|
+
*
|
|
19
|
+
* `login` is deliberately omitted: the cross-process test binaries wire a
|
|
20
|
+
* fast `TestingArgon2idHasher`, so a login scenario would measure dispatch
|
|
21
|
+
* rather than real Argon2 cost — misleading without its own clearly-labeled
|
|
22
|
+
* tier.
|
|
23
|
+
*/
|
|
24
|
+
export const default_bench_scenarios = [
|
|
25
|
+
{ name: 'account_verify', run: rpc_scenario('account_verify') },
|
|
26
|
+
{ name: 'account_session_list', run: rpc_scenario('account_session_list') },
|
|
27
|
+
{ name: 'audit_log_list', run: rpc_scenario('audit_log_list', { limit: 20 }) },
|
|
28
|
+
];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* One-call spawn + bootstrap helper.
|
|
4
|
+
*
|
|
5
|
+
* Composes `spawn_backend(config)` and `bootstrap({transport, config})` so a
|
|
6
|
+
* consumer's vitest `globalSetup` reduces to a single await:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import {bootstrap_backend} from '@fuzdev/fuz_app/testing/cross_backend/bootstrap_backend.js';
|
|
10
|
+
*
|
|
11
|
+
* export default async function ({provide}) {
|
|
12
|
+
* const bootstrapped = await bootstrap_backend(deno_backend_config());
|
|
13
|
+
* provide('backend_handle', bootstrapped);
|
|
14
|
+
* return async () => {
|
|
15
|
+
* await bootstrapped.teardown();
|
|
16
|
+
* };
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* If `bootstrap()` throws — typically a bad token, port collision, or
|
|
21
|
+
* keeper-username mismatch — the spawned binary is torn down before the
|
|
22
|
+
* error propagates so vitest doesn't strand the port.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import type { BackendConfig } from './backend_config.js';
|
|
27
|
+
import type { BootstrappedBackendHandle } from './setup.js';
|
|
28
|
+
/**
|
|
29
|
+
* Spawn the test binary described by `config`, bootstrap a keeper, and
|
|
30
|
+
* return the enriched handle.
|
|
31
|
+
*
|
|
32
|
+
* The keeper transport is constructed against `config.base_url` with no
|
|
33
|
+
* initial cookies; `bootstrap()` populates its jar with the session
|
|
34
|
+
* cookie returned by `POST {config.bootstrap_path}`. Subsequent calls
|
|
35
|
+
* against `bootstrapped.keeper_transport` are authenticated as keeper.
|
|
36
|
+
*
|
|
37
|
+
* Mirrors the composition `default_cross_process_setup`'s caller would
|
|
38
|
+
* otherwise hand-roll in every consumer's `globalSetup`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const bootstrap_backend: (config: BackendConfig) => Promise<BootstrappedBackendHandle>;
|
|
41
|
+
//# sourceMappingURL=bootstrap_backend.d.ts.map
|