@fuzdev/fuz_app 0.148.0 → 0.149.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 +2 -2
- package/dist/actions/action_event.d.ts.map +1 -1
- package/dist/actions/action_event.js +14 -1
- package/dist/actions/perform_action.d.ts +17 -1
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +22 -2
- package/dist/auth/CLAUDE.md +3 -2
- package/dist/auth/account_action_specs.d.ts +12 -0
- package/dist/auth/account_action_specs.d.ts.map +1 -1
- package/dist/auth/account_action_specs.js +16 -2
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +7 -4
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +29 -8
- package/dist/auth/audit_emitter.d.ts +14 -0
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +5 -1
- package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_action_specs.js +5 -3
- package/dist/http/CLAUDE.md +14 -2
- package/dist/http/error_schemas.d.ts +8 -0
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +12 -0
- package/dist/http/jsonrpc_errors.d.ts +40 -15
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +90 -35
- package/dist/schema_meta.d.ts +60 -0
- package/dist/schema_meta.d.ts.map +1 -1
- package/dist/schema_meta.js +64 -0
- package/dist/testing/CLAUDE.md +162 -61
- package/dist/testing/adversarial_input_cases.d.ts +67 -7
- package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
- package/dist/testing/adversarial_input_cases.js +210 -165
- package/dist/testing/audit_drift_guard.d.ts +3 -2
- package/dist/testing/audit_drift_guard.d.ts.map +1 -1
- package/dist/testing/audit_drift_guard.js +5 -3
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
- package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
- package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
- package/dist/testing/cross_backend/capabilities.d.ts +27 -4
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +10 -6
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
- package/dist/testing/cross_backend/cell_crud.js +117 -2
- package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
- package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
- package/dist/testing/cross_backend/cross_rpc.js +2 -2
- package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +3 -3
- package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
- package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
- package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
- package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
- package/dist/testing/cross_backend/setup.d.ts +2 -2
- package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
- package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_suites.js +291 -20
- package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
- package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
- package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
- package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
- package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_backdoor.js +136 -41
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
- package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
- package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
- package/dist/testing/rpc_attack_surface.d.ts +10 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
- package/dist/testing/rpc_attack_surface.js +15 -3
- package/dist/testing/rpc_envelope_cases.d.ts +5 -2
- package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
- package/dist/testing/rpc_envelope_cases.js +4 -1
- package/dist/testing/rpc_round_trip.js +3 -3
- package/dist/testing/schema_generators.d.ts +99 -2
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +244 -76
- package/dist/testing/stubs.d.ts +4 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +6 -4
- package/dist/zod.d.ts +9 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +20 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import '../assert_dev_env.js';
|
|
2
|
+
import type { TestingBackdoorMethod } from './spine_surface_constants.js';
|
|
2
3
|
/**
|
|
3
4
|
* Optional behaviors a backend may support. Each flag's TSDoc names the
|
|
4
5
|
* tests that gate on it; add a new flag here before referencing it from
|
|
@@ -61,7 +62,8 @@ export interface BackendCapabilities {
|
|
|
61
62
|
/**
|
|
62
63
|
* The cell-gated fact-serving route (`GET /api/cells/:cell_id/facts/:hash`)
|
|
63
64
|
* is live-mounted on the backend, its DB carries the `fuz_facts` migration
|
|
64
|
-
* namespace, and it
|
|
65
|
+
* namespace, and it mounts the `_testing_put_fact` seeder — declared in
|
|
66
|
+
* `testing_backdoors`, which `describe_spine_cross_suites` requires. Gates
|
|
65
67
|
* `describe_fact_serving_cross_tests`' per-reference read model (cell-scoped
|
|
66
68
|
* admit via a viewable cell, cross-owner-dedup-no-leak, 404-mask),
|
|
67
69
|
* `describe_cell_ref_admission_cross_tests`, and the fact cases of the
|
|
@@ -191,6 +193,25 @@ export interface BackendCapabilities {
|
|
|
191
193
|
* Deno, Bun, and the Rust spine, which send the response as built.
|
|
192
194
|
*/
|
|
193
195
|
readonly ws_refusal_body: boolean;
|
|
196
|
+
/**
|
|
197
|
+
* The `_testing_*` backdoors the backend mounts on its RPC endpoint, from
|
|
198
|
+
* `TESTING_BACKDOOR_METHODS` (a consumer's own seams stay off it). Not a
|
|
199
|
+
* flag, but declared like a `MountFlag`: no family default, so every config
|
|
200
|
+
* states it. `describe_testing_backdoor_cross_tests` checks the declaration
|
|
201
|
+
* against the backend — a declared method must be mounted, an undeclared one
|
|
202
|
+
* must answer `method_not_found` — and runs its credential-gate rows over
|
|
203
|
+
* the declared ones; `describe_spine_cross_suites` requires `_testing_reset`
|
|
204
|
+
* and `_testing_drain_effects` (the per-test reset and the audit barrier),
|
|
205
|
+
* skips a suite whose backdoor is undeclared, naming it — `credential_deadline`
|
|
206
|
+
* (`_testing_shorten_credential_expiry`), `origin_conformance`
|
|
207
|
+
* (`_testing_origin_check`), `ws_outbound_overflow`
|
|
208
|
+
* (`_testing_flood_notifications`) — and requires `_testing_put_fact` of a
|
|
209
|
+
* backend declaring `fact_serving`.
|
|
210
|
+
*
|
|
211
|
+
* fuz_app's spine presets declare every one; the in-process transport none
|
|
212
|
+
* (the backdoor suites are cross-process only).
|
|
213
|
+
*/
|
|
214
|
+
readonly testing_backdoors: ReadonlyArray<TestingBackdoorMethod>;
|
|
194
215
|
}
|
|
195
216
|
/**
|
|
196
217
|
* The capabilities that describe a backend's own mounts, never a runtime
|
|
@@ -203,8 +224,9 @@ export interface BackendCapabilities {
|
|
|
203
224
|
*/
|
|
204
225
|
export type MountFlag = keyof Pick<BackendCapabilities, 'ws' | 'sse' | 'cell_crud' | 'cell_relations' | 'cell_gated_create' | 'fact_serving' | 'fact_serving_admin_hash' | 'ready' | 'peer_request' | 'ws_account_actions' | 'ws_role_gated'>;
|
|
205
226
|
/**
|
|
206
|
-
* A family's default capabilities: every flag but the `MountFlag`s
|
|
207
|
-
* each backend config states itself. Spread
|
|
227
|
+
* A family's default capabilities: every flag but the `MountFlag`s and the
|
|
228
|
+
* `testing_backdoors` list, which each backend config states itself. Spread
|
|
229
|
+
* one and add every mount flag and the backdoor list:
|
|
208
230
|
*
|
|
209
231
|
* ```ts
|
|
210
232
|
* capabilities: {
|
|
@@ -213,10 +235,11 @@ export type MountFlag = keyof Pick<BackendCapabilities, 'ws' | 'sse' | 'cell_cru
|
|
|
213
235
|
* cell_crud: false, cell_relations: false, cell_gated_create: false,
|
|
214
236
|
* fact_serving: false, fact_serving_admin_hash: false,
|
|
215
237
|
* ws_account_actions: false, ws_role_gated: false,
|
|
238
|
+
* testing_backdoors: ['_testing_reset', '_testing_drain_effects'],
|
|
216
239
|
* }
|
|
217
240
|
* ```
|
|
218
241
|
*/
|
|
219
|
-
export type FamilyDefaultCapabilities = Omit<BackendCapabilities, MountFlag>;
|
|
242
|
+
export type FamilyDefaultCapabilities = Omit<BackendCapabilities, MountFlag | 'testing_backdoors'>;
|
|
220
243
|
/**
|
|
221
244
|
* Capability declarations for the in-process Hono transport. Nearly every
|
|
222
245
|
* flag is `true` because in-process testing exercises the full backend
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkD9B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC;IAC1C;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,iBAAiB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CACjE;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI,CACjC,mBAAmB,EACjB,IAAI,GACJ,KAAK,GACL,WAAW,GACX,gBAAgB,GAChB,mBAAmB,GACnB,cAAc,GACd,yBAAyB,GACzB,OAAO,GACP,cAAc,GACd,oBAAoB,GACpB,eAAe,CACjB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,mBAAmB,CAAC,CAAC;AAEnG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBA4BpC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
|
|
@@ -28,9 +28,9 @@ import '../assert_dev_env.js';
|
|
|
28
28
|
* `rust_spine_stub_backend_config.ts` (the rust family default plus every mount flag).
|
|
29
29
|
*
|
|
30
30
|
* **Mount flags have no default.** A flag naming something a backend mounts
|
|
31
|
-
* (a `MountFlag`) is never inherited
|
|
32
|
-
* `FamilyDefaultCapabilities`, which omits them, so every
|
|
33
|
-
* each one. The family defaults carry only what the family guarantees — the
|
|
31
|
+
* (a `MountFlag`) is never inherited, nor is the `testing_backdoors` list: the
|
|
32
|
+
* family defaults are `FamilyDefaultCapabilities`, which omits them, so every
|
|
33
|
+
* backend config states each one. The family defaults carry only what the family guarantees — the
|
|
34
34
|
* declared spine surface's mounts (`account_lifecycle`, `account_status`) and
|
|
35
35
|
* the runtime behavior of its reference runtime (`ws_handshake_pipelining`,
|
|
36
36
|
* `ws_refusal_body`: Node for TS, hyper for Rust), which a backend on another
|
|
@@ -38,8 +38,9 @@ import '../assert_dev_env.js';
|
|
|
38
38
|
*
|
|
39
39
|
* **Gating flags only.** `BackendCapabilities` holds only flags a suite
|
|
40
40
|
* actually gates on (each has a `test_if(capabilities.X, ...)` reader, or —
|
|
41
|
-
* `ws_refusal_body` — picks how a case reads a refusal)
|
|
42
|
-
*
|
|
41
|
+
* `ws_refusal_body` — picks how a case reads a refusal), plus the
|
|
42
|
+
* `testing_backdoors` list the backdoor suite and `describe_spine_cross_suites`
|
|
43
|
+
* read. A wiring fact no suite reads is not a capability.
|
|
43
44
|
*
|
|
44
45
|
* @module
|
|
45
46
|
*/
|
|
@@ -81,7 +82,10 @@ export const in_process_capabilities = Object.freeze({
|
|
|
81
82
|
ws_role_gated: false,
|
|
82
83
|
// Read only by the front-door table's WS rows, which skip in-process (no
|
|
83
84
|
// socket to upgrade); Hono's own refusals carry their bodies.
|
|
84
|
-
ws_refusal_body: true
|
|
85
|
+
ws_refusal_body: true,
|
|
86
|
+
// The in-process app mounts no `_testing_*` action; the suites reading this
|
|
87
|
+
// are cross-process only.
|
|
88
|
+
testing_backdoors: Object.freeze([])
|
|
85
89
|
});
|
|
86
90
|
/**
|
|
87
91
|
* Conditional `test()` wrapper — registers a vitest case only when
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0E9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAgE1E,eAAO,MAAM,8BAA8B,GAC1C,SAAS,uCAAuC,KAC9C,IA4uBF,CAAC"}
|
|
@@ -38,7 +38,13 @@ import '../assert_dev_env.js';
|
|
|
38
38
|
* accepted as a list filter that matches nothing;
|
|
39
39
|
* - a `kind` of exactly `CELL_KIND_LENGTH_MAX` UTF-16 code units is stored
|
|
40
40
|
* whole, and one unit past it is `invalid_params` on create and as a list
|
|
41
|
-
* filter
|
|
41
|
+
* filter;
|
|
42
|
+
* - a `data` that is no `CellData` — not an object, or a `label` / `summary`
|
|
43
|
+
* that is no string — is 400 `invalid_params` with the generic message and
|
|
44
|
+
* no reason on `cell_create`, `cell_update` and (with
|
|
45
|
+
* `capabilities.cell_relations`) `cell_clone`'s `with_data_patch`, ahead of
|
|
46
|
+
* every cell check (a missing or hidden cell refuses it the same way, never
|
|
47
|
+
* with the 404 mask), and writes no cell.
|
|
42
48
|
*
|
|
43
49
|
* The visibility-manage-tier 403 (`cell_visibility_manage_only`) needs a
|
|
44
50
|
* non-owner editor, which only a `cell_grant` can produce, so it lives in
|
|
@@ -48,11 +54,64 @@ import '../assert_dev_env.js';
|
|
|
48
54
|
* @module
|
|
49
55
|
*/
|
|
50
56
|
import { describe, assert } from 'vitest';
|
|
51
|
-
import {
|
|
57
|
+
import { jsonrpc_error_messages } from '../../http/jsonrpc_errors.js';
|
|
58
|
+
import { CELL_KIND_LENGTH_MAX, CELL_LIST_LIMIT_MAX, CellCreateOutput, CellDeleteOutput, CellGetOutput, CellListOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
|
|
52
59
|
import { test_if } from './capabilities.js';
|
|
53
60
|
import { cross_rpc_call, error_reason, expect_output } from './cross_rpc.js';
|
|
54
61
|
import { assert_iso8601_seconds, assert_iso8601_seconds_nullable } from './wire_shapes.js';
|
|
55
62
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
63
|
+
/**
|
|
64
|
+
* `data` values that are no `CellData` — not an object, or an object whose
|
|
65
|
+
* `label` / `summary` is no string. Twin of the Rust spine's mistyped-data
|
|
66
|
+
* table in its cell authz tests.
|
|
67
|
+
*/
|
|
68
|
+
const MISTYPED_CELL_DATA = [
|
|
69
|
+
'not_an_object',
|
|
70
|
+
42,
|
|
71
|
+
true,
|
|
72
|
+
[1],
|
|
73
|
+
{ label: 123 },
|
|
74
|
+
{ label: null },
|
|
75
|
+
{ summary: {} },
|
|
76
|
+
{ summary: false }
|
|
77
|
+
];
|
|
78
|
+
/** The message every schema refusal answers, naming no field. */
|
|
79
|
+
const INVALID_PARAMS_MESSAGE = jsonrpc_error_messages.invalid_params().message;
|
|
80
|
+
/**
|
|
81
|
+
* Assert `r` is a schema refusal: 400 `invalid_params` with the generic
|
|
82
|
+
* message and no `data`, save the Zod `issues` a TS spine adds under DEV
|
|
83
|
+
* alone — an array of issue objects, each with a string `code` and
|
|
84
|
+
* `message`, and never a `reason`.
|
|
85
|
+
*/
|
|
86
|
+
const assert_schema_refusal = (r, label) => {
|
|
87
|
+
const detail = `${label}: got ${r.status} ${JSON.stringify(r.error ?? r.result)}`;
|
|
88
|
+
assert.strictEqual(r.status, 400, detail);
|
|
89
|
+
const { error } = r;
|
|
90
|
+
assert.ok(error, detail);
|
|
91
|
+
assert.strictEqual(error.code, -32602, detail);
|
|
92
|
+
assert.strictEqual(error.message, INVALID_PARAMS_MESSAGE, detail);
|
|
93
|
+
const { data } = error;
|
|
94
|
+
if (data === undefined)
|
|
95
|
+
return;
|
|
96
|
+
assert.ok(typeof data === 'object' && data !== null && Object.keys(data).join() === 'issues', `${detail} — data beyond the DEV-only issues`);
|
|
97
|
+
const { issues } = data;
|
|
98
|
+
assert.ok(Array.isArray(issues), `${detail} — DEV-only issues that are no array`);
|
|
99
|
+
for (const issue of issues) {
|
|
100
|
+
assert.ok(typeof issue === 'object' &&
|
|
101
|
+
issue !== null &&
|
|
102
|
+
typeof issue.code === 'string' &&
|
|
103
|
+
typeof issue.message === 'string', `${detail} — a DEV-only issue that is no Zod issue`);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Assert `r` is the IDOR mask: 404 with reason `cell_not_found` — the
|
|
108
|
+
* well-typed control beside a schema refusal of the same call.
|
|
109
|
+
*/
|
|
110
|
+
const assert_cell_not_found = (r, label) => {
|
|
111
|
+
const detail = `${label}: got ${r.status} ${JSON.stringify(r.error ?? r.result)}`;
|
|
112
|
+
assert.strictEqual(r.status, 404, detail);
|
|
113
|
+
assert.strictEqual(error_reason(r), 'cell_not_found', detail);
|
|
114
|
+
};
|
|
56
115
|
export const describe_cell_crud_cross_tests = (options) => {
|
|
57
116
|
const { setup_test, capabilities } = options;
|
|
58
117
|
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
@@ -327,5 +386,61 @@ export const describe_cell_crud_cross_tests = (options) => {
|
|
|
327
386
|
}
|
|
328
387
|
}
|
|
329
388
|
});
|
|
389
|
+
describe('mistyped data', () => {
|
|
390
|
+
/** Count every cell, read as the keeper (an admin sees every row). */
|
|
391
|
+
const count_cells = async (t, keeper_headers) => {
|
|
392
|
+
const { cells } = expect_output(await cross_rpc_call(t, rpc_path, 'cell_list', { limit: CELL_LIST_LIMIT_MAX }, keeper_headers), CellListOutput);
|
|
393
|
+
assert.ok(cells.length < CELL_LIST_LIMIT_MAX, 'the count needs a page that holds every cell');
|
|
394
|
+
return cells.length;
|
|
395
|
+
};
|
|
396
|
+
test_if(capabilities.cell_crud, 'cell_create and cell_update refuse it as invalid_params before any cell check', async () => {
|
|
397
|
+
const fixture = await setup_test();
|
|
398
|
+
const writer = await fixture.create_account({ username: 'cell_data_writer' });
|
|
399
|
+
const stranger = await fixture.create_account({ username: 'cell_data_stranger' });
|
|
400
|
+
const t = fixture.fresh_transport();
|
|
401
|
+
const keeper_headers = fixture.create_session_headers();
|
|
402
|
+
const writer_headers = writer.create_session_headers();
|
|
403
|
+
// the keeper's private cell, hidden from the stranger; the keeper
|
|
404
|
+
// creates it so a backend that refuses a non-admin root runs the case
|
|
405
|
+
const hidden = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, keeper_headers), CellCreateOutput).cell.id;
|
|
406
|
+
const missing = crypto.randomUUID();
|
|
407
|
+
const before = await count_cells(t, keeper_headers);
|
|
408
|
+
for (const data of MISTYPED_CELL_DATA) {
|
|
409
|
+
const shown = JSON.stringify(data);
|
|
410
|
+
assert_schema_refusal(await cross_rpc_call(t, rpc_path, 'cell_create', { data }, writer_headers), `cell_create data ${shown}`);
|
|
411
|
+
for (const [cell_id, headers, who] of [
|
|
412
|
+
[missing, writer_headers, 'a missing cell'],
|
|
413
|
+
[hidden, stranger.create_session_headers(), 'a hidden cell']
|
|
414
|
+
]) {
|
|
415
|
+
assert_schema_refusal(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data }, headers), `cell_update of ${who} data ${shown}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// the control: well-typed, the same call meets the 404 mask, so the
|
|
419
|
+
// 400s above came before the cell check
|
|
420
|
+
assert_cell_not_found(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id: hidden, data: {} }, stranger.create_session_headers()), 'cell_update of a hidden cell data {}');
|
|
421
|
+
assert.strictEqual(await count_cells(t, keeper_headers), before, 'a refused write wrote a cell');
|
|
422
|
+
});
|
|
423
|
+
test_if(capabilities.cell_crud && capabilities.cell_relations, 'cell_clone refuses it in with_data_patch as invalid_params before any cell check', async () => {
|
|
424
|
+
const fixture = await setup_test();
|
|
425
|
+
const writer = await fixture.create_account({ username: 'cell_patch_writer' });
|
|
426
|
+
const stranger = await fixture.create_account({ username: 'cell_patch_stranger' });
|
|
427
|
+
const t = fixture.fresh_transport();
|
|
428
|
+
const keeper_headers = fixture.create_session_headers();
|
|
429
|
+
const hidden = expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', { kind: 'note', data: {} }, keeper_headers), CellCreateOutput).cell.id;
|
|
430
|
+
const missing = crypto.randomUUID();
|
|
431
|
+
const before = await count_cells(t, keeper_headers);
|
|
432
|
+
for (const with_data_patch of MISTYPED_CELL_DATA) {
|
|
433
|
+
for (const [source_id, headers, who] of [
|
|
434
|
+
[missing, writer.create_session_headers(), 'a missing cell'],
|
|
435
|
+
[hidden, stranger.create_session_headers(), 'a hidden cell']
|
|
436
|
+
]) {
|
|
437
|
+
assert_schema_refusal(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id, with_data_patch }, headers), `cell_clone of ${who} with_data_patch ${JSON.stringify(with_data_patch)}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// the control: well-typed, the same clone meets the 404 mask
|
|
441
|
+
assert_cell_not_found(await cross_rpc_call(t, rpc_path, 'cell_clone', { source_id: hidden, with_data_patch: { label: 'patched' } }, stranger.create_session_headers()), 'cell_clone of a hidden cell with_data_patch {label}');
|
|
442
|
+
assert.strictEqual(await count_cells(t, keeper_headers), before, 'a refused clone wrote a cell');
|
|
443
|
+
});
|
|
444
|
+
});
|
|
330
445
|
});
|
|
331
446
|
};
|
|
@@ -3,6 +3,8 @@ import type { z } from 'zod';
|
|
|
3
3
|
import type { FetchTransport } from '../transports/fetch_transport.js';
|
|
4
4
|
/** Minimal JSON-RPC envelope shape the suites read off responses. */
|
|
5
5
|
export interface RpcResult {
|
|
6
|
+
/** The HTTP status the reply carried. */
|
|
7
|
+
readonly status: number;
|
|
6
8
|
readonly ok: boolean;
|
|
7
9
|
readonly result?: unknown;
|
|
8
10
|
readonly error?: {
|
|
@@ -22,7 +24,7 @@ export interface RpcResult {
|
|
|
22
24
|
* @param method - the JSON-RPC method
|
|
23
25
|
* @param params - the request params
|
|
24
26
|
* @param headers - auth and other headers, merged over the JSON defaults
|
|
25
|
-
* @returns the response's `ok`, `result` and `error`
|
|
27
|
+
* @returns the response's HTTP `status`, `ok`, `result` and `error`
|
|
26
28
|
* @throws Error when the reply is not a JSON object — naming the method, the
|
|
27
29
|
* HTTP status and the head of the body, so a proxy page or a crash reads
|
|
28
30
|
* as what it is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cross_rpc.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAgB9B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9F;AAKD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAmBnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAUrE,CAAC"}
|
|
1
|
+
{"version":3,"file":"cross_rpc.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cross_rpc.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAgB9B,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAEvE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9F;AAKD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAmBnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAUrE,CAAC"}
|
|
@@ -26,7 +26,7 @@ const BODY_HEAD_LENGTH = 200;
|
|
|
26
26
|
* @param method - the JSON-RPC method
|
|
27
27
|
* @param params - the request params
|
|
28
28
|
* @param headers - auth and other headers, merged over the JSON defaults
|
|
29
|
-
* @returns the response's `ok`, `result` and `error`
|
|
29
|
+
* @returns the response's HTTP `status`, `ok`, `result` and `error`
|
|
30
30
|
* @throws Error when the reply is not a JSON object — naming the method, the
|
|
31
31
|
* HTTP status and the head of the body, so a proxy page or a crash reads
|
|
32
32
|
* as what it is
|
|
@@ -48,7 +48,7 @@ export const cross_rpc_call = async (transport, path, method, params, headers) =
|
|
|
48
48
|
JSON.stringify(text.slice(0, BODY_HEAD_LENGTH)));
|
|
49
49
|
}
|
|
50
50
|
const envelope = body;
|
|
51
|
-
return { ok: res.ok, result: envelope.result, error: envelope.error };
|
|
51
|
+
return { status: res.status, ok: res.ok, result: envelope.result, error: envelope.error };
|
|
52
52
|
};
|
|
53
53
|
/** Pull `error.data.reason` off an RPC error envelope (undefined if absent). */
|
|
54
54
|
export const error_reason = (r) => r.error && typeof r.error.data === 'object' && r.error.data !== null
|
|
@@ -34,14 +34,14 @@ export declare const action_rate_limit_env: (options: ActionRateLimitEnvOptions)
|
|
|
34
34
|
/**
|
|
35
35
|
* Capabilities shared by TS-family backends — only what the family
|
|
36
36
|
* guarantees: the declared spine surface's mounts and the behavior of its
|
|
37
|
-
* reference runtime, Node. Carries no `MountFlag
|
|
38
|
-
* those itself (`FamilyDefaultCapabilities`).
|
|
37
|
+
* reference runtime, Node. Carries no `MountFlag` and no `testing_backdoors`;
|
|
38
|
+
* each backend config states those itself (`FamilyDefaultCapabilities`).
|
|
39
39
|
*/
|
|
40
40
|
export declare const ts_default_capabilities: FamilyDefaultCapabilities;
|
|
41
41
|
/**
|
|
42
42
|
* Capabilities for the Rust family — only what the family guarantees, as with
|
|
43
43
|
* `ts_default_capabilities`: the declared spine surface's mounts and the
|
|
44
|
-
* behavior of hyper. Carries no `MountFlag`.
|
|
44
|
+
* behavior of hyper. Carries no `MountFlag` and no `testing_backdoors`.
|
|
45
45
|
*/
|
|
46
46
|
export declare const rust_default_capabilities: FamilyDefaultCapabilities;
|
|
47
47
|
export interface MakeDefaultTsBackendConfigOptions {
|
|
@@ -57,8 +57,9 @@ export interface MakeDefaultTsBackendConfigOptions {
|
|
|
57
57
|
readonly extra_env?: Readonly<Record<string, string>>;
|
|
58
58
|
/**
|
|
59
59
|
* Every capability, stated: spread `ts_default_capabilities` and add each
|
|
60
|
-
* `MountFlag
|
|
61
|
-
* than a suite that
|
|
60
|
+
* `MountFlag` and the `testing_backdoors` list. Required, so a mount flag or
|
|
61
|
+
* the backdoor list left out is a type error rather than a suite that
|
|
62
|
+
* silently skips.
|
|
62
63
|
*/
|
|
63
64
|
readonly capabilities: BackendCapabilities;
|
|
64
65
|
/** Pre-computed paths; defaults to `build_test_backend_paths(name)`. */
|
|
@@ -113,8 +114,9 @@ export interface MakeDefaultRustBackendConfigOptions {
|
|
|
113
114
|
readonly extra_env?: Readonly<Record<string, string>>;
|
|
114
115
|
/**
|
|
115
116
|
* Every capability, stated: spread `rust_default_capabilities` and add each
|
|
116
|
-
* `MountFlag
|
|
117
|
-
* than a suite that
|
|
117
|
+
* `MountFlag` and the `testing_backdoors` list. Required, so a mount flag or
|
|
118
|
+
* the backdoor list left out is a type error rather than a suite that
|
|
119
|
+
* silently skips.
|
|
118
120
|
*/
|
|
119
121
|
readonly capabilities: BackendCapabilities;
|
|
120
122
|
/** Pre-computed paths; defaults to `build_test_backend_paths(name)`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqC9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AACxF,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAehG;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,cAAc,CAAC;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,mBAAmB,MAAM,KAAG,MAS/D,CAAC;AAcF,2CAA2C;AAC3C,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,SAAS,yBAAyB,KAChC,MAAM,CAAC,MAAM,EAAE,MAAM,CAKtB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,yBAgBpC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,yBAatC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,mDAAmD;IACnD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD
|
|
1
|
+
{"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"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"}
|
|
@@ -81,8 +81,8 @@ export const action_rate_limit_env = (options) => ({
|
|
|
81
81
|
/**
|
|
82
82
|
* Capabilities shared by TS-family backends — only what the family
|
|
83
83
|
* guarantees: the declared spine surface's mounts and the behavior of its
|
|
84
|
-
* reference runtime, Node. Carries no `MountFlag
|
|
85
|
-
* those itself (`FamilyDefaultCapabilities`).
|
|
84
|
+
* reference runtime, Node. Carries no `MountFlag` and no `testing_backdoors`;
|
|
85
|
+
* each backend config states those itself (`FamilyDefaultCapabilities`).
|
|
86
86
|
*/
|
|
87
87
|
export const ts_default_capabilities = Object.freeze({
|
|
88
88
|
// The account-lifecycle verbs are on the declared surface
|
|
@@ -104,7 +104,7 @@ export const ts_default_capabilities = Object.freeze({
|
|
|
104
104
|
/**
|
|
105
105
|
* Capabilities for the Rust family — only what the family guarantees, as with
|
|
106
106
|
* `ts_default_capabilities`: the declared spine surface's mounts and the
|
|
107
|
-
* behavior of hyper. Carries no `MountFlag`.
|
|
107
|
+
* behavior of hyper. Carries no `MountFlag` and no `testing_backdoors`.
|
|
108
108
|
*/
|
|
109
109
|
export const rust_default_capabilities = Object.freeze({
|
|
110
110
|
// The account-lifecycle verbs are in the spine's admin action set, on the
|
|
@@ -14,6 +14,15 @@ export interface RpcAdversarialCrossTestOptions extends RpcPathCrossSuiteOptions
|
|
|
14
14
|
readonly live_rpc_endpoints?: ReadonlyArray<RpcEndpointSpec>;
|
|
15
15
|
/** Methods to leave out, beyond the protocol actions always skipped. */
|
|
16
16
|
readonly skip_methods?: ReadonlyArray<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Valid params per method (method name → params) to build its params cases
|
|
19
|
+
* from, in place of the generated base — for an input whose valid shape
|
|
20
|
+
* the generator can't reach. Each must pass the method's input schema; one
|
|
21
|
+
* that doesn't throws at collection, naming the method. The cases are sent
|
|
22
|
+
* as a single-actor principal, so an override omits `acting`.
|
|
23
|
+
* `describe_spine_cross_suites` passes its `rpc_input_overrides` here.
|
|
24
|
+
*/
|
|
25
|
+
readonly input_overrides?: ReadonlyMap<string, Record<string, unknown>>;
|
|
17
26
|
/**
|
|
18
27
|
* Generated params cases a backend is known to answer otherwise, each run
|
|
19
28
|
* as its own `xfail_until` test; the method's other cases keep their test.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc_adversarial.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rpc_adversarial.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"rpc_adversarial.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rpc_adversarial.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6E9B,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;AAoB/B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAKxE,0DAA0D;AAC1D,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;IAC/E,qGAAqG;IACrG,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC7D,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxE;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACtD;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAmGD;;;;GAIG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,8BAA8B,KACrC,IAqKF,CAAC"}
|
|
@@ -30,8 +30,10 @@ import '../assert_dev_env.js';
|
|
|
30
30
|
* - **Params** — for each declared method (and each live-mounted one, below),
|
|
31
31
|
* `generate_rpc_params_test_cases`
|
|
32
32
|
* (the in-process generator minus its structural cases), each sent as the
|
|
33
|
-
* method's authorized principal and owing 400 `invalid_params` with
|
|
34
|
-
* `data.reason
|
|
33
|
+
* method's authorized principal and owing 400 `invalid_params` with the
|
|
34
|
+
* `data.reason` the broken field declares (`invalid_reason` in its
|
|
35
|
+
* `.meta()`, read by `expected_invalid_reason`) — none for an undeclared
|
|
36
|
+
* field or a whole-body case. One test per method, reporting every case that answers
|
|
35
37
|
* otherwise; `xfail_params` runs named cases of a method as their own
|
|
36
38
|
* `xfail_until` tests, for a known divergence on one backend.
|
|
37
39
|
*
|
|
@@ -42,7 +44,9 @@ import '../assert_dev_env.js';
|
|
|
42
44
|
* role a declared method gates on (`default_cross_process_setup` seeds
|
|
43
45
|
* `keeper` and `admin`; pass `extra_keeper_roles` for others), and it has a
|
|
44
46
|
* single actor. No generated body carries a valid `acting` — the generator's
|
|
45
|
-
* base fills
|
|
47
|
+
* base fills required and defaulted fields, and an optional one only where a
|
|
48
|
+
* refinement needs it (each alone first, so an `acting` declared ahead of an
|
|
49
|
+
* at-least-one or exactly-one-of pair stays out) — and a malformed `acting` is
|
|
46
50
|
* read as omitted ahead of validation on both spines, so a single-actor
|
|
47
51
|
* keeper resolves its actor and validation is what refuses. A keeper with a
|
|
48
52
|
* second actor answers `actor_required` first; such a suite lists the
|
|
@@ -78,6 +82,7 @@ import { assert_rpc_envelope_refusal, create_rpc_envelope_cases, generate_rpc_pa
|
|
|
78
82
|
import { resolve_conformance_principal } from './conformance_table.js';
|
|
79
83
|
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
80
84
|
import { xfail_until } from './xfail.js';
|
|
85
|
+
import { expected_invalid_reason } from '../adversarial_input_cases.js';
|
|
81
86
|
const PROTOCOL_METHODS = new Set(PROTOCOL_ACTION_METHODS);
|
|
82
87
|
/** The keeper principal on each builtin credential channel. */
|
|
83
88
|
const CHANNEL_PRINCIPALS = {
|
|
@@ -231,12 +236,14 @@ export const describe_rpc_adversarial_cross_tests = (options) => {
|
|
|
231
236
|
const check_case = async (fixture, action, tc) => {
|
|
232
237
|
const { status, body } = await send_as(fixture, action.spec.auth, 'authorized', rpc_path, create_rpc_post_init(action.spec.method, tc.body));
|
|
233
238
|
const code = body?.error?.code;
|
|
239
|
+
const expected_reason = expected_invalid_reason(action.spec.input, tc);
|
|
234
240
|
if (status === 400 &&
|
|
235
241
|
code === JSONRPC_ERROR_CODES.invalid_params &&
|
|
236
|
-
reason_of(body) ===
|
|
242
|
+
reason_of(body) === expected_reason) {
|
|
237
243
|
return null;
|
|
238
244
|
}
|
|
239
|
-
|
|
245
|
+
const owed = expected_reason === undefined ? 'no reason' : `reason '${expected_reason}'`;
|
|
246
|
+
return `[${tc.label}] params ${JSON.stringify(tc.body)} → ${status} ${JSON.stringify(body)} (owed 400 invalid_params, ${owed})`;
|
|
240
247
|
};
|
|
241
248
|
// the declared methods, then the live-mounted ones the declared surface lacks
|
|
242
249
|
const declared_actions = endpoint_spec.actions.filter((action) => !action.spec.method.startsWith(TESTING_METHOD_PREFIX) &&
|
|
@@ -247,7 +254,10 @@ export const describe_rpc_adversarial_cross_tests = (options) => {
|
|
|
247
254
|
continue;
|
|
248
255
|
if (skip.has(method))
|
|
249
256
|
continue;
|
|
250
|
-
const all_cases = generate_rpc_params_test_cases(input
|
|
257
|
+
const all_cases = generate_rpc_params_test_cases(input, {
|
|
258
|
+
base: options.input_overrides?.get(method),
|
|
259
|
+
label: method
|
|
260
|
+
});
|
|
251
261
|
if (all_cases.length === 0)
|
|
252
262
|
continue;
|
|
253
263
|
total += all_cases.length;
|
|
@@ -10,7 +10,7 @@ import '../assert_dev_env.js';
|
|
|
10
10
|
* actions plus the consumer's own. This module builds that surface from the
|
|
11
11
|
* hono-free route shapes and the consumer's action specs, so a consumer
|
|
12
12
|
* supplies only what is its own. A server that mounts no signup router (a
|
|
13
|
-
* single-keeper daemon
|
|
13
|
+
* single-keeper daemon) is not this shape.
|
|
14
14
|
*
|
|
15
15
|
* The route shapes come from `create_account_route_shapes` and friends, never
|
|
16
16
|
* the live route factories, which statically pull `hono/cookie` and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"rust_spine_stub_backend_config.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/rust_spine_stub_backend_config.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAczD,uGAAuG;AACvG,eAAO,MAAM,uBAAuB,oCAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,6CAA6C,CAAC;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,6CAA6C,CAAC;AAoCnG,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,8EAA8E;AAC9E,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAEzD,0FAA0F;AAC1F,eAAO,MAAM,oCAAoC,sDACG,CAAC;AAErD,MAAM,WAAW,6BAA6B;IAC7C,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAC5C;;;;;OAKG;IACH,QAAQ,CAAC,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,8BAA8B,GAC1C,UAAS,6BAAkC,KACzC,aAmDF,CAAC"}
|
|
@@ -36,7 +36,7 @@ import '../assert_dev_env.js';
|
|
|
36
36
|
*/
|
|
37
37
|
import { fileURLToPath } from 'node:url';
|
|
38
38
|
import { build_test_backend_paths } from './build_test_backend_paths.js';
|
|
39
|
-
import { LOGIN_RATE_LIMIT_ENABLED_ENV, SPINE_EXPECTED_SCHEMA_URL } from './spine_surface_constants.js';
|
|
39
|
+
import { LOGIN_RATE_LIMIT_ENABLED_ENV, SPINE_EXPECTED_SCHEMA_URL, TESTING_BACKDOOR_METHODS } from './spine_surface_constants.js';
|
|
40
40
|
import { action_rate_limit_env, make_default_rust_backend_config, rust_default_capabilities } from './default_backend_configs.js';
|
|
41
41
|
/** Env var naming the prebuilt `testing_spine_stub` binary. Required when `binary_path` is omitted. */
|
|
42
42
|
export const RUST_SPINE_STUB_BIN_ENV = 'FUZ_TESTING_RUST_SPINE_STUB_BIN';
|
|
@@ -66,7 +66,9 @@ export const RUST_SPINE_STUB_EXPECTED_SCHEMA_PATH_ENV = 'FUZ_RUST_SPINE_STUB_EXP
|
|
|
66
66
|
* serves one action registry on `/api/rpc` and `/api/ws`), `cell_gated_create`
|
|
67
67
|
* (it mounts the `TestCellGatedCreateAuthorize` policy on its cell layer), and
|
|
68
68
|
* `ws_role_gated` (it mounts the role-gated WS endpoint at
|
|
69
|
-
* `SPINE_ROLE_GATED_WS_PATH`, sharing its connection registry)
|
|
69
|
+
* `SPINE_ROLE_GATED_WS_PATH`, sharing its connection registry), and every
|
|
70
|
+
* `TESTING_BACKDOOR_METHODS` entry as `testing_backdoors` (it mounts them all,
|
|
71
|
+
* on one registry for both transports). Named
|
|
70
72
|
* (not inline) so every spine preset is greppable, mirroring
|
|
71
73
|
* `ts_spine_capabilities`.
|
|
72
74
|
*/
|
|
@@ -82,7 +84,8 @@ const rust_spine_stub_capabilities = Object.freeze({
|
|
|
82
84
|
ready: true,
|
|
83
85
|
ws_account_actions: true,
|
|
84
86
|
cell_gated_create: true,
|
|
85
|
-
ws_role_gated: true
|
|
87
|
+
ws_role_gated: true,
|
|
88
|
+
testing_backdoors: TESTING_BACKDOOR_METHODS
|
|
86
89
|
});
|
|
87
90
|
/** Default listening port — slots beside zzz's 1175/1176; matches the binary's `DEFAULT_PORT`. */
|
|
88
91
|
export const RUST_SPINE_STUB_DEFAULT_PORT = 1177;
|
|
@@ -229,7 +229,7 @@ export interface CapabilityGatedCrossSuiteOptions extends CrossSuiteOptions {
|
|
|
229
229
|
* `CrossSuiteOptions` plus the RPC endpoint path the suite dispatches
|
|
230
230
|
* against — the shape every JSON-RPC-driven imperative suite takes (cell
|
|
231
231
|
* verbs, origin, body-size, actor lookup, account lifecycle, app
|
|
232
|
-
* settings
|
|
232
|
+
* settings). Suites alias this under a self-documenting
|
|
233
233
|
* per-suite name (e.g. `OriginCrossTestOptions = RpcPathCrossSuiteOptions`).
|
|
234
234
|
*/
|
|
235
235
|
export interface RpcPathCrossSuiteOptions extends CrossSuiteOptions {
|
|
@@ -241,7 +241,7 @@ export interface RpcPathCrossSuiteOptions extends CrossSuiteOptions {
|
|
|
241
241
|
* of `RpcPathCrossSuiteOptions` and `CapabilityGatedCrossSuiteOptions`, and
|
|
242
242
|
* the most common option shape among the imperative cross suites (every
|
|
243
243
|
* `cell_*` suite, `query_shape`, `token_scope_surface`, `fact_serving`,
|
|
244
|
-
* `account_lifecycle`).
|
|
244
|
+
* `account_lifecycle`, `testing_backdoor`).
|
|
245
245
|
*/
|
|
246
246
|
export interface RpcPathCapabilityGatedCrossSuiteOptions extends RpcPathCrossSuiteOptions, CapabilityGatedCrossSuiteOptions {
|
|
247
247
|
}
|