@fuzdev/fuz_app 0.63.0 → 0.65.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 +525 -827
- 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 +65 -0
- package/dist/actions/connection_closer.d.ts.map +1 -0
- package/dist/actions/connection_closer.js +38 -0
- package/dist/actions/register_action_ws.d.ts +2 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/register_ws_endpoint.d.ts +12 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -5
- package/dist/actions/transports_ws_auth_guard.d.ts +25 -10
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +24 -9
- package/dist/actions/ws_endpoint_spec.d.ts +119 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
- package/dist/actions/ws_endpoint_spec.js +13 -0
- package/dist/auth/CLAUDE.md +592 -1808
- package/dist/auth/account_action_specs.d.ts +1 -1
- package/dist/auth/account_actions.d.ts +13 -0
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +31 -1
- package/dist/auth/account_routes.d.ts +12 -2
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +55 -8
- package/dist/auth/account_schema.d.ts +4 -4
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.d.ts +8 -8
- package/dist/auth/admin_actions.d.ts +11 -0
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +25 -0
- package/dist/auth/api_token_queries.js +1 -1
- package/dist/auth/audit_emitter.d.ts +56 -12
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +38 -12
- 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.d.ts +5 -3
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +5 -3
- 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 +8 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +15 -11
- package/dist/auth/invite_schema.d.ts +2 -2
- 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 +1 -1
- package/dist/auth/standard_rpc_actions.d.ts +1 -0
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -0
- 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/http/CLAUDE.md +225 -483
- 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 +100 -0
- package/dist/http/ip_canonical.d.ts.map +1 -0
- package/dist/http/ip_canonical.js +195 -0
- package/dist/http/origin.d.ts +14 -6
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +14 -32
- package/dist/http/pending_effects.d.ts +1 -1
- package/dist/http/pending_effects.js +1 -1
- package/dist/http/proxy.d.ts +13 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +15 -23
- package/dist/http/surface.d.ts +50 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +27 -1
- package/dist/primitive_schemas.d.ts +20 -4
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +25 -4
- package/dist/realtime/sse_auth_guard.d.ts +16 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +15 -3
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +66 -19
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +57 -34
- package/dist/server/app_server.d.ts +101 -10
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +105 -6
- 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/startup.d.ts.map +1 -1
- package/dist/server/startup.js +12 -0
- package/dist/server/static.d.ts +4 -4
- package/dist/server/static.js +7 -7
- package/dist/testing/CLAUDE.md +269 -59
- 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 +159 -202
- package/dist/testing/adversarial_headers.d.ts +6 -0
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -5
- package/dist/testing/app_server.d.ts +148 -60
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +143 -54
- package/dist/testing/attack_surface.d.ts +8 -7
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +12 -8
- package/dist/testing/audit_completeness.d.ts +23 -22
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +199 -158
- package/dist/testing/audit_drift_guard.d.ts +116 -0
- package/dist/testing/audit_drift_guard.d.ts.map +1 -0
- package/dist/testing/audit_drift_guard.js +134 -0
- 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/connection_closer_helpers.d.ts +44 -0
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
- package/dist/testing/connection_closer_helpers.js +48 -0
- package/dist/testing/cross_backend/capabilities.d.ts +64 -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/setup.d.ts +215 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/setup.js +101 -0
- package/dist/testing/data_exposure.d.ts +14 -15
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +127 -146
- package/dist/testing/db_entities.d.ts +11 -1
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +13 -1
- package/dist/testing/integration.d.ts +35 -21
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +231 -293
- 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 +13 -4
- 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 +21 -16
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +65 -86
- package/dist/testing/rpc_helpers.d.ts +2 -1
- package/dist/testing/rpc_helpers.d.ts.map +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 +91 -106
- 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 +12 -6
- package/dist/testing/standard.d.ts +57 -25
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +62 -5
- package/dist/testing/stubs.d.ts +22 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +28 -21
- package/dist/testing/surface_invariants.d.ts +66 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +103 -1
- package/dist/testing/transports/surface_source.d.ts +51 -0
- package/dist/testing/transports/surface_source.d.ts.map +1 -0
- package/dist/testing/transports/surface_source.js +19 -0
- package/dist/ui/SurfaceExplorer.svelte +161 -2
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -15,6 +15,12 @@ export interface AdversarialHeaderCase {
|
|
|
15
15
|
/**
|
|
16
16
|
* 7 standard adversarial header cases applicable to any middleware stack.
|
|
17
17
|
*
|
|
18
|
+
* Origin verification is Origin-only — fuz_app's `verify_request_source`
|
|
19
|
+
* no longer falls back to `Referer` (matches `zzz_server::auth::is_request_origin_allowed`).
|
|
20
|
+
* Bearer auth still treats a `Referer` header as a browser-context
|
|
21
|
+
* indicator and silently discards the bearer token — so Referer-bearing
|
|
22
|
+
* requests reach the route as unauthenticated rather than 403.
|
|
23
|
+
*
|
|
18
24
|
* @param allowed_origin - an origin that passes the origin check
|
|
19
25
|
*/
|
|
20
26
|
export declare const create_standard_adversarial_cases: (allowed_origin: string) => Array<AdversarialHeaderCase>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAIzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID
|
|
1
|
+
{"version":3,"file":"adversarial_headers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/adversarial_headers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAY7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,iBAAiB,CAAC;AAIzB,+DAA+D;AAC/D,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAClC,qGAAqG;IACrG,oBAAoB,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC9C;AAID;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,GAC7C,gBAAgB,MAAM,KACpB,KAAK,CAAC,qBAAqB,CAiE7B,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qCAAqC,GACjD,YAAY,MAAM,EAClB,SAAS,0BAA0B,EACnC,gBAAgB,MAAM,EACtB,cAAc,KAAK,CAAC,qBAAqB,CAAC,KACxC,IAkCF,CAAC"}
|
|
@@ -8,12 +8,18 @@ import './assert_dev_env.js';
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import { test, assert, describe } from 'vitest';
|
|
11
|
-
import { ApiError, ERROR_FORBIDDEN_ORIGIN
|
|
11
|
+
import { ApiError, ERROR_FORBIDDEN_ORIGIN } from '../http/error_schemas.js';
|
|
12
12
|
import { create_test_middleware_stack_app, TEST_MIDDLEWARE_PATH, } from './middleware.js';
|
|
13
13
|
// --- Standard adversarial header cases ---
|
|
14
14
|
/**
|
|
15
15
|
* 7 standard adversarial header cases applicable to any middleware stack.
|
|
16
16
|
*
|
|
17
|
+
* Origin verification is Origin-only — fuz_app's `verify_request_source`
|
|
18
|
+
* no longer falls back to `Referer` (matches `zzz_server::auth::is_request_origin_allowed`).
|
|
19
|
+
* Bearer auth still treats a `Referer` header as a browser-context
|
|
20
|
+
* indicator and silently discards the bearer token — so Referer-bearing
|
|
21
|
+
* requests reach the route as unauthenticated rather than 403.
|
|
22
|
+
*
|
|
17
23
|
* @param allowed_origin - an origin that passes the origin check
|
|
18
24
|
*/
|
|
19
25
|
export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
@@ -61,17 +67,19 @@ export const create_standard_adversarial_cases = (allowed_origin) => [
|
|
|
61
67
|
validate_expectation: 'called',
|
|
62
68
|
},
|
|
63
69
|
{
|
|
64
|
-
name: 'bearer token with Referer
|
|
70
|
+
name: 'bearer token with rogue Referer (no Origin) passes origin check, bearer silently discarded (browser-context indicator)',
|
|
71
|
+
// Origin-only verification ignores Referer entirely. Bearer auth still
|
|
72
|
+
// treats Referer presence as a browser-context indicator and discards
|
|
73
|
+
// the token, so the request reaches the route as unauthenticated.
|
|
65
74
|
headers: {
|
|
66
75
|
Authorization: 'Bearer secret_fuz_token_test',
|
|
67
76
|
Referer: 'https://attacker.com/page',
|
|
68
77
|
},
|
|
69
|
-
expected_status:
|
|
70
|
-
expected_error: ERROR_FORBIDDEN_REFERER,
|
|
78
|
+
expected_status: 200,
|
|
71
79
|
validate_expectation: 'not_called',
|
|
72
80
|
},
|
|
73
81
|
{
|
|
74
|
-
name: 'bearer token with Referer
|
|
82
|
+
name: 'bearer token with allowed Referer (no Origin) — bearer silently discarded (browser context)',
|
|
75
83
|
headers: {
|
|
76
84
|
Authorization: 'Bearer secret_fuz_token_test',
|
|
77
85
|
Referer: `${allowed_origin}/page`,
|
|
@@ -18,9 +18,8 @@ import { type Keyring } from '../auth/keyring.js';
|
|
|
18
18
|
import type { Db, DbType } from '../db/db.js';
|
|
19
19
|
import type { PasswordHashDeps } from '../auth/password.js';
|
|
20
20
|
import { type SessionOptions } from '../auth/session_cookie.js';
|
|
21
|
-
import type
|
|
22
|
-
import type
|
|
23
|
-
import { type AppServerOptions, type AppServerContext } from '../server/app_server.js';
|
|
21
|
+
import { type AppBackend, type AuditFactory } from '../server/app_backend.js';
|
|
22
|
+
import { type AppServerOptions, type AppServerContext, type BootstrapServerOptions, type BootstrapLiveOptions } from '../server/app_server.js';
|
|
24
23
|
import type { AppSurface, AppSurfaceSpec } from '../http/surface.js';
|
|
25
24
|
import type { RouteSpec } from '../http/route_spec.js';
|
|
26
25
|
import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
|
|
@@ -34,9 +33,12 @@ export declare const stub_password_deps: PasswordHashDeps;
|
|
|
34
33
|
/** 64-hex-char test cookie secret — deterministic, never used in production. */
|
|
35
34
|
export declare const TEST_COOKIE_SECRET: string;
|
|
36
35
|
/**
|
|
37
|
-
* Options for `
|
|
36
|
+
* Options for `bootstrap_test_keeper` and `create_test_account_with_credentials`.
|
|
37
|
+
*
|
|
38
|
+
* Same shape for both — the data inserted is identical; the only behavioral
|
|
39
|
+
* difference is the lock flip on the keeper path.
|
|
38
40
|
*/
|
|
39
|
-
export interface
|
|
41
|
+
export interface CreateTestAccountWithCredentialsOptions {
|
|
40
42
|
db: Db;
|
|
41
43
|
keyring: Keyring;
|
|
42
44
|
session_options: SessionOptions<string>;
|
|
@@ -45,17 +47,48 @@ export interface BootstrapTestAccountOptions {
|
|
|
45
47
|
password_value?: string;
|
|
46
48
|
roles?: Array<string>;
|
|
47
49
|
}
|
|
50
|
+
/** Alias for the keeper-flavored call site. Same shape. */
|
|
51
|
+
export type BootstrapTestKeeperOptions = CreateTestAccountWithCredentialsOptions;
|
|
48
52
|
/**
|
|
49
|
-
*
|
|
53
|
+
* Create a test account with credentials. Use for additional accounts
|
|
54
|
+
* minted alongside the keeper (e.g. `TestApp.create_account` for
|
|
55
|
+
* cross-account / multi-user tests). Does NOT flip `bootstrap_lock` —
|
|
56
|
+
* non-keeper accounts should not appear to the system as bootstrap
|
|
57
|
+
* having happened.
|
|
50
58
|
*
|
|
51
59
|
* Creates an account with actor, grants roles, creates an API token,
|
|
52
|
-
* creates a session, and signs a session cookie.
|
|
53
|
-
* `create_test_app_server` and `TestApp.create_account`.
|
|
60
|
+
* creates a session, and signs a session cookie.
|
|
54
61
|
*
|
|
55
62
|
* @mutates the underlying `options.db` — inserts rows into `account`, `actor`,
|
|
56
63
|
* `role_grant` (one per role), `api_token`, and `auth_session`.
|
|
57
64
|
*/
|
|
58
|
-
export declare const
|
|
65
|
+
export declare const create_test_account_with_credentials: (options: CreateTestAccountWithCredentialsOptions) => Promise<{
|
|
66
|
+
account: {
|
|
67
|
+
id: Uuid;
|
|
68
|
+
username: string;
|
|
69
|
+
};
|
|
70
|
+
actor: {
|
|
71
|
+
id: Uuid;
|
|
72
|
+
};
|
|
73
|
+
api_token: string;
|
|
74
|
+
session_cookie: string;
|
|
75
|
+
}>;
|
|
76
|
+
/**
|
|
77
|
+
* Bootstrap the test-DB keeper. Direct-query shortcut for the default
|
|
78
|
+
* `create_test_app` path — bootstrap is not what most tests exercise, so
|
|
79
|
+
* we skip the real `bootstrap_account` flow (no audit row, no
|
|
80
|
+
* `on_bootstrap` callback). Tests that need the full success-path flow
|
|
81
|
+
* use `create_test_app_for_bootstrap` instead.
|
|
82
|
+
*
|
|
83
|
+
* Flips `bootstrap_lock.bootstrapped = true` so the post-insert DB state
|
|
84
|
+
* matches a real bootstrap completion — production code can trust the
|
|
85
|
+
* lock as the single signal without a belt-and-suspenders
|
|
86
|
+
* `query_account_has_any` defense.
|
|
87
|
+
*
|
|
88
|
+
* @mutates the underlying `options.db` — inserts the account/actor/roles/
|
|
89
|
+
* API token/session_cookie rows AND flips `bootstrap_lock.bootstrapped`.
|
|
90
|
+
*/
|
|
91
|
+
export declare const bootstrap_test_keeper: (options: BootstrapTestKeeperOptions) => Promise<{
|
|
59
92
|
account: {
|
|
60
93
|
id: Uuid;
|
|
61
94
|
username: string;
|
|
@@ -107,44 +140,22 @@ export interface TestAppServerOptions {
|
|
|
107
140
|
/** Roles to grant. Default: `[ROLE_KEEPER]`. */
|
|
108
141
|
roles?: Array<string>;
|
|
109
142
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
* Optional audit log config — threaded into `create_audit_emitter` and
|
|
119
|
-
* captured inside `backend.deps.audit`'s closure.
|
|
143
|
+
* Build the bound `AuditEmitter` used by the test backend. Defaults to
|
|
144
|
+
* `default_audit_factory` (a no-listener `create_audit_emitter` over
|
|
145
|
+
* the test backend's `{db, log}`). Pass a custom factory when a test
|
|
146
|
+
* needs:
|
|
147
|
+
* - to capture audit events (compose `on_audit_event` inside the body)
|
|
148
|
+
* - to register consumer event-type schemas (pass `audit_log_config`)
|
|
149
|
+
* - to instrument `emit` ordering (`create_emit_ordering_audit_factory`)
|
|
150
|
+
* - to wrap or replace the emitter for some other reason
|
|
120
151
|
*
|
|
121
|
-
*
|
|
122
|
-
* `
|
|
123
|
-
*
|
|
124
|
-
*
|
|
152
|
+
* Matches the production shape — `create_app_backend` requires an
|
|
153
|
+
* `audit_factory` and `create_test_app_server` mirrors that contract
|
|
154
|
+
* end-to-end. The earlier `on_audit_event` / `audit_log_config` sugar
|
|
155
|
+
* fields were removed alongside the `CreateAppBackendOptions` rename.
|
|
125
156
|
*/
|
|
126
|
-
|
|
157
|
+
audit_factory?: AuditFactory;
|
|
127
158
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Create an app server with a bootstrapped account for testing.
|
|
130
|
-
*
|
|
131
|
-
* Sets up:
|
|
132
|
-
* - Auth tables (via cached PGlite factory, or reuses existing `db`)
|
|
133
|
-
* - A keeper account with hashed password
|
|
134
|
-
* - Role role_grants for each role in `options.roles`
|
|
135
|
-
* - An API token for Bearer auth
|
|
136
|
-
* - A session with a signed cookie value
|
|
137
|
-
*
|
|
138
|
-
* Uses `stub_password_deps` by default — deterministic hashing that works
|
|
139
|
-
* correctly for login/logout tests without Argon2 overhead.
|
|
140
|
-
*
|
|
141
|
-
* @param options - session options and optional overrides
|
|
142
|
-
* @returns a `TestAppServer` ready for HTTP testing
|
|
143
|
-
* @mutates the underlying database — when `db` is supplied, resets singleton
|
|
144
|
-
* state (`bootstrap_lock.bootstrapped`, `app_settings.open_signup`) before
|
|
145
|
-
* bootstrapping; in either branch inserts an account, actor, role role_grants,
|
|
146
|
-
* API token, and session row.
|
|
147
|
-
*/
|
|
148
159
|
export declare const create_test_app_server: (options: TestAppServerOptions) => Promise<TestAppServer>;
|
|
149
160
|
/**
|
|
150
161
|
* Configuration for `create_test_app`.
|
|
@@ -154,27 +165,43 @@ export interface CreateTestAppOptions extends TestAppServerOptions {
|
|
|
154
165
|
create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
|
|
155
166
|
/**
|
|
156
167
|
* RPC endpoints mounted by `create_app_server` — eager array or
|
|
157
|
-
* `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory.
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* both are set `app_options` wins and `console.warn` fires.
|
|
168
|
+
* `(ctx: AppServerContext) => Array<RpcEndpointSpec>` factory. Single
|
|
169
|
+
* source of truth; the equivalent slot under `app_options` is `Omit`'d
|
|
170
|
+
* so setup-time path lookup and runtime dispatch read from one place.
|
|
171
|
+
* Symmetric with the suite-level `rpc_endpoints` option on
|
|
172
|
+
* `describe_standard_admin_integration_tests` etc.
|
|
163
173
|
*/
|
|
164
174
|
rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
165
|
-
/**
|
|
166
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Bootstrap config — symmetric with `AppServerOptions.bootstrap`. Same
|
|
177
|
+
* single-source-of-truth precedent as `rpc_endpoints`: setup-time surface
|
|
178
|
+
* generation and runtime dispatch both read this slot, so the equivalent
|
|
179
|
+
* field under `app_options` is `Omit`'d. Discriminated union over
|
|
180
|
+
* `{mode: 'disabled' | 'surface_only' | 'live'}`. Omit (or pass
|
|
181
|
+
* `{mode: 'disabled'}`) for the default — no bootstrap route mounted.
|
|
182
|
+
*
|
|
183
|
+
* For tests that exercise the bootstrap success path against a real
|
|
184
|
+
* token + empty DB, use `create_test_app_for_bootstrap` instead — it
|
|
185
|
+
* skips the keeper pre-creation that blocks the success branch.
|
|
186
|
+
*/
|
|
187
|
+
bootstrap?: BootstrapServerOptions;
|
|
188
|
+
/**
|
|
189
|
+
* Optional overrides for `AppServerOptions`. Excludes fields
|
|
190
|
+
* `create_test_app` manages directly: `backend`, `session_options`,
|
|
191
|
+
* `create_route_specs`, `rpc_endpoints`, `bootstrap` (top-level slots
|
|
192
|
+
* above).
|
|
193
|
+
*/
|
|
194
|
+
app_options?: SuiteAppOptions;
|
|
167
195
|
}
|
|
168
196
|
/**
|
|
169
|
-
* `app_options` shape accepted by DB-backed suite
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* callers still pass `rpc_endpoints` via `app_options`.
|
|
197
|
+
* `app_options` shape accepted by `create_test_app` and the DB-backed suite
|
|
198
|
+
* helpers. Excludes fields the helpers manage directly — `backend` /
|
|
199
|
+
* `session_options` / `create_route_specs` are constructed by the helper
|
|
200
|
+
* itself; `rpc_endpoints` and `bootstrap` live on top-level options so
|
|
201
|
+
* setup-time surface lookup and runtime dispatch read from one source of
|
|
202
|
+
* truth.
|
|
176
203
|
*/
|
|
177
|
-
export type SuiteAppOptions = Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs' | 'rpc_endpoints'>>;
|
|
204
|
+
export type SuiteAppOptions = Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs' | 'rpc_endpoints' | 'bootstrap'>>;
|
|
178
205
|
/**
|
|
179
206
|
* A bootstrapped test account with credentials.
|
|
180
207
|
*/
|
|
@@ -233,4 +260,65 @@ export interface TestApp {
|
|
|
233
260
|
* @returns a `TestApp` ready for HTTP testing
|
|
234
261
|
*/
|
|
235
262
|
export declare const create_test_app: (options: CreateTestAppOptions) => Promise<TestApp>;
|
|
263
|
+
/**
|
|
264
|
+
* Configuration for `create_test_app_for_bootstrap`. Like
|
|
265
|
+
* `CreateTestAppOptions` but the keeper-related fields drop (no
|
|
266
|
+
* pre-bootstrap keeper) and `bootstrap` is required + narrowed to
|
|
267
|
+
* `live` mode (the helper exists specifically to drive the success
|
|
268
|
+
* path).
|
|
269
|
+
*/
|
|
270
|
+
export interface CreateTestAppForBootstrapOptions {
|
|
271
|
+
session_options: SessionOptions<string>;
|
|
272
|
+
create_route_specs: (context: AppServerContext) => Array<RouteSpec>;
|
|
273
|
+
rpc_endpoints?: RpcEndpointsSuiteOption;
|
|
274
|
+
app_options?: SuiteAppOptions;
|
|
275
|
+
/** Live bootstrap config — the test drives `POST /bootstrap` against this. */
|
|
276
|
+
bootstrap: BootstrapLiveOptions;
|
|
277
|
+
/**
|
|
278
|
+
* Token contents the stub fs returns when reading `bootstrap.token_path`.
|
|
279
|
+
* The test posts a body containing this same value as `token` to satisfy
|
|
280
|
+
* the timing-safe equality check inside `bootstrap_account`.
|
|
281
|
+
*/
|
|
282
|
+
bootstrap_token: string;
|
|
283
|
+
db?: Db;
|
|
284
|
+
db_type?: DbType;
|
|
285
|
+
password?: PasswordHashDeps;
|
|
286
|
+
audit_factory?: AuditFactory;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* A fully assembled test app in the pre-bootstrap state — empty DB,
|
|
290
|
+
* `bootstrap_lock.bootstrapped = false`, no keeper account. Test drives
|
|
291
|
+
* `POST /bootstrap` itself.
|
|
292
|
+
*/
|
|
293
|
+
export interface TestAppForBootstrap {
|
|
294
|
+
app: Hono;
|
|
295
|
+
backend: AppBackend;
|
|
296
|
+
surface_spec: AppSurfaceSpec;
|
|
297
|
+
surface: AppSurface;
|
|
298
|
+
route_specs: Array<RouteSpec>;
|
|
299
|
+
/** Build host/origin request headers for the anonymous bootstrap POST. */
|
|
300
|
+
create_request_headers: (extra?: Record<string, string>) => Record<string, string>;
|
|
301
|
+
/** Release test resources (no-op when DB is injected or factory-cached). */
|
|
302
|
+
cleanup: () => Promise<void>;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Create a test app in the pre-bootstrap state for exercising the
|
|
306
|
+
* bootstrap success path end-to-end.
|
|
307
|
+
*
|
|
308
|
+
* Skips the keeper pre-creation `create_test_app` does by default —
|
|
309
|
+
* `bootstrap_lock.bootstrapped` stays at `false` and the DB has no
|
|
310
|
+
* accounts. The fs stubs return `options.bootstrap_token` when the
|
|
311
|
+
* bootstrap handler reads `bootstrap.token_path`, so a `POST /bootstrap`
|
|
312
|
+
* with `{token: bootstrap_token, username, password}` reaches the
|
|
313
|
+
* success branch.
|
|
314
|
+
*
|
|
315
|
+
* Pair with `describe_bootstrap_success_tests` for the consumer-runnable
|
|
316
|
+
* suite that drives the full happy path + adjacent assertions on
|
|
317
|
+
* observable state (account exists, audit row emitted, on_bootstrap
|
|
318
|
+
* callback fired).
|
|
319
|
+
*
|
|
320
|
+
* @param options - bootstrap config + factory inputs
|
|
321
|
+
* @returns a `TestAppForBootstrap` ready for the test to drive bootstrap
|
|
322
|
+
*/
|
|
323
|
+
export declare const create_test_app_for_bootstrap: (options: CreateTestAppForBootstrapOptions) => Promise<TestAppForBootstrap>;
|
|
236
324
|
//# sourceMappingURL=app_server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAG/B,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAGjD,OAAO,EAA2B,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAU1D,OAAO,EAA8B,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG3F,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,MAAM,CAAC;AAG/B,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAGjD,OAAO,EAA2B,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAE1E,OAAO,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,qBAAqB,CAAC;AAU1D,OAAO,EAA8B,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAG3F,OAAO,EAAwB,KAAK,UAAU,EAAE,KAAK,YAAY,EAAC,MAAM,0BAA0B,CAAC;AACnG,OAAO,EAEN,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAC,UAAU,EAAE,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACnE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAOrD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,kBAAkB,CAAC;AAE9D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAIhC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AASjD;;;;;GAKG;AACH,MAAM,WAAW,uCAAuC;IACvD,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtB;AAED,2DAA2D;AAC3D,MAAM,MAAM,0BAA0B,GAAG,uCAAuC,CAAC;AAEjF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,uCAAuC,KAC9C,OAAO,CAAC;IACV,OAAO,EAAE;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACtC,KAAK,EAAE;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB,CAyCA,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,0BAA0B,KACjC,OAAO,CAAC;IACV,OAAO,EAAE;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACtC,KAAK,EAAE;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACvB,CAQA,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,UAAU;IAChD,gCAAgC;IAChC,OAAO,EAAE;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACtC,uCAAuC;IACvC,KAAK,EAAE;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IAClB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,+FAA+F;IAC/F,OAAO,EAAE,OAAO,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,mDAAmD;IACnD,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kGAAkG;IAClG,EAAE,CAAC,EAAE,EAAE,CAAC;IACR,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yHAAyH;IACzH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,YAAY,CAAC;CAC7B;AA4HD,eAAO,MAAM,sBAAsB,GAClC,SAAS,oBAAoB,KAC3B,OAAO,CAAC,aAAa,CA2BvB,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IACjE,yEAAyE;IACzE,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,CACpC,IAAI,CACH,gBAAgB,EAChB,SAAS,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,eAAe,GAAG,WAAW,CACpF,CACD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE;QAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAC;IACtC,KAAK,EAAE;QAAC,EAAE,EAAE,IAAI,CAAA;KAAC,CAAC;IAClB,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,8DAA8D;IAC9D,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClF;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACvB,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,aAAa,CAAC;IACvB,YAAY,EAAE,cAAc,CAAC;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,kEAAkE;IAClE,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,gEAAgE;IAChE,qBAAqB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClF,iEAAiE;IACjE,2BAA2B,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxF,qDAAqD;IACrD,cAAc,EAAE,CAAC,OAAO,CAAC,EAAE;QAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;KACtB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3B,8DAA8D;IAC9D,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAU,SAAS,oBAAoB,KAAG,OAAO,CAAC,OAAO,CAoGpF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,gCAAgC;IAChD,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IACpE,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,8EAA8E;IAC9E,SAAS,EAAE,oBAAoB,CAAC;IAChC;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,EAAE,CAAC,EAAE,EAAE,CAAC;IACR,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,aAAa,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,UAAU,CAAC;IACpB,YAAY,EAAE,cAAc,CAAC;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,WAAW,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9B,0EAA0E;IAC1E,sBAAsB,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnF,4EAA4E;IAC5E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,6BAA6B,GACzC,SAAS,gCAAgC,KACvC,OAAO,CAAC,mBAAmB,CAuE7B,CAAC"}
|
|
@@ -11,11 +11,10 @@ import { query_create_api_token } from '../auth/api_token_queries.js';
|
|
|
11
11
|
import { create_session_cookie_value } from '../auth/session_cookie.js';
|
|
12
12
|
import { run_migrations } from '../db/migrate.js';
|
|
13
13
|
import { auth_migration_ns } from '../auth/migrations.js';
|
|
14
|
-
import {
|
|
14
|
+
import { default_audit_factory } from '../server/app_backend.js';
|
|
15
15
|
import { create_app_server, } from '../server/app_server.js';
|
|
16
16
|
import { generate_daemon_token, DAEMON_TOKEN_HEADER, } from '../auth/daemon_token.js';
|
|
17
17
|
import { create_pglite_factory } from './db.js';
|
|
18
|
-
/* eslint-disable @typescript-eslint/require-await */
|
|
19
18
|
/**
|
|
20
19
|
* Fast password stub for tests that don't exercise login/password flows.
|
|
21
20
|
*
|
|
@@ -36,16 +35,19 @@ const fallback_pglite_factory = create_pglite_factory(async (db) => {
|
|
|
36
35
|
await run_migrations(db, [auth_migration_ns]);
|
|
37
36
|
});
|
|
38
37
|
/**
|
|
39
|
-
*
|
|
38
|
+
* Create a test account with credentials. Use for additional accounts
|
|
39
|
+
* minted alongside the keeper (e.g. `TestApp.create_account` for
|
|
40
|
+
* cross-account / multi-user tests). Does NOT flip `bootstrap_lock` —
|
|
41
|
+
* non-keeper accounts should not appear to the system as bootstrap
|
|
42
|
+
* having happened.
|
|
40
43
|
*
|
|
41
44
|
* Creates an account with actor, grants roles, creates an API token,
|
|
42
|
-
* creates a session, and signs a session cookie.
|
|
43
|
-
* `create_test_app_server` and `TestApp.create_account`.
|
|
45
|
+
* creates a session, and signs a session cookie.
|
|
44
46
|
*
|
|
45
47
|
* @mutates the underlying `options.db` — inserts rows into `account`, `actor`,
|
|
46
48
|
* `role_grant` (one per role), `api_token`, and `auth_session`.
|
|
47
49
|
*/
|
|
48
|
-
export const
|
|
50
|
+
export const create_test_account_with_credentials = async (options) => {
|
|
49
51
|
const { db, keyring, session_options, password, username = 'keeper', password_value = 'test-password-123', roles = [], } = options;
|
|
50
52
|
const deps = { db };
|
|
51
53
|
const password_hash = await password.hash_password(password_value);
|
|
@@ -73,60 +75,63 @@ export const bootstrap_test_account = async (options) => {
|
|
|
73
75
|
session_cookie,
|
|
74
76
|
};
|
|
75
77
|
};
|
|
76
|
-
/** Silent logger for tests — suppresses all output. */
|
|
77
|
-
const test_log = new Logger('test', { level: 'off' });
|
|
78
78
|
/**
|
|
79
|
-
*
|
|
79
|
+
* Bootstrap the test-DB keeper. Direct-query shortcut for the default
|
|
80
|
+
* `create_test_app` path — bootstrap is not what most tests exercise, so
|
|
81
|
+
* we skip the real `bootstrap_account` flow (no audit row, no
|
|
82
|
+
* `on_bootstrap` callback). Tests that need the full success-path flow
|
|
83
|
+
* use `create_test_app_for_bootstrap` instead.
|
|
80
84
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* - An API token for Bearer auth
|
|
86
|
-
* - A session with a signed cookie value
|
|
85
|
+
* Flips `bootstrap_lock.bootstrapped = true` so the post-insert DB state
|
|
86
|
+
* matches a real bootstrap completion — production code can trust the
|
|
87
|
+
* lock as the single signal without a belt-and-suspenders
|
|
88
|
+
* `query_account_has_any` defense.
|
|
87
89
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
+
* @mutates the underlying `options.db` — inserts the account/actor/roles/
|
|
91
|
+
* API token/session_cookie rows AND flips `bootstrap_lock.bootstrapped`.
|
|
92
|
+
*/
|
|
93
|
+
export const bootstrap_test_keeper = async (options) => {
|
|
94
|
+
const result = await create_test_account_with_credentials(options);
|
|
95
|
+
// Lock flip — mirrors production `bootstrap_account` so test/prod write
|
|
96
|
+
// semantics stay in parity.
|
|
97
|
+
await options.db.query('UPDATE bootstrap_lock SET bootstrapped = true WHERE id = 1 AND bootstrapped = false');
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
/** Silent logger for tests — suppresses all output. */
|
|
101
|
+
const test_log = new Logger('test', { level: 'off' });
|
|
102
|
+
const default_test_fs_stubs = {
|
|
103
|
+
stat: async () => null,
|
|
104
|
+
read_text_file: async () => '',
|
|
105
|
+
delete_file: async () => { },
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Shared backend-assembly path for `create_test_app_server` and
|
|
109
|
+
* `create_test_app_for_bootstrap`. Returns the raw `AppBackend` + the
|
|
110
|
+
* keyring used to sign session cookies; callers wrap with their own
|
|
111
|
+
* concerns (keeper pre-creation vs. pre-bootstrap state).
|
|
90
112
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* API token, and session row.
|
|
113
|
+
* Resets `app_settings` singleton row for caller-supplied DBs so prior
|
|
114
|
+
* tests don't leak `open_signup`. Does NOT reset `bootstrap_lock` —
|
|
115
|
+
* callers own that policy (`create_test_app_server` lets
|
|
116
|
+
* `bootstrap_test_keeper` flip it; `create_test_app_for_bootstrap`
|
|
117
|
+
* resets it to false before this runs).
|
|
97
118
|
*/
|
|
98
|
-
|
|
99
|
-
const {
|
|
100
|
-
audit_log_config, } = options;
|
|
101
|
-
// Keyring from test secret
|
|
119
|
+
const _build_test_backend = async (options) => {
|
|
120
|
+
const { db: existing_db, db_type = 'pglite-memory', password = stub_password_deps, audit_factory = default_audit_factory, fs_stubs = default_test_fs_stubs, } = options;
|
|
102
121
|
const keyring_result = create_validated_keyring(TEST_COOKIE_SECRET);
|
|
103
122
|
if (!keyring_result.ok) {
|
|
104
123
|
throw new Error(`Test keyring failed: ${keyring_result.errors.join(', ')}`);
|
|
105
124
|
}
|
|
106
|
-
const fs_stubs = {
|
|
107
|
-
stat: async () => null,
|
|
108
|
-
read_text_file: async () => '',
|
|
109
|
-
delete_file: async (_path) => { }, // eslint-disable-line @typescript-eslint/no-empty-function
|
|
110
|
-
};
|
|
111
125
|
let backend;
|
|
112
126
|
if (existing_db) {
|
|
113
|
-
// Reset singleton config
|
|
114
|
-
// Harmless for fresh pglite (these are already at defaults).
|
|
115
|
-
await existing_db.query('UPDATE bootstrap_lock SET bootstrapped = false WHERE bootstrapped = true');
|
|
127
|
+
// Reset singleton config row from a previous test (harmless on fresh pglite).
|
|
116
128
|
await existing_db.query('UPDATE app_settings SET open_signup = false, updated_at = NULL, updated_by = NULL WHERE open_signup = true OR updated_at IS NOT NULL');
|
|
117
|
-
|
|
118
|
-
// Caller owns the DB lifecycle — close is a no-op.
|
|
119
|
-
const audit = create_audit_emitter({
|
|
120
|
-
db: existing_db,
|
|
121
|
-
log: test_log,
|
|
122
|
-
on_audit_event,
|
|
123
|
-
audit_log_config,
|
|
124
|
-
});
|
|
129
|
+
const audit = audit_factory({ db: existing_db, log: test_log });
|
|
125
130
|
backend = {
|
|
126
131
|
db_type,
|
|
127
132
|
db_name: 'test',
|
|
128
|
-
migration_results: [], // migrations ran in the factory's init_schema
|
|
129
|
-
close: async () => { },
|
|
133
|
+
migration_results: [], // migrations ran in the factory's init_schema
|
|
134
|
+
close: async () => { },
|
|
130
135
|
deps: {
|
|
131
136
|
keyring: keyring_result.keyring,
|
|
132
137
|
password,
|
|
@@ -142,12 +147,12 @@ export const create_test_app_server = async (options) => {
|
|
|
142
147
|
// instead of creating a new PGlite each time. Schema is reset and migrations re-run
|
|
143
148
|
// on each call, but the expensive WASM cold start only happens once per worker thread.
|
|
144
149
|
const db = await fallback_pglite_factory.create();
|
|
145
|
-
const audit =
|
|
150
|
+
const audit = audit_factory({ db, log: test_log });
|
|
146
151
|
backend = {
|
|
147
152
|
db_type: 'pglite-memory',
|
|
148
153
|
db_name: '(memory)',
|
|
149
154
|
migration_results: [],
|
|
150
|
-
close: async () => { },
|
|
155
|
+
close: async () => { },
|
|
151
156
|
deps: {
|
|
152
157
|
keyring: keyring_result.keyring,
|
|
153
158
|
password,
|
|
@@ -158,9 +163,14 @@ export const create_test_app_server = async (options) => {
|
|
|
158
163
|
},
|
|
159
164
|
};
|
|
160
165
|
}
|
|
161
|
-
|
|
166
|
+
return { backend, keyring: keyring_result.keyring };
|
|
167
|
+
};
|
|
168
|
+
export const create_test_app_server = async (options) => {
|
|
169
|
+
const { session_options, password = stub_password_deps, username = 'keeper', password_value = 'test-password-123', roles = [ROLE_KEEPER], } = options;
|
|
170
|
+
const { backend, keyring } = await _build_test_backend(options);
|
|
171
|
+
const bootstrapped = await bootstrap_test_keeper({
|
|
162
172
|
db: backend.deps.db,
|
|
163
|
-
keyring
|
|
173
|
+
keyring,
|
|
164
174
|
session_options,
|
|
165
175
|
password,
|
|
166
176
|
username,
|
|
@@ -170,7 +180,7 @@ export const create_test_app_server = async (options) => {
|
|
|
170
180
|
return {
|
|
171
181
|
...backend,
|
|
172
182
|
...bootstrapped,
|
|
173
|
-
keyring
|
|
183
|
+
keyring,
|
|
174
184
|
cleanup: () => backend.close(),
|
|
175
185
|
};
|
|
176
186
|
};
|
|
@@ -198,9 +208,6 @@ export const create_test_app = async (options) => {
|
|
|
198
208
|
rotated_at: new Date(),
|
|
199
209
|
keeper_account_id: test_server.account.id,
|
|
200
210
|
};
|
|
201
|
-
if (options.rpc_endpoints !== undefined && options.app_options?.rpc_endpoints !== undefined) {
|
|
202
|
-
console.warn('create_test_app: both top-level `rpc_endpoints` and `app_options.rpc_endpoints` are set; preferring `app_options.rpc_endpoints` (back-compat).');
|
|
203
|
-
}
|
|
204
211
|
const result = await create_app_server({
|
|
205
212
|
backend: test_server,
|
|
206
213
|
session_options: options.session_options,
|
|
@@ -214,6 +221,7 @@ export const create_test_app = async (options) => {
|
|
|
214
221
|
await_pending_effects: true,
|
|
215
222
|
daemon_token_state,
|
|
216
223
|
rpc_endpoints: options.rpc_endpoints,
|
|
224
|
+
bootstrap: options.bootstrap,
|
|
217
225
|
...options.app_options,
|
|
218
226
|
create_route_specs: options.create_route_specs,
|
|
219
227
|
});
|
|
@@ -239,7 +247,7 @@ export const create_test_app = async (options) => {
|
|
|
239
247
|
let account_counter = 0;
|
|
240
248
|
const create_account = async (account_options) => {
|
|
241
249
|
account_counter++;
|
|
242
|
-
const bootstrapped = await
|
|
250
|
+
const bootstrapped = await create_test_account_with_credentials({
|
|
243
251
|
db: test_server.deps.db,
|
|
244
252
|
keyring: test_server.keyring,
|
|
245
253
|
session_options: options.session_options,
|
|
@@ -276,3 +284,84 @@ export const create_test_app = async (options) => {
|
|
|
276
284
|
cleanup: () => test_server.cleanup(),
|
|
277
285
|
};
|
|
278
286
|
};
|
|
287
|
+
/**
|
|
288
|
+
* Create a test app in the pre-bootstrap state for exercising the
|
|
289
|
+
* bootstrap success path end-to-end.
|
|
290
|
+
*
|
|
291
|
+
* Skips the keeper pre-creation `create_test_app` does by default —
|
|
292
|
+
* `bootstrap_lock.bootstrapped` stays at `false` and the DB has no
|
|
293
|
+
* accounts. The fs stubs return `options.bootstrap_token` when the
|
|
294
|
+
* bootstrap handler reads `bootstrap.token_path`, so a `POST /bootstrap`
|
|
295
|
+
* with `{token: bootstrap_token, username, password}` reaches the
|
|
296
|
+
* success branch.
|
|
297
|
+
*
|
|
298
|
+
* Pair with `describe_bootstrap_success_tests` for the consumer-runnable
|
|
299
|
+
* suite that drives the full happy path + adjacent assertions on
|
|
300
|
+
* observable state (account exists, audit row emitted, on_bootstrap
|
|
301
|
+
* callback fired).
|
|
302
|
+
*
|
|
303
|
+
* @param options - bootstrap config + factory inputs
|
|
304
|
+
* @returns a `TestAppForBootstrap` ready for the test to drive bootstrap
|
|
305
|
+
*/
|
|
306
|
+
export const create_test_app_for_bootstrap = async (options) => {
|
|
307
|
+
const { session_options, bootstrap, bootstrap_token } = options;
|
|
308
|
+
// Caller-supplied DB may carry lock state from a prior test — reset to false
|
|
309
|
+
// before `_build_test_backend` runs (which doesn't touch the lock itself).
|
|
310
|
+
// Fresh pglite already starts at false (factory init).
|
|
311
|
+
if (options.db) {
|
|
312
|
+
await options.db.query('UPDATE bootstrap_lock SET bootstrapped = false WHERE bootstrapped = true');
|
|
313
|
+
}
|
|
314
|
+
// Token-aware fs stubs: the bootstrap route's filesystem operations resolve
|
|
315
|
+
// against the configured token_path; everything else returns no-op defaults.
|
|
316
|
+
let token_file_deleted = false;
|
|
317
|
+
const fs_stubs = {
|
|
318
|
+
stat: async (path) => path === bootstrap.token_path && !token_file_deleted
|
|
319
|
+
? { is_file: true, is_directory: false }
|
|
320
|
+
: null,
|
|
321
|
+
read_text_file: async (path) => path === bootstrap.token_path && !token_file_deleted ? bootstrap_token : '',
|
|
322
|
+
delete_file: async (path) => {
|
|
323
|
+
if (path === bootstrap.token_path)
|
|
324
|
+
token_file_deleted = true;
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
const { backend } = await _build_test_backend({ ...options, fs_stubs });
|
|
328
|
+
// Daemon token state isn't reachable pre-bootstrap (no keeper account)
|
|
329
|
+
// but the field is required by AppServerOptions; pass a placeholder.
|
|
330
|
+
const daemon_token_state = {
|
|
331
|
+
current_token: generate_daemon_token(),
|
|
332
|
+
previous_token: null,
|
|
333
|
+
rotated_at: new Date(),
|
|
334
|
+
keeper_account_id: null,
|
|
335
|
+
};
|
|
336
|
+
const result = await create_app_server({
|
|
337
|
+
backend,
|
|
338
|
+
session_options,
|
|
339
|
+
allowed_origins: [/^http:\/\/localhost/],
|
|
340
|
+
proxy: { trusted_proxies: ['127.0.0.1'], get_connection_ip: () => '127.0.0.1' },
|
|
341
|
+
env_schema: z.object({}),
|
|
342
|
+
ip_rate_limiter: null,
|
|
343
|
+
login_account_rate_limiter: null,
|
|
344
|
+
signup_account_rate_limiter: null,
|
|
345
|
+
bearer_ip_rate_limiter: null,
|
|
346
|
+
await_pending_effects: true,
|
|
347
|
+
daemon_token_state,
|
|
348
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
349
|
+
bootstrap,
|
|
350
|
+
...options.app_options,
|
|
351
|
+
create_route_specs: options.create_route_specs,
|
|
352
|
+
});
|
|
353
|
+
const create_request_headers = (extra) => ({
|
|
354
|
+
host: 'localhost',
|
|
355
|
+
origin: 'http://localhost:5173',
|
|
356
|
+
...extra,
|
|
357
|
+
});
|
|
358
|
+
return {
|
|
359
|
+
app: result.app,
|
|
360
|
+
backend,
|
|
361
|
+
surface_spec: result.surface_spec,
|
|
362
|
+
surface: result.surface_spec.surface,
|
|
363
|
+
route_specs: result.surface_spec.route_specs,
|
|
364
|
+
create_request_headers,
|
|
365
|
+
cleanup: () => backend.close(),
|
|
366
|
+
};
|
|
367
|
+
};
|