@fuzdev/fuz_app 0.5.0 → 0.7.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/action_bridge.d.ts +3 -3
- package/dist/actions/action_bridge.d.ts.map +1 -1
- package/dist/actions/action_bridge.js +4 -3
- package/dist/actions/action_rpc.d.ts +89 -0
- package/dist/actions/action_rpc.d.ts.map +1 -0
- package/dist/actions/action_rpc.js +248 -0
- package/dist/http/jsonrpc.d.ts +62 -0
- package/dist/http/jsonrpc.d.ts.map +1 -0
- package/dist/http/jsonrpc.js +49 -0
- package/dist/http/jsonrpc_errors.d.ts +132 -0
- package/dist/http/jsonrpc_errors.d.ts.map +1 -0
- package/dist/http/jsonrpc_errors.js +197 -0
- package/dist/http/route_spec.d.ts +2 -1
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +43 -7
- package/dist/http/surface.d.ts +25 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +16 -1
- package/dist/server/app_server.d.ts +3 -1
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +2 -1
- package/dist/testing/adversarial_input.d.ts.map +1 -1
- package/dist/testing/adversarial_input.js +22 -7
- package/dist/testing/app_server.d.ts +2 -1
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +1 -0
- package/dist/testing/rpc_attack_surface.d.ts +23 -0
- package/dist/testing/rpc_attack_surface.d.ts.map +1 -0
- package/dist/testing/rpc_attack_surface.js +376 -0
- package/dist/testing/rpc_helpers.d.ts +44 -0
- package/dist/testing/rpc_helpers.d.ts.map +1 -0
- package/dist/testing/rpc_helpers.js +74 -0
- package/dist/testing/rpc_round_trip.d.ts +41 -0
- package/dist/testing/rpc_round_trip.d.ts.map +1 -0
- package/dist/testing/rpc_round_trip.js +163 -0
- package/dist/testing/stubs.d.ts +3 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +2 -1
- package/dist/testing/surface_invariants.d.ts +4 -0
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Schema-driven round-trip validation for RPC endpoints.
|
|
4
|
+
*
|
|
5
|
+
* For every RPC method, generates valid params and fires JSON-RPC requests
|
|
6
|
+
* (POST for all methods, GET for reads), validating that responses are
|
|
7
|
+
* well-formed JSON-RPC. Successful responses are validated against the
|
|
8
|
+
* method's declared output schema. DB-backed via `create_test_app`.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { describe, test, beforeAll, afterAll } from 'vitest';
|
|
13
|
+
import { ROLE_ADMIN } from '../auth/role_schema.js';
|
|
14
|
+
import { create_test_app } from './app_server.js';
|
|
15
|
+
import { create_pglite_factory } from './db.js';
|
|
16
|
+
import { generate_valid_body } from './schema_generators.js';
|
|
17
|
+
import { run_migrations } from '../db/migrate.js';
|
|
18
|
+
import { AUTH_MIGRATION_NS } from '../auth/migrations.js';
|
|
19
|
+
import { create_rpc_post_init, create_rpc_get_url, assert_jsonrpc_error_response, assert_jsonrpc_success_response, } from './rpc_helpers.js';
|
|
20
|
+
/**
|
|
21
|
+
* Pick auth headers matching an RPC method's auth requirement.
|
|
22
|
+
*/
|
|
23
|
+
const pick_rpc_auth_headers = (method, test_app, authed_account, admin_account) => {
|
|
24
|
+
switch (method.auth.type) {
|
|
25
|
+
case 'none':
|
|
26
|
+
return { host: 'localhost', origin: 'http://localhost:5173' };
|
|
27
|
+
case 'authenticated':
|
|
28
|
+
return authed_account.create_session_headers();
|
|
29
|
+
case 'role':
|
|
30
|
+
if (method.auth.role === ROLE_ADMIN) {
|
|
31
|
+
return admin_account.create_session_headers();
|
|
32
|
+
}
|
|
33
|
+
// keeper role uses the bootstrapped account
|
|
34
|
+
return test_app.create_session_headers();
|
|
35
|
+
case 'keeper':
|
|
36
|
+
return test_app.create_bearer_headers();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Run schema-driven round-trip validation for RPC endpoints.
|
|
41
|
+
*
|
|
42
|
+
* For each method:
|
|
43
|
+
* 1. Generate valid params from the action's input schema
|
|
44
|
+
* 2. Fire a POST request with JSON-RPC envelope
|
|
45
|
+
* 3. For `side_effects: false` methods, also fire a GET request
|
|
46
|
+
* 4. Validate response is well-formed JSON-RPC; successful responses are
|
|
47
|
+
* also validated against the method's declared output schema
|
|
48
|
+
*
|
|
49
|
+
* Error responses (from missing DB state, etc.) are expected and validated
|
|
50
|
+
* as well-formed JSON-RPC errors. Successful responses are validated against
|
|
51
|
+
* `action.spec.output`.
|
|
52
|
+
*
|
|
53
|
+
* @param options - round-trip test configuration
|
|
54
|
+
*/
|
|
55
|
+
export const describe_rpc_round_trip_tests = (options) => {
|
|
56
|
+
const skip_set = new Set(options.skip_methods);
|
|
57
|
+
const init_schema = async (db) => {
|
|
58
|
+
await run_migrations(db, [AUTH_MIGRATION_NS]);
|
|
59
|
+
};
|
|
60
|
+
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
61
|
+
for (const factory of factories) {
|
|
62
|
+
describe(`RPC round-trip validation (${factory.name})`, () => {
|
|
63
|
+
if (factory.skip)
|
|
64
|
+
return;
|
|
65
|
+
let test_app;
|
|
66
|
+
let authed_account;
|
|
67
|
+
let admin_account;
|
|
68
|
+
let db;
|
|
69
|
+
beforeAll(async () => {
|
|
70
|
+
db = await factory.create();
|
|
71
|
+
test_app = await create_test_app({
|
|
72
|
+
session_options: options.session_options,
|
|
73
|
+
create_route_specs: options.create_route_specs,
|
|
74
|
+
db,
|
|
75
|
+
app_options: {
|
|
76
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
77
|
+
...options.app_options,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
authed_account = await test_app.create_account({
|
|
81
|
+
username: 'rpc_round_trip_authed',
|
|
82
|
+
roles: [],
|
|
83
|
+
});
|
|
84
|
+
admin_account = await test_app.create_account({
|
|
85
|
+
username: 'rpc_round_trip_admin',
|
|
86
|
+
roles: [ROLE_ADMIN],
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
afterAll(async () => {
|
|
90
|
+
await test_app.cleanup();
|
|
91
|
+
await factory.close(db);
|
|
92
|
+
});
|
|
93
|
+
test('all RPC methods produce valid JSON-RPC responses (POST)', async () => {
|
|
94
|
+
for (const ep_spec of options.rpc_endpoints) {
|
|
95
|
+
const surface_ep = test_app.surface_spec.surface.rpc_endpoints.find((e) => e.path === ep_spec.path);
|
|
96
|
+
if (!surface_ep)
|
|
97
|
+
continue;
|
|
98
|
+
for (const action of ep_spec.actions) {
|
|
99
|
+
if (skip_set.has(action.spec.method))
|
|
100
|
+
continue;
|
|
101
|
+
const surface_method = surface_ep.methods.find((m) => m.name === action.spec.method);
|
|
102
|
+
if (!surface_method)
|
|
103
|
+
continue;
|
|
104
|
+
// generate or override params
|
|
105
|
+
const override = options.input_overrides?.get(action.spec.method);
|
|
106
|
+
const params = override ?? generate_valid_body(action.spec.input) ?? null;
|
|
107
|
+
// pick auth
|
|
108
|
+
const headers = pick_rpc_auth_headers(surface_method, test_app, authed_account, admin_account);
|
|
109
|
+
const init = create_rpc_post_init(action.spec.method, params);
|
|
110
|
+
// merge auth headers into init
|
|
111
|
+
Object.assign(init.headers, headers);
|
|
112
|
+
const res = await test_app.app.request(ep_spec.path, init); // eslint-disable-line no-await-in-loop
|
|
113
|
+
const body = await res.json(); // eslint-disable-line no-await-in-loop
|
|
114
|
+
// validate well-formed JSON-RPC; successful responses also checked against output schema
|
|
115
|
+
try {
|
|
116
|
+
if (res.ok) {
|
|
117
|
+
assert_jsonrpc_success_response(body, action.spec.output);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
assert_jsonrpc_error_response(body);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
throw new Error(`RPC round-trip POST failed for ${action.spec.method} (status ${res.status}): ${e.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
test('all read RPC methods produce valid JSON-RPC responses (GET)', async () => {
|
|
130
|
+
for (const ep_spec of options.rpc_endpoints) {
|
|
131
|
+
const surface_ep = test_app.surface_spec.surface.rpc_endpoints.find((e) => e.path === ep_spec.path);
|
|
132
|
+
if (!surface_ep)
|
|
133
|
+
continue;
|
|
134
|
+
const read_actions = ep_spec.actions.filter((a) => !a.spec.side_effects);
|
|
135
|
+
for (const action of read_actions) {
|
|
136
|
+
if (skip_set.has(action.spec.method))
|
|
137
|
+
continue;
|
|
138
|
+
const surface_method = surface_ep.methods.find((m) => m.name === action.spec.method);
|
|
139
|
+
if (!surface_method)
|
|
140
|
+
continue;
|
|
141
|
+
const override = options.input_overrides?.get(action.spec.method);
|
|
142
|
+
const params = override ?? generate_valid_body(action.spec.input) ?? undefined;
|
|
143
|
+
const headers = pick_rpc_auth_headers(surface_method, test_app, authed_account, admin_account);
|
|
144
|
+
const url = create_rpc_get_url(ep_spec.path, action.spec.method, params);
|
|
145
|
+
const res = await test_app.app.request(url, { headers }); // eslint-disable-line no-await-in-loop
|
|
146
|
+
const body = await res.json(); // eslint-disable-line no-await-in-loop
|
|
147
|
+
try {
|
|
148
|
+
if (res.ok) {
|
|
149
|
+
assert_jsonrpc_success_response(body, action.spec.output);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
assert_jsonrpc_error_response(body);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
throw new Error(`RPC round-trip GET failed for ${action.spec.method} (status ${res.status}): ${e.message}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
};
|
package/dist/testing/stubs.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { AppDeps } from '../auth/deps.js';
|
|
|
6
6
|
import type { AppServerContext } from '../server/app_server.js';
|
|
7
7
|
import { Db } from '../db/db.js';
|
|
8
8
|
import { type RouteSpec } from '../http/route_spec.js';
|
|
9
|
-
import { type AppSurfaceSpec } from '../http/surface.js';
|
|
9
|
+
import { type AppSurfaceSpec, type RpcEndpointSpec } from '../http/surface.js';
|
|
10
10
|
import type { SseEventSpec } from '../realtime/sse.js';
|
|
11
11
|
/**
|
|
12
12
|
* Create a Proxy that throws descriptive errors on any property access or method call.
|
|
@@ -76,6 +76,8 @@ export interface CreateTestAppSurfaceSpecOptions {
|
|
|
76
76
|
env_schema?: z.ZodObject;
|
|
77
77
|
/** SSE event specs for surface generation. */
|
|
78
78
|
event_specs?: Array<SseEventSpec>;
|
|
79
|
+
/** RPC endpoint specs for surface generation. */
|
|
80
|
+
rpc_endpoints?: Array<RpcEndpointSpec>;
|
|
79
81
|
/** Transform middleware array (e.g., tx's `extend_middleware_for_tx_binary`). */
|
|
80
82
|
transform_middleware?: (specs: Array<MiddlewareSpec>) => Array<MiddlewareSpec>;
|
|
81
83
|
/** Bootstrap route prefix (default: `'/api/account'`). */
|
|
@@ -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,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAC/B,OAAO,EAAqB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEzE,OAAO,
|
|
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,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAC/B,OAAO,EAAqB,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAEzE,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAKrD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,CAAC,GAAG,GAAG,EAAE,OAAO,MAAM,KAAG,CAqBtD,CAAC;AAET;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,CAAC,GAAG,GAAG,EAAE,QAAQ,MAAM,EAAE,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,CAOxF,CAAC;AAET,iEAAiE;AACjE,eAAO,MAAM,IAAI,EAAE,GAAkC,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,QAAO,EAI/B,CAAC;AAEJ,gDAAgD;AAChD,eAAO,MAAM,YAAY,QAAO,QAAgC,CAAC;AAEjE,2CAA2C;AAC3C,eAAO,MAAM,OAAO,GAAU,IAAI,GAAG,EAAE,MAAM,GAAG,KAAG,OAAO,CAAC,IAAI,CAAW,CAAC;AAI3E,2EAA2E;AAC3E,eAAO,MAAM,aAAa,EAAE,OAS3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAAO,OAStC,CAAC;AAEH,2FAA2F;AAC3F,eAAO,MAAM,0BAA0B,GAAI,UAAU;IACpD,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAC/B,KAAG,KAAK,CAAC,cAAc,CAqBvB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,8BAA8B,GAC1C,iBAAiB,cAAc,CAAC,MAAM,CAAC,KACrC,gBAmBF,CAAC;AAEF,kDAAkD;AAClD,MAAM,WAAW,+BAA+B;IAC/C,6DAA6D;IAC7D,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,qFAAqF;IACrF,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,oEAAoE;IACpE,UAAU,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC;IACzB,8CAA8C;IAC9C,WAAW,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAClC,iDAAiD;IACjD,aAAa,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IACvC,iFAAiF;IACjF,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,KAAK,CAAC,cAAc,CAAC,CAAC;IAC/E,0DAA0D;IAC1D,sBAAsB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,cAyBF,CAAC"}
|
package/dist/testing/stubs.js
CHANGED
|
@@ -12,7 +12,7 @@ import { ApiError, RateLimitError } from '../http/error_schemas.js';
|
|
|
12
12
|
import { Db } from '../db/db.js';
|
|
13
13
|
import { prefix_route_specs } from '../http/route_spec.js';
|
|
14
14
|
import { create_bootstrap_route_specs } from '../auth/bootstrap_routes.js';
|
|
15
|
-
import { create_app_surface_spec } from '../http/surface.js';
|
|
15
|
+
import { create_app_surface_spec, } from '../http/surface.js';
|
|
16
16
|
import { BaseServerEnv } from '../server/env.js';
|
|
17
17
|
/* eslint-disable @typescript-eslint/require-await */
|
|
18
18
|
/**
|
|
@@ -188,5 +188,6 @@ export const create_test_app_surface_spec = (options) => {
|
|
|
188
188
|
route_specs,
|
|
189
189
|
env_schema: options.env_schema ?? BaseServerEnv,
|
|
190
190
|
event_specs: options.event_specs,
|
|
191
|
+
rpc_endpoints: options.rpc_endpoints,
|
|
191
192
|
});
|
|
192
193
|
};
|
|
@@ -134,6 +134,10 @@ export declare const assert_no_unexpected_public_mutations: (surface: AppSurface
|
|
|
134
134
|
* suspicious — they bypass browser security assumptions about GET being idempotent.
|
|
135
135
|
* Query-string-driven filtering (audit log, list endpoints) should use params schemas
|
|
136
136
|
* or query string parsing, not input schemas.
|
|
137
|
+
*
|
|
138
|
+
* Note: RPC endpoints (`create_rpc_endpoint`) use `input: z.null()` on their
|
|
139
|
+
* route specs — the dispatcher handles body/query parsing internally. Real input
|
|
140
|
+
* schemas live in `rpc_endpoints` surface, not on routes.
|
|
137
141
|
*/
|
|
138
142
|
export declare const assert_mutation_routes_use_post: (surface: AppSurface) => void;
|
|
139
143
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAuB7B,OAAO,KAAK,EAAC,UAAU,EAAuB,MAAM,oBAAoB,CAAC;AAczE;;GAEG;AACH,eAAO,MAAM,mCAAmC,GAAI,SAAS,UAAU,KAAG,IAQzE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,GAAI,SAAS,UAAU,KAAG,IASpE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,UAAU,KAAG,IAQrE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,UAAU,KAAG,IAQtE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,UAAU,KAAG,IAQrE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,SAAS,UAAU,KAAG,IAIjE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,GAAI,SAAS,UAAU,KAAG,IAOhE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mCAAmC,GAAI,SAAS,UAAU,KAAG,IAezE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,uCAAuC,GAAI,SAAS,UAAU,KAAG,IAgB7E,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oCAAoC,GAAI,SAAS,UAAU,KAAG,IAuC1E,CAAC;AA0CF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sCAAsC,GAAI,SAAS,UAAU,KAAG,IAU5E,CAAC;AAIF,4DAA4D;AAC5D,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAEpE,iEAAiE;AACjE,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,sBAAsB,CAAC;IACpC,qDAAqD;IACrD,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CAClC;AA+BD;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,UAAU,KAAG,KAAK,CAAC,qBAAqB,CAgB7F,CAAC;AAIF;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;OAGG;IACH,wBAAwB,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAClD;;;OAGG;IACH,yBAAyB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1C;;;OAGG;IACH,qBAAqB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtC;AAUD;;;;;;GAMG;AACH,eAAO,MAAM,oCAAoC,GAChD,SAAS,UAAU,EACnB,qBAAoB,KAAK,CAAC,MAAM,GAAG,MAAM,CAA8B,KACrE,IAcF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qCAAqC,GACjD,SAAS,UAAU,EACnB,YAAW,KAAK,CAAC,MAAM,CAAM,KAC3B,IAYF,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"surface_invariants.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/surface_invariants.ts"],"names":[],"mappings":"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"}
|
|
@@ -347,6 +347,10 @@ export const assert_no_unexpected_public_mutations = (surface, allowlist = []) =
|
|
|
347
347
|
* suspicious — they bypass browser security assumptions about GET being idempotent.
|
|
348
348
|
* Query-string-driven filtering (audit log, list endpoints) should use params schemas
|
|
349
349
|
* or query string parsing, not input schemas.
|
|
350
|
+
*
|
|
351
|
+
* Note: RPC endpoints (`create_rpc_endpoint`) use `input: z.null()` on their
|
|
352
|
+
* route specs — the dispatcher handles body/query parsing internally. Real input
|
|
353
|
+
* schemas live in `rpc_endpoints` surface, not on routes.
|
|
350
354
|
*/
|
|
351
355
|
export const assert_mutation_routes_use_post = (surface) => {
|
|
352
356
|
const input_routes = filter_routes_with_input(surface);
|