@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
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Structural diff between two snapshots — empty array means parity holds.
|
|
4
|
+
*
|
|
5
|
+
* Order of diffs is deterministic: schema_version first, then tables in
|
|
6
|
+
* sorted order (with column/index/constraint sub-diffs grouped per table),
|
|
7
|
+
* then sequences. Consumers can rely on this for stable diff output.
|
|
8
|
+
*/
|
|
9
|
+
export const diff_schema_snapshots = (a, b) => {
|
|
10
|
+
const diffs = [];
|
|
11
|
+
diff_schema_version(a.schema_version, b.schema_version, diffs);
|
|
12
|
+
const all_tables = new Set([...Object.keys(a.tables), ...Object.keys(b.tables)]);
|
|
13
|
+
for (const table of [...all_tables].sort()) {
|
|
14
|
+
const ta = a.tables[table];
|
|
15
|
+
const tb = b.tables[table];
|
|
16
|
+
if (!ta) {
|
|
17
|
+
diffs.push({ kind: 'table_only_in', where: 'b', table });
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (!tb) {
|
|
21
|
+
diffs.push({ kind: 'table_only_in', where: 'a', table });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
diff_table(table, ta, tb, diffs);
|
|
25
|
+
}
|
|
26
|
+
const all_sequences = new Set([...Object.keys(a.sequences), ...Object.keys(b.sequences)]);
|
|
27
|
+
for (const sequence of [...all_sequences].sort()) {
|
|
28
|
+
const sa = a.sequences[sequence];
|
|
29
|
+
const sb = b.sequences[sequence];
|
|
30
|
+
if (!sa) {
|
|
31
|
+
diffs.push({ kind: 'sequence_only_in', where: 'b', sequence });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!sb) {
|
|
35
|
+
diffs.push({ kind: 'sequence_only_in', where: 'a', sequence });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
diff_sequence(sequence, sa, sb, diffs);
|
|
39
|
+
}
|
|
40
|
+
return diffs;
|
|
41
|
+
};
|
|
42
|
+
const diff_schema_version = (a, b, out) => {
|
|
43
|
+
const key = (r) => `${r.namespace}\x00${r.name}`;
|
|
44
|
+
const a_by_key = new Map(a.map((r) => [key(r), r]));
|
|
45
|
+
const b_by_key = new Map(b.map((r) => [key(r), r]));
|
|
46
|
+
const keys = new Set([...a_by_key.keys(), ...b_by_key.keys()]);
|
|
47
|
+
for (const k of [...keys].sort()) {
|
|
48
|
+
const row_a = a_by_key.get(k);
|
|
49
|
+
const row_b = b_by_key.get(k);
|
|
50
|
+
if (!row_a) {
|
|
51
|
+
out.push({ kind: 'schema_version_only_in', where: 'b', row: row_b });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (!row_b) {
|
|
55
|
+
out.push({ kind: 'schema_version_only_in', where: 'a', row: row_a });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (row_a.sequence !== row_b.sequence) {
|
|
59
|
+
out.push({
|
|
60
|
+
kind: 'schema_version_sequence_differs',
|
|
61
|
+
namespace: row_a.namespace,
|
|
62
|
+
name: row_a.name,
|
|
63
|
+
a: row_a.sequence,
|
|
64
|
+
b: row_b.sequence,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const COLUMN_FIELDS = [
|
|
70
|
+
'data_type',
|
|
71
|
+
'udt_name',
|
|
72
|
+
'is_nullable',
|
|
73
|
+
'column_default',
|
|
74
|
+
'is_identity',
|
|
75
|
+
];
|
|
76
|
+
const diff_table = (table, a, b, out) => {
|
|
77
|
+
const all_columns = new Set([...Object.keys(a.columns), ...Object.keys(b.columns)]);
|
|
78
|
+
for (const column of [...all_columns].sort()) {
|
|
79
|
+
const ca = a.columns[column];
|
|
80
|
+
const cb = b.columns[column];
|
|
81
|
+
if (!ca) {
|
|
82
|
+
out.push({ kind: 'column_only_in', where: 'b', table, column });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!cb) {
|
|
86
|
+
out.push({ kind: 'column_only_in', where: 'a', table, column });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
for (const field of COLUMN_FIELDS) {
|
|
90
|
+
if (ca[field] !== cb[field]) {
|
|
91
|
+
out.push({
|
|
92
|
+
kind: 'column_field_differs',
|
|
93
|
+
table,
|
|
94
|
+
column,
|
|
95
|
+
field,
|
|
96
|
+
a: ca[field],
|
|
97
|
+
b: cb[field],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const a_indexes = new Map(a.indexes.map((i) => [i.name, i.definition]));
|
|
103
|
+
const b_indexes = new Map(b.indexes.map((i) => [i.name, i.definition]));
|
|
104
|
+
const all_indexes = new Set([...a_indexes.keys(), ...b_indexes.keys()]);
|
|
105
|
+
for (const index of [...all_indexes].sort()) {
|
|
106
|
+
const def_a = a_indexes.get(index);
|
|
107
|
+
const def_b = b_indexes.get(index);
|
|
108
|
+
if (def_a === undefined) {
|
|
109
|
+
out.push({ kind: 'index_only_in', where: 'b', table, index });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (def_b === undefined) {
|
|
113
|
+
out.push({ kind: 'index_only_in', where: 'a', table, index });
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (def_a !== def_b) {
|
|
117
|
+
out.push({ kind: 'index_definition_differs', table, index, a: def_a, b: def_b });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const a_constraints = new Map(a.constraints.map((c) => [c.name, c]));
|
|
121
|
+
const b_constraints = new Map(b.constraints.map((c) => [c.name, c]));
|
|
122
|
+
const all_constraints = new Set([...a_constraints.keys(), ...b_constraints.keys()]);
|
|
123
|
+
for (const constraint of [...all_constraints].sort()) {
|
|
124
|
+
const ca = a_constraints.get(constraint);
|
|
125
|
+
const cb = b_constraints.get(constraint);
|
|
126
|
+
if (!ca) {
|
|
127
|
+
out.push({ kind: 'constraint_only_in', where: 'b', table, constraint });
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!cb) {
|
|
131
|
+
out.push({ kind: 'constraint_only_in', where: 'a', table, constraint });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (ca.type !== cb.type || ca.definition !== cb.definition) {
|
|
135
|
+
out.push({
|
|
136
|
+
kind: 'constraint_differs',
|
|
137
|
+
table,
|
|
138
|
+
constraint,
|
|
139
|
+
a: { type: ca.type, definition: ca.definition },
|
|
140
|
+
b: { type: cb.type, definition: cb.definition },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const diff_sequence = (sequence, a, b, out) => {
|
|
146
|
+
if (a.data_type !== b.data_type) {
|
|
147
|
+
out.push({
|
|
148
|
+
kind: 'sequence_data_type_differs',
|
|
149
|
+
sequence,
|
|
150
|
+
a: a.data_type,
|
|
151
|
+
b: b.data_type,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Render a diff list as a human-readable multi-line string. Empty diffs
|
|
157
|
+
* produce an empty string.
|
|
158
|
+
*/
|
|
159
|
+
export const format_schema_diffs = (diffs, labels = {}) => {
|
|
160
|
+
if (diffs.length === 0)
|
|
161
|
+
return '';
|
|
162
|
+
const label_a = labels.a ?? 'a';
|
|
163
|
+
const label_b = labels.b ?? 'b';
|
|
164
|
+
const where_label = (where) => (where === 'a' ? label_a : label_b);
|
|
165
|
+
const lines = [];
|
|
166
|
+
for (const d of diffs) {
|
|
167
|
+
switch (d.kind) {
|
|
168
|
+
case 'schema_version_only_in':
|
|
169
|
+
lines.push(` schema_version row only in ${where_label(d.where)}: ${d.row.namespace}/${d.row.name} (sequence ${d.row.sequence})`);
|
|
170
|
+
break;
|
|
171
|
+
case 'schema_version_sequence_differs':
|
|
172
|
+
lines.push(` schema_version sequence differs for ${d.namespace}/${d.name}: ${label_a}=${d.a}, ${label_b}=${d.b}`);
|
|
173
|
+
break;
|
|
174
|
+
case 'table_only_in':
|
|
175
|
+
lines.push(` table ${d.table} only in ${where_label(d.where)}`);
|
|
176
|
+
break;
|
|
177
|
+
case 'column_only_in':
|
|
178
|
+
lines.push(` ${d.table}.${d.column} only in ${where_label(d.where)}`);
|
|
179
|
+
break;
|
|
180
|
+
case 'column_field_differs':
|
|
181
|
+
lines.push(` ${d.table}.${d.column} ${d.field} differs: ${label_a}=${JSON.stringify(d.a)}, ${label_b}=${JSON.stringify(d.b)}`);
|
|
182
|
+
break;
|
|
183
|
+
case 'index_only_in':
|
|
184
|
+
lines.push(` index ${d.index} on ${d.table} only in ${where_label(d.where)}`);
|
|
185
|
+
break;
|
|
186
|
+
case 'index_definition_differs':
|
|
187
|
+
lines.push(` index ${d.index} on ${d.table} differs:\n ${label_a}: ${d.a}\n ${label_b}: ${d.b}`);
|
|
188
|
+
break;
|
|
189
|
+
case 'constraint_only_in':
|
|
190
|
+
lines.push(` constraint ${d.constraint} on ${d.table} only in ${where_label(d.where)}`);
|
|
191
|
+
break;
|
|
192
|
+
case 'constraint_differs':
|
|
193
|
+
lines.push(` constraint ${d.constraint} on ${d.table} differs:\n ${label_a}: ${d.a.type} ${d.a.definition}\n ${label_b}: ${d.b.type} ${d.b.definition}`);
|
|
194
|
+
break;
|
|
195
|
+
case 'sequence_only_in':
|
|
196
|
+
lines.push(` sequence ${d.sequence} only in ${where_label(d.where)}`);
|
|
197
|
+
break;
|
|
198
|
+
case 'sequence_data_type_differs':
|
|
199
|
+
lines.push(` sequence ${d.sequence} data_type differs: ${label_a}=${d.a}, ${label_b}=${d.b}`);
|
|
200
|
+
break;
|
|
201
|
+
default:
|
|
202
|
+
// Compile-time exhaustiveness — a new SchemaDiff variant without a
|
|
203
|
+
// case here makes `d` non-never and fails type-check.
|
|
204
|
+
d;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return lines.join('\n');
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Throw if the two snapshots disagree. The error message names the impls
|
|
212
|
+
* (via `labels`) and lists every diff, so the failure is self-diagnosing.
|
|
213
|
+
*
|
|
214
|
+
* Consumers wire this after bootstrapping each impl against an isolated DB:
|
|
215
|
+
*
|
|
216
|
+
* ```ts
|
|
217
|
+
* await drop_recreate_db('zzz_test');
|
|
218
|
+
* await spawn_backend(deno_config);
|
|
219
|
+
* const snapshot_deno = await query_schema_snapshot(db, {});
|
|
220
|
+
* await drop_recreate_db('zzz_test');
|
|
221
|
+
* await spawn_backend(rust_config);
|
|
222
|
+
* const snapshot_rust = await query_schema_snapshot(db, {});
|
|
223
|
+
* assert_schema_snapshots_equal(snapshot_deno, snapshot_rust, {a: 'deno', b: 'rust'});
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
export const assert_schema_snapshots_equal = (a, b, labels = {}) => {
|
|
227
|
+
const diffs = diff_schema_snapshots(a, b);
|
|
228
|
+
if (diffs.length === 0)
|
|
229
|
+
return;
|
|
230
|
+
const label_a = labels.a ?? 'a';
|
|
231
|
+
const label_b = labels.b ?? 'b';
|
|
232
|
+
throw new Error(`Schema parity failed: ${diffs.length} diff(s) between ${label_a} and ${label_b}\n${format_schema_diffs(diffs, labels)}`);
|
|
233
|
+
};
|
|
@@ -2,59 +2,91 @@ import './assert_dev_env.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Combined standard test suite helper.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Bundles every DB-backed suite carrying the standard option shape, each
|
|
6
|
+
* gated on its relevant config — silent-skip when the gate isn't met
|
|
7
|
+
* (same precedent as `describe_standard_admin_integration_tests` skipping
|
|
8
|
+
* when `roles` isn't provided). Consumers wire the standard surface in
|
|
9
|
+
* one call instead of seven; forgetting a suite no longer silently loses
|
|
10
|
+
* coverage.
|
|
11
|
+
*
|
|
12
|
+
* Attack surface suites stay separate — their option shape is
|
|
13
|
+
* `{build, snapshot_path, expected_public_routes, ...}` rather than
|
|
14
|
+
* `{setup_test, surface_source, capabilities}`. A peer bundler lives
|
|
15
|
+
* for that side if/when needed.
|
|
8
16
|
*
|
|
9
17
|
* @module
|
|
10
18
|
*/
|
|
11
19
|
import type { SessionOptions } from '../auth/session_cookie.js';
|
|
12
|
-
import type { AppServerContext, AppServerOptions } from '../server/app_server.js';
|
|
13
|
-
import type { RouteSpec } from '../http/route_spec.js';
|
|
14
20
|
import type { RoleSchemaResult } from '../auth/role_schema.js';
|
|
15
|
-
import type {
|
|
21
|
+
import type { AppServerContext, BootstrapServerOptions } from '../server/app_server.js';
|
|
22
|
+
import type { RouteSpec } from '../http/route_spec.js';
|
|
16
23
|
import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
|
|
24
|
+
import type { BackendCapabilities } from './cross_backend/capabilities.js';
|
|
25
|
+
import type { SetupTest } from './cross_backend/setup.js';
|
|
26
|
+
import type { SurfaceSource } from './transports/surface_source.js';
|
|
27
|
+
import type { SuiteAppOptions } from './app_server.js';
|
|
17
28
|
/**
|
|
18
29
|
* Configuration for `describe_standard_tests`.
|
|
19
30
|
*/
|
|
20
31
|
export interface StandardTestOptions {
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
/** Route spec factory — same one used in production. */
|
|
24
|
-
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
25
|
-
/** Optional overrides for `AppServerOptions`. */
|
|
26
|
-
app_options?: Partial<Omit<AppServerOptions, 'backend' | 'session_options' | 'create_route_specs'>>;
|
|
32
|
+
/** Per-test fixture-producing function. */
|
|
33
|
+
setup_test: SetupTest;
|
|
27
34
|
/**
|
|
28
|
-
*
|
|
35
|
+
* Source of the app surface. Currently requires `kind: 'inline'` —
|
|
36
|
+
* the cross-process snapshot variant lands alongside the spawned-backend
|
|
37
|
+
* transport plumbing.
|
|
29
38
|
*/
|
|
30
|
-
|
|
39
|
+
surface_source: SurfaceSource;
|
|
40
|
+
/** Backend capability declarations. */
|
|
41
|
+
capabilities: BackendCapabilities;
|
|
42
|
+
/** Session config — needed for cookie_name + factory-form rpc_endpoints resolution. */
|
|
43
|
+
session_options: SessionOptions<string>;
|
|
31
44
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
45
|
+
* Route spec factory — same one used in production. Required by
|
|
46
|
+
* `describe_rate_limiting_tests`, which builds a fresh `TestApp` per test
|
|
47
|
+
* (bypasses the shared `setup_test` fixture) so it can pass tight
|
|
48
|
+
* per-test rate-limiter overrides.
|
|
34
49
|
*/
|
|
35
|
-
|
|
50
|
+
create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
|
|
36
51
|
/**
|
|
37
52
|
* RPC endpoint specs — required. The standard integration tests drive
|
|
38
53
|
* `account_verify`, `account_session_*`, `account_token_*` through the
|
|
39
54
|
* RPC surface (and admin tests, when wired, drive role_grant grant/revoke
|
|
40
55
|
* through it too).
|
|
41
|
-
*
|
|
42
|
-
* Accepts either an array (eager) or a factory
|
|
43
|
-
* `(ctx: AppServerContext) => Array<RpcEndpointSpec>`. Round-tripped
|
|
44
|
-
* to both sub-suites unchanged — see their docstrings for the full
|
|
45
|
-
* factory-form contract.
|
|
46
56
|
*/
|
|
47
57
|
rpc_endpoints: RpcEndpointsSuiteOption;
|
|
58
|
+
/**
|
|
59
|
+
* Role schema result from `create_role_schema()`.
|
|
60
|
+
* When provided, admin integration + audit completeness suites are included.
|
|
61
|
+
*/
|
|
62
|
+
roles?: RoleSchemaResult;
|
|
63
|
+
/**
|
|
64
|
+
* Bootstrap config — when set to `mode: 'live'`, the bootstrap success
|
|
65
|
+
* suite runs against `create_test_app_for_bootstrap`. Other modes
|
|
66
|
+
* (`'disabled'` / `'surface_only'` / omission) silent-skip the suite.
|
|
67
|
+
*/
|
|
68
|
+
bootstrap?: BootstrapServerOptions;
|
|
69
|
+
/** Optional overrides forwarded to `describe_rate_limiting_tests`. */
|
|
70
|
+
rate_limiting_app_options?: SuiteAppOptions;
|
|
48
71
|
/**
|
|
49
72
|
* Path prefix where admin routes are mounted.
|
|
50
73
|
* Default `'/api/admin'`.
|
|
51
74
|
*/
|
|
52
75
|
admin_prefix?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Forwarded to `describe_standard_integration_tests` — overrides the
|
|
78
|
+
* default error-coverage threshold on the scoped REST surface. Set to
|
|
79
|
+
* `0` to skip the assertion entirely.
|
|
80
|
+
*/
|
|
81
|
+
error_coverage_min?: number;
|
|
82
|
+
/** Override the bootstrap-success suite's synthetic token. */
|
|
83
|
+
bootstrap_token?: string;
|
|
53
84
|
}
|
|
54
85
|
/**
|
|
55
|
-
* Run
|
|
56
|
-
*
|
|
57
|
-
*
|
|
86
|
+
* Run the full standard test bundle — integration, admin (when `roles`
|
|
87
|
+
* provided), audit completeness (when `roles` provided), bootstrap
|
|
88
|
+
* success (when `bootstrap.mode === 'live'`), round trip, RPC round
|
|
89
|
+
* trip, data exposure, rate limiting.
|
|
58
90
|
*/
|
|
59
91
|
export declare const describe_standard_tests: (options: StandardTestOptions) => void;
|
|
60
92
|
//# sourceMappingURL=standard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/standard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B
|
|
1
|
+
{"version":3,"file":"standard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/standard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAC,gBAAgB,EAAE,sBAAsB,EAAC,MAAM,yBAAyB,CAAC;AACtF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AASrD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,gCAAgC,CAAC;AAClE,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,iBAAiB,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,2CAA2C;IAC3C,UAAU,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,cAAc,EAAE,aAAa,CAAC;IAC9B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,uFAAuF;IACvF,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE;;;;;OAKG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,sEAAsE;IACtE,yBAAyB,CAAC,EAAE,eAAe,CAAC;IAC5C;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,SAAS,mBAAmB,KAAG,IA2DtE,CAAC"}
|
package/dist/testing/standard.js
CHANGED
|
@@ -1,18 +1,75 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
2
|
import { describe_standard_integration_tests } from './integration.js';
|
|
3
3
|
import { describe_standard_admin_integration_tests } from './admin_integration.js';
|
|
4
|
+
import { describe_round_trip_validation } from './round_trip.js';
|
|
5
|
+
import { describe_data_exposure_tests } from './data_exposure.js';
|
|
6
|
+
import { describe_rpc_round_trip_tests } from './rpc_round_trip.js';
|
|
7
|
+
import { describe_audit_completeness_tests } from './audit_completeness.js';
|
|
8
|
+
import { describe_rate_limiting_tests } from './rate_limiting.js';
|
|
9
|
+
import { describe_bootstrap_success_tests } from './bootstrap_success.js';
|
|
4
10
|
/**
|
|
5
|
-
* Run
|
|
6
|
-
*
|
|
7
|
-
*
|
|
11
|
+
* Run the full standard test bundle — integration, admin (when `roles`
|
|
12
|
+
* provided), audit completeness (when `roles` provided), bootstrap
|
|
13
|
+
* success (when `bootstrap.mode === 'live'`), round trip, RPC round
|
|
14
|
+
* trip, data exposure, rate limiting.
|
|
8
15
|
*/
|
|
9
16
|
export const describe_standard_tests = (options) => {
|
|
10
|
-
describe_standard_integration_tests(
|
|
17
|
+
describe_standard_integration_tests({
|
|
18
|
+
setup_test: options.setup_test,
|
|
19
|
+
surface_source: options.surface_source,
|
|
20
|
+
capabilities: options.capabilities,
|
|
21
|
+
session_options: options.session_options,
|
|
22
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
23
|
+
error_coverage_min: options.error_coverage_min,
|
|
24
|
+
});
|
|
25
|
+
describe_round_trip_validation({
|
|
26
|
+
setup_test: options.setup_test,
|
|
27
|
+
surface_source: options.surface_source,
|
|
28
|
+
capabilities: options.capabilities,
|
|
29
|
+
});
|
|
30
|
+
describe_rpc_round_trip_tests({
|
|
31
|
+
setup_test: options.setup_test,
|
|
32
|
+
surface_source: options.surface_source,
|
|
33
|
+
capabilities: options.capabilities,
|
|
34
|
+
session_options: options.session_options,
|
|
35
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
36
|
+
});
|
|
37
|
+
describe_data_exposure_tests({
|
|
38
|
+
setup_test: options.setup_test,
|
|
39
|
+
surface_source: options.surface_source,
|
|
40
|
+
capabilities: options.capabilities,
|
|
41
|
+
});
|
|
42
|
+
describe_rate_limiting_tests({
|
|
43
|
+
session_options: options.session_options,
|
|
44
|
+
create_route_specs: options.create_route_specs,
|
|
45
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
46
|
+
app_options: options.rate_limiting_app_options,
|
|
47
|
+
});
|
|
11
48
|
if (options.roles) {
|
|
12
49
|
describe_standard_admin_integration_tests({
|
|
13
|
-
|
|
50
|
+
setup_test: options.setup_test,
|
|
51
|
+
surface_source: options.surface_source,
|
|
52
|
+
capabilities: options.capabilities,
|
|
53
|
+
session_options: options.session_options,
|
|
14
54
|
roles: options.roles,
|
|
15
55
|
rpc_endpoints: options.rpc_endpoints,
|
|
56
|
+
admin_prefix: options.admin_prefix,
|
|
57
|
+
});
|
|
58
|
+
describe_audit_completeness_tests({
|
|
59
|
+
setup_test: options.setup_test,
|
|
60
|
+
surface_source: options.surface_source,
|
|
61
|
+
capabilities: options.capabilities,
|
|
62
|
+
session_options: options.session_options,
|
|
63
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (options.bootstrap?.mode === 'live') {
|
|
67
|
+
describe_bootstrap_success_tests({
|
|
68
|
+
session_options: options.session_options,
|
|
69
|
+
create_route_specs: options.create_route_specs,
|
|
70
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
71
|
+
bootstrap: options.bootstrap,
|
|
72
|
+
bootstrap_token: options.bootstrap_token,
|
|
16
73
|
});
|
|
17
74
|
}
|
|
18
75
|
};
|
package/dist/testing/stubs.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { SessionOptions } from '../auth/session_cookie.js';
|
|
|
4
4
|
import type { MiddlewareSpec } from '../http/middleware_spec.js';
|
|
5
5
|
import type { AppDeps } from '../auth/deps.js';
|
|
6
6
|
import type { AuditEmitter } from '../auth/audit_emitter.js';
|
|
7
|
-
import type { AppServerContext } from '../server/app_server.js';
|
|
7
|
+
import type { AppServerContext, BootstrapServerOptions } from '../server/app_server.js';
|
|
8
8
|
import { Db } from '../db/db.js';
|
|
9
9
|
import { type RouteSpec } from '../http/route_spec.js';
|
|
10
10
|
import { type AppSurfaceSpec, type RpcEndpointSpec } from '../http/surface.js';
|
|
@@ -129,8 +129,16 @@ export interface CreateTestAppSurfaceSpecOptions {
|
|
|
129
129
|
ws_endpoints?: ReadonlyArray<WsEndpointSpec> | ((ctx: AppServerContext) => ReadonlyArray<WsEndpointSpec>);
|
|
130
130
|
/** Transform middleware array (e.g., tx's `extend_middleware_for_tx_binary`). */
|
|
131
131
|
transform_middleware?: (specs: Array<MiddlewareSpec>) => Array<MiddlewareSpec>;
|
|
132
|
-
/**
|
|
133
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Bootstrap config — symmetric with `AppServerOptions.bootstrap`. Discriminated
|
|
134
|
+
* by `mode`: `'disabled'` skips the route (same as omission), `'surface_only'`
|
|
135
|
+
* mounts the route shape, `'live'` accepts a `token_path` for production
|
|
136
|
+
* symmetry (surface assembly only uses it for shape symmetry; the value is a
|
|
137
|
+
* live-execution concern handled by `create_test_app` → `create_app_server`).
|
|
138
|
+
*
|
|
139
|
+
* Surface assembly only reads `route_prefix` (default `'/api/account'`).
|
|
140
|
+
*/
|
|
141
|
+
bootstrap?: BootstrapServerOptions;
|
|
134
142
|
}
|
|
135
143
|
/**
|
|
136
144
|
* Create an `AppSurfaceSpec` for attack surface testing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAa7B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAE3B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,4BAA4B,CAAC;AAE/D,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AAE3D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/stubs.js
CHANGED
|
@@ -17,7 +17,6 @@ import { create_app_surface_spec, } from '../http/surface.js';
|
|
|
17
17
|
import { AUDIT_LOG_SSE_MAX_PER_SCOPE } from '../realtime/sse_auth_guard.js';
|
|
18
18
|
import { SubscriberRegistry } from '../realtime/subscriber_registry.js';
|
|
19
19
|
import { BaseServerEnv } from '../server/env.js';
|
|
20
|
-
/* eslint-disable @typescript-eslint/require-await */
|
|
21
20
|
/**
|
|
22
21
|
* Create a Proxy that throws descriptive errors on any property access or method call.
|
|
23
22
|
*
|
|
@@ -102,10 +101,10 @@ const stub_db = create_noop_stub('stub_db');
|
|
|
102
101
|
* `create_test_app` already does this on the test backend.
|
|
103
102
|
*/
|
|
104
103
|
export const create_test_audit_emitter = () => ({
|
|
105
|
-
emit: () => { },
|
|
106
|
-
emit_role_grant_target: () => { },
|
|
107
|
-
emit_pool: async () => { },
|
|
108
|
-
notify: () => { },
|
|
104
|
+
emit: () => { },
|
|
105
|
+
emit_role_grant_target: () => { },
|
|
106
|
+
emit_pool: async () => { },
|
|
107
|
+
notify: () => { },
|
|
109
108
|
on_event_chain: Object.freeze([]),
|
|
110
109
|
});
|
|
111
110
|
/**
|
|
@@ -123,9 +122,9 @@ export const create_stub_audit_sse = () => {
|
|
|
123
122
|
max_per_scope: AUDIT_LOG_SSE_MAX_PER_SCOPE,
|
|
124
123
|
});
|
|
125
124
|
return {
|
|
126
|
-
subscribe: () => () => { },
|
|
125
|
+
subscribe: () => () => { },
|
|
127
126
|
log: new Logger('test:audit_sse', { level: 'off' }),
|
|
128
|
-
on_audit_event: () => { },
|
|
127
|
+
on_audit_event: () => { },
|
|
129
128
|
registry,
|
|
130
129
|
};
|
|
131
130
|
};
|
|
@@ -146,7 +145,7 @@ export const stub_app_deps = {
|
|
|
146
145
|
export const create_stub_app_deps = () => ({
|
|
147
146
|
stat: async () => null,
|
|
148
147
|
read_text_file: async () => '',
|
|
149
|
-
delete_file: async (_path) => { },
|
|
148
|
+
delete_file: async (_path) => { },
|
|
150
149
|
keyring: create_noop_stub('keyring'),
|
|
151
150
|
password: create_noop_stub('password'),
|
|
152
151
|
db: stub_db,
|
|
@@ -193,7 +192,7 @@ export const create_stub_app_server_context = (session_options) => {
|
|
|
193
192
|
db_type: 'pglite-memory',
|
|
194
193
|
db_name: 'test',
|
|
195
194
|
migration_results: [],
|
|
196
|
-
close: async () => { },
|
|
195
|
+
close: async () => { },
|
|
197
196
|
},
|
|
198
197
|
bootstrap_status: { available: false, token_path: null },
|
|
199
198
|
session_options,
|
|
@@ -220,13 +219,6 @@ export const create_stub_app_server_context = (session_options) => {
|
|
|
220
219
|
export const create_test_app_surface_spec = (options) => {
|
|
221
220
|
const ctx = create_stub_app_server_context(options.session_options);
|
|
222
221
|
const consumer_routes = options.create_route_specs(ctx);
|
|
223
|
-
// Mirror create_app_server's factory-managed route assembly
|
|
224
|
-
const bootstrap_routes = create_bootstrap_route_specs(ctx.deps, {
|
|
225
|
-
session_options: options.session_options,
|
|
226
|
-
bootstrap_status: { available: false, token_path: null },
|
|
227
|
-
ip_rate_limiter: null,
|
|
228
|
-
});
|
|
229
|
-
const prefix = options.bootstrap_route_prefix ?? '/api/account';
|
|
230
222
|
// Auto-mount rpc endpoints (mirrors create_app_server) so consumer
|
|
231
223
|
// `create_route_specs` does not need to call `create_rpc_endpoint`.
|
|
232
224
|
const resolved_rpc_endpoints = typeof options.rpc_endpoints === 'function'
|
|
@@ -240,11 +232,22 @@ export const create_test_app_surface_spec = (options) => {
|
|
|
240
232
|
// Resolve ws endpoints (mirrors create_app_server). Surface-only —
|
|
241
233
|
// no `register_ws_endpoint` call here, so no `upgradeWebSocket` needed.
|
|
242
234
|
const resolved_ws_endpoints = typeof options.ws_endpoints === 'function' ? options.ws_endpoints(ctx) : options.ws_endpoints;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
235
|
+
// Bootstrap routes mirror `create_app_server`: mounted for `surface_only`
|
|
236
|
+
// and `live` modes; omitted for `disabled` / undefined. Surface generation
|
|
237
|
+
// uses an `available: false` placeholder regardless of mode — the handler
|
|
238
|
+
// short-circuits to 403 ALREADY_BOOTSTRAPPED, which is what surface tests
|
|
239
|
+
// assert on. Live token_path is passed through for shape symmetry only.
|
|
240
|
+
const bootstrap_route_specs = options.bootstrap && options.bootstrap.mode !== 'disabled'
|
|
241
|
+
? prefix_route_specs(options.bootstrap.route_prefix ?? '/api/account', create_bootstrap_route_specs(ctx.deps, {
|
|
242
|
+
session_options: options.session_options,
|
|
243
|
+
bootstrap_status: {
|
|
244
|
+
available: false,
|
|
245
|
+
token_path: options.bootstrap.mode === 'live' ? options.bootstrap.token_path : null,
|
|
246
|
+
},
|
|
247
|
+
ip_rate_limiter: null,
|
|
248
|
+
}))
|
|
249
|
+
: [];
|
|
250
|
+
const route_specs = [...consumer_routes, ...rpc_route_specs, ...bootstrap_route_specs];
|
|
248
251
|
let middleware_specs = create_stub_api_middleware();
|
|
249
252
|
if (options.transform_middleware) {
|
|
250
253
|
middleware_specs = options.transform_middleware(middleware_specs);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discriminated source for the `AppSurface` a suite asserts against.
|
|
4
|
+
*
|
|
5
|
+
* In-process callers pass `{kind: 'inline', spec}` — the full
|
|
6
|
+
* `AppSurfaceSpec` with route closures intact. Cross-process callers
|
|
7
|
+
* pass `{kind: 'snapshot', path}` — a committed JSON file containing
|
|
8
|
+
* only the JSON-serializable `AppSurface` shape.
|
|
9
|
+
*
|
|
10
|
+
* Backs the suite parameter that previously was `build: () => AppSurfaceSpec`.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import type { AppSurface, AppSurfaceSpec } from '../../http/surface.js';
|
|
15
|
+
/**
|
|
16
|
+
* Where a suite reads the `AppSurface` from for its assertions.
|
|
17
|
+
*
|
|
18
|
+
* Two variants. The `inline` variant carries the full `AppSurfaceSpec`
|
|
19
|
+
* (with route closures) — the only variant in-process suites can use
|
|
20
|
+
* for route-iteration tests, since route closures aren't JSON-serializable.
|
|
21
|
+
* The `snapshot` variant carries a path to a committed JSON file containing
|
|
22
|
+
* the `AppSurface` shape only — cross-process consumers use this to assert
|
|
23
|
+
* against a backend that doesn't share TS handler closures.
|
|
24
|
+
*
|
|
25
|
+
* No `'fetched'` (live `/api/surface` endpoint) variant: a dead union
|
|
26
|
+
* case is dead weight and a code-shaped invitation to add a debug
|
|
27
|
+
* endpoint the design explicitly rejected; committed snapshot is the
|
|
28
|
+
* contract.
|
|
29
|
+
*/
|
|
30
|
+
export type SurfaceSource = {
|
|
31
|
+
readonly kind: 'inline';
|
|
32
|
+
readonly spec: AppSurfaceSpec;
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: 'snapshot';
|
|
35
|
+
readonly path: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a `SurfaceSource` to the underlying surface shape.
|
|
39
|
+
*
|
|
40
|
+
* The `inline` variant returns the full `AppSurfaceSpec` (route closures
|
|
41
|
+
* available). The `snapshot` variant returns the serialized `AppSurface`
|
|
42
|
+
* shape only. Asymmetric on purpose — suites that need `route_specs`
|
|
43
|
+
* (with closures) must use the `inline` variant; suites working on the
|
|
44
|
+
* `AppSurface` shape work with either.
|
|
45
|
+
*
|
|
46
|
+
* @throws Error when called with `{kind: 'snapshot'}` — the snapshot
|
|
47
|
+
* variant lands alongside the cross-process transport plumbing.
|
|
48
|
+
* No in-process caller exercises this branch today.
|
|
49
|
+
*/
|
|
50
|
+
export declare const resolve_surface_source: (src: SurfaceSource) => Promise<AppSurface | AppSurfaceSpec>;
|
|
51
|
+
//# sourceMappingURL=surface_source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface_source.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/transports/surface_source.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,UAAU,EAAE,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAEtE;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,GACtB;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAC,GACxD;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC;AAEtD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK,aAAa,KAChB,OAAO,CAAC,UAAU,GAAG,cAAc,CAKrC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a `SurfaceSource` to the underlying surface shape.
|
|
4
|
+
*
|
|
5
|
+
* The `inline` variant returns the full `AppSurfaceSpec` (route closures
|
|
6
|
+
* available). The `snapshot` variant returns the serialized `AppSurface`
|
|
7
|
+
* shape only. Asymmetric on purpose — suites that need `route_specs`
|
|
8
|
+
* (with closures) must use the `inline` variant; suites working on the
|
|
9
|
+
* `AppSurface` shape work with either.
|
|
10
|
+
*
|
|
11
|
+
* @throws Error when called with `{kind: 'snapshot'}` — the snapshot
|
|
12
|
+
* variant lands alongside the cross-process transport plumbing.
|
|
13
|
+
* No in-process caller exercises this branch today.
|
|
14
|
+
*/
|
|
15
|
+
export const resolve_surface_source = async (src) => {
|
|
16
|
+
if (src.kind === 'inline')
|
|
17
|
+
return src.spec;
|
|
18
|
+
throw new Error(`surface_source.kind === 'snapshot' is not yet implemented; lands with cross-process transport plumbing (path=${src.path})`);
|
|
19
|
+
};
|