@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.
Files changed (42) hide show
  1. package/dist/actions/action_bridge.d.ts +3 -3
  2. package/dist/actions/action_bridge.d.ts.map +1 -1
  3. package/dist/actions/action_bridge.js +4 -3
  4. package/dist/actions/action_rpc.d.ts +89 -0
  5. package/dist/actions/action_rpc.d.ts.map +1 -0
  6. package/dist/actions/action_rpc.js +248 -0
  7. package/dist/http/jsonrpc.d.ts +62 -0
  8. package/dist/http/jsonrpc.d.ts.map +1 -0
  9. package/dist/http/jsonrpc.js +49 -0
  10. package/dist/http/jsonrpc_errors.d.ts +132 -0
  11. package/dist/http/jsonrpc_errors.d.ts.map +1 -0
  12. package/dist/http/jsonrpc_errors.js +197 -0
  13. package/dist/http/route_spec.d.ts +2 -1
  14. package/dist/http/route_spec.d.ts.map +1 -1
  15. package/dist/http/route_spec.js +43 -7
  16. package/dist/http/surface.d.ts +25 -0
  17. package/dist/http/surface.d.ts.map +1 -1
  18. package/dist/http/surface.js +16 -1
  19. package/dist/server/app_server.d.ts +3 -1
  20. package/dist/server/app_server.d.ts.map +1 -1
  21. package/dist/server/app_server.js +2 -1
  22. package/dist/testing/adversarial_input.d.ts.map +1 -1
  23. package/dist/testing/adversarial_input.js +22 -7
  24. package/dist/testing/app_server.d.ts +2 -1
  25. package/dist/testing/app_server.d.ts.map +1 -1
  26. package/dist/testing/app_server.js +1 -0
  27. package/dist/testing/rpc_attack_surface.d.ts +23 -0
  28. package/dist/testing/rpc_attack_surface.d.ts.map +1 -0
  29. package/dist/testing/rpc_attack_surface.js +376 -0
  30. package/dist/testing/rpc_helpers.d.ts +44 -0
  31. package/dist/testing/rpc_helpers.d.ts.map +1 -0
  32. package/dist/testing/rpc_helpers.js +74 -0
  33. package/dist/testing/rpc_round_trip.d.ts +41 -0
  34. package/dist/testing/rpc_round_trip.d.ts.map +1 -0
  35. package/dist/testing/rpc_round_trip.js +163 -0
  36. package/dist/testing/stubs.d.ts +3 -1
  37. package/dist/testing/stubs.d.ts.map +1 -1
  38. package/dist/testing/stubs.js +2 -1
  39. package/dist/testing/surface_invariants.d.ts +4 -0
  40. package/dist/testing/surface_invariants.d.ts.map +1 -1
  41. package/dist/testing/surface_invariants.js +4 -0
  42. 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
+ };
@@ -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,EAA0B,KAAK,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAChF,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,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,cAwBF,CAAC"}
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"}
@@ -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":"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"}
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",