@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
|
@@ -8,15 +8,16 @@ import './assert_dev_env.js';
|
|
|
8
8
|
* - Cross-privilege: verifies admin routes return 403 for non-admin users,
|
|
9
9
|
* and non-admin responses exclude admin-only fields
|
|
10
10
|
*
|
|
11
|
+
* Cadence: per-describe `setup_test()` call (see `round_trip.ts` module
|
|
12
|
+
* docstring). The runtime body manages its own request ordering (auth-free
|
|
13
|
+
* → cross-privilege → 2xx) to avoid session-invalidation contamination
|
|
14
|
+
* between assertions, so per-test fixture re-creation isn't needed.
|
|
15
|
+
*
|
|
11
16
|
* @module
|
|
12
17
|
*/
|
|
13
|
-
import { describe, test, beforeAll,
|
|
18
|
+
import { describe, test, beforeAll, assert } from 'vitest';
|
|
14
19
|
import { ROLE_ADMIN } from '../auth/role_schema.js';
|
|
15
|
-
import { create_test_app } from './app_server.js';
|
|
16
|
-
import { create_pglite_factory } from './db.js';
|
|
17
20
|
import { resolve_valid_path, generate_valid_body } from './schema_generators.js';
|
|
18
|
-
import { run_migrations } from '../db/migrate.js';
|
|
19
|
-
import { auth_migration_ns } from '../auth/migrations.js';
|
|
20
21
|
import { is_null_schema, is_strict_object_schema } from '../http/schema_helpers.js';
|
|
21
22
|
import { is_keeper_auth, is_public_auth } from '../http/auth_shape.js';
|
|
22
23
|
import { sensitive_field_blocklist, admin_only_field_blocklist, assert_no_sensitive_fields_in_json, pick_auth_headers, } from './integration_helpers.js';
|
|
@@ -92,9 +93,11 @@ export const assert_non_admin_schemas_no_admin_fields = (surface, admin_only_fie
|
|
|
92
93
|
* contain no sensitive fields
|
|
93
94
|
*/
|
|
94
95
|
export const describe_data_exposure_tests = (options) => {
|
|
95
|
-
const {
|
|
96
|
+
const { surface, route_specs } = options.surface_source;
|
|
97
|
+
const { sensitive_fields = sensitive_field_blocklist, admin_only_fields = admin_only_field_blocklist, } = options;
|
|
98
|
+
const skip_set = new Set(options.skip_routes);
|
|
99
|
+
void options.capabilities;
|
|
96
100
|
describe('data exposure — schema-level', () => {
|
|
97
|
-
const { surface } = build();
|
|
98
101
|
test('no sensitive fields in any output schema', () => {
|
|
99
102
|
assert_output_schemas_no_sensitive_fields(surface, sensitive_fields);
|
|
100
103
|
});
|
|
@@ -114,151 +117,125 @@ export const describe_data_exposure_tests = (options) => {
|
|
|
114
117
|
}
|
|
115
118
|
});
|
|
116
119
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
127
|
-
for (const factory of factories) {
|
|
128
|
-
const describe_fn = factory.skip ? describe.skip : describe;
|
|
129
|
-
describe_fn(`data exposure — runtime (${factory.name})`, () => {
|
|
130
|
-
let test_app;
|
|
131
|
-
let authed_account;
|
|
132
|
-
let admin_account;
|
|
133
|
-
let db;
|
|
134
|
-
beforeAll(async () => {
|
|
135
|
-
db = await factory.create();
|
|
136
|
-
test_app = await create_test_app({
|
|
137
|
-
session_options: options.session_options,
|
|
138
|
-
create_route_specs: options.create_route_specs,
|
|
139
|
-
db,
|
|
140
|
-
app_options: options.app_options,
|
|
141
|
-
});
|
|
142
|
-
authed_account = await test_app.create_account({
|
|
143
|
-
username: 'exposure_authed',
|
|
144
|
-
roles: [],
|
|
145
|
-
});
|
|
146
|
-
admin_account = await test_app.create_account({
|
|
147
|
-
username: 'exposure_admin',
|
|
148
|
-
roles: [ROLE_ADMIN],
|
|
149
|
-
});
|
|
120
|
+
describe('data exposure — runtime', () => {
|
|
121
|
+
let fixture;
|
|
122
|
+
let authed_account;
|
|
123
|
+
let admin_account;
|
|
124
|
+
beforeAll(async () => {
|
|
125
|
+
fixture = await options.setup_test();
|
|
126
|
+
authed_account = await fixture.create_account({
|
|
127
|
+
username: 'exposure_authed',
|
|
128
|
+
roles: [],
|
|
150
129
|
});
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
130
|
+
admin_account = await fixture.create_account({
|
|
131
|
+
username: 'exposure_admin',
|
|
132
|
+
roles: [ROLE_ADMIN],
|
|
154
133
|
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
let error_body;
|
|
173
|
-
try {
|
|
174
|
-
error_body = await res.clone().json();
|
|
175
|
-
}
|
|
176
|
-
catch {
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
assert_no_sensitive_fields_in_json(error_body, sensitive_fields, `unauthenticated ${route_key} (${res.status})`);
|
|
134
|
+
});
|
|
135
|
+
// Tests that don't fire authenticated requests run first — they don't
|
|
136
|
+
// invalidate sessions and are independent of test order.
|
|
137
|
+
test('unauthenticated error responses contain no sensitive fields', async () => {
|
|
138
|
+
const protected_specs = route_specs.filter((s) => !is_public_auth(s.auth));
|
|
139
|
+
for (const spec of protected_specs) {
|
|
140
|
+
const route_key = `${spec.method} ${spec.path}`;
|
|
141
|
+
if (skip_set.has(route_key))
|
|
142
|
+
continue;
|
|
143
|
+
const url = resolve_valid_path(spec.path, spec.params);
|
|
144
|
+
const res = await fixture.transport(url, {
|
|
145
|
+
method: spec.method,
|
|
146
|
+
headers: { host: 'localhost', origin: 'http://localhost:5173' },
|
|
147
|
+
});
|
|
148
|
+
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
149
|
+
await res.body?.cancel();
|
|
150
|
+
continue;
|
|
180
151
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
test('admin routes return 403 for non-admin user', async () => {
|
|
185
|
-
const admin_specs = test_app.route_specs.filter((s) => s.auth.roles?.includes('admin') ?? false);
|
|
186
|
-
for (const spec of admin_specs) {
|
|
187
|
-
const route_key = `${spec.method} ${spec.path}`;
|
|
188
|
-
if (skip_set.has(route_key))
|
|
189
|
-
continue;
|
|
190
|
-
const url = resolve_valid_path(spec.path, spec.params);
|
|
191
|
-
const headers = authed_account.create_session_headers();
|
|
192
|
-
const res = await test_app.app.request(url, {
|
|
193
|
-
method: spec.method,
|
|
194
|
-
headers,
|
|
195
|
-
});
|
|
196
|
-
assert.strictEqual(res.status, 403, `${route_key} should return 403 for non-admin user`);
|
|
197
|
-
let error_body;
|
|
198
|
-
try {
|
|
199
|
-
error_body = await res.clone().json();
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
assert_no_sensitive_fields_in_json(error_body, sensitive_fields, `${route_key} 403`);
|
|
152
|
+
let error_body;
|
|
153
|
+
try {
|
|
154
|
+
error_body = await res.clone().json();
|
|
205
155
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
156
|
+
catch {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
assert_no_sensitive_fields_in_json(error_body, sensitive_fields, `unauthenticated ${route_key} (${res.status})`);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// Cross-privilege test runs before 2xx tests — admin routes reject
|
|
163
|
+
// without calling handlers, so sessions stay intact.
|
|
164
|
+
test('admin routes return 403 for non-admin user', async () => {
|
|
165
|
+
const admin_specs = route_specs.filter((s) => s.auth.roles?.includes('admin') ?? false);
|
|
166
|
+
for (const spec of admin_specs) {
|
|
167
|
+
const route_key = `${spec.method} ${spec.path}`;
|
|
168
|
+
if (skip_set.has(route_key))
|
|
169
|
+
continue;
|
|
170
|
+
const url = resolve_valid_path(spec.path, spec.params);
|
|
171
|
+
const headers = authed_account.create_session_headers();
|
|
172
|
+
const res = await fixture.transport(url, {
|
|
173
|
+
method: spec.method,
|
|
174
|
+
headers,
|
|
219
175
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const body = generate_valid_body(spec.input);
|
|
228
|
-
const headers = pick_auth_headers(spec, test_app, authed_account, admin_account);
|
|
229
|
-
const request_init = {
|
|
230
|
-
method: spec.method,
|
|
231
|
-
headers: {
|
|
232
|
-
...headers,
|
|
233
|
-
...(body ? { 'content-type': 'application/json' } : {}),
|
|
234
|
-
},
|
|
235
|
-
...(body ? { body: JSON.stringify(body) } : {}),
|
|
236
|
-
};
|
|
237
|
-
const res = await test_app.app.request(url, request_init);
|
|
238
|
-
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
239
|
-
await res.body?.cancel();
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
if (!res.ok)
|
|
243
|
-
continue;
|
|
244
|
-
let response_body;
|
|
245
|
-
try {
|
|
246
|
-
response_body = await res.clone().json();
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
assert_no_sensitive_fields_in_json(response_body, sensitive_fields, `${route_key} (${res.status})`);
|
|
252
|
-
// Admin-only field check applies to non-elevated routes with strict
|
|
253
|
-
// output schemas. Loose schemas (e.g. surface route returning JSON
|
|
254
|
-
// Schema representations) may contain admin field names as metadata.
|
|
255
|
-
if (!is_elevated &&
|
|
256
|
-
!is_null_schema(spec.output) &&
|
|
257
|
-
is_strict_object_schema(spec.output)) {
|
|
258
|
-
assert_no_sensitive_fields_in_json(response_body, admin_only_fields, `non-admin ${route_key} (${res.status})`);
|
|
259
|
-
}
|
|
176
|
+
assert.strictEqual(res.status, 403, `${route_key} should return 403 for non-admin user`);
|
|
177
|
+
let error_body;
|
|
178
|
+
try {
|
|
179
|
+
error_body = await res.clone().json();
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
continue;
|
|
260
183
|
}
|
|
184
|
+
assert_no_sensitive_fields_in_json(error_body, sensitive_fields, `${route_key} 403`);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
// 2xx tests run last — handlers like logout and session-revoke-all
|
|
188
|
+
// invalidate sessions as a side effect. Sort GET before POST so
|
|
189
|
+
// data-returning routes are checked before destructive routes fire.
|
|
190
|
+
test('all 2xx responses pass field blocklists', async () => {
|
|
191
|
+
// sort GET before mutations to check data-returning routes
|
|
192
|
+
// before destructive routes (logout, revoke-all) invalidate sessions
|
|
193
|
+
const sorted_specs = [...route_specs].sort((a, b) => {
|
|
194
|
+
if (a.method === 'GET' && b.method !== 'GET')
|
|
195
|
+
return -1;
|
|
196
|
+
if (a.method !== 'GET' && b.method === 'GET')
|
|
197
|
+
return 1;
|
|
198
|
+
return 0;
|
|
261
199
|
});
|
|
200
|
+
for (const spec of sorted_specs) {
|
|
201
|
+
const route_key = `${spec.method} ${spec.path}`;
|
|
202
|
+
if (skip_set.has(route_key))
|
|
203
|
+
continue;
|
|
204
|
+
// keeper auth (daemon token) is strictly more privileged than admin
|
|
205
|
+
const is_elevated = is_keeper_auth(spec.auth) || (spec.auth.roles?.includes('admin') ?? false);
|
|
206
|
+
const url = resolve_valid_path(spec.path, spec.params);
|
|
207
|
+
const body = generate_valid_body(spec.input);
|
|
208
|
+
const headers = pick_auth_headers(spec, fixture, authed_account, admin_account);
|
|
209
|
+
const request_init = {
|
|
210
|
+
method: spec.method,
|
|
211
|
+
headers: {
|
|
212
|
+
...headers,
|
|
213
|
+
...(body ? { 'content-type': 'application/json' } : {}),
|
|
214
|
+
},
|
|
215
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
216
|
+
};
|
|
217
|
+
const res = await fixture.transport(url, request_init);
|
|
218
|
+
if (res.headers.get('Content-Type')?.includes('text/event-stream')) {
|
|
219
|
+
await res.body?.cancel();
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (!res.ok)
|
|
223
|
+
continue;
|
|
224
|
+
let response_body;
|
|
225
|
+
try {
|
|
226
|
+
response_body = await res.clone().json();
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
assert_no_sensitive_fields_in_json(response_body, sensitive_fields, `${route_key} (${res.status})`);
|
|
232
|
+
// Admin-only field check applies to non-elevated routes with strict
|
|
233
|
+
// output schemas. Loose schemas (e.g. surface route returning JSON
|
|
234
|
+
// Schema representations) may contain admin field names as metadata.
|
|
235
|
+
if (!is_elevated && !is_null_schema(spec.output) && is_strict_object_schema(spec.output)) {
|
|
236
|
+
assert_no_sensitive_fields_in_json(response_body, admin_only_fields, `non-admin ${route_key} (${res.status})`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
262
239
|
});
|
|
263
|
-
}
|
|
240
|
+
});
|
|
264
241
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
|
-
import type { Account, Actor } from '../auth/account_schema.js';
|
|
2
|
+
import type { Account, Actor, CreateRoleGrantInput, RoleGrant } from '../auth/account_schema.js';
|
|
3
3
|
import type { Db } from '../db/db.js';
|
|
4
4
|
/** The `{account, actor}` row pair returned by `create_test_account_with_actor`. */
|
|
5
5
|
export interface TestAccountWithActor {
|
|
@@ -19,4 +19,25 @@ export declare const create_test_account_with_actor: (db: Db, options: {
|
|
|
19
19
|
username: string;
|
|
20
20
|
password_hash?: string;
|
|
21
21
|
}) => Promise<TestAccountWithActor>;
|
|
22
|
+
/**
|
|
23
|
+
* Materialize a `role_grant` directly via `query_create_role_grant`,
|
|
24
|
+
* bypassing the production offer/accept consent flow.
|
|
25
|
+
*
|
|
26
|
+
* **In-process only.** This helper takes a raw `Db` handle and seeds
|
|
27
|
+
* rows without firing audit fan-out, WebSocket broadcasts, or the
|
|
28
|
+
* `_supersede` notification chain a real grant emits. Cross-process
|
|
29
|
+
* suites must instead drive `role_grant_offer_create_action_spec` +
|
|
30
|
+
* `role_grant_offer_accept_action_spec` via
|
|
31
|
+
* `role_grant_helpers.ts`'s `role_grant_offer_and_accept` so the
|
|
32
|
+
* fixture observes the full post-commit fan-out the way production
|
|
33
|
+
* does — otherwise tests would mask real divergence between the TS
|
|
34
|
+
* and Rust spines.
|
|
35
|
+
*
|
|
36
|
+
* Use this helper for query-level (`*.db.test.ts`) tests that
|
|
37
|
+
* exercise revoke or isolation semantics — not the consent path
|
|
38
|
+
* itself. The schema's `source_offer_id = null` shape is an
|
|
39
|
+
* intentional admin-direct escape; this helper exposes it so
|
|
40
|
+
* suites don't reimplement the same direct-seed wrapper.
|
|
41
|
+
*/
|
|
42
|
+
export declare const create_test_role_grant_direct: (db: Db, input: CreateRoleGrantInput) => Promise<RoleGrant>;
|
|
22
43
|
//# sourceMappingURL=db_entities.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db_entities.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db_entities.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"db_entities.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db_entities.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAC,MAAM,2BAA2B,CAAC;AAC/F,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,IAAI,EAAE,EACN,SAAS;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAC,KACjD,OAAO,CAAC,oBAAoB,CAI7B,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,6BAA6B,GACzC,IAAI,EAAE,EACN,OAAO,oBAAoB,KACzB,OAAO,CAAC,SAAS,CAAyC,CAAC"}
|
|
@@ -10,12 +10,14 @@ import './assert_dev_env.js';
|
|
|
10
10
|
* wrapper in every file.
|
|
11
11
|
*
|
|
12
12
|
* For full-fledged test accounts that also need an API token + signed
|
|
13
|
-
* session cookie + role_grants, use `
|
|
13
|
+
* session cookie + role_grants, use `bootstrap_test_keeper` (keeper) or
|
|
14
|
+
* `create_test_account_with_credentials` (additional accounts) from
|
|
14
15
|
* `app_server.ts` instead.
|
|
15
16
|
*
|
|
16
17
|
* @module
|
|
17
18
|
*/
|
|
18
19
|
import { query_create_account_with_actor } from '../auth/account_queries.js';
|
|
20
|
+
import { query_create_role_grant } from '../auth/role_grant_queries.js';
|
|
19
21
|
/**
|
|
20
22
|
* Create an `account` + `actor` row pair in the database for tests.
|
|
21
23
|
*
|
|
@@ -26,3 +28,24 @@ import { query_create_account_with_actor } from '../auth/account_queries.js';
|
|
|
26
28
|
* test suite.
|
|
27
29
|
*/
|
|
28
30
|
export const create_test_account_with_actor = async (db, options) => query_create_account_with_actor({ db }, { username: options.username, password_hash: options.password_hash ?? 'hash' });
|
|
31
|
+
/**
|
|
32
|
+
* Materialize a `role_grant` directly via `query_create_role_grant`,
|
|
33
|
+
* bypassing the production offer/accept consent flow.
|
|
34
|
+
*
|
|
35
|
+
* **In-process only.** This helper takes a raw `Db` handle and seeds
|
|
36
|
+
* rows without firing audit fan-out, WebSocket broadcasts, or the
|
|
37
|
+
* `_supersede` notification chain a real grant emits. Cross-process
|
|
38
|
+
* suites must instead drive `role_grant_offer_create_action_spec` +
|
|
39
|
+
* `role_grant_offer_accept_action_spec` via
|
|
40
|
+
* `role_grant_helpers.ts`'s `role_grant_offer_and_accept` so the
|
|
41
|
+
* fixture observes the full post-commit fan-out the way production
|
|
42
|
+
* does — otherwise tests would mask real divergence between the TS
|
|
43
|
+
* and Rust spines.
|
|
44
|
+
*
|
|
45
|
+
* Use this helper for query-level (`*.db.test.ts`) tests that
|
|
46
|
+
* exercise revoke or isolation semantics — not the consent path
|
|
47
|
+
* itself. The schema's `source_offer_id = null` shape is an
|
|
48
|
+
* intentional admin-direct escape; this helper exposes it so
|
|
49
|
+
* suites don't reimplement the same direct-seed wrapper.
|
|
50
|
+
*/
|
|
51
|
+
export const create_test_role_grant_direct = async (db, input) => query_create_role_grant({ db }, input);
|
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
2
|
import type { SessionOptions } from '../auth/session_cookie.js';
|
|
3
|
-
import type { AppServerContext } from '../server/app_server.js';
|
|
4
|
-
import type { RouteSpec } from '../http/route_spec.js';
|
|
5
|
-
import { type SuiteAppOptions } from './app_server.js';
|
|
6
|
-
import { type DbFactory } from './db.js';
|
|
7
3
|
import { type RpcEndpointsSuiteOption } from './rpc_helpers.js';
|
|
4
|
+
import type { AppSurfaceSpec } from '../http/surface.js';
|
|
5
|
+
import { type BackendCapabilities } from './cross_backend/capabilities.js';
|
|
6
|
+
import type { SetupTest } from './cross_backend/setup.js';
|
|
8
7
|
/**
|
|
9
8
|
* Configuration for `describe_standard_integration_tests`.
|
|
10
9
|
*/
|
|
11
10
|
export interface StandardIntegrationTestOptions {
|
|
12
|
-
/** Session config for cookie-based auth. */
|
|
13
|
-
session_options: SessionOptions<string>;
|
|
14
|
-
/** Route spec factory — same one used in production. */
|
|
15
|
-
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
16
|
-
/** Optional overrides for `AppServerOptions`. */
|
|
17
|
-
app_options?: SuiteAppOptions;
|
|
18
11
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
12
|
+
* Per-test fixture-producing function. The integration suite calls
|
|
13
|
+
* this in every `test()` body — auth_integration_truncate_tables
|
|
14
|
+
* clears `account`, so each test re-bootstraps the keeper.
|
|
15
|
+
*/
|
|
16
|
+
setup_test: SetupTest;
|
|
17
|
+
/**
|
|
18
|
+
* App surface (with route specs + middleware specs) for route iteration
|
|
19
|
+
* and error-coverage scoping. The same shape feeds both in-process and
|
|
20
|
+
* cross-process tests — the test process always constructs the spec in
|
|
21
|
+
* TS (via `create_test_app_surface_spec` or a consumer's equivalent);
|
|
22
|
+
* cross-process-ness is a property of the transport + per-test fixture,
|
|
23
|
+
* not the schema source. The on-disk `auth_attack_surface.json` is an
|
|
24
|
+
* observability artifact for human inspection + gen-time drift gating,
|
|
25
|
+
* not the source the test runtime reads from.
|
|
21
26
|
*/
|
|
22
|
-
|
|
27
|
+
surface_source: AppSurfaceSpec;
|
|
28
|
+
/** Backend capability declarations — companion to `fixture.in_process` narrowing. */
|
|
29
|
+
capabilities: BackendCapabilities;
|
|
30
|
+
/**
|
|
31
|
+
* Session config — needed to resolve factory-form `rpc_endpoints`
|
|
32
|
+
* against a stub `AppServerContext` at setup time and to read
|
|
33
|
+
* `cookie_name` for manual cookie composition in the origin-verify
|
|
34
|
+
* cases.
|
|
35
|
+
*/
|
|
36
|
+
session_options: SessionOptions<string>;
|
|
23
37
|
/**
|
|
24
38
|
* RPC endpoint specs — required. This suite dispatches
|
|
25
39
|
* `account_verify`, `account_session_*`, and `account_token_*` via
|
|
@@ -28,16 +42,21 @@ export interface StandardIntegrationTestOptions {
|
|
|
28
42
|
* `require_rpc_endpoint_path` on setup so consumer projects see a
|
|
29
43
|
* clear setup error instead of confusing test failures.
|
|
30
44
|
*
|
|
31
|
-
* Accepts either an array (eager) or a factory
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `create_standard_rpc_actions(ctx.deps, {app_settings: ctx.app_settings})`
|
|
36
|
-
* pattern). The factory must return the same endpoint `path` regardless
|
|
37
|
-
* of ctx — it is invoked once at setup with a stub ctx for path lookup
|
|
38
|
-
* and again per-test by `create_app_server` for live dispatch.
|
|
45
|
+
* Accepts either an array (eager) or a factory — see `rpc_helpers.ts`
|
|
46
|
+
* for the union semantics. The factory must return the same endpoint
|
|
47
|
+
* `path` regardless of ctx — invoked once at setup with a stub ctx
|
|
48
|
+
* for path lookup; the running backend handles live dispatch.
|
|
39
49
|
*/
|
|
40
50
|
rpc_endpoints: RpcEndpointsSuiteOption;
|
|
51
|
+
/**
|
|
52
|
+
* Minimum error-coverage ratio to enforce on the scoped REST surface
|
|
53
|
+
* (login / logout / password / signup + the shared RPC endpoint).
|
|
54
|
+
* Default `DEFAULT_INTEGRATION_ERROR_COVERAGE` (0.2). Set to `0` to
|
|
55
|
+
* skip the assertion entirely — useful for consumers with minimal
|
|
56
|
+
* route sets whose declared error codes outpace the suite's
|
|
57
|
+
* denial-path drivers.
|
|
58
|
+
*/
|
|
59
|
+
error_coverage_min?: number;
|
|
41
60
|
}
|
|
42
61
|
/**
|
|
43
62
|
* Standard integration test suite for fuz_app auth routes.
|
|
@@ -51,6 +70,22 @@ export interface StandardIntegrationTestOptions {
|
|
|
51
70
|
* Each test group asserts that required routes exist, failing with a descriptive
|
|
52
71
|
* message if the consumer's route specs are misconfigured.
|
|
53
72
|
*
|
|
73
|
+
* The two signup-invite-edge-case tests call `invite_create_action_spec`
|
|
74
|
+
* (admin-gated) over the fixture's session, so consumers wiring signup +
|
|
75
|
+
* admin actions must thread `extra_keeper_roles: [ROLE_ADMIN]` through
|
|
76
|
+
* either `default_in_process_suite_options` or
|
|
77
|
+
* `default_cross_process_setup(handle, {extra_keeper_roles:
|
|
78
|
+
* [ROLE_ADMIN]})`. In both modes the fixture's `fixture.account` is the
|
|
79
|
+
* fresh keeper, and the extra-keeper-roles list grants the bootstrapped
|
|
80
|
+
* keeper additional roles inline (cross-process via `_testing_reset`'s
|
|
81
|
+
* bootstrap-time seeding; in-process via `bootstrap_test_keeper`) — no
|
|
82
|
+
* per-role RPC cost, no offer/accept round-trip. The tests run against
|
|
83
|
+
* the production `open_signup: false` default — the cross-process
|
|
84
|
+
* per-test secondary mint via `fixture.create_account()` is
|
|
85
|
+
* invite-gated (keeper drives `invite_create` before signup) so the
|
|
86
|
+
* harness doesn't need to flip the setting. Consumers that don't wire
|
|
87
|
+
* signup or `invite_create` silently skip these two tests.
|
|
88
|
+
*
|
|
54
89
|
* @throws Error at setup time when `options.rpc_endpoints` is empty — the
|
|
55
90
|
* suite hard-fails via `require_rpc_endpoint_path` rather than running
|
|
56
91
|
* tests that would crash mid-suite trying to dispatch
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAO9D,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAU,KAAK,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AAClF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,qFAAqF;IACrF,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;;OAKG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAy5CF,CAAC"}
|