@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
|
@@ -3,8 +3,18 @@ import './assert_dev_env.js';
|
|
|
3
3
|
* Composable audit log completeness test suite.
|
|
4
4
|
*
|
|
5
5
|
* Verifies that every auth mutation route produces the expected audit log
|
|
6
|
-
* event. Uses the real middleware stack and database
|
|
7
|
-
*
|
|
6
|
+
* event. Uses the real middleware stack and database, then **reads back
|
|
7
|
+
* through the `audit_log_list` RPC** — the production observation path the
|
|
8
|
+
* admin UI consumes. This is intentional end-to-end coverage: emit →
|
|
9
|
+
* persist → query → wire response, all in one round-trip.
|
|
10
|
+
*
|
|
11
|
+
* The trade is a deliberate transport coupling: a regression in
|
|
12
|
+
* `audit_log_list_action_spec`'s auth or response shape can surface here as
|
|
13
|
+
* a secondary failure. `describe_rpc_round_trip_tests` covers that RPC
|
|
14
|
+
* directly, so primary breakages localize there first. For *unit-level*
|
|
15
|
+
* "did the handler emit?" assertions without the persistence path, use
|
|
16
|
+
* `create_recording_audit_emitter` from `audit_drift_guard.ts` — that
|
|
17
|
+
* captures emits before they hit DB or transport.
|
|
8
18
|
*
|
|
9
19
|
* Bootstrap is excluded because it requires filesystem token state that
|
|
10
20
|
* `create_test_app` does not provide. Bootstrap audit logging is tested
|
|
@@ -13,21 +23,48 @@ import './assert_dev_env.js';
|
|
|
13
23
|
* @module
|
|
14
24
|
*/
|
|
15
25
|
import { describe, test, assert } from 'vitest';
|
|
16
|
-
import {
|
|
17
|
-
import { AUDIT_EVENT_TYPES } from '../auth/audit_log_schema.js';
|
|
18
|
-
import {
|
|
19
|
-
import { create_test_app, } from './app_server.js';
|
|
20
|
-
import { create_pglite_factory, create_describe_db, auth_integration_truncate_tables, } from './db.js';
|
|
26
|
+
import { ROLE_ADMIN } from '../auth/role_schema.js';
|
|
27
|
+
import { AUDIT_EVENT_TYPES, } from '../auth/audit_log_schema.js';
|
|
28
|
+
import { DEFAULT_TEST_PASSWORD } from './app_server.js';
|
|
21
29
|
import { find_auth_route } from './integration_helpers.js';
|
|
22
|
-
import { run_migrations } from '../db/migrate.js';
|
|
23
|
-
import { query_accept_offer } from '../auth/role_grant_offer_queries.js';
|
|
24
30
|
import { rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
31
|
+
import { role_grant_offer_and_accept } from './role_grant_helpers.js';
|
|
32
|
+
import { role_grant_offer_accept_action_spec, role_grant_offer_create_action_spec, role_grant_revoke_action_spec, } from '../auth/role_grant_offer_action_specs.js';
|
|
33
|
+
import { admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, app_settings_update_action_spec, audit_log_list_action_spec, AUDIT_LOG_LIST_LIMIT_MAX, invite_create_action_spec, invite_delete_action_spec, } from '../auth/admin_action_specs.js';
|
|
27
34
|
import { account_session_list_action_spec, account_session_revoke_action_spec, account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_list_action_spec, account_token_revoke_action_spec, } from '../auth/account_action_specs.js';
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Mint a dedicated admin account whose sole job is to read the audit log
|
|
37
|
+
* via RPC. Decoupling the *observer* from the *subject* keeps the helper
|
|
38
|
+
* shape uniform across every audit-touching test — even ones whose
|
|
39
|
+
* mutation revokes the bootstrapped admin's credentials (logout,
|
|
40
|
+
* session_revoke, password_change). The observer has no role-grants the
|
|
41
|
+
* test exercises and no credentials the test mutates, so it survives
|
|
42
|
+
* every flow.
|
|
43
|
+
*/
|
|
44
|
+
const create_admin_observer = (fixture) => fixture.create_account({ username: 'audit_observer', roles: [ROLE_ADMIN] });
|
|
45
|
+
/**
|
|
46
|
+
* List audit log events via the `audit_log_list` RPC. Replaces the previous
|
|
47
|
+
* raw `SELECT FROM audit_log` query — the RPC is the documented contract and
|
|
48
|
+
* the same path the admin UI consumes. The RPC orders newest-first
|
|
49
|
+
* (`ORDER BY seq DESC`); assertions use `.some()` / `.find()` so ordering is
|
|
50
|
+
* invisible to test logic. Default `limit: AUDIT_LOG_LIST_LIMIT_MAX` (200)
|
|
51
|
+
* future-proofs against tests with more emissions; per-test
|
|
52
|
+
* `auth_integration_truncate_tables` keeps the table empty between cases.
|
|
53
|
+
*
|
|
54
|
+
* `observer` is a dedicated admin account (see {@link create_admin_observer})
|
|
55
|
+
* — its credentials are never the subject of the mutation under test, so the
|
|
56
|
+
* read works uniformly across every flow including session-revoking ones.
|
|
57
|
+
*/
|
|
58
|
+
const list_audit_events = async (app, rpc_path, observer, params = {}) => {
|
|
59
|
+
const res = await rpc_call_for_spec({
|
|
60
|
+
app,
|
|
61
|
+
path: rpc_path,
|
|
62
|
+
spec: audit_log_list_action_spec,
|
|
63
|
+
params: { limit: AUDIT_LOG_LIST_LIMIT_MAX, ...params },
|
|
64
|
+
headers: observer.create_session_headers(),
|
|
65
|
+
});
|
|
66
|
+
assert.ok(res.ok, `audit_log_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
67
|
+
return res.result.events;
|
|
31
68
|
};
|
|
32
69
|
/** Assert that audit events contain the expected event type. */
|
|
33
70
|
const assert_has_event = (events, expected, context) => {
|
|
@@ -44,15 +81,6 @@ const assert_event_credential_type = (events, expected, credential_type, context
|
|
|
44
81
|
const recorded = (match.metadata ?? {}).credential_type;
|
|
45
82
|
assert.strictEqual(recorded, credential_type, `Expected '${expected}' audit metadata.credential_type === '${credential_type}' after ${context} (got ${JSON.stringify(recorded)})`);
|
|
46
83
|
};
|
|
47
|
-
/** Build CreateTestAppOptions with admin+keeper roles. */
|
|
48
|
-
const build_options = (options, db) => ({
|
|
49
|
-
session_options: options.session_options,
|
|
50
|
-
create_route_specs: options.create_route_specs,
|
|
51
|
-
db,
|
|
52
|
-
roles: [ROLE_KEEPER, ROLE_ADMIN],
|
|
53
|
-
rpc_endpoints: options.rpc_endpoints,
|
|
54
|
-
app_options: options.app_options,
|
|
55
|
-
});
|
|
56
84
|
/** Headers for unauthenticated JSON requests (login, signup). */
|
|
57
85
|
const UNAUTHENTICATED_JSON_HEADERS = {
|
|
58
86
|
host: 'localhost',
|
|
@@ -60,7 +88,7 @@ const UNAUTHENTICATED_JSON_HEADERS = {
|
|
|
60
88
|
'content-type': 'application/json',
|
|
61
89
|
};
|
|
62
90
|
/** Standard request headers for session-authenticated JSON requests. */
|
|
63
|
-
const json_session_headers = (
|
|
91
|
+
const json_session_headers = (fixture, extra) => fixture.create_session_headers({
|
|
64
92
|
'content-type': 'application/json',
|
|
65
93
|
...extra,
|
|
66
94
|
});
|
|
@@ -69,7 +97,8 @@ const json_session_headers = (test_app, extra) => test_app.create_session_header
|
|
|
69
97
|
*
|
|
70
98
|
* Verifies that every auth mutation route produces the correct audit log
|
|
71
99
|
* event type. Exercises routes via HTTP requests against a real PGlite
|
|
72
|
-
* database, then
|
|
100
|
+
* database, then reads events back through the `audit_log_list` RPC
|
|
101
|
+
* (the production observation path the admin UI consumes).
|
|
73
102
|
*
|
|
74
103
|
* @throws Error at setup time when `options.rpc_endpoints` is empty — the
|
|
75
104
|
* mutation-audit tests drive role_grant flow, session/token revoke-all, and
|
|
@@ -77,294 +106,297 @@ const json_session_headers = (test_app, extra) => test_app.create_session_header
|
|
|
77
106
|
* `require_rpc_endpoint_path`.
|
|
78
107
|
*/
|
|
79
108
|
export const describe_audit_completeness_tests = (options) => {
|
|
109
|
+
const route_specs = options.surface_source.route_specs;
|
|
80
110
|
// Hard-fail early so consumers see a clear setup error instead of a
|
|
81
|
-
// confusing test failure when `rpc_endpoints` is missing.
|
|
82
|
-
// callers are resolved with a stub ctx purely to extract the endpoint
|
|
83
|
-
// path; real handlers run per-test via the top-level `rpc_endpoints` slot on `CreateTestAppOptions`.
|
|
111
|
+
// confusing test failure when `rpc_endpoints` is missing.
|
|
84
112
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
85
113
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
};
|
|
89
|
-
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
90
|
-
const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
|
|
91
|
-
describe_db('audit_log_completeness', (get_db) => {
|
|
114
|
+
void options.capabilities;
|
|
115
|
+
describe('audit_log_completeness', () => {
|
|
92
116
|
// --- Account routes ---
|
|
93
117
|
describe('account mutation audit events', () => {
|
|
94
118
|
test('login success produces login event', async () => {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
119
|
+
const fixture = await options.setup_test();
|
|
120
|
+
const observer = await create_admin_observer(fixture);
|
|
121
|
+
const login_route = find_auth_route(route_specs, '/login', 'POST');
|
|
97
122
|
assert.ok(login_route, 'Expected POST /login route');
|
|
98
|
-
const res = await
|
|
123
|
+
const res = await fixture.transport(login_route.path, {
|
|
99
124
|
method: 'POST',
|
|
100
125
|
headers: UNAUTHENTICATED_JSON_HEADERS,
|
|
101
126
|
body: JSON.stringify({
|
|
102
|
-
username:
|
|
103
|
-
password:
|
|
127
|
+
username: fixture.account.username,
|
|
128
|
+
password: DEFAULT_TEST_PASSWORD,
|
|
104
129
|
}),
|
|
105
130
|
});
|
|
106
131
|
assert.strictEqual(res.status, 200);
|
|
107
|
-
const events = await
|
|
132
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
108
133
|
assert_has_event(events, 'login', 'POST /login (success)');
|
|
109
134
|
});
|
|
110
135
|
test('login failure produces login event with failure outcome', async () => {
|
|
111
|
-
const
|
|
112
|
-
const
|
|
136
|
+
const fixture = await options.setup_test();
|
|
137
|
+
const observer = await create_admin_observer(fixture);
|
|
138
|
+
const login_route = find_auth_route(route_specs, '/login', 'POST');
|
|
113
139
|
assert.ok(login_route, 'Expected POST /login route');
|
|
114
|
-
const res = await
|
|
140
|
+
const res = await fixture.transport(login_route.path, {
|
|
115
141
|
method: 'POST',
|
|
116
142
|
headers: UNAUTHENTICATED_JSON_HEADERS,
|
|
117
143
|
body: JSON.stringify({
|
|
118
|
-
username:
|
|
144
|
+
username: fixture.account.username,
|
|
119
145
|
password: 'wrong-password',
|
|
120
146
|
}),
|
|
121
147
|
});
|
|
122
148
|
assert.strictEqual(res.status, 401);
|
|
123
|
-
const events = await
|
|
149
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
124
150
|
assert_has_event(events, 'login', 'POST /login (failure)');
|
|
125
151
|
});
|
|
126
152
|
test('logout produces logout event', async () => {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
153
|
+
const fixture = await options.setup_test();
|
|
154
|
+
const observer = await create_admin_observer(fixture);
|
|
155
|
+
const logout_route = find_auth_route(route_specs, '/logout', 'POST');
|
|
129
156
|
assert.ok(logout_route, 'Expected POST /logout route');
|
|
130
|
-
const res = await
|
|
157
|
+
const res = await fixture.transport(logout_route.path, {
|
|
131
158
|
method: 'POST',
|
|
132
|
-
headers:
|
|
159
|
+
headers: fixture.create_session_headers(),
|
|
133
160
|
});
|
|
134
161
|
assert.strictEqual(res.status, 200);
|
|
135
|
-
const events = await
|
|
162
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
136
163
|
assert_has_event(events, 'logout', 'POST /logout');
|
|
137
164
|
});
|
|
138
165
|
test('token create produces token_create event', async () => {
|
|
139
|
-
const
|
|
166
|
+
const fixture = await options.setup_test();
|
|
167
|
+
const observer = await create_admin_observer(fixture);
|
|
140
168
|
const res = await rpc_call_for_spec({
|
|
141
|
-
app:
|
|
169
|
+
app: { request: fixture.transport },
|
|
142
170
|
path: rpc_path,
|
|
143
171
|
spec: account_token_create_action_spec,
|
|
144
172
|
params: { name: 'audit-test' },
|
|
145
|
-
headers:
|
|
173
|
+
headers: fixture.create_session_headers(),
|
|
146
174
|
});
|
|
147
175
|
assert.ok(res.ok, `account_token_create failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
148
|
-
const events = await
|
|
176
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
149
177
|
assert_has_event(events, 'token_create', 'account_token_create RPC');
|
|
150
178
|
assert_event_credential_type(events, 'token_create', 'session', 'account_token_create RPC');
|
|
151
179
|
});
|
|
152
180
|
test('token revoke produces token_revoke event', async () => {
|
|
153
|
-
const
|
|
181
|
+
const fixture = await options.setup_test();
|
|
182
|
+
const observer = await create_admin_observer(fixture);
|
|
154
183
|
// get a token ID to revoke
|
|
155
184
|
const list_res = await rpc_call_for_spec({
|
|
156
|
-
app:
|
|
185
|
+
app: { request: fixture.transport },
|
|
157
186
|
path: rpc_path,
|
|
158
187
|
spec: account_token_list_action_spec,
|
|
159
188
|
params: undefined,
|
|
160
|
-
headers:
|
|
189
|
+
headers: fixture.create_session_headers(),
|
|
161
190
|
});
|
|
162
191
|
assert.ok(list_res.ok, 'account_token_list should succeed');
|
|
163
192
|
const { tokens } = list_res.result;
|
|
164
193
|
assert.ok(tokens.length > 0, 'Expected at least one token');
|
|
165
194
|
const res = await rpc_call_for_spec({
|
|
166
|
-
app:
|
|
195
|
+
app: { request: fixture.transport },
|
|
167
196
|
path: rpc_path,
|
|
168
197
|
spec: account_token_revoke_action_spec,
|
|
169
198
|
params: { token_id: tokens[0].id },
|
|
170
|
-
headers:
|
|
199
|
+
headers: fixture.create_session_headers(),
|
|
171
200
|
});
|
|
172
201
|
assert.ok(res.ok, `account_token_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
173
|
-
const events = await
|
|
202
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
174
203
|
assert_has_event(events, 'token_revoke', 'account_token_revoke RPC');
|
|
175
204
|
assert_event_credential_type(events, 'token_revoke', 'session', 'account_token_revoke RPC');
|
|
176
205
|
});
|
|
177
206
|
test('session revoke produces session_revoke event', async () => {
|
|
178
|
-
const
|
|
207
|
+
const fixture = await options.setup_test();
|
|
208
|
+
const observer = await create_admin_observer(fixture);
|
|
179
209
|
// login to create a second session we can revoke
|
|
180
|
-
const login_route = find_auth_route(
|
|
210
|
+
const login_route = find_auth_route(route_specs, '/login', 'POST');
|
|
181
211
|
assert.ok(login_route, 'Expected POST /login route');
|
|
182
|
-
await
|
|
212
|
+
await fixture.transport(login_route.path, {
|
|
183
213
|
method: 'POST',
|
|
184
214
|
headers: UNAUTHENTICATED_JSON_HEADERS,
|
|
185
215
|
body: JSON.stringify({
|
|
186
|
-
username:
|
|
187
|
-
password:
|
|
216
|
+
username: fixture.account.username,
|
|
217
|
+
password: DEFAULT_TEST_PASSWORD,
|
|
188
218
|
}),
|
|
189
219
|
});
|
|
190
|
-
// get session IDs (newest first
|
|
220
|
+
// get session IDs (newest first — `account_session_list` orders DESC
|
|
221
|
+
// by `created_at`, so [0] is the just-logged-in session and [1] is
|
|
222
|
+
// the bootstrap session driving the RPC call).
|
|
191
223
|
const list_res = await rpc_call_for_spec({
|
|
192
|
-
app:
|
|
224
|
+
app: { request: fixture.transport },
|
|
193
225
|
path: rpc_path,
|
|
194
226
|
spec: account_session_list_action_spec,
|
|
195
227
|
params: undefined,
|
|
196
|
-
headers:
|
|
228
|
+
headers: fixture.create_session_headers(),
|
|
197
229
|
});
|
|
198
230
|
assert.ok(list_res.ok, 'account_session_list should succeed');
|
|
199
231
|
const { sessions } = list_res.result;
|
|
200
232
|
assert.ok(sessions.length >= 2, 'Expected at least 2 sessions');
|
|
201
|
-
// revoke the
|
|
233
|
+
// revoke the newest session — not the bootstrap one driving auth.
|
|
202
234
|
const res = await rpc_call_for_spec({
|
|
203
|
-
app:
|
|
235
|
+
app: { request: fixture.transport },
|
|
204
236
|
path: rpc_path,
|
|
205
237
|
spec: account_session_revoke_action_spec,
|
|
206
|
-
params: { session_id: sessions[
|
|
207
|
-
headers:
|
|
238
|
+
params: { session_id: sessions[0].id },
|
|
239
|
+
headers: fixture.create_session_headers(),
|
|
208
240
|
});
|
|
209
241
|
assert.ok(res.ok, `account_session_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
210
|
-
const events = await
|
|
242
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
211
243
|
assert_has_event(events, 'session_revoke', 'account_session_revoke RPC');
|
|
212
244
|
assert_event_credential_type(events, 'session_revoke', 'session', 'account_session_revoke RPC');
|
|
213
245
|
});
|
|
214
246
|
test('session revoke-all produces session_revoke_all event', async () => {
|
|
215
|
-
const
|
|
247
|
+
const fixture = await options.setup_test();
|
|
248
|
+
const observer = await create_admin_observer(fixture);
|
|
216
249
|
const res = await rpc_call_for_spec({
|
|
217
|
-
app:
|
|
250
|
+
app: { request: fixture.transport },
|
|
218
251
|
path: rpc_path,
|
|
219
252
|
spec: account_session_revoke_all_action_spec,
|
|
220
253
|
params: undefined,
|
|
221
|
-
headers:
|
|
254
|
+
headers: fixture.create_session_headers(),
|
|
222
255
|
});
|
|
223
256
|
assert.ok(res.ok, `account_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
224
|
-
const events = await
|
|
257
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
225
258
|
assert_has_event(events, 'session_revoke_all', 'account_session_revoke_all RPC');
|
|
226
259
|
assert_event_credential_type(events, 'session_revoke_all', 'session', 'account_session_revoke_all RPC');
|
|
227
260
|
});
|
|
228
261
|
test('password change produces password_change event', async () => {
|
|
229
|
-
const
|
|
230
|
-
const
|
|
262
|
+
const fixture = await options.setup_test();
|
|
263
|
+
const observer = await create_admin_observer(fixture);
|
|
264
|
+
const route = find_auth_route(route_specs, '/password', 'POST');
|
|
231
265
|
assert.ok(route, 'Expected POST /password route');
|
|
232
|
-
const res = await
|
|
266
|
+
const res = await fixture.transport(route.path, {
|
|
233
267
|
method: 'POST',
|
|
234
|
-
headers: json_session_headers(
|
|
268
|
+
headers: json_session_headers(fixture),
|
|
235
269
|
body: JSON.stringify({
|
|
236
|
-
current_password:
|
|
270
|
+
current_password: DEFAULT_TEST_PASSWORD,
|
|
237
271
|
new_password: 'new-password-456',
|
|
238
272
|
}),
|
|
239
273
|
});
|
|
240
274
|
assert.strictEqual(res.status, 200);
|
|
241
|
-
const events = await
|
|
275
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
242
276
|
assert_has_event(events, 'password_change', 'POST /password');
|
|
243
277
|
assert_event_credential_type(events, 'password_change', 'session', 'POST /password');
|
|
244
278
|
});
|
|
245
279
|
});
|
|
246
280
|
// --- Admin routes ---
|
|
247
281
|
describe('admin mutation audit events', () => {
|
|
248
|
-
test('admin offer (RPC) + accept produces role_grant_offer_create and role_grant_create events', async () => {
|
|
249
|
-
const
|
|
250
|
-
const
|
|
282
|
+
test('admin offer (RPC) + accept (RPC) produces role_grant_offer_create, role_grant_offer_accept, and role_grant_create events', async () => {
|
|
283
|
+
const fixture = await options.setup_test();
|
|
284
|
+
const observer = await create_admin_observer(fixture);
|
|
285
|
+
const target = await fixture.create_account({ username: 'audit_target' });
|
|
251
286
|
const offer_res = await rpc_call_for_spec({
|
|
252
|
-
app:
|
|
287
|
+
app: { request: fixture.transport },
|
|
253
288
|
path: rpc_path,
|
|
254
289
|
spec: role_grant_offer_create_action_spec,
|
|
255
290
|
params: { to_account_id: target.account.id, role: ROLE_ADMIN },
|
|
256
|
-
headers:
|
|
291
|
+
headers: fixture.create_session_headers(),
|
|
257
292
|
});
|
|
258
293
|
assert.ok(offer_res.ok, `role_grant_offer_create failed: ${offer_res.ok ? '' : JSON.stringify(offer_res.error)}`);
|
|
259
294
|
const { offer } = offer_res.result;
|
|
260
295
|
// Admin offer emits `role_grant_offer_create` only — the role_grant doesn't
|
|
261
|
-
// exist yet. Drive the accept to confirm `
|
|
262
|
-
// downstream consent transition.
|
|
263
|
-
const events_after_offer = await
|
|
296
|
+
// exist yet. Drive the accept to confirm `role_grant_offer_accept` and
|
|
297
|
+
// `role_grant_create` both fire on the downstream consent transition.
|
|
298
|
+
const events_after_offer = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
264
299
|
assert_has_event(events_after_offer, 'role_grant_offer_create', 'role_grant_offer_create RPC');
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
});
|
|
300
|
+
const accept_res = await rpc_call_for_spec({
|
|
301
|
+
app: { request: fixture.transport },
|
|
302
|
+
path: rpc_path,
|
|
303
|
+
spec: role_grant_offer_accept_action_spec,
|
|
304
|
+
params: { offer_id: offer.id },
|
|
305
|
+
headers: target.create_session_headers(),
|
|
272
306
|
});
|
|
273
|
-
|
|
274
|
-
|
|
307
|
+
assert.ok(accept_res.ok, `role_grant_offer_accept failed: ${accept_res.ok ? '' : JSON.stringify(accept_res.error)}`);
|
|
308
|
+
const events_after_accept = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
309
|
+
assert_has_event(events_after_accept, 'role_grant_offer_accept', 'offer accept RPC');
|
|
310
|
+
assert_has_event(events_after_accept, 'role_grant_create', 'offer accept RPC');
|
|
275
311
|
});
|
|
276
312
|
test('role_grant revoke (RPC) produces role_grant_revoke event with both target columns', async () => {
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const accept_result = await get_db().transaction(async (tx) => {
|
|
290
|
-
return query_accept_offer({ db: tx }, {
|
|
291
|
-
offer_id: offer.id,
|
|
292
|
-
to_account_id: target.account.id,
|
|
293
|
-
actor_id: target.actor.id,
|
|
294
|
-
ip: null,
|
|
295
|
-
});
|
|
313
|
+
const fixture = await options.setup_test();
|
|
314
|
+
const observer = await create_admin_observer(fixture);
|
|
315
|
+
const target = await fixture.create_account({ username: 'audit_revoke_target' });
|
|
316
|
+
// Offer + accept to materialize a role_grant we can revoke. The
|
|
317
|
+
// consent path itself is covered by the `offer + accept` test above;
|
|
318
|
+
// here we only need the role_grant to exist.
|
|
319
|
+
const { role_grant_id } = await role_grant_offer_and_accept({
|
|
320
|
+
app: { request: fixture.transport },
|
|
321
|
+
rpc_path,
|
|
322
|
+
grantor: fixture,
|
|
323
|
+
recipient: target,
|
|
324
|
+
role: ROLE_ADMIN,
|
|
296
325
|
});
|
|
297
326
|
// Revoke via RPC.
|
|
298
327
|
const revoke_res = await rpc_call_for_spec({
|
|
299
|
-
app:
|
|
328
|
+
app: { request: fixture.transport },
|
|
300
329
|
path: rpc_path,
|
|
301
330
|
spec: role_grant_revoke_action_spec,
|
|
302
|
-
params: { actor_id: target.actor.id, role_grant_id
|
|
303
|
-
headers:
|
|
331
|
+
params: { actor_id: target.actor.id, role_grant_id },
|
|
332
|
+
headers: fixture.create_session_headers(),
|
|
304
333
|
});
|
|
305
334
|
assert.ok(revoke_res.ok, `role_grant_revoke failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
306
|
-
const events = await
|
|
335
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
307
336
|
assert_has_event(events, 'role_grant_revoke', 'role_grant_revoke RPC');
|
|
308
337
|
// Audit envelope must populate both target columns —
|
|
309
338
|
// `role_grant_revoke` is the canonical actor-bound-subject event.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
assert.strictEqual(
|
|
314
|
-
assert.strictEqual(
|
|
339
|
+
// RPC orders newest-first, so `.find` picks up the just-emitted row.
|
|
340
|
+
const revoke = events.find((e) => e.event_type === 'role_grant_revoke');
|
|
341
|
+
assert.ok(revoke, 'Expected role_grant_revoke audit event');
|
|
342
|
+
assert.strictEqual(revoke.target_account_id, target.account.id);
|
|
343
|
+
assert.strictEqual(revoke.target_actor_id, target.actor.id);
|
|
315
344
|
});
|
|
316
345
|
test('admin session revoke-all produces session_revoke_all event', async () => {
|
|
317
|
-
const
|
|
318
|
-
const
|
|
346
|
+
const fixture = await options.setup_test();
|
|
347
|
+
const observer = await create_admin_observer(fixture);
|
|
348
|
+
const target = await fixture.create_account({ username: 'audit_sessions_target' });
|
|
319
349
|
const res = await rpc_call_for_spec({
|
|
320
|
-
app:
|
|
350
|
+
app: { request: fixture.transport },
|
|
321
351
|
path: rpc_path,
|
|
322
352
|
spec: admin_session_revoke_all_action_spec,
|
|
323
353
|
params: { account_id: target.account.id },
|
|
324
|
-
headers:
|
|
354
|
+
headers: fixture.create_session_headers(),
|
|
325
355
|
});
|
|
326
356
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
327
|
-
const events = await
|
|
357
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
328
358
|
// admin session revoke-all also produces session_revoke_all
|
|
329
359
|
assert_has_event(events, 'session_revoke_all', 'admin_session_revoke_all RPC');
|
|
330
360
|
});
|
|
331
361
|
test('admin token revoke-all produces token_revoke_all event', async () => {
|
|
332
|
-
const
|
|
333
|
-
const
|
|
362
|
+
const fixture = await options.setup_test();
|
|
363
|
+
const observer = await create_admin_observer(fixture);
|
|
364
|
+
const target = await fixture.create_account({ username: 'audit_tokens_target' });
|
|
334
365
|
const res = await rpc_call_for_spec({
|
|
335
|
-
app:
|
|
366
|
+
app: { request: fixture.transport },
|
|
336
367
|
path: rpc_path,
|
|
337
368
|
spec: admin_token_revoke_all_action_spec,
|
|
338
369
|
params: { account_id: target.account.id },
|
|
339
|
-
headers:
|
|
370
|
+
headers: fixture.create_session_headers(),
|
|
340
371
|
});
|
|
341
372
|
assert.ok(res.ok, `admin_token_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
342
|
-
const events = await
|
|
373
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
343
374
|
assert_has_event(events, 'token_revoke_all', 'admin_token_revoke_all RPC');
|
|
344
375
|
});
|
|
345
376
|
});
|
|
346
377
|
// --- Invite RPC actions ---
|
|
347
378
|
describe('invite mutation audit events', () => {
|
|
348
379
|
test('invite create and delete produce audit events', async () => {
|
|
349
|
-
const
|
|
380
|
+
const fixture = await options.setup_test();
|
|
381
|
+
const observer = await create_admin_observer(fixture);
|
|
350
382
|
const create_res = await rpc_call_for_spec({
|
|
351
|
-
app:
|
|
383
|
+
app: { request: fixture.transport },
|
|
352
384
|
path: rpc_path,
|
|
353
385
|
spec: invite_create_action_spec,
|
|
354
386
|
params: { username: 'invited_user' },
|
|
355
|
-
headers:
|
|
387
|
+
headers: fixture.create_session_headers(),
|
|
356
388
|
});
|
|
357
389
|
assert.ok(create_res.ok, `invite_create failed: ${create_res.ok ? '' : JSON.stringify(create_res.error)}`);
|
|
358
390
|
const { invite } = create_res.result;
|
|
359
391
|
const delete_res = await rpc_call_for_spec({
|
|
360
|
-
app:
|
|
392
|
+
app: { request: fixture.transport },
|
|
361
393
|
path: rpc_path,
|
|
362
394
|
spec: invite_delete_action_spec,
|
|
363
395
|
params: { invite_id: invite.id },
|
|
364
|
-
headers:
|
|
396
|
+
headers: fixture.create_session_headers(),
|
|
365
397
|
});
|
|
366
398
|
assert.ok(delete_res.ok, `invite_delete failed: ${delete_res.ok ? '' : JSON.stringify(delete_res.error)}`);
|
|
367
|
-
const events = await
|
|
399
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
368
400
|
assert_has_event(events, 'invite_create', 'invite_create RPC');
|
|
369
401
|
assert_has_event(events, 'invite_delete', 'invite_delete RPC');
|
|
370
402
|
});
|
|
@@ -372,36 +404,42 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
372
404
|
// --- App settings RPC action ---
|
|
373
405
|
describe('app settings mutation audit events', () => {
|
|
374
406
|
test('settings update produces app_settings_update event', async () => {
|
|
375
|
-
const
|
|
407
|
+
const fixture = await options.setup_test();
|
|
408
|
+
const observer = await create_admin_observer(fixture);
|
|
376
409
|
const res = await rpc_call_for_spec({
|
|
377
|
-
app:
|
|
410
|
+
app: { request: fixture.transport },
|
|
378
411
|
path: rpc_path,
|
|
379
412
|
spec: app_settings_update_action_spec,
|
|
380
413
|
params: { open_signup: true },
|
|
381
|
-
headers:
|
|
414
|
+
headers: fixture.create_session_headers(),
|
|
382
415
|
});
|
|
383
416
|
assert.ok(res.ok, `app_settings_update failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
384
|
-
const events = await
|
|
417
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
385
418
|
assert_has_event(events, 'app_settings_update', 'app_settings_update RPC');
|
|
386
419
|
});
|
|
387
420
|
});
|
|
388
421
|
// --- Signup route ---
|
|
389
422
|
describe('signup audit events', () => {
|
|
390
423
|
test('signup produces signup event', async () => {
|
|
391
|
-
const
|
|
424
|
+
const fixture = await options.setup_test();
|
|
425
|
+
// signup is optional — consumers that don't wire `POST /signup` (e.g.
|
|
426
|
+
// admin-only apps) skip this audit check; signup completeness for
|
|
427
|
+
// surfaces that DO wire it is still asserted by COVERED_EVENT_TYPES
|
|
428
|
+
// below. Mirrors `integration.ts`'s signup-block presence-gate.
|
|
429
|
+
const signup_route = find_auth_route(route_specs, '/signup', 'POST');
|
|
430
|
+
if (!signup_route)
|
|
431
|
+
return;
|
|
432
|
+
const observer = await create_admin_observer(fixture);
|
|
392
433
|
// enable open signup via RPC
|
|
393
434
|
const settings_res = await rpc_call_for_spec({
|
|
394
|
-
app:
|
|
435
|
+
app: { request: fixture.transport },
|
|
395
436
|
path: rpc_path,
|
|
396
437
|
spec: app_settings_update_action_spec,
|
|
397
438
|
params: { open_signup: true },
|
|
398
|
-
headers:
|
|
439
|
+
headers: fixture.create_session_headers(),
|
|
399
440
|
});
|
|
400
441
|
assert.ok(settings_res.ok, `app_settings_update failed: ${settings_res.ok ? '' : JSON.stringify(settings_res.error)}`);
|
|
401
|
-
|
|
402
|
-
const signup_route = find_auth_route(test_app.route_specs, '/signup', 'POST');
|
|
403
|
-
assert.ok(signup_route, 'Expected POST /signup route');
|
|
404
|
-
const res = await test_app.app.request(signup_route.path, {
|
|
442
|
+
const res = await fixture.transport(signup_route.path, {
|
|
405
443
|
method: 'POST',
|
|
406
444
|
headers: UNAUTHENTICATED_JSON_HEADERS,
|
|
407
445
|
body: JSON.stringify({
|
|
@@ -410,7 +448,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
410
448
|
}),
|
|
411
449
|
});
|
|
412
450
|
assert.strictEqual(res.status, 200);
|
|
413
|
-
const events = await
|
|
451
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
414
452
|
assert_has_event(events, 'signup', 'POST /signup');
|
|
415
453
|
});
|
|
416
454
|
});
|
|
@@ -431,6 +469,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
431
469
|
'token_revoke',
|
|
432
470
|
'token_revoke_all',
|
|
433
471
|
'role_grant_offer_create',
|
|
472
|
+
'role_grant_offer_accept',
|
|
434
473
|
'role_grant_create',
|
|
435
474
|
'role_grant_revoke',
|
|
436
475
|
'invite_create',
|
|
@@ -440,8 +479,9 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
440
479
|
/** Event types excluded with justification. */
|
|
441
480
|
const EXCLUDED_EVENT_TYPES = new Set([
|
|
442
481
|
'bootstrap', // requires filesystem token — tested in bootstrap_account.db.test.ts
|
|
443
|
-
// The remaining `role_grant_offer_*` events fire only via
|
|
444
|
-
//
|
|
482
|
+
// The remaining `role_grant_offer_*` events fire only via terminal
|
|
483
|
+
// transitions (decline, retract) or downstream effects (supersede on
|
|
484
|
+
// accept of a sibling, or as a fan-out of `role_grant_revoke`). Direct
|
|
445
485
|
// coverage lives in `role_grant_offer_queries.db.test.ts`,
|
|
446
486
|
// `role_grant_offer_actions.db.test.ts`,
|
|
447
487
|
// `role_grant_offer_actions.notifications.db.test.ts`, and
|
|
@@ -449,7 +489,6 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
449
489
|
// `role_grant_offer_expire` fires from the cleanup sweep
|
|
450
490
|
// (`cleanup_expired_role_grant_offers` in `auth/cleanup.ts`) —
|
|
451
491
|
// covered in `cleanup.db.test.ts`.
|
|
452
|
-
'role_grant_offer_accept',
|
|
453
492
|
'role_grant_offer_decline',
|
|
454
493
|
'role_grant_offer_retract',
|
|
455
494
|
'role_grant_offer_expire',
|