@fuzdev/fuz_app 0.64.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 +513 -928
- 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 +591 -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/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/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/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 +224 -498
- 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/origin.d.ts +1 -1
- package/dist/http/origin.js +1 -1
- package/dist/runtime/mock.js +1 -1
- 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 +220 -46
- 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 -201
- package/dist/testing/app_server.d.ts +125 -38
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +140 -42
- 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 -156
- 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/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 -291
- 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 +21 -16
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +65 -86
- 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 -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/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 +11 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +24 -21
- 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/package.json +4 -4
|
@@ -28,18 +28,15 @@ import './assert_dev_env.js';
|
|
|
28
28
|
import { describe, test, assert, afterAll } from 'vitest';
|
|
29
29
|
import { ROLE_KEEPER, ROLE_ADMIN } from '../auth/role_schema.js';
|
|
30
30
|
import { GRANT_PATH_ADMIN } from '../auth/grant_path_schema.js';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
31
|
+
import {} from './app_server.js';
|
|
32
|
+
import { create_test_role_grant_direct } from './db_entities.js';
|
|
33
|
+
import { role_grant_offer_and_accept } from './role_grant_helpers.js';
|
|
34
34
|
import { find_auth_route } from './integration_helpers.js';
|
|
35
|
-
import { run_migrations } from '../db/migrate.js';
|
|
36
35
|
import { ErrorCoverageCollector, assert_error_coverage, DEFAULT_INTEGRATION_ERROR_COVERAGE, } from './error_coverage.js';
|
|
37
36
|
import { rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
|
|
38
37
|
import { role_grant_offer_create_action_spec, role_grant_revoke_action_spec, } from '../auth/role_grant_offer_action_specs.js';
|
|
39
38
|
import { admin_account_list_action_spec, admin_session_list_action_spec, admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, audit_log_list_action_spec, audit_log_role_grant_history_action_spec, } from '../auth/admin_action_specs.js';
|
|
40
39
|
import { account_token_create_action_spec, account_verify_action_spec, } from '../auth/account_action_specs.js';
|
|
41
|
-
import { query_create_role_grant } from '../auth/role_grant_queries.js';
|
|
42
|
-
import { query_accept_offer } from '../auth/role_grant_offer_queries.js';
|
|
43
40
|
/**
|
|
44
41
|
* Pick a role for admin-grant testing, preferring a non-admin app-defined
|
|
45
42
|
* role whose `RoleSpec.grant_paths` includes `'admin'` (the
|
|
@@ -53,17 +50,6 @@ const pick_grantable_role = (role_specs) => {
|
|
|
53
50
|
}
|
|
54
51
|
return ROLE_ADMIN; // fallback
|
|
55
52
|
};
|
|
56
|
-
/**
|
|
57
|
-
* Build `CreateTestAppOptions` from admin test options plus a database and roles.
|
|
58
|
-
*/
|
|
59
|
-
const build_admin_test_app_options = (options, db, roles) => ({
|
|
60
|
-
session_options: options.session_options,
|
|
61
|
-
create_route_specs: options.create_route_specs,
|
|
62
|
-
db,
|
|
63
|
-
roles: roles ?? [ROLE_KEEPER, ROLE_ADMIN],
|
|
64
|
-
rpc_endpoints: options.rpc_endpoints,
|
|
65
|
-
app_options: options.app_options,
|
|
66
|
-
});
|
|
67
53
|
/**
|
|
68
54
|
* Standard admin integration test suite for fuz_app admin routes.
|
|
69
55
|
*
|
|
@@ -79,56 +65,44 @@ const build_admin_test_app_options = (options, db, roles) => ({
|
|
|
79
65
|
* see a clear setup error rather than `method not found` mid-suite.
|
|
80
66
|
*/
|
|
81
67
|
export const describe_standard_admin_integration_tests = (options) => {
|
|
68
|
+
if (options.surface_source.kind !== 'inline') {
|
|
69
|
+
throw new Error("describe_standard_admin_integration_tests requires surface_source.kind === 'inline' — " +
|
|
70
|
+
'the cross-process snapshot variant lands with the spawned-backend transport');
|
|
71
|
+
}
|
|
72
|
+
const route_specs = options.surface_source.spec.route_specs;
|
|
82
73
|
// Hard-fail early so consumers see a clear setup error instead of a
|
|
83
|
-
// confusing test failure when `rpc_endpoints` is missing.
|
|
84
|
-
// callers are resolved with a stub ctx purely to extract the endpoint
|
|
85
|
-
// path; real handlers run per-test via the top-level `rpc_endpoints`
|
|
86
|
-
// slot on `CreateTestAppOptions`.
|
|
74
|
+
// confusing test failure when `rpc_endpoints` is missing.
|
|
87
75
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
88
76
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
};
|
|
92
|
-
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
93
|
-
const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
|
|
94
|
-
describe_db('standard_admin_integration', (get_db) => {
|
|
77
|
+
void options.capabilities;
|
|
78
|
+
describe('standard_admin_integration', () => {
|
|
95
79
|
const { cookie_name } = options.session_options;
|
|
96
80
|
const { role_specs } = options.roles;
|
|
97
81
|
const grantable_role = pick_grantable_role(role_specs);
|
|
98
82
|
// Error coverage tracking across test groups
|
|
99
83
|
const error_collector = new ErrorCoverageCollector();
|
|
100
|
-
let captured_route_specs = null;
|
|
101
84
|
afterAll(() => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// of asserting.
|
|
120
|
-
// The admin RPC surface is covered by
|
|
121
|
-
// `describe_rpc_round_trip_tests`, not this collector.
|
|
122
|
-
if (admin_routes.length <= 1) {
|
|
123
|
-
console.log(`[error coverage] skipped admin REST coverage assertion — ` +
|
|
124
|
-
`scoped surface has ${admin_routes.length} route(s); ` +
|
|
125
|
-
`admin RPC surface is covered by describe_rpc_round_trip_tests`);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
assert_error_coverage(error_collector, admin_routes, {
|
|
129
|
-
min_coverage: DEFAULT_INTEGRATION_ERROR_COVERAGE,
|
|
130
|
-
});
|
|
85
|
+
// Scope coverage to admin auth-related routes. Account listing,
|
|
86
|
+
// session/token revoke-all, audit-log reads, and invite CRUD all
|
|
87
|
+
// live on the RPC surface; the only admin REST route remaining
|
|
88
|
+
// is the optional `GET /audit/stream` SSE (admin RPC methods
|
|
89
|
+
// live behind spec-level role auth on the shared endpoint path).
|
|
90
|
+
const admin_routes = route_specs.filter((s) => s.path.endsWith('/audit/stream') && (s.auth.roles?.includes('admin') ?? false));
|
|
91
|
+
// Adaptive threshold: when the scoped admin REST surface is
|
|
92
|
+
// effectively empty (0–1 routes — typical for the RPC-first
|
|
93
|
+
// admin surface), the 20% baseline is meaningless — a single
|
|
94
|
+
// SSE route that can't be exercised against an error schema
|
|
95
|
+
// drops the ratio to 0.0%. Log an informational skip instead
|
|
96
|
+
// of asserting.
|
|
97
|
+
if (admin_routes.length <= 1) {
|
|
98
|
+
console.log(`[error coverage] skipped admin REST coverage assertion — ` +
|
|
99
|
+
`scoped surface has ${admin_routes.length} route(s); ` +
|
|
100
|
+
`admin RPC surface is covered by describe_rpc_round_trip_tests`);
|
|
101
|
+
return;
|
|
131
102
|
}
|
|
103
|
+
assert_error_coverage(error_collector, admin_routes, {
|
|
104
|
+
min_coverage: DEFAULT_INTEGRATION_ERROR_COVERAGE,
|
|
105
|
+
});
|
|
132
106
|
});
|
|
133
107
|
/** Make request headers for a given session cookie. */
|
|
134
108
|
const create_headers = (session_cookie, extra) => ({
|
|
@@ -138,41 +112,22 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
138
112
|
...extra,
|
|
139
113
|
});
|
|
140
114
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* side; exercising the recipient's UI-wired accept path is covered by
|
|
145
|
-
* `describe_rpc_round_trip_tests` + fuz_app's own action suite.
|
|
115
|
+
* Suite-local wrapper around `role_grant_offer_and_accept` that closes
|
|
116
|
+
* over `rpc_path` so call sites stay terse. See the shared helper for
|
|
117
|
+
* the cross-suite contract.
|
|
146
118
|
*/
|
|
147
|
-
const offer_and_accept =
|
|
148
|
-
const res = await rpc_call_for_spec({
|
|
149
|
-
app: args.app,
|
|
150
|
-
path: rpc_path,
|
|
151
|
-
spec: role_grant_offer_create_action_spec,
|
|
152
|
-
params: { to_account_id: args.to_account_id, role: args.role },
|
|
153
|
-
headers: args.admin_headers,
|
|
154
|
-
});
|
|
155
|
-
assert.ok(res.ok, `role_grant_offer_create failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
156
|
-
const { offer } = res.result;
|
|
157
|
-
const accept_result = await get_db().transaction(async (tx) => query_accept_offer({ db: tx }, {
|
|
158
|
-
offer_id: offer.id,
|
|
159
|
-
to_account_id: args.to_account_id,
|
|
160
|
-
actor_id: args.to_actor_id,
|
|
161
|
-
ip: null,
|
|
162
|
-
}));
|
|
163
|
-
return { offer_id: offer.id, role_grant_id: accept_result.role_grant.id };
|
|
164
|
-
};
|
|
119
|
+
const offer_and_accept = (args) => role_grant_offer_and_accept({ ...args, rpc_path });
|
|
165
120
|
// --- 1. Admin account listing (RPC) ---
|
|
166
121
|
describe('admin account listing', () => {
|
|
167
122
|
test('admin can list all accounts', async () => {
|
|
168
|
-
const
|
|
169
|
-
const user_two = await
|
|
123
|
+
const fixture = await options.setup_test();
|
|
124
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
170
125
|
const res = await rpc_call_for_spec({
|
|
171
|
-
app:
|
|
126
|
+
app: { request: fixture.transport },
|
|
172
127
|
path: rpc_path,
|
|
173
128
|
spec: admin_account_list_action_spec,
|
|
174
129
|
params: {},
|
|
175
|
-
headers:
|
|
130
|
+
headers: fixture.create_session_headers(),
|
|
176
131
|
});
|
|
177
132
|
assert.ok(res.ok, `admin_account_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
178
133
|
assert.ok(Array.isArray(res.result.accounts), 'Expected accounts array');
|
|
@@ -183,14 +138,19 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
183
138
|
assert.ok(found, 'Expected user_two in accounts listing');
|
|
184
139
|
});
|
|
185
140
|
test('non-admin cannot list accounts', async () => {
|
|
186
|
-
const
|
|
187
|
-
|
|
141
|
+
const fixture = await options.setup_test();
|
|
142
|
+
// Default keeper has both ROLE_KEEPER + ROLE_ADMIN; mint a fresh
|
|
143
|
+
// account with only ROLE_KEEPER (no admin) to probe the 403 path.
|
|
144
|
+
const keeper_only = await fixture.create_account({
|
|
145
|
+
username: 'keeper_only',
|
|
146
|
+
roles: [ROLE_KEEPER],
|
|
147
|
+
});
|
|
188
148
|
const res = await rpc_call_for_spec({
|
|
189
|
-
app:
|
|
149
|
+
app: { request: fixture.transport },
|
|
190
150
|
path: rpc_path,
|
|
191
151
|
spec: admin_account_list_action_spec,
|
|
192
152
|
params: {},
|
|
193
|
-
headers:
|
|
153
|
+
headers: keeper_only.create_session_headers(),
|
|
194
154
|
});
|
|
195
155
|
assert.ok(!res.ok, 'Expected admin_account_list to fail for non-admin');
|
|
196
156
|
assert.strictEqual(res.status, 403);
|
|
@@ -207,25 +167,25 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
207
167
|
// --- 3. Admin session management ---
|
|
208
168
|
describe('admin session management', () => {
|
|
209
169
|
test('admin can list all active sessions', async () => {
|
|
210
|
-
const
|
|
211
|
-
await
|
|
170
|
+
const fixture = await options.setup_test();
|
|
171
|
+
await fixture.create_account({ username: 'user_two' });
|
|
212
172
|
const res = await rpc_call_for_spec({
|
|
213
|
-
app:
|
|
173
|
+
app: { request: fixture.transport },
|
|
214
174
|
path: rpc_path,
|
|
215
175
|
spec: admin_session_list_action_spec,
|
|
216
176
|
params: {},
|
|
217
|
-
headers:
|
|
177
|
+
headers: fixture.create_session_headers(),
|
|
218
178
|
});
|
|
219
179
|
assert.ok(res.ok, `admin_session_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
220
180
|
assert.ok(Array.isArray(res.result.sessions), 'Expected sessions array');
|
|
221
181
|
assert.ok(res.result.sessions.length >= 2, 'Expected sessions from multiple accounts');
|
|
222
182
|
});
|
|
223
183
|
test('admin can revoke all sessions for another account', async () => {
|
|
224
|
-
const
|
|
225
|
-
const user_two = await
|
|
184
|
+
const fixture = await options.setup_test();
|
|
185
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
226
186
|
// Verify user_two's session works via `account_verify` RPC
|
|
227
187
|
const before = await rpc_call_for_spec({
|
|
228
|
-
app:
|
|
188
|
+
app: { request: fixture.transport },
|
|
229
189
|
path: rpc_path,
|
|
230
190
|
spec: account_verify_action_spec,
|
|
231
191
|
params: undefined,
|
|
@@ -234,18 +194,18 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
234
194
|
assert.strictEqual(before.status, 200);
|
|
235
195
|
// Admin revokes all sessions for user_two via RPC
|
|
236
196
|
const res = await rpc_call_for_spec({
|
|
237
|
-
app:
|
|
197
|
+
app: { request: fixture.transport },
|
|
238
198
|
path: rpc_path,
|
|
239
199
|
spec: admin_session_revoke_all_action_spec,
|
|
240
200
|
params: { account_id: user_two.account.id },
|
|
241
|
-
headers:
|
|
201
|
+
headers: fixture.create_session_headers(),
|
|
242
202
|
});
|
|
243
203
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
244
204
|
assert.strictEqual(res.result.ok, true);
|
|
245
205
|
assert.ok(res.result.count >= 1, 'Expected at least 1 revoked session');
|
|
246
206
|
// Verify user_two's session no longer works
|
|
247
207
|
const after = await rpc_call_for_spec({
|
|
248
|
-
app:
|
|
208
|
+
app: { request: fixture.transport },
|
|
249
209
|
path: rpc_path,
|
|
250
210
|
spec: account_verify_action_spec,
|
|
251
211
|
params: undefined,
|
|
@@ -254,25 +214,25 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
254
214
|
assert.strictEqual(after.status, 401);
|
|
255
215
|
});
|
|
256
216
|
test('admin revoking own sessions invalidates own session', async () => {
|
|
257
|
-
const
|
|
217
|
+
const fixture = await options.setup_test();
|
|
258
218
|
// Admin revokes own sessions via RPC
|
|
259
219
|
const res = await rpc_call_for_spec({
|
|
260
|
-
app:
|
|
220
|
+
app: { request: fixture.transport },
|
|
261
221
|
path: rpc_path,
|
|
262
222
|
spec: admin_session_revoke_all_action_spec,
|
|
263
|
-
params: { account_id:
|
|
264
|
-
headers:
|
|
223
|
+
params: { account_id: fixture.account.id },
|
|
224
|
+
headers: fixture.create_session_headers(),
|
|
265
225
|
});
|
|
266
226
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
267
227
|
assert.strictEqual(res.result.ok, true);
|
|
268
228
|
assert.ok(res.result.count >= 1, 'Expected at least 1 revoked session');
|
|
269
229
|
// Admin's own session should no longer work
|
|
270
230
|
const after = await rpc_call_for_spec({
|
|
271
|
-
app:
|
|
231
|
+
app: { request: fixture.transport },
|
|
272
232
|
path: rpc_path,
|
|
273
233
|
spec: account_verify_action_spec,
|
|
274
234
|
params: undefined,
|
|
275
|
-
headers:
|
|
235
|
+
headers: fixture.create_session_headers(),
|
|
276
236
|
});
|
|
277
237
|
assert.strictEqual(after.status, 401);
|
|
278
238
|
});
|
|
@@ -280,11 +240,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
280
240
|
// --- 4. Admin token management ---
|
|
281
241
|
describe('admin token management', () => {
|
|
282
242
|
test('admin can revoke all tokens for another account', async () => {
|
|
283
|
-
const
|
|
284
|
-
const user_two = await
|
|
243
|
+
const fixture = await options.setup_test();
|
|
244
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
285
245
|
// Verify user_two's bearer token works via `account_verify` RPC
|
|
286
246
|
const before = await rpc_call_for_spec({
|
|
287
|
-
app:
|
|
247
|
+
app: { request: fixture.transport },
|
|
288
248
|
path: rpc_path,
|
|
289
249
|
spec: account_verify_action_spec,
|
|
290
250
|
params: undefined,
|
|
@@ -294,18 +254,18 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
294
254
|
assert.strictEqual(before.status, 200);
|
|
295
255
|
// Admin revokes all tokens for user_two via RPC
|
|
296
256
|
const res = await rpc_call_for_spec({
|
|
297
|
-
app:
|
|
257
|
+
app: { request: fixture.transport },
|
|
298
258
|
path: rpc_path,
|
|
299
259
|
spec: admin_token_revoke_all_action_spec,
|
|
300
260
|
params: { account_id: user_two.account.id },
|
|
301
|
-
headers:
|
|
261
|
+
headers: fixture.create_session_headers(),
|
|
302
262
|
});
|
|
303
263
|
assert.ok(res.ok, `admin_token_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
304
264
|
assert.strictEqual(res.result.ok, true);
|
|
305
265
|
assert.ok(res.result.count >= 1, 'Expected at least 1 revoked token');
|
|
306
266
|
// Verify user_two's bearer token no longer works
|
|
307
267
|
const after = await rpc_call_for_spec({
|
|
308
|
-
app:
|
|
268
|
+
app: { request: fixture.transport },
|
|
309
269
|
path: rpc_path,
|
|
310
270
|
spec: account_verify_action_spec,
|
|
311
271
|
params: undefined,
|
|
@@ -318,36 +278,36 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
318
278
|
// --- 5. Audit log RPC reads ---
|
|
319
279
|
describe('audit log RPC reads', () => {
|
|
320
280
|
test('admin can list audit log events', async () => {
|
|
321
|
-
const
|
|
281
|
+
const fixture = await options.setup_test();
|
|
322
282
|
const res = await rpc_call_for_spec({
|
|
323
|
-
app:
|
|
283
|
+
app: { request: fixture.transport },
|
|
324
284
|
path: rpc_path,
|
|
325
285
|
spec: audit_log_list_action_spec,
|
|
326
286
|
params: {},
|
|
327
|
-
headers:
|
|
287
|
+
headers: fixture.create_session_headers(),
|
|
328
288
|
});
|
|
329
289
|
assert.ok(res.ok, `audit_log_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
330
290
|
assert.ok(Array.isArray(res.result.events), 'Expected events array');
|
|
331
291
|
});
|
|
332
292
|
test('audit log supports event_type filter', async () => {
|
|
333
|
-
const
|
|
293
|
+
const fixture = await options.setup_test();
|
|
334
294
|
// Admin offer emits `role_grant_offer_create`. The downstream
|
|
335
295
|
// `role_grant_create` only fires on accept — out of scope for this test.
|
|
336
|
-
const user_two = await
|
|
296
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
337
297
|
const offer_res = await rpc_call_for_spec({
|
|
338
|
-
app:
|
|
298
|
+
app: { request: fixture.transport },
|
|
339
299
|
path: rpc_path,
|
|
340
300
|
spec: role_grant_offer_create_action_spec,
|
|
341
301
|
params: { to_account_id: user_two.account.id, role: grantable_role },
|
|
342
|
-
headers:
|
|
302
|
+
headers: fixture.create_session_headers(),
|
|
343
303
|
});
|
|
344
304
|
assert.ok(offer_res.ok, 'role_grant_offer_create should succeed');
|
|
345
305
|
const res = await rpc_call_for_spec({
|
|
346
|
-
app:
|
|
306
|
+
app: { request: fixture.transport },
|
|
347
307
|
path: rpc_path,
|
|
348
308
|
spec: audit_log_list_action_spec,
|
|
349
309
|
params: { event_type: 'role_grant_offer_create' },
|
|
350
|
-
headers:
|
|
310
|
+
headers: fixture.create_session_headers(),
|
|
351
311
|
});
|
|
352
312
|
assert.ok(res.ok, `audit_log_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
353
313
|
assert.ok(res.result.events.length >= 1, 'Expected at least 1 role_grant_offer_create event');
|
|
@@ -356,23 +316,22 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
356
316
|
}
|
|
357
317
|
});
|
|
358
318
|
test('admin can view role_grant history', async () => {
|
|
359
|
-
const
|
|
319
|
+
const fixture = await options.setup_test();
|
|
360
320
|
// Drive the full consent flow so `role_grant_create` lands in the audit log
|
|
361
321
|
// — `query_audit_log_list_role_grant_history` filters to (role_grant_create, role_grant_revoke).
|
|
362
|
-
const user_two = await
|
|
322
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
363
323
|
await offer_and_accept({
|
|
364
|
-
app:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
to_actor_id: user_two.actor.id,
|
|
324
|
+
app: { request: fixture.transport },
|
|
325
|
+
grantor: fixture,
|
|
326
|
+
recipient: user_two,
|
|
368
327
|
role: grantable_role,
|
|
369
328
|
});
|
|
370
329
|
const res = await rpc_call_for_spec({
|
|
371
|
-
app:
|
|
330
|
+
app: { request: fixture.transport },
|
|
372
331
|
path: rpc_path,
|
|
373
332
|
spec: audit_log_role_grant_history_action_spec,
|
|
374
333
|
params: {},
|
|
375
|
-
headers:
|
|
334
|
+
headers: fixture.create_session_headers(),
|
|
376
335
|
});
|
|
377
336
|
assert.ok(res.ok, `audit_log_role_grant_history failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
378
337
|
assert.ok(res.result.events.length >= 1, 'Expected at least 1 role_grant history event');
|
|
@@ -381,93 +340,94 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
381
340
|
// --- 6. Admin audit trail ---
|
|
382
341
|
describe('admin audit trail', () => {
|
|
383
342
|
test('role_grant revoke creates audit event', async () => {
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
const
|
|
343
|
+
const fixture = await options.setup_test();
|
|
344
|
+
assert(fixture.in_process, 'direct role_grant seed requires in-process db');
|
|
345
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
346
|
+
const role_grant = await create_test_role_grant_direct(fixture.backend_internals.deps.db, {
|
|
387
347
|
actor_id: user_two.actor.id,
|
|
388
348
|
role: grantable_role,
|
|
389
|
-
granted_by:
|
|
349
|
+
granted_by: fixture.actor.id,
|
|
390
350
|
});
|
|
391
351
|
// Revoke via RPC
|
|
392
352
|
const revoke_res = await rpc_call_for_spec({
|
|
393
|
-
app:
|
|
353
|
+
app: { request: fixture.transport },
|
|
394
354
|
path: rpc_path,
|
|
395
355
|
spec: role_grant_revoke_action_spec,
|
|
396
356
|
params: { actor_id: user_two.actor.id, role_grant_id: role_grant.id },
|
|
397
|
-
headers:
|
|
357
|
+
headers: fixture.create_session_headers(),
|
|
398
358
|
});
|
|
399
359
|
assert.ok(revoke_res.ok, `role_grant_revoke failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
400
360
|
// Check audit log for role_grant_revoke event
|
|
401
361
|
const audit_res = await rpc_call_for_spec({
|
|
402
|
-
app:
|
|
362
|
+
app: { request: fixture.transport },
|
|
403
363
|
path: rpc_path,
|
|
404
364
|
spec: audit_log_list_action_spec,
|
|
405
365
|
params: { event_type: 'role_grant_revoke' },
|
|
406
|
-
headers:
|
|
366
|
+
headers: fixture.create_session_headers(),
|
|
407
367
|
});
|
|
408
368
|
assert.ok(audit_res.ok, `audit_log_list failed: ${audit_res.ok ? '' : JSON.stringify(audit_res.error)}`);
|
|
409
369
|
assert.ok(audit_res.result.events.length >= 1, 'Expected role_grant_revoke audit event');
|
|
410
370
|
assert.strictEqual(audit_res.result.events[0].event_type, 'role_grant_revoke');
|
|
411
371
|
});
|
|
412
372
|
test('admin session revoke-all creates audit event', async () => {
|
|
413
|
-
const
|
|
414
|
-
const user_two = await
|
|
373
|
+
const fixture = await options.setup_test();
|
|
374
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
415
375
|
// Revoke all sessions for user_two via RPC
|
|
416
376
|
const revoke_res = await rpc_call_for_spec({
|
|
417
|
-
app:
|
|
377
|
+
app: { request: fixture.transport },
|
|
418
378
|
path: rpc_path,
|
|
419
379
|
spec: admin_session_revoke_all_action_spec,
|
|
420
380
|
params: { account_id: user_two.account.id },
|
|
421
|
-
headers:
|
|
381
|
+
headers: fixture.create_session_headers(),
|
|
422
382
|
});
|
|
423
383
|
assert.ok(revoke_res.ok, `admin_session_revoke_all failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
424
384
|
// Check audit log
|
|
425
385
|
const audit_res = await rpc_call_for_spec({
|
|
426
|
-
app:
|
|
386
|
+
app: { request: fixture.transport },
|
|
427
387
|
path: rpc_path,
|
|
428
388
|
spec: audit_log_list_action_spec,
|
|
429
389
|
params: { event_type: 'session_revoke_all' },
|
|
430
|
-
headers:
|
|
390
|
+
headers: fixture.create_session_headers(),
|
|
431
391
|
});
|
|
432
392
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
433
393
|
assert.ok(audit_res.result.events.length >= 1, 'Expected session_revoke_all audit event');
|
|
434
394
|
assert.strictEqual(audit_res.result.events[0].event_type, 'session_revoke_all');
|
|
435
395
|
});
|
|
436
396
|
test('admin token revoke-all creates audit event', async () => {
|
|
437
|
-
const
|
|
438
|
-
const user_two = await
|
|
397
|
+
const fixture = await options.setup_test();
|
|
398
|
+
const user_two = await fixture.create_account({ username: 'user_two' });
|
|
439
399
|
// Revoke all tokens for user_two via RPC
|
|
440
400
|
const revoke_res = await rpc_call_for_spec({
|
|
441
|
-
app:
|
|
401
|
+
app: { request: fixture.transport },
|
|
442
402
|
path: rpc_path,
|
|
443
403
|
spec: admin_token_revoke_all_action_spec,
|
|
444
404
|
params: { account_id: user_two.account.id },
|
|
445
|
-
headers:
|
|
405
|
+
headers: fixture.create_session_headers(),
|
|
446
406
|
});
|
|
447
407
|
assert.ok(revoke_res.ok, `admin_token_revoke_all failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
448
408
|
// Check audit log
|
|
449
409
|
const audit_res = await rpc_call_for_spec({
|
|
450
|
-
app:
|
|
410
|
+
app: { request: fixture.transport },
|
|
451
411
|
path: rpc_path,
|
|
452
412
|
spec: audit_log_list_action_spec,
|
|
453
413
|
params: { event_type: 'token_revoke_all' },
|
|
454
|
-
headers:
|
|
414
|
+
headers: fixture.create_session_headers(),
|
|
455
415
|
});
|
|
456
416
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
457
417
|
assert.ok(audit_res.result.events.length >= 1, 'Expected token_revoke_all audit event');
|
|
458
418
|
assert.strictEqual(audit_res.result.events[0].event_type, 'token_revoke_all');
|
|
459
419
|
});
|
|
460
420
|
test('admin session revoke-all 404 emits failure audit', async () => {
|
|
461
|
-
const
|
|
421
|
+
const fixture = await options.setup_test();
|
|
462
422
|
// `Uuid = z.uuid()` is v4-strict; use a valid v4 shape so we hit the
|
|
463
423
|
// handler's account lookup rather than failing at param validation.
|
|
464
424
|
const missing_id = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaa01';
|
|
465
425
|
const res = await rpc_call_for_spec({
|
|
466
|
-
app:
|
|
426
|
+
app: { request: fixture.transport },
|
|
467
427
|
path: rpc_path,
|
|
468
428
|
spec: admin_session_revoke_all_action_spec,
|
|
469
429
|
params: { account_id: missing_id },
|
|
470
|
-
headers:
|
|
430
|
+
headers: fixture.create_session_headers(),
|
|
471
431
|
});
|
|
472
432
|
assert.ok(!res.ok, 'Expected 404 for missing account');
|
|
473
433
|
assert.strictEqual(res.status, 404);
|
|
@@ -476,11 +436,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
476
436
|
// `target_account_id` is null (FK prevents referencing a missing id)
|
|
477
437
|
// — the probed id is preserved under `metadata.attempted_account_id`.
|
|
478
438
|
const audit_res = await rpc_call_for_spec({
|
|
479
|
-
app:
|
|
439
|
+
app: { request: fixture.transport },
|
|
480
440
|
path: rpc_path,
|
|
481
441
|
spec: audit_log_list_action_spec,
|
|
482
442
|
params: { event_type: 'session_revoke_all' },
|
|
483
|
-
headers:
|
|
443
|
+
headers: fixture.create_session_headers(),
|
|
484
444
|
});
|
|
485
445
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
486
446
|
const failure = audit_res.result.events.find((e) => e.outcome === 'failure');
|
|
@@ -491,24 +451,24 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
491
451
|
assert.strictEqual(failure_meta.attempted_account_id, missing_id);
|
|
492
452
|
});
|
|
493
453
|
test('admin token revoke-all 404 emits failure audit', async () => {
|
|
494
|
-
const
|
|
454
|
+
const fixture = await options.setup_test();
|
|
495
455
|
const missing_id = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaa02';
|
|
496
456
|
const res = await rpc_call_for_spec({
|
|
497
|
-
app:
|
|
457
|
+
app: { request: fixture.transport },
|
|
498
458
|
path: rpc_path,
|
|
499
459
|
spec: admin_token_revoke_all_action_spec,
|
|
500
460
|
params: { account_id: missing_id },
|
|
501
|
-
headers:
|
|
461
|
+
headers: fixture.create_session_headers(),
|
|
502
462
|
});
|
|
503
463
|
assert.ok(!res.ok, 'Expected 404 for missing account');
|
|
504
464
|
assert.strictEqual(res.status, 404);
|
|
505
465
|
assert.strictEqual(res.error.data.reason, 'account_not_found');
|
|
506
466
|
const audit_res = await rpc_call_for_spec({
|
|
507
|
-
app:
|
|
467
|
+
app: { request: fixture.transport },
|
|
508
468
|
path: rpc_path,
|
|
509
469
|
spec: audit_log_list_action_spec,
|
|
510
470
|
params: { event_type: 'token_revoke_all' },
|
|
511
|
-
headers:
|
|
471
|
+
headers: fixture.create_session_headers(),
|
|
512
472
|
});
|
|
513
473
|
assert.ok(audit_res.ok, 'audit_log_list should succeed');
|
|
514
474
|
const failure = audit_res.result.events.find((e) => e.outcome === 'failure');
|
|
@@ -521,19 +481,19 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
521
481
|
});
|
|
522
482
|
// --- 7. Audit log completeness ---
|
|
523
483
|
describe('audit log completeness', () => {
|
|
524
|
-
test('auth mutations each produce
|
|
525
|
-
const
|
|
526
|
-
const login_route = find_auth_route(
|
|
527
|
-
const logout_route = find_auth_route(
|
|
528
|
-
const password_route = find_auth_route(
|
|
484
|
+
test('auth mutations each produce at least one audit event', async () => {
|
|
485
|
+
const fixture = await options.setup_test();
|
|
486
|
+
const login_route = find_auth_route(route_specs, '/login', 'POST');
|
|
487
|
+
const logout_route = find_auth_route(route_specs, '/logout', 'POST');
|
|
488
|
+
const password_route = find_auth_route(route_specs, '/password', 'POST');
|
|
529
489
|
// skip if required routes are missing (consumer may not wire all routes).
|
|
530
490
|
// Token creation goes through `account_token_create` RPC — always wired
|
|
531
491
|
// because `rpc_endpoints` is required at the suite level.
|
|
532
492
|
if (!login_route || !logout_route || !password_route)
|
|
533
493
|
return;
|
|
534
|
-
const user_two = await
|
|
494
|
+
const user_two = await fixture.create_account({ username: 'audit_user' });
|
|
535
495
|
// 1. login (user_two logs in)
|
|
536
|
-
const login_res = await
|
|
496
|
+
const login_res = await fixture.transport(login_route.path, {
|
|
537
497
|
method: 'POST',
|
|
538
498
|
headers: {
|
|
539
499
|
host: 'localhost',
|
|
@@ -549,7 +509,7 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
549
509
|
const user_two_cookie = cookie_match?.[1];
|
|
550
510
|
// 2. logout (user_two logs out)
|
|
551
511
|
if (user_two_cookie) {
|
|
552
|
-
await
|
|
512
|
+
await fixture.transport(logout_route.path, {
|
|
553
513
|
method: 'POST',
|
|
554
514
|
headers: {
|
|
555
515
|
host: 'localhost',
|
|
@@ -562,34 +522,33 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
562
522
|
// consentful flow: offer + accept so both `role_grant_offer_create` and
|
|
563
523
|
// `role_grant_create` audit events land.
|
|
564
524
|
const { role_grant_id } = await offer_and_accept({
|
|
565
|
-
app:
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
to_actor_id: user_two.actor.id,
|
|
525
|
+
app: { request: fixture.transport },
|
|
526
|
+
grantor: fixture,
|
|
527
|
+
recipient: user_two,
|
|
569
528
|
role: grantable_role,
|
|
570
529
|
});
|
|
571
530
|
// 4. revoke role_grant (RPC)
|
|
572
531
|
const revoke_res = await rpc_call_for_spec({
|
|
573
|
-
app:
|
|
532
|
+
app: { request: fixture.transport },
|
|
574
533
|
path: rpc_path,
|
|
575
534
|
spec: role_grant_revoke_action_spec,
|
|
576
535
|
params: { actor_id: user_two.actor.id, role_grant_id },
|
|
577
|
-
headers:
|
|
536
|
+
headers: fixture.create_session_headers(),
|
|
578
537
|
});
|
|
579
538
|
assert.ok(revoke_res.ok, `role_grant_revoke failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
580
539
|
// 5. create token (RPC)
|
|
581
540
|
const token_res = await rpc_call_for_spec({
|
|
582
|
-
app:
|
|
541
|
+
app: { request: fixture.transport },
|
|
583
542
|
path: rpc_path,
|
|
584
543
|
spec: account_token_create_action_spec,
|
|
585
544
|
params: { name: 'audit-test-token' },
|
|
586
|
-
headers:
|
|
545
|
+
headers: fixture.create_session_headers(),
|
|
587
546
|
});
|
|
588
547
|
assert.ok(token_res.ok, `account_token_create failed: ${token_res.ok ? '' : JSON.stringify(token_res.error)}`);
|
|
589
548
|
// 6. password change
|
|
590
|
-
await
|
|
549
|
+
await fixture.transport(password_route.path, {
|
|
591
550
|
method: 'POST',
|
|
592
|
-
headers:
|
|
551
|
+
headers: fixture.create_session_headers({
|
|
593
552
|
'content-type': 'application/json',
|
|
594
553
|
}),
|
|
595
554
|
body: JSON.stringify({
|
|
@@ -599,7 +558,7 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
599
558
|
});
|
|
600
559
|
// query audit log and verify events
|
|
601
560
|
// re-login as admin since password change revoked sessions
|
|
602
|
-
const relogin_res = await
|
|
561
|
+
const relogin_res = await fixture.transport(login_route.path, {
|
|
603
562
|
method: 'POST',
|
|
604
563
|
headers: {
|
|
605
564
|
host: 'localhost',
|
|
@@ -607,7 +566,7 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
607
566
|
'content-type': 'application/json',
|
|
608
567
|
},
|
|
609
568
|
body: JSON.stringify({
|
|
610
|
-
username:
|
|
569
|
+
username: fixture.account.username,
|
|
611
570
|
password: 'new-audit-password-789',
|
|
612
571
|
}),
|
|
613
572
|
});
|
|
@@ -621,7 +580,7 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
621
580
|
cookie: `${cookie_name}=${relogin_match[1]}`,
|
|
622
581
|
};
|
|
623
582
|
const audit_res = await rpc_call_for_spec({
|
|
624
|
-
app:
|
|
583
|
+
app: { request: fixture.transport },
|
|
625
584
|
path: rpc_path,
|
|
626
585
|
spec: audit_log_list_action_spec,
|
|
627
586
|
params: {},
|
|
@@ -652,23 +611,23 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
652
611
|
// --- 8. Admin-to-admin isolation ---
|
|
653
612
|
describe('admin-to-admin isolation', () => {
|
|
654
613
|
test('admin B revoking own role_grant via RPC succeeds', async () => {
|
|
655
|
-
const
|
|
656
|
-
captured_route_specs ??= test_app.route_specs;
|
|
614
|
+
const fixture = await options.setup_test();
|
|
657
615
|
// Bootstrap user is admin A. Create admin B.
|
|
658
|
-
const admin_b = await
|
|
616
|
+
const admin_b = await fixture.create_account({
|
|
659
617
|
username: 'admin_b_iso',
|
|
660
618
|
roles: ['admin'],
|
|
661
619
|
});
|
|
662
620
|
// Seed an active role_grant directly — the revoke IDOR check is the
|
|
663
621
|
// subject of this test, not the grant→accept cycle.
|
|
664
|
-
|
|
622
|
+
assert(fixture.in_process, 'direct role_grant seed requires in-process db');
|
|
623
|
+
const role_grant = await create_test_role_grant_direct(fixture.backend_internals.deps.db, {
|
|
665
624
|
actor_id: admin_b.actor.id,
|
|
666
625
|
role: grantable_role,
|
|
667
|
-
granted_by:
|
|
626
|
+
granted_by: fixture.actor.id,
|
|
668
627
|
});
|
|
669
628
|
// Admin B revokes their own role_grant via RPC — should succeed
|
|
670
629
|
const revoke_res = await rpc_call_for_spec({
|
|
671
|
-
app:
|
|
630
|
+
app: { request: fixture.transport },
|
|
672
631
|
path: rpc_path,
|
|
673
632
|
spec: role_grant_revoke_action_spec,
|
|
674
633
|
params: { actor_id: admin_b.actor.id, role_grant_id: role_grant.id },
|
|
@@ -678,32 +637,32 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
678
637
|
assert.strictEqual(revoke_res.result.revoked, true);
|
|
679
638
|
});
|
|
680
639
|
test('admin revoke-all sessions for another admin works', async () => {
|
|
681
|
-
const
|
|
682
|
-
const admin_b = await
|
|
640
|
+
const fixture = await options.setup_test();
|
|
641
|
+
const admin_b = await fixture.create_account({
|
|
683
642
|
username: 'admin_b_sess',
|
|
684
643
|
roles: ['admin'],
|
|
685
644
|
});
|
|
686
645
|
// Admin A revokes all of admin B's sessions via RPC
|
|
687
646
|
const res = await rpc_call_for_spec({
|
|
688
|
-
app:
|
|
647
|
+
app: { request: fixture.transport },
|
|
689
648
|
path: rpc_path,
|
|
690
649
|
spec: admin_session_revoke_all_action_spec,
|
|
691
650
|
params: { account_id: admin_b.account.id },
|
|
692
|
-
headers:
|
|
651
|
+
headers: fixture.create_session_headers(),
|
|
693
652
|
});
|
|
694
653
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
695
654
|
assert.ok(typeof res.result.count === 'number', 'Expected count field in response');
|
|
696
655
|
assert.ok(res.result.count >= 1, 'Expected at least 1 session revoked');
|
|
697
656
|
});
|
|
698
657
|
test('admin revoke-all tokens for another admin works', async () => {
|
|
699
|
-
const
|
|
700
|
-
const admin_b = await
|
|
658
|
+
const fixture = await options.setup_test();
|
|
659
|
+
const admin_b = await fixture.create_account({
|
|
701
660
|
username: 'admin_b_tok',
|
|
702
661
|
roles: ['admin'],
|
|
703
662
|
});
|
|
704
663
|
// Admin B creates an API token via RPC
|
|
705
664
|
const token_res = await rpc_call_for_spec({
|
|
706
|
-
app:
|
|
665
|
+
app: { request: fixture.transport },
|
|
707
666
|
path: rpc_path,
|
|
708
667
|
spec: account_token_create_action_spec,
|
|
709
668
|
params: { name: 'admin-b-token' },
|
|
@@ -712,11 +671,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
712
671
|
assert.ok(token_res.ok, `account_token_create failed: ${token_res.ok ? '' : JSON.stringify(token_res.error)}`);
|
|
713
672
|
// Admin A revokes all of admin B's tokens via RPC
|
|
714
673
|
const res = await rpc_call_for_spec({
|
|
715
|
-
app:
|
|
674
|
+
app: { request: fixture.transport },
|
|
716
675
|
path: rpc_path,
|
|
717
676
|
spec: admin_token_revoke_all_action_spec,
|
|
718
677
|
params: { account_id: admin_b.account.id },
|
|
719
|
-
headers:
|
|
678
|
+
headers: fixture.create_session_headers(),
|
|
720
679
|
});
|
|
721
680
|
assert.ok(res.ok, `admin_token_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
722
681
|
assert.ok(typeof res.result.count === 'number', 'Expected count field in response');
|
|
@@ -724,11 +683,11 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
724
683
|
assert.ok(res.result.count >= 1, 'Expected at least 1 token revoked');
|
|
725
684
|
});
|
|
726
685
|
test('non-admin cannot access admin routes for another account', async () => {
|
|
727
|
-
const
|
|
728
|
-
const regular_user = await
|
|
686
|
+
const fixture = await options.setup_test();
|
|
687
|
+
const regular_user = await fixture.create_account({ username: 'regular_user_iso' });
|
|
729
688
|
// Regular user tries to list accounts via the admin RPC — should 403
|
|
730
689
|
const res = await rpc_call_for_spec({
|
|
731
|
-
app:
|
|
690
|
+
app: { request: fixture.transport },
|
|
732
691
|
path: rpc_path,
|
|
733
692
|
spec: admin_account_list_action_spec,
|
|
734
693
|
params: {},
|
|
@@ -741,23 +700,22 @@ export const describe_standard_admin_integration_tests = (options) => {
|
|
|
741
700
|
// --- 8a. Error coverage: unauthenticated access to admin routes ---
|
|
742
701
|
describe('error coverage breadth', () => {
|
|
743
702
|
test('exercises 401/403 on admin routes for error coverage', async () => {
|
|
744
|
-
const
|
|
745
|
-
captured_route_specs ??= test_app.route_specs;
|
|
703
|
+
const fixture = await options.setup_test();
|
|
746
704
|
// `/api/admin` is nearly empty — admin reads and mutations live
|
|
747
705
|
// on the RPC endpoint behind spec-level role auth. The path-prefix carve is still the right scope here
|
|
748
706
|
// because error coverage is tracked against REST `RouteSpec`s,
|
|
749
707
|
// not RPC method specs (`describe_rpc_round_trip_tests` covers
|
|
750
708
|
// the admin RPC surface separately).
|
|
751
709
|
const prefix = options.admin_prefix ?? '/api/admin';
|
|
752
|
-
const admin_routes =
|
|
710
|
+
const admin_routes = route_specs.filter((s) => s.path.startsWith(prefix) && (s.auth.roles?.includes('admin') ?? false));
|
|
753
711
|
// Hit admin routes without auth to exercise 401 error schemas.
|
|
754
712
|
for (const route of admin_routes) {
|
|
755
|
-
const res = await
|
|
713
|
+
const res = await fixture.transport(route.path, {
|
|
756
714
|
method: route.method,
|
|
757
715
|
headers: { host: 'localhost' },
|
|
758
716
|
});
|
|
759
717
|
if (res.status === 401 || res.status === 403) {
|
|
760
|
-
error_collector.record(
|
|
718
|
+
error_collector.record(route_specs, route.method, route.path, res.status);
|
|
761
719
|
}
|
|
762
720
|
}
|
|
763
721
|
});
|