@fuzdev/fuz_app 0.63.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.
Files changed (181) hide show
  1. package/dist/actions/CLAUDE.md +525 -827
  2. package/dist/actions/broadcast_api.d.ts +1 -1
  3. package/dist/actions/broadcast_api.js +1 -1
  4. package/dist/actions/cancel.d.ts +2 -2
  5. package/dist/actions/cancel.js +3 -3
  6. package/dist/actions/connection_closer.d.ts +65 -0
  7. package/dist/actions/connection_closer.d.ts.map +1 -0
  8. package/dist/actions/connection_closer.js +38 -0
  9. package/dist/actions/register_action_ws.d.ts +2 -2
  10. package/dist/actions/register_action_ws.d.ts.map +1 -1
  11. package/dist/actions/register_action_ws.js +23 -2
  12. package/dist/actions/register_ws_endpoint.d.ts +12 -10
  13. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  14. package/dist/actions/register_ws_endpoint.js +5 -5
  15. package/dist/actions/transports_ws_auth_guard.d.ts +25 -10
  16. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  17. package/dist/actions/transports_ws_auth_guard.js +24 -9
  18. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  19. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  20. package/dist/actions/ws_endpoint_spec.js +13 -0
  21. package/dist/auth/CLAUDE.md +592 -1808
  22. package/dist/auth/account_action_specs.d.ts +1 -1
  23. package/dist/auth/account_actions.d.ts +13 -0
  24. package/dist/auth/account_actions.d.ts.map +1 -1
  25. package/dist/auth/account_actions.js +31 -1
  26. package/dist/auth/account_routes.d.ts +12 -2
  27. package/dist/auth/account_routes.d.ts.map +1 -1
  28. package/dist/auth/account_routes.js +55 -8
  29. package/dist/auth/account_schema.d.ts +4 -4
  30. package/dist/auth/account_schema.d.ts.map +1 -1
  31. package/dist/auth/admin_action_specs.d.ts +8 -8
  32. package/dist/auth/admin_actions.d.ts +11 -0
  33. package/dist/auth/admin_actions.d.ts.map +1 -1
  34. package/dist/auth/admin_actions.js +25 -0
  35. package/dist/auth/api_token_queries.js +1 -1
  36. package/dist/auth/audit_emitter.d.ts +56 -12
  37. package/dist/auth/audit_emitter.d.ts.map +1 -1
  38. package/dist/auth/audit_emitter.js +38 -12
  39. package/dist/auth/audit_log_ddl.d.ts +1 -1
  40. package/dist/auth/audit_log_ddl.d.ts.map +1 -1
  41. package/dist/auth/audit_log_ddl.js +1 -1
  42. package/dist/auth/audit_log_schema.d.ts +5 -3
  43. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  44. package/dist/auth/audit_log_schema.js +5 -3
  45. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  46. package/dist/auth/bootstrap_account.js +1 -5
  47. package/dist/auth/bootstrap_routes.d.ts +8 -2
  48. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  49. package/dist/auth/bootstrap_routes.js +15 -11
  50. package/dist/auth/invite_schema.d.ts +2 -2
  51. package/dist/auth/keyring.d.ts +6 -6
  52. package/dist/auth/keyring.js +8 -8
  53. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  54. package/dist/auth/role_grant_offer_actions.js +4 -2
  55. package/dist/auth/signup_routes.d.ts +1 -1
  56. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  57. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  58. package/dist/auth/standard_rpc_actions.js +1 -0
  59. package/dist/db/create_db.d.ts.map +1 -1
  60. package/dist/db/create_db.js +13 -0
  61. package/dist/dev/setup.d.ts +2 -2
  62. package/dist/dev/setup.js +3 -3
  63. package/dist/http/CLAUDE.md +225 -483
  64. package/dist/http/error_schemas.d.ts +0 -4
  65. package/dist/http/error_schemas.d.ts.map +1 -1
  66. package/dist/http/error_schemas.js +0 -4
  67. package/dist/http/ip_canonical.d.ts +100 -0
  68. package/dist/http/ip_canonical.d.ts.map +1 -0
  69. package/dist/http/ip_canonical.js +195 -0
  70. package/dist/http/origin.d.ts +14 -6
  71. package/dist/http/origin.d.ts.map +1 -1
  72. package/dist/http/origin.js +14 -32
  73. package/dist/http/pending_effects.d.ts +1 -1
  74. package/dist/http/pending_effects.js +1 -1
  75. package/dist/http/proxy.d.ts +13 -5
  76. package/dist/http/proxy.d.ts.map +1 -1
  77. package/dist/http/proxy.js +15 -23
  78. package/dist/http/surface.d.ts +50 -0
  79. package/dist/http/surface.d.ts.map +1 -1
  80. package/dist/http/surface.js +27 -1
  81. package/dist/primitive_schemas.d.ts +20 -4
  82. package/dist/primitive_schemas.d.ts.map +1 -1
  83. package/dist/primitive_schemas.js +25 -4
  84. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  85. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  86. package/dist/realtime/sse_auth_guard.js +15 -3
  87. package/dist/runtime/mock.js +1 -1
  88. package/dist/server/app_backend.d.ts +66 -19
  89. package/dist/server/app_backend.d.ts.map +1 -1
  90. package/dist/server/app_backend.js +57 -34
  91. package/dist/server/app_server.d.ts +101 -10
  92. package/dist/server/app_server.d.ts.map +1 -1
  93. package/dist/server/app_server.js +105 -6
  94. package/dist/server/env.d.ts +7 -7
  95. package/dist/server/env.d.ts.map +1 -1
  96. package/dist/server/env.js +14 -14
  97. package/dist/server/startup.d.ts.map +1 -1
  98. package/dist/server/startup.js +12 -0
  99. package/dist/server/static.d.ts +4 -4
  100. package/dist/server/static.js +7 -7
  101. package/dist/testing/CLAUDE.md +269 -59
  102. package/dist/testing/admin_integration.d.ts +18 -23
  103. package/dist/testing/admin_integration.d.ts.map +1 -1
  104. package/dist/testing/admin_integration.js +159 -202
  105. package/dist/testing/adversarial_headers.d.ts +6 -0
  106. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  107. package/dist/testing/adversarial_headers.js +13 -5
  108. package/dist/testing/app_server.d.ts +148 -60
  109. package/dist/testing/app_server.d.ts.map +1 -1
  110. package/dist/testing/app_server.js +143 -54
  111. package/dist/testing/attack_surface.d.ts +8 -7
  112. package/dist/testing/attack_surface.d.ts.map +1 -1
  113. package/dist/testing/attack_surface.js +12 -8
  114. package/dist/testing/audit_completeness.d.ts +23 -22
  115. package/dist/testing/audit_completeness.d.ts.map +1 -1
  116. package/dist/testing/audit_completeness.js +199 -158
  117. package/dist/testing/audit_drift_guard.d.ts +116 -0
  118. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  119. package/dist/testing/audit_drift_guard.js +134 -0
  120. package/dist/testing/bootstrap_success.d.ts +28 -0
  121. package/dist/testing/bootstrap_success.d.ts.map +1 -0
  122. package/dist/testing/bootstrap_success.js +144 -0
  123. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  124. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  125. package/dist/testing/connection_closer_helpers.js +48 -0
  126. package/dist/testing/cross_backend/capabilities.d.ts +64 -0
  127. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/capabilities.js +47 -0
  129. package/dist/testing/cross_backend/setup.d.ts +215 -0
  130. package/dist/testing/cross_backend/setup.d.ts.map +1 -0
  131. package/dist/testing/cross_backend/setup.js +101 -0
  132. package/dist/testing/data_exposure.d.ts +14 -15
  133. package/dist/testing/data_exposure.d.ts.map +1 -1
  134. package/dist/testing/data_exposure.js +127 -146
  135. package/dist/testing/db_entities.d.ts +11 -1
  136. package/dist/testing/db_entities.d.ts.map +1 -1
  137. package/dist/testing/db_entities.js +13 -1
  138. package/dist/testing/integration.d.ts +35 -21
  139. package/dist/testing/integration.d.ts.map +1 -1
  140. package/dist/testing/integration.js +231 -293
  141. package/dist/testing/integration_helpers.d.ts +16 -6
  142. package/dist/testing/integration_helpers.d.ts.map +1 -1
  143. package/dist/testing/integration_helpers.js +7 -7
  144. package/dist/testing/mock_fs.d.ts.map +1 -1
  145. package/dist/testing/mock_fs.js +0 -2
  146. package/dist/testing/rate_limiting.d.ts.map +1 -1
  147. package/dist/testing/rate_limiting.js +13 -4
  148. package/dist/testing/role_grant_helpers.d.ts +31 -0
  149. package/dist/testing/role_grant_helpers.d.ts.map +1 -0
  150. package/dist/testing/role_grant_helpers.js +46 -0
  151. package/dist/testing/round_trip.d.ts +21 -16
  152. package/dist/testing/round_trip.d.ts.map +1 -1
  153. package/dist/testing/round_trip.js +65 -86
  154. package/dist/testing/rpc_helpers.d.ts +2 -1
  155. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  156. package/dist/testing/rpc_round_trip.d.ts +24 -21
  157. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  158. package/dist/testing/rpc_round_trip.js +91 -106
  159. package/dist/testing/schema_introspect.d.ts +106 -0
  160. package/dist/testing/schema_introspect.d.ts.map +1 -0
  161. package/dist/testing/schema_introspect.js +123 -0
  162. package/dist/testing/schema_parity.d.ts +144 -0
  163. package/dist/testing/schema_parity.d.ts.map +1 -0
  164. package/dist/testing/schema_parity.js +233 -0
  165. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  166. package/dist/testing/sse_round_trip.js +12 -6
  167. package/dist/testing/standard.d.ts +57 -25
  168. package/dist/testing/standard.d.ts.map +1 -1
  169. package/dist/testing/standard.js +62 -5
  170. package/dist/testing/stubs.d.ts +22 -3
  171. package/dist/testing/stubs.d.ts.map +1 -1
  172. package/dist/testing/stubs.js +28 -21
  173. package/dist/testing/surface_invariants.d.ts +66 -1
  174. package/dist/testing/surface_invariants.d.ts.map +1 -1
  175. package/dist/testing/surface_invariants.js +103 -1
  176. package/dist/testing/transports/surface_source.d.ts +51 -0
  177. package/dist/testing/transports/surface_source.d.ts.map +1 -0
  178. package/dist/testing/transports/surface_source.js +19 -0
  179. package/dist/ui/SurfaceExplorer.svelte +161 -2
  180. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  181. package/package.json +4 -4
@@ -0,0 +1,116 @@
1
+ import './assert_dev_env.js';
2
+ import { type AuditEmitter, type CreateAuditEmitterOptions } from '../auth/audit_emitter.js';
3
+ import type { AuditLogInput } from '../auth/audit_log_schema.js';
4
+ import type { AuditFactory } from '../server/app_backend.js';
5
+ /**
6
+ * Register per-test `beforeEach` + `afterEach` hooks that catch any audit
7
+ * emission with a metadata shape that fails its `audit_metadata_schemas`
8
+ * entry, or an `event_type` not present in the active `AuditLogConfig`.
9
+ *
10
+ * The production validation in `query_audit_log` is fail-open — it bumps
11
+ * process-wide counters and proceeds, so a regression that emits an
12
+ * undeclared metadata field or a typo'd event-type lands a row that
13
+ * passes downstream queries but breaks forensics. Tests that exercise
14
+ * audit emits should fail loudly when this happens.
15
+ *
16
+ * Call at the top of every `describe` / `describe_db` block that fires
17
+ * audit writes through `deps.audit.emit`. Resets counters before each
18
+ * test and asserts zero on completion.
19
+ *
20
+ * Pair with `await_pending_effects: true` (the default for
21
+ * `create_test_app`) so fire-and-forget audit writes have completed by
22
+ * the time the after-each check observes counter state.
23
+ */
24
+ export declare const install_audit_drift_guard: () => void;
25
+ /**
26
+ * Marker pushed into a shared sequence array by an emit-recording
27
+ * `audit_factory`. Pair with `RecordedClose` from
28
+ * `connection_closer_helpers.ts` to test close-vs-emit ordering at
29
+ * handler call sites — see `create_emit_ordering_audit_factory` below.
30
+ */
31
+ export interface AuditEmitMarker {
32
+ kind: 'emit';
33
+ at: number;
34
+ }
35
+ /**
36
+ * Pair returned by {@link create_recording_audit_emitter} — the
37
+ * `AuditEmitter` to inject as `deps.audit`, plus the shared `calls`
38
+ * array that records every captured emission. Both fields are live —
39
+ * callers read `calls` after exercising the handler to assert on the
40
+ * audit metadata shape.
41
+ */
42
+ export interface RecordingAuditEmitter {
43
+ emitter: AuditEmitter;
44
+ calls: Array<AuditLogInput>;
45
+ }
46
+ /**
47
+ * Build a no-op `AuditEmitter` that records every `emit`, `emit_pool`, and
48
+ * `emit_role_grant_target` call into `calls` as an `AuditLogInput`. Use to
49
+ * capture audit metadata shapes in unit tests (e.g. password change failure
50
+ * outcome, role-grant create denial) without standing up the full PGlite +
51
+ * `query_audit_log` pipeline.
52
+ *
53
+ * **Capture scope — all four production fan-out shapes.**
54
+ * `emit_role_grant_target` mirrors `create_audit_emitter`'s lift logic in
55
+ * place — `actor_id` / `account_id` / `ip` are populated from `auth` + `ctx`
56
+ * and the `event_type` / `outcome` / `target_*_id` / `metadata` fields
57
+ * forward from the input envelope. Tests asserting on role-grant-shape
58
+ * emissions read out of the same homogeneous `calls` array.
59
+ * `notify` is a no-op; `on_event_chain` is an empty array.
60
+ *
61
+ * `emit` AND `emit_pool` both append to `calls` so cleanup-sweep tests
62
+ * (which use `emit_pool` exclusively — see `auth/cleanup.ts`) can also
63
+ * read assertions off the same array.
64
+ *
65
+ * Pass `calls_ref` to write into a caller-owned array (callers that
66
+ * declared `const events: Array<AuditLogInput> = []` and want to keep
67
+ * the reference). Omit to let the helper allocate a fresh array and
68
+ * return it on the `calls` field of the result.
69
+ *
70
+ * The returned emitter is deliberately NOT frozen — slots stay mutable
71
+ * so a test can override one when it needs bespoke shape (e.g. an
72
+ * `emit_pool` that throws on the first call). The production
73
+ * `create_audit_emitter` freeze invariant exists to catch the
74
+ * `patch_audit_emit_capture` hot-patch footgun against the
75
+ * closure-captured `emit`; the recording emitter has no inner closure,
76
+ * so the freeze isn't load-bearing here.
77
+ */
78
+ export declare const create_recording_audit_emitter: (calls_ref?: Array<AuditLogInput>) => RecordingAuditEmitter;
79
+ /**
80
+ * Build an `audit_factory` that produces a real `create_audit_emitter`
81
+ * with its `emit` decorated to push a `{kind: 'emit', at: seq.value++}`
82
+ * marker into a shared sequence + events array. Used by the close-vs-emit
83
+ * ordering test to compose against a shared sequence counter (typically
84
+ * `create_recording_closer(seq_ref)` capturing eager-close calls).
85
+ *
86
+ * Pass the returned factory through `create_test_app({audit_factory: …})`
87
+ * — the test backend invokes it with its constructed `{db, log}` and
88
+ * lands the decorated emitter on `backend.deps.audit`. Production
89
+ * handlers dereference `deps.audit.emit` at call time, so the decorator
90
+ * sees every subsequent handler invocation. The underlying `emit` still
91
+ * runs — the decorator records the call, it does not suppress side
92
+ * effects.
93
+ *
94
+ * **Scope — both `emit` and `emit_role_grant_target`.** The decorator
95
+ * is captured by `emit_role_grant_target`'s closure inside
96
+ * `create_audit_emitter` (and re-exposed as the outer `emit` slot), so
97
+ * role-grant-shape emissions land in `events_ref` alongside bare `emit`
98
+ * calls. `emit_pool` and `notify` are not decorated — they take
99
+ * `AuditLogInput` / `AuditLogEvent` directly without going through
100
+ * `emit`, so handler-side `emit_pool` writes (today only
101
+ * `auth/cleanup.ts`) skip capture. Close-firing handlers all reach for
102
+ * `emit` or `emit_role_grant_target`, so the ordering test sees them
103
+ * regardless of which entry point a future refactor picks.
104
+ *
105
+ * Optionally accept `extra_options` to thread `on_audit_event` /
106
+ * `audit_log_config` into the inner emitter — useful when a test wants
107
+ * both ordering capture and a real SSE/WS guard wired into the same
108
+ * emitter chain.
109
+ */
110
+ export declare const create_emit_ordering_audit_factory: <E extends {
111
+ kind: string;
112
+ at: number;
113
+ }>(seq_ref: {
114
+ value: number;
115
+ }, events_ref: Array<AuditEmitMarker | E>, extra_options?: Omit<CreateAuditEmitterOptions, "db" | "log" | "emit_decorator">) => AuditFactory;
116
+ //# sourceMappingURL=audit_drift_guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAU7B,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,yBAAyB,EAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAiB5C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACX;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBA0BF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,kCAAkC,GAAI,CAAC,SAAS;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAC,EACtF,SAAS;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,EACxB,YAAY,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC,EACtC,gBAAgB,IAAI,CAAC,yBAAyB,EAAE,IAAI,GAAG,KAAK,GAAG,gBAAgB,CAAC,KAC9E,YAWF,CAAC"}
@@ -0,0 +1,134 @@
1
+ import './assert_dev_env.js';
2
+ import { assert, beforeEach, afterEach } from 'vitest';
3
+ import { get_audit_metadata_validation_failures, get_audit_unknown_event_type_failures, reset_audit_metadata_validation_failures, reset_audit_unknown_event_type_failures, } from '../auth/audit_log_queries.js';
4
+ import { create_audit_emitter, } from '../auth/audit_emitter.js';
5
+ /**
6
+ * Register per-test `beforeEach` + `afterEach` hooks that catch any audit
7
+ * emission with a metadata shape that fails its `audit_metadata_schemas`
8
+ * entry, or an `event_type` not present in the active `AuditLogConfig`.
9
+ *
10
+ * The production validation in `query_audit_log` is fail-open — it bumps
11
+ * process-wide counters and proceeds, so a regression that emits an
12
+ * undeclared metadata field or a typo'd event-type lands a row that
13
+ * passes downstream queries but breaks forensics. Tests that exercise
14
+ * audit emits should fail loudly when this happens.
15
+ *
16
+ * Call at the top of every `describe` / `describe_db` block that fires
17
+ * audit writes through `deps.audit.emit`. Resets counters before each
18
+ * test and asserts zero on completion.
19
+ *
20
+ * Pair with `await_pending_effects: true` (the default for
21
+ * `create_test_app`) so fire-and-forget audit writes have completed by
22
+ * the time the after-each check observes counter state.
23
+ */
24
+ export const install_audit_drift_guard = () => {
25
+ beforeEach(() => {
26
+ reset_audit_metadata_validation_failures();
27
+ reset_audit_unknown_event_type_failures();
28
+ });
29
+ afterEach(() => {
30
+ assert.strictEqual(get_audit_metadata_validation_failures(), 0, 'audit metadata failed schema validation — see audit_log_schema.audit_metadata_schemas');
31
+ assert.strictEqual(get_audit_unknown_event_type_failures(), 0, 'audit emitted an unknown event_type — see AUDIT_EVENT_TYPES');
32
+ });
33
+ };
34
+ /**
35
+ * Build a no-op `AuditEmitter` that records every `emit`, `emit_pool`, and
36
+ * `emit_role_grant_target` call into `calls` as an `AuditLogInput`. Use to
37
+ * capture audit metadata shapes in unit tests (e.g. password change failure
38
+ * outcome, role-grant create denial) without standing up the full PGlite +
39
+ * `query_audit_log` pipeline.
40
+ *
41
+ * **Capture scope — all four production fan-out shapes.**
42
+ * `emit_role_grant_target` mirrors `create_audit_emitter`'s lift logic in
43
+ * place — `actor_id` / `account_id` / `ip` are populated from `auth` + `ctx`
44
+ * and the `event_type` / `outcome` / `target_*_id` / `metadata` fields
45
+ * forward from the input envelope. Tests asserting on role-grant-shape
46
+ * emissions read out of the same homogeneous `calls` array.
47
+ * `notify` is a no-op; `on_event_chain` is an empty array.
48
+ *
49
+ * `emit` AND `emit_pool` both append to `calls` so cleanup-sweep tests
50
+ * (which use `emit_pool` exclusively — see `auth/cleanup.ts`) can also
51
+ * read assertions off the same array.
52
+ *
53
+ * Pass `calls_ref` to write into a caller-owned array (callers that
54
+ * declared `const events: Array<AuditLogInput> = []` and want to keep
55
+ * the reference). Omit to let the helper allocate a fresh array and
56
+ * return it on the `calls` field of the result.
57
+ *
58
+ * The returned emitter is deliberately NOT frozen — slots stay mutable
59
+ * so a test can override one when it needs bespoke shape (e.g. an
60
+ * `emit_pool` that throws on the first call). The production
61
+ * `create_audit_emitter` freeze invariant exists to catch the
62
+ * `patch_audit_emit_capture` hot-patch footgun against the
63
+ * closure-captured `emit`; the recording emitter has no inner closure,
64
+ * so the freeze isn't load-bearing here.
65
+ */
66
+ export const create_recording_audit_emitter = (calls_ref) => {
67
+ const calls = calls_ref ?? [];
68
+ const emitter = {
69
+ emit: (_ctx, input) => {
70
+ calls.push(input);
71
+ },
72
+ emit_role_grant_target: (ctx, auth, input) => {
73
+ calls.push({
74
+ event_type: input.event_type,
75
+ actor_id: auth.actor.id,
76
+ account_id: auth.account.id,
77
+ outcome: input.outcome,
78
+ target_account_id: input.target_account_id,
79
+ target_actor_id: input.target_actor_id,
80
+ ip: ctx.client_ip,
81
+ metadata: input.metadata,
82
+ });
83
+ },
84
+ emit_pool: (input) => {
85
+ calls.push(input);
86
+ return Promise.resolve();
87
+ },
88
+ notify: () => undefined,
89
+ on_event_chain: [],
90
+ };
91
+ return { emitter, calls };
92
+ };
93
+ /**
94
+ * Build an `audit_factory` that produces a real `create_audit_emitter`
95
+ * with its `emit` decorated to push a `{kind: 'emit', at: seq.value++}`
96
+ * marker into a shared sequence + events array. Used by the close-vs-emit
97
+ * ordering test to compose against a shared sequence counter (typically
98
+ * `create_recording_closer(seq_ref)` capturing eager-close calls).
99
+ *
100
+ * Pass the returned factory through `create_test_app({audit_factory: …})`
101
+ * — the test backend invokes it with its constructed `{db, log}` and
102
+ * lands the decorated emitter on `backend.deps.audit`. Production
103
+ * handlers dereference `deps.audit.emit` at call time, so the decorator
104
+ * sees every subsequent handler invocation. The underlying `emit` still
105
+ * runs — the decorator records the call, it does not suppress side
106
+ * effects.
107
+ *
108
+ * **Scope — both `emit` and `emit_role_grant_target`.** The decorator
109
+ * is captured by `emit_role_grant_target`'s closure inside
110
+ * `create_audit_emitter` (and re-exposed as the outer `emit` slot), so
111
+ * role-grant-shape emissions land in `events_ref` alongside bare `emit`
112
+ * calls. `emit_pool` and `notify` are not decorated — they take
113
+ * `AuditLogInput` / `AuditLogEvent` directly without going through
114
+ * `emit`, so handler-side `emit_pool` writes (today only
115
+ * `auth/cleanup.ts`) skip capture. Close-firing handlers all reach for
116
+ * `emit` or `emit_role_grant_target`, so the ordering test sees them
117
+ * regardless of which entry point a future refactor picks.
118
+ *
119
+ * Optionally accept `extra_options` to thread `on_audit_event` /
120
+ * `audit_log_config` into the inner emitter — useful when a test wants
121
+ * both ordering capture and a real SSE/WS guard wired into the same
122
+ * emitter chain.
123
+ */
124
+ export const create_emit_ordering_audit_factory = (seq_ref, events_ref, extra_options) => {
125
+ return ({ db, log }) => create_audit_emitter({
126
+ ...extra_options,
127
+ db,
128
+ log,
129
+ emit_decorator: (inner) => (ctx, input) => {
130
+ events_ref.push({ kind: 'emit', at: seq_ref.value++ });
131
+ inner(ctx, input);
132
+ },
133
+ });
134
+ };
@@ -0,0 +1,28 @@
1
+ import './assert_dev_env.js';
2
+ import type { SessionOptions } from '../auth/session_cookie.js';
3
+ import type { AppServerContext, BootstrapLiveOptions } from '../server/app_server.js';
4
+ import type { RouteSpec } from '../http/route_spec.js';
5
+ import type { RpcEndpointsSuiteOption } from './rpc_helpers.js';
6
+ /** Options for `describe_bootstrap_success_tests`. */
7
+ export interface BootstrapSuccessTestOptions {
8
+ session_options: SessionOptions<string>;
9
+ /** Same factory the consumer's production server uses. */
10
+ create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
11
+ /** RPC endpoints — passed through to `create_app_server` for shape parity. */
12
+ rpc_endpoints?: RpcEndpointsSuiteOption;
13
+ /**
14
+ * Live bootstrap config — the suite drives `POST /bootstrap` against
15
+ * `bootstrap.token_path`. The suite does NOT assert on `on_bootstrap`
16
+ * callback invocation (Hono-coupled signature is in-process only);
17
+ * assertions land on observable DB state.
18
+ */
19
+ bootstrap: BootstrapLiveOptions;
20
+ /** Override the synthetic token text. Default deterministic. */
21
+ bootstrap_token?: string;
22
+ }
23
+ /**
24
+ * Run the bootstrap success-path test suite against the consumer's
25
+ * production-shaped wiring.
26
+ */
27
+ export declare const describe_bootstrap_success_tests: (options: BootstrapSuccessTestOptions) => void;
28
+ //# sourceMappingURL=bootstrap_success.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap_success.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/bootstrap_success.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAE,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AACpF,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAGrD,OAAO,KAAK,EAAC,uBAAuB,EAAC,MAAM,kBAAkB,CAAC;AAM9D,sDAAsD;AACtD,MAAM,WAAW,2BAA2B;IAC3C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,0DAA0D;IAC1D,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,8EAA8E;IAC9E,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;OAKG;IACH,SAAS,EAAE,oBAAoB,CAAC;IAChC,gEAAgE;IAChE,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,2BAA2B,KAAG,IAyIvF,CAAC"}
@@ -0,0 +1,144 @@
1
+ import './assert_dev_env.js';
2
+ /**
3
+ * Bootstrap success-path suite for consumer projects.
4
+ *
5
+ * Exercises `POST /bootstrap` against an empty DB (no pre-keeper, lock
6
+ * unflipped) through the real `bootstrap_account` flow. Asserts on
7
+ * observable state — account exists, `bootstrap_lock.bootstrapped` is
8
+ * true, audit row emitted, response body shape — rather than
9
+ * `on_bootstrap` callback invocation, so the suite stays cross-impl
10
+ * friendly when Phase 3 cross-process testing wires it against a
11
+ * spawned Rust backend.
12
+ *
13
+ * Folded into `describe_standard_tests` with a `bootstrap.mode === 'live'`
14
+ * silent-skip gate; consumers wiring live bootstrap pick up success-path
15
+ * coverage by default.
16
+ *
17
+ * @module
18
+ */
19
+ import { describe, test, assert } from 'vitest';
20
+ import { ERROR_ALREADY_BOOTSTRAPPED, ERROR_INVALID_TOKEN } from '../http/error_schemas.js';
21
+ import { create_test_app_for_bootstrap } from './app_server.js';
22
+ const DEFAULT_TEST_TOKEN = 'test-bootstrap-token-value-deterministic';
23
+ const TEST_USERNAME = 'keeper';
24
+ const TEST_PASSWORD = 'test-password-with-min-12-chars';
25
+ /**
26
+ * Run the bootstrap success-path test suite against the consumer's
27
+ * production-shaped wiring.
28
+ */
29
+ export const describe_bootstrap_success_tests = (options) => {
30
+ const token = options.bootstrap_token ?? DEFAULT_TEST_TOKEN;
31
+ const route_prefix = options.bootstrap.route_prefix ?? '/api/account';
32
+ const bootstrap_path = `${route_prefix}/bootstrap`;
33
+ describe('bootstrap success path', () => {
34
+ test('POST /bootstrap with valid token creates the keeper account and flips the lock', async () => {
35
+ const test_app = await create_test_app_for_bootstrap({
36
+ session_options: options.session_options,
37
+ create_route_specs: options.create_route_specs,
38
+ rpc_endpoints: options.rpc_endpoints,
39
+ bootstrap: options.bootstrap,
40
+ bootstrap_token: token,
41
+ });
42
+ try {
43
+ const response = await test_app.app.request(bootstrap_path, {
44
+ method: 'POST',
45
+ headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
46
+ body: JSON.stringify({
47
+ token,
48
+ username: TEST_USERNAME,
49
+ password: TEST_PASSWORD,
50
+ }),
51
+ });
52
+ // Response shape
53
+ assert.strictEqual(response.status, 200);
54
+ const body = (await response.json());
55
+ assert.strictEqual(body.ok, true);
56
+ assert.strictEqual(body.account.username, TEST_USERNAME);
57
+ assert.ok(body.account.id);
58
+ assert.ok(body.actor.id);
59
+ // Observable state: account exists in DB
60
+ const account = await test_app.backend.deps.db.query_one('SELECT username FROM account WHERE username = $1', [TEST_USERNAME]);
61
+ assert.ok(account);
62
+ // Observable state: bootstrap_lock flipped to true
63
+ const lock = await test_app.backend.deps.db.query_one('SELECT bootstrapped FROM bootstrap_lock WHERE id = 1');
64
+ assert.ok(lock);
65
+ assert.strictEqual(lock.bootstrapped, true);
66
+ // Observable state: audit row emitted
67
+ const audit_row = await test_app.backend.deps.db.query_one("SELECT event_type, account_id FROM audit_log WHERE event_type = 'bootstrap' AND outcome = 'success' LIMIT 1");
68
+ assert.ok(audit_row);
69
+ assert.strictEqual(audit_row.account_id, body.account.id);
70
+ }
71
+ finally {
72
+ await test_app.cleanup();
73
+ }
74
+ });
75
+ test('second POST /bootstrap returns 403 ALREADY_BOOTSTRAPPED', async () => {
76
+ const test_app = await create_test_app_for_bootstrap({
77
+ session_options: options.session_options,
78
+ create_route_specs: options.create_route_specs,
79
+ rpc_endpoints: options.rpc_endpoints,
80
+ bootstrap: options.bootstrap,
81
+ bootstrap_token: token,
82
+ });
83
+ try {
84
+ // First bootstrap succeeds
85
+ const first = await test_app.app.request(bootstrap_path, {
86
+ method: 'POST',
87
+ headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
88
+ body: JSON.stringify({
89
+ token,
90
+ username: TEST_USERNAME,
91
+ password: TEST_PASSWORD,
92
+ }),
93
+ });
94
+ assert.strictEqual(first.status, 200);
95
+ // Second attempt blocked by lock
96
+ const second = await test_app.app.request(bootstrap_path, {
97
+ method: 'POST',
98
+ headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
99
+ body: JSON.stringify({
100
+ token,
101
+ username: 'another_user',
102
+ password: TEST_PASSWORD,
103
+ }),
104
+ });
105
+ assert.strictEqual(second.status, 403);
106
+ const body = (await second.json());
107
+ assert.strictEqual(body.error, ERROR_ALREADY_BOOTSTRAPPED);
108
+ }
109
+ finally {
110
+ await test_app.cleanup();
111
+ }
112
+ });
113
+ test('POST /bootstrap with wrong token returns 401 INVALID_TOKEN', async () => {
114
+ const test_app = await create_test_app_for_bootstrap({
115
+ session_options: options.session_options,
116
+ create_route_specs: options.create_route_specs,
117
+ rpc_endpoints: options.rpc_endpoints,
118
+ bootstrap: options.bootstrap,
119
+ bootstrap_token: token,
120
+ });
121
+ try {
122
+ const response = await test_app.app.request(bootstrap_path, {
123
+ method: 'POST',
124
+ headers: test_app.create_request_headers({ 'content-type': 'application/json' }),
125
+ body: JSON.stringify({
126
+ token: 'wrong-token-value-that-does-not-match',
127
+ username: TEST_USERNAME,
128
+ password: TEST_PASSWORD,
129
+ }),
130
+ });
131
+ assert.strictEqual(response.status, 401);
132
+ const body = (await response.json());
133
+ assert.strictEqual(body.error, ERROR_INVALID_TOKEN);
134
+ // Observable state: lock NOT flipped (transaction rolled back on auth failure)
135
+ const lock = await test_app.backend.deps.db.query_one('SELECT bootstrapped FROM bootstrap_lock WHERE id = 1');
136
+ assert.ok(lock);
137
+ assert.strictEqual(lock.bootstrapped, false);
138
+ }
139
+ finally {
140
+ await test_app.cleanup();
141
+ }
142
+ });
143
+ });
144
+ };
@@ -0,0 +1,44 @@
1
+ import './assert_dev_env.js';
2
+ import type { ConnectionCloser } from '../actions/connection_closer.js';
3
+ /**
4
+ * Record of a single `ConnectionCloser` method invocation. `at` is the
5
+ * value of a monotonically-increasing sequence counter at the time of
6
+ * the call — pair with `create_emit_ordering_audit_factory` to record both
7
+ * close + audit emit calls into the same sequence for ordering tests.
8
+ */
9
+ export interface RecordedClose {
10
+ method: 'session' | 'token' | 'account';
11
+ id: string;
12
+ at: number;
13
+ }
14
+ export interface RecordingCloser {
15
+ closer: ConnectionCloser;
16
+ calls: Array<RecordedClose>;
17
+ }
18
+ /**
19
+ * Build a `ConnectionCloser` that records every call into `calls` rather
20
+ * than touching real transports. Each method returns 1 ("one socket
21
+ * closed") regardless of whether a real socket exists — handlers
22
+ * typically ignore the return value.
23
+ *
24
+ * Pass `seq_ref` to share the sequence counter with a sibling
25
+ * `create_emit_ordering_audit_factory` so tests can pin close-vs-emit
26
+ * ordering at the handler call site. Without `seq_ref`, the closer
27
+ * uses a fresh internal counter — `at: N` values within a single test
28
+ * are meaningful, but cannot be compared against audit emit ordering.
29
+ */
30
+ export declare const create_recording_closer: (seq_ref?: {
31
+ value: number;
32
+ }) => RecordingCloser;
33
+ /**
34
+ * Pin `{method, id}` on a single recorded close call without baking in
35
+ * the `at: N` sequence number. Use at every "did the closer fire?"
36
+ * assertion site; the sequence number is only meaningful for dedicated
37
+ * ordering tests (paired with `create_emit_ordering_audit_factory`).
38
+ *
39
+ * Throws via `assert.ok` if `call` is `undefined` — index a recorded
40
+ * `calls` array directly (`calls[0]`) and let this helper handle the
41
+ * missing-element case.
42
+ */
43
+ export declare const assert_close_call: (call: RecordedClose | undefined, method: "session" | "token" | "account", id: string) => void;
44
+ //# sourceMappingURL=connection_closer_helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection_closer_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/connection_closer_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAI7B,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB,GAAI,UAAU;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,KAAG,eAkBnE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAC7B,MAAM,aAAa,GAAG,SAAS,EAC/B,QAAQ,SAAS,GAAG,OAAO,GAAG,SAAS,EACvC,IAAI,MAAM,KACR,IAIF,CAAC"}
@@ -0,0 +1,48 @@
1
+ import './assert_dev_env.js';
2
+ import { assert } from 'vitest';
3
+ /**
4
+ * Build a `ConnectionCloser` that records every call into `calls` rather
5
+ * than touching real transports. Each method returns 1 ("one socket
6
+ * closed") regardless of whether a real socket exists — handlers
7
+ * typically ignore the return value.
8
+ *
9
+ * Pass `seq_ref` to share the sequence counter with a sibling
10
+ * `create_emit_ordering_audit_factory` so tests can pin close-vs-emit
11
+ * ordering at the handler call site. Without `seq_ref`, the closer
12
+ * uses a fresh internal counter — `at: N` values within a single test
13
+ * are meaningful, but cannot be compared against audit emit ordering.
14
+ */
15
+ export const create_recording_closer = (seq_ref) => {
16
+ const calls = [];
17
+ const seq = seq_ref ?? { value: 0 };
18
+ const closer = {
19
+ close_sockets_for_session: (id) => {
20
+ calls.push({ method: 'session', id, at: seq.value++ });
21
+ return 1;
22
+ },
23
+ close_sockets_for_token: (id) => {
24
+ calls.push({ method: 'token', id, at: seq.value++ });
25
+ return 1;
26
+ },
27
+ close_sockets_for_account: (id) => {
28
+ calls.push({ method: 'account', id, at: seq.value++ });
29
+ return 1;
30
+ },
31
+ };
32
+ return { closer, calls };
33
+ };
34
+ /**
35
+ * Pin `{method, id}` on a single recorded close call without baking in
36
+ * the `at: N` sequence number. Use at every "did the closer fire?"
37
+ * assertion site; the sequence number is only meaningful for dedicated
38
+ * ordering tests (paired with `create_emit_ordering_audit_factory`).
39
+ *
40
+ * Throws via `assert.ok` if `call` is `undefined` — index a recorded
41
+ * `calls` array directly (`calls[0]`) and let this helper handle the
42
+ * missing-element case.
43
+ */
44
+ export const assert_close_call = (call, method, id) => {
45
+ assert.ok(call, 'expected a recorded close call');
46
+ assert.strictEqual(call.method, method);
47
+ assert.strictEqual(call.id, id);
48
+ };
@@ -0,0 +1,64 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Optional behaviors a backend may support. Each flag's TSDoc names the
4
+ * tests that gate on it; add a new flag here before referencing it from
5
+ * a suite body, and document the gating tests inline.
6
+ */
7
+ export interface BackendCapabilities {
8
+ /**
9
+ * Bearer token auth (`Authorization: Bearer <token>`) is wired through
10
+ * the backend's middleware stack. Gates the bearer-token cases in
11
+ * `describe_standard_integration_tests` and `describe_rate_limiting_tests`.
12
+ */
13
+ readonly bearer_auth: boolean;
14
+ /**
15
+ * Trusted-proxy XFF parsing is wired (`X-Forwarded-For` etc.). Gates
16
+ * the proxy-resolution cases in `describe_standard_integration_tests`
17
+ * and the future cross-process proxy integration suite.
18
+ */
19
+ readonly trusted_proxy: boolean;
20
+ /**
21
+ * Per-account login rate limiting is wired. Gates the per-account
22
+ * rate-limit cases in `describe_rate_limiting_tests`.
23
+ */
24
+ readonly login_rate_limit: boolean;
25
+ /**
26
+ * WebSocket transport is reachable end-to-end. Gates the cross-process
27
+ * WS round-trip suite; the in-process `describe_ws_round_trip_tests`
28
+ * runs against `register_action_ws` directly and ignores this flag.
29
+ */
30
+ readonly ws: boolean;
31
+ /**
32
+ * SSE transport is reachable end-to-end. Gates the cross-process SSE
33
+ * close-detection cases; in-process SSE uses the
34
+ * `on_audit_event` hook and ignores this flag.
35
+ */
36
+ readonly sse: boolean;
37
+ /**
38
+ * Test has direct access to backend-internal state (keyring for
39
+ * signing cookies, DB pool for FK-structural raw queries). Always
40
+ * `true` for in-process Hono via `default_in_process_setup`; always
41
+ * `false` cross-process. Gates the 3 keyring reads in
42
+ * `describe_standard_integration_tests` (expired-cookie generation)
43
+ * and the FK-structural raw query in `describe_audit_completeness_tests`.
44
+ */
45
+ readonly in_process_only: boolean;
46
+ }
47
+ /**
48
+ * Capability declarations for the in-process Hono transport. Every flag
49
+ * is `true` because in-process testing exercises the full backend with
50
+ * no missing optional behaviors. Cross-process consumers
51
+ * declare each flag explicitly per backend.
52
+ */
53
+ export declare const in_process_capabilities: BackendCapabilities;
54
+ /**
55
+ * Conditional `test()` wrapper — registers a vitest case only when
56
+ * `cond` is true; otherwise registers it as `.skip` so the run still
57
+ * surfaces the gated coverage in the report.
58
+ *
59
+ * Thin wrapper around vitest's `test.skipIf(!cond)` with the argument
60
+ * order flipped to match the more readable `test_if(capabilities.bearer_auth, ...)`
61
+ * call pattern.
62
+ */
63
+ export declare const test_if: (cond: boolean, name: string, fn: () => void | Promise<void>) => void;
64
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +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;AAmB9B;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;CAClC;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAOpC,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"}
@@ -0,0 +1,47 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Capability vocabulary for cross-backend integration testing.
4
+ *
5
+ * Backends declare which optional behaviors they support; suite bodies
6
+ * call `test_if(capabilities.X, ...)` to skip cases the backend doesn't
7
+ * implement. No `if (config.name === 'rust')` branches anywhere — name-
8
+ * checking is a code smell that says capability vocabulary is missing.
9
+ *
10
+ * In-process Hono via `default_in_process_setup` declares every
11
+ * capability `true` (see `in_process_capabilities`). Cross-process
12
+ * backends opt in per-flag on their `BackendConfig`.
13
+ *
14
+ * @module
15
+ */
16
+ import { test } from 'vitest';
17
+ /**
18
+ * Capability declarations for the in-process Hono transport. Every flag
19
+ * is `true` because in-process testing exercises the full backend with
20
+ * no missing optional behaviors. Cross-process consumers
21
+ * declare each flag explicitly per backend.
22
+ */
23
+ export const in_process_capabilities = Object.freeze({
24
+ bearer_auth: true,
25
+ trusted_proxy: true,
26
+ login_rate_limit: true,
27
+ ws: true,
28
+ sse: true,
29
+ in_process_only: true,
30
+ });
31
+ /**
32
+ * Conditional `test()` wrapper — registers a vitest case only when
33
+ * `cond` is true; otherwise registers it as `.skip` so the run still
34
+ * surfaces the gated coverage in the report.
35
+ *
36
+ * Thin wrapper around vitest's `test.skipIf(!cond)` with the argument
37
+ * order flipped to match the more readable `test_if(capabilities.bearer_auth, ...)`
38
+ * call pattern.
39
+ */
40
+ export const test_if = (cond, name, fn) => {
41
+ if (cond) {
42
+ test(name, fn);
43
+ }
44
+ else {
45
+ test.skip(name, fn);
46
+ }
47
+ };