@fuzdev/fuz_app 0.146.1 → 0.148.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 (313) hide show
  1. package/dist/actions/CLAUDE.md +6 -4
  2. package/dist/actions/action_codegen.d.ts +15 -1
  3. package/dist/actions/action_codegen.d.ts.map +1 -1
  4. package/dist/actions/action_codegen.js +23 -2
  5. package/dist/actions/perform_action.js +1 -1
  6. package/dist/auth/CLAUDE.md +1 -1
  7. package/dist/auth/bootstrap_account.d.ts +2 -1
  8. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  9. package/dist/auth/bootstrap_account.js +17 -6
  10. package/dist/auth/cell_action_specs.d.ts +3 -1
  11. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  12. package/dist/auth/cell_action_specs.js +5 -5
  13. package/dist/auth/cell_actions.d.ts.map +1 -1
  14. package/dist/auth/cell_actions.js +2 -3
  15. package/dist/auth/cell_data_schema.d.ts +1 -1
  16. package/dist/auth/cell_data_schema.js +1 -1
  17. package/dist/auth/cell_field_action_specs.d.ts +4 -3
  18. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  19. package/dist/auth/cell_field_action_specs.js +5 -6
  20. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  21. package/dist/auth/cell_field_actions.js +8 -1
  22. package/dist/auth/cell_item_action_specs.d.ts +4 -2
  23. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  24. package/dist/auth/cell_item_action_specs.js +5 -5
  25. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  26. package/dist/auth/cell_item_actions.js +8 -1
  27. package/dist/auth/keyring.d.ts +35 -38
  28. package/dist/auth/keyring.d.ts.map +1 -1
  29. package/dist/auth/keyring.js +43 -50
  30. package/dist/auth/password_argon2.d.ts.map +1 -1
  31. package/dist/auth/password_argon2.js +11 -8
  32. package/dist/auth/role_grant_offer_action_specs.d.ts +2 -0
  33. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  34. package/dist/auth/role_grant_offer_action_specs.js +3 -1
  35. package/dist/auth/session_cookie.d.ts.map +1 -1
  36. package/dist/auth/session_cookie.js +5 -2
  37. package/dist/db/CLAUDE.md +18 -16
  38. package/dist/db/cell_queries.d.ts +3 -2
  39. package/dist/db/cell_queries.d.ts.map +1 -1
  40. package/dist/db/cell_queries.js +3 -2
  41. package/dist/db/fact_disk_storage.d.ts +15 -15
  42. package/dist/db/fact_disk_storage.d.ts.map +1 -1
  43. package/dist/db/fact_disk_storage.js +22 -28
  44. package/dist/db/fact_queries.d.ts +2 -3
  45. package/dist/db/fact_queries.d.ts.map +1 -1
  46. package/dist/db/fact_queries.js +2 -3
  47. package/dist/db/fact_store.d.ts +20 -18
  48. package/dist/db/fact_store.d.ts.map +1 -1
  49. package/dist/db/fact_store.js +30 -44
  50. package/dist/db/file_fact_url.d.ts +19 -4
  51. package/dist/db/file_fact_url.d.ts.map +1 -1
  52. package/dist/db/file_fact_url.js +19 -4
  53. package/dist/primitive_schemas.d.ts +1 -1
  54. package/dist/primitive_schemas.d.ts.map +1 -1
  55. package/dist/primitive_schemas.js +2 -1
  56. package/dist/schema_meta.d.ts +20 -0
  57. package/dist/schema_meta.d.ts.map +1 -1
  58. package/dist/schema_meta.js +7 -1
  59. package/dist/server/env.d.ts.map +1 -1
  60. package/dist/server/env.js +2 -2
  61. package/dist/server/serve_fact_route.d.ts +9 -5
  62. package/dist/server/serve_fact_route.d.ts.map +1 -1
  63. package/dist/server/serve_fact_route.js +15 -7
  64. package/dist/testing/CLAUDE.md +737 -114
  65. package/dist/testing/admin_integration.d.ts.map +1 -1
  66. package/dist/testing/admin_integration.js +73 -5
  67. package/dist/testing/adversarial_input.d.ts +0 -47
  68. package/dist/testing/adversarial_input.d.ts.map +1 -1
  69. package/dist/testing/adversarial_input.js +4 -340
  70. package/dist/testing/adversarial_input_cases.d.ts +64 -0
  71. package/dist/testing/adversarial_input_cases.d.ts.map +1 -0
  72. package/dist/testing/adversarial_input_cases.js +348 -0
  73. package/dist/testing/app_server.d.ts.map +1 -1
  74. package/dist/testing/app_server.js +2 -2
  75. package/dist/testing/audit_completeness.d.ts.map +1 -1
  76. package/dist/testing/audit_completeness.js +24 -20
  77. package/dist/testing/audit_drift_guard.d.ts +27 -1
  78. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  79. package/dist/testing/audit_drift_guard.js +50 -0
  80. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  81. package/dist/testing/cross_backend/account_lifecycle.js +29 -12
  82. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  83. package/dist/testing/cross_backend/actor_lookup.js +7 -25
  84. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/app_settings.js +4 -6
  86. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +51 -0
  87. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -0
  88. package/dist/testing/cross_backend/audit_cross_helpers.js +78 -0
  89. package/dist/testing/cross_backend/backend_config.d.ts +2 -1
  90. package/dist/testing/cross_backend/backend_config.d.ts.map +1 -1
  91. package/dist/testing/cross_backend/body_size.d.ts +6 -0
  92. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  93. package/dist/testing/cross_backend/body_size.js +26 -44
  94. package/dist/testing/cross_backend/body_size_smuggling.d.ts +5 -0
  95. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -1
  96. package/dist/testing/cross_backend/body_size_smuggling.js +7 -14
  97. package/dist/testing/cross_backend/bootstrap_backend.d.ts +11 -1
  98. package/dist/testing/cross_backend/bootstrap_backend.d.ts.map +1 -1
  99. package/dist/testing/cross_backend/bootstrap_backend.js +2 -1
  100. package/dist/testing/cross_backend/build_test_backend_paths.d.ts +3 -1
  101. package/dist/testing/cross_backend/build_test_backend_paths.d.ts.map +1 -1
  102. package/dist/testing/cross_backend/capabilities.d.ts +55 -59
  103. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  104. package/dist/testing/cross_backend/capabilities.js +20 -23
  105. package/dist/testing/cross_backend/cargo_binary.d.ts +70 -0
  106. package/dist/testing/cross_backend/cargo_binary.d.ts.map +1 -0
  107. package/dist/testing/cross_backend/cargo_binary.js +148 -0
  108. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  109. package/dist/testing/cross_backend/cell_crud.js +57 -1
  110. package/dist/testing/cross_backend/cell_gated_create.d.ts +0 -60
  111. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  112. package/dist/testing/cross_backend/cell_gated_create.js +9 -487
  113. package/dist/testing/cross_backend/cell_grant_role.d.ts +14 -2
  114. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  115. package/dist/testing/cross_backend/cell_grant_role.js +22 -12
  116. package/dist/testing/cross_backend/cell_moderate.d.ts +66 -0
  117. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -0
  118. package/dist/testing/cross_backend/cell_moderate.js +511 -0
  119. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/cell_ref_admission.js +1 -1
  121. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/cell_relations.js +40 -19
  123. package/dist/testing/cross_backend/conformance_case.d.ts +43 -4
  124. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/conformance_case.js +29 -1
  126. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts +93 -0
  127. package/dist/testing/cross_backend/conformance_rpc_auth.d.ts.map +1 -0
  128. package/dist/testing/cross_backend/conformance_rpc_auth.js +125 -0
  129. package/dist/testing/cross_backend/conformance_table.d.ts +144 -4
  130. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/conformance_table.js +297 -65
  132. package/dist/testing/cross_backend/cookie_attributes.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/cookie_attributes.js +3 -2
  134. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +38 -5
  135. package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/create_cross_backend_global_setup.js +24 -3
  137. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts +5 -5
  138. package/dist/testing/cross_backend/create_dual_spawn_global_setup.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/credential_deadline.d.ts +10 -6
  140. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -1
  141. package/dist/testing/cross_backend/credential_deadline.js +32 -31
  142. package/dist/testing/cross_backend/credential_header_robustness.d.ts.map +1 -1
  143. package/dist/testing/cross_backend/credential_header_robustness.js +4 -6
  144. package/dist/testing/cross_backend/{cell_cross_helpers.d.ts → cross_rpc.d.ts} +12 -2
  145. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -0
  146. package/dist/testing/cross_backend/{cell_cross_helpers.js → cross_rpc.js} +31 -7
  147. package/dist/testing/cross_backend/default_backend_configs.d.ts +56 -61
  148. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/default_backend_configs.js +97 -91
  150. package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/default_spine_surface.js +2 -2
  152. package/dist/testing/cross_backend/differential.d.ts +126 -0
  153. package/dist/testing/cross_backend/differential.d.ts.map +1 -0
  154. package/dist/testing/cross_backend/differential.js +201 -0
  155. package/dist/testing/cross_backend/differential_normalize.d.ts +150 -0
  156. package/dist/testing/cross_backend/differential_normalize.d.ts.map +1 -0
  157. package/dist/testing/cross_backend/differential_normalize.js +153 -0
  158. package/dist/testing/cross_backend/fact_serving.d.ts +5 -0
  159. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  160. package/dist/testing/cross_backend/fact_serving.js +41 -7
  161. package/dist/testing/cross_backend/full_spine_mount.d.ts +15 -0
  162. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/full_spine_mount.js +55 -1
  164. package/dist/testing/cross_backend/identity_parity.d.ts.map +1 -1
  165. package/dist/testing/cross_backend/identity_parity.js +6 -5
  166. package/dist/testing/cross_backend/in_process_setup.d.ts +12 -2
  167. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
  168. package/dist/testing/cross_backend/in_process_setup.js +10 -7
  169. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  170. package/dist/testing/cross_backend/login_security.js +13 -18
  171. package/dist/testing/cross_backend/origin.js +1 -1
  172. package/dist/testing/cross_backend/origin_conformance.js +1 -1
  173. package/dist/testing/cross_backend/origin_scope.d.ts +6 -0
  174. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -1
  175. package/dist/testing/cross_backend/origin_scope.js +13 -12
  176. package/dist/testing/cross_backend/peer_ping_ws.d.ts +2 -5
  177. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  178. package/dist/testing/cross_backend/peer_ping_ws.js +14 -24
  179. package/dist/testing/cross_backend/postgres_database.d.ts +85 -0
  180. package/dist/testing/cross_backend/postgres_database.d.ts.map +1 -0
  181. package/dist/testing/cross_backend/postgres_database.js +140 -0
  182. package/dist/testing/cross_backend/provided_context.d.ts +28 -0
  183. package/dist/testing/cross_backend/provided_context.d.ts.map +1 -0
  184. package/dist/testing/cross_backend/provided_context.js +35 -0
  185. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  186. package/dist/testing/cross_backend/query_shape.js +10 -12
  187. package/dist/testing/cross_backend/ready.js +2 -2
  188. package/dist/testing/cross_backend/revocation_drivers.d.ts +4 -3
  189. package/dist/testing/cross_backend/revocation_drivers.d.ts.map +1 -1
  190. package/dist/testing/cross_backend/revocation_drivers.js +1 -1
  191. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +3 -2
  192. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -1
  193. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts +13 -0
  194. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -0
  195. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +203 -0
  196. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +2 -5
  197. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  198. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +203 -111
  199. package/dist/testing/cross_backend/role_grant_participation.d.ts +13 -0
  200. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  201. package/dist/testing/cross_backend/role_grant_participation.js +32 -21
  202. package/dist/testing/cross_backend/role_grant_role_grammar.d.ts.map +1 -1
  203. package/dist/testing/cross_backend/role_grant_role_grammar.js +6 -9
  204. package/dist/testing/cross_backend/rpc_adversarial.d.ts +38 -0
  205. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -0
  206. package/dist/testing/cross_backend/rpc_adversarial.js +292 -0
  207. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts +24 -0
  208. package/dist/testing/cross_backend/rpc_success_fixtures.d.ts.map +1 -0
  209. package/dist/testing/cross_backend/rpc_success_fixtures.js +124 -0
  210. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +101 -0
  211. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts.map +1 -0
  212. package/dist/testing/cross_backend/rust_spine_consumer_surface.js +113 -0
  213. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts +10 -0
  214. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  215. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +27 -18
  216. package/dist/testing/cross_backend/session_cap.d.ts +9 -8
  217. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  218. package/dist/testing/cross_backend/session_cap.js +9 -4
  219. package/dist/testing/cross_backend/session_cookie_codec.d.ts +19 -0
  220. package/dist/testing/cross_backend/session_cookie_codec.d.ts.map +1 -0
  221. package/dist/testing/cross_backend/session_cookie_codec.js +128 -0
  222. package/dist/testing/cross_backend/setup.d.ts +56 -54
  223. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  224. package/dist/testing/cross_backend/setup.js +29 -29
  225. package/dist/testing/cross_backend/simple_request.d.ts.map +1 -1
  226. package/dist/testing/cross_backend/simple_request.js +4 -4
  227. package/dist/testing/cross_backend/spawn_backend.d.ts +59 -10
  228. package/dist/testing/cross_backend/spawn_backend.d.ts.map +1 -1
  229. package/dist/testing/cross_backend/spawn_backend.js +219 -60
  230. package/dist/testing/cross_backend/spine_suites.d.ts +231 -0
  231. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -0
  232. package/dist/testing/cross_backend/spine_suites.js +626 -0
  233. package/dist/testing/cross_backend/spine_surface_constants.d.ts +29 -0
  234. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  235. package/dist/testing/cross_backend/spine_surface_constants.js +29 -0
  236. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  237. package/dist/testing/cross_backend/sse_round_trip.js +3 -4
  238. package/dist/testing/cross_backend/standard.d.ts +18 -1
  239. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  240. package/dist/testing/cross_backend/standard.js +2 -0
  241. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +24 -0
  242. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  243. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +15 -0
  244. package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -8
  245. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  246. package/dist/testing/cross_backend/testing_backdoor.js +7 -10
  247. package/dist/testing/cross_backend/testing_reset_actions.d.ts +10 -5
  248. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  249. package/dist/testing/cross_backend/testing_reset_actions.js +21 -12
  250. package/dist/testing/cross_backend/testing_server_bun.js +1 -1
  251. package/dist/testing/cross_backend/token_lifetime.js +1 -1
  252. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  253. package/dist/testing/cross_backend/token_scope_surface.js +9 -13
  254. package/dist/testing/cross_backend/transport_composition.d.ts +26 -2
  255. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -1
  256. package/dist/testing/cross_backend/transport_composition.js +81 -51
  257. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +8 -0
  258. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  259. package/dist/testing/cross_backend/ts_spine_backend_config.js +17 -10
  260. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts +2 -5
  261. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  262. package/dist/testing/cross_backend/ws_action_rate_limit.js +8 -14
  263. package/dist/testing/cross_backend/ws_connection_cap.d.ts +4 -9
  264. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -1
  265. package/dist/testing/cross_backend/ws_connection_cap.js +11 -12
  266. package/dist/testing/cross_backend/ws_endpoint.d.ts +52 -0
  267. package/dist/testing/cross_backend/ws_endpoint.d.ts.map +1 -0
  268. package/dist/testing/cross_backend/ws_endpoint.js +54 -0
  269. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +2 -5
  270. package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -1
  271. package/dist/testing/cross_backend/ws_inbound_dispatch.js +9 -15
  272. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts +2 -7
  273. package/dist/testing/cross_backend/ws_outbound_overflow.d.ts.map +1 -1
  274. package/dist/testing/cross_backend/ws_outbound_overflow.js +5 -9
  275. package/dist/testing/cross_backend/ws_role_gated.d.ts +4 -5
  276. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -1
  277. package/dist/testing/cross_backend/ws_role_gated.js +12 -21
  278. package/dist/testing/cross_backend/ws_round_trip.d.ts +12 -9
  279. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  280. package/dist/testing/cross_backend/ws_round_trip.js +29 -34
  281. package/dist/testing/db_entities.d.ts +16 -3
  282. package/dist/testing/db_entities.d.ts.map +1 -1
  283. package/dist/testing/db_entities.js +28 -5
  284. package/dist/testing/integration_helpers.d.ts +61 -2
  285. package/dist/testing/integration_helpers.d.ts.map +1 -1
  286. package/dist/testing/integration_helpers.js +75 -18
  287. package/dist/testing/round_trip.d.ts +17 -0
  288. package/dist/testing/round_trip.d.ts.map +1 -1
  289. package/dist/testing/round_trip.js +67 -24
  290. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  291. package/dist/testing/rpc_attack_surface.js +13 -138
  292. package/dist/testing/rpc_envelope_cases.d.ts +111 -0
  293. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -0
  294. package/dist/testing/rpc_envelope_cases.js +240 -0
  295. package/dist/testing/rpc_helpers.d.ts +31 -0
  296. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  297. package/dist/testing/rpc_helpers.js +35 -14
  298. package/dist/testing/rpc_round_trip.d.ts +62 -6
  299. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  300. package/dist/testing/rpc_round_trip.js +151 -39
  301. package/dist/testing/schema_generators.d.ts.map +1 -1
  302. package/dist/testing/schema_generators.js +22 -2
  303. package/dist/testing/standard.d.ts +26 -0
  304. package/dist/testing/standard.d.ts.map +1 -1
  305. package/dist/testing/standard.js +4 -1
  306. package/dist/testing/transports/raw_http.d.ts +21 -0
  307. package/dist/testing/transports/raw_http.d.ts.map +1 -1
  308. package/dist/testing/transports/raw_http.js +30 -0
  309. package/package.json +10 -8
  310. package/dist/testing/cross_backend/actor_search.d.ts +0 -6
  311. package/dist/testing/cross_backend/actor_search.d.ts.map +0 -1
  312. package/dist/testing/cross_backend/actor_search.js +0 -90
  313. package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +0 -1
@@ -0,0 +1,54 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Where a spawned backend's WebSocket endpoint is, and how the cross suites
4
+ * open a socket on it as one account's session.
5
+ *
6
+ * The socket-driving cross suites take `WsEndpointTarget` as their endpoint
7
+ * options (flat, or nested as `ws` on a suite whose socket cases are
8
+ * optional), and open a socket on a fixture account's session through
9
+ * `open_session_ws`, so the cookie read, the missing-cookie assertion and the
10
+ * transport choice are written once. Sockets on another credential — a daemon
11
+ * token, a bearer, a session a case logged in on its own transport — and the
12
+ * raw-socket cases open their own way.
13
+ *
14
+ * @module
15
+ */
16
+ import { assert } from 'vitest';
17
+ import { create_admitted_ws_transport, create_ws_transport } from '../transports/ws_transport.js';
18
+ /**
19
+ * The account's session cookie (`<name>=<value>`), asserted present.
20
+ *
21
+ * @param account - the account whose session headers carry the cookie
22
+ * @returns the `Cookie` header value
23
+ */
24
+ export const to_session_cookie = (account) => {
25
+ const { cookie } = account.create_session_headers();
26
+ assert.ok(cookie, 'expected a session cookie for the WS upgrade');
27
+ return cookie;
28
+ };
29
+ /**
30
+ * Open a real-upgrade socket on `target` as `account`'s session: its session
31
+ * cookie on the upgrade, `target.origin` (or `base_url`) as the `Origin`.
32
+ *
33
+ * The cookie is the static one `account.create_session_headers` carries, not
34
+ * whatever `fixture.transport`'s jar holds now, so a case that rotates the
35
+ * session through `fixture.transport` first upgrades on the stale cookie.
36
+ *
37
+ * @param target - the backend's WebSocket endpoint
38
+ * @param account - the account whose session the socket upgrades on
39
+ * @param options - whether to wait for admission, plus transport extras
40
+ * @returns the open socket
41
+ * @throws Error if the account carries no session cookie, or the upgrade (or
42
+ * the admission heartbeat) fails
43
+ */
44
+ export const open_session_ws = (target, account, options) => {
45
+ const { admitted, ...transport_options } = options;
46
+ const open = admitted ? create_admitted_ws_transport : create_ws_transport;
47
+ return open({
48
+ base_url: target.base_url,
49
+ ws_path: target.ws_path,
50
+ cookies: [to_session_cookie(account)],
51
+ origin: target.origin ?? target.base_url,
52
+ ...transport_options
53
+ });
54
+ };
@@ -1,16 +1,13 @@
1
1
  import '../assert_dev_env.js';
2
2
  import { type BackendCapabilities } from './capabilities.js';
3
3
  import type { SetupTest } from './setup.js';
4
+ import { type WsEndpointTarget } from './ws_endpoint.js';
4
5
  /** Configuration for `describe_ws_inbound_dispatch_cross_tests`. */
5
- export interface WsInboundDispatchCrossTestOptions {
6
+ export interface WsInboundDispatchCrossTestOptions extends WsEndpointTarget {
6
7
  /** Per-test fixture producer (`default_cross_process_setup(handle)`). */
7
8
  readonly setup_test: SetupTest;
8
9
  /** Backend capability flags; every case gates on `capabilities.peer_request`. */
9
10
  readonly capabilities: BackendCapabilities;
10
- /** Base URL the backend is reachable at (e.g. `http://localhost:1177`). */
11
- readonly base_url: string;
12
- /** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
13
- readonly ws_path: string;
14
11
  }
15
12
  /**
16
13
  * Register the duplicate-id and in-flight-ceiling cases. Gated on
@@ -1 +1 @@
1
- {"version":3,"file":"ws_inbound_dispatch.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_inbound_dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2C9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAiB5C,oEAAoE;AACpE,MAAM,WAAW,iCAAiC;IACjD,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAiED;;;GAGG;AACH,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IA8GF,CAAC"}
1
+ {"version":3,"file":"ws_inbound_dispatch.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_inbound_dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA0C9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AACzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAiB1E,oEAAoE;AACpE,MAAM,WAAW,iCAAkC,SAAQ,gBAAgB;IAC1E,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;CAC3C;AAiED;;;GAGG;AACH,eAAO,MAAM,wCAAwC,GACpD,SAAS,iCAAiC,KACxC,IAsGF,CAAC"}
@@ -30,9 +30,9 @@ import '../assert_dev_env.js';
30
30
  */
31
31
  import { assert, describe } from 'vitest';
32
32
  import { create_jsonrpc_request } from '../../http/jsonrpc_helpers.js';
33
- import { create_ws_transport } from '../transports/ws_transport.js';
34
33
  import { is_response_for } from '../transports/ws_client.js';
35
34
  import { test_if } from './capabilities.js';
35
+ import { open_session_ws } from './ws_endpoint.js';
36
36
  /** `data.reason` of the duplicate-id refusal. */
37
37
  const REASON_DUPLICATE_REQUEST_ID = 'duplicate_request_id';
38
38
  /** The per-socket in-flight ceiling on both spines. */
@@ -92,18 +92,12 @@ const is_error_for = (id) => (msg) => is_response_for(id)(msg) && 'error' in msg
92
92
  * `capabilities.peer_request` (the cases hold requests with `peer/ping`).
93
93
  */
94
94
  export const describe_ws_inbound_dispatch_cross_tests = (options) => {
95
- const { setup_test, capabilities, base_url, ws_path } = options;
96
- const open_ws = (cookie, on_request) => {
97
- assert.ok(cookie, 'expected a session cookie for the WS upgrade');
98
- return create_ws_transport({
99
- base_url,
100
- ws_path,
101
- cookies: [cookie],
102
- origin: base_url,
103
- on_request,
104
- default_timeout_ms: FRAME_TIMEOUT_MS
105
- });
106
- };
95
+ const { setup_test, capabilities } = options;
96
+ const open_ws = (fixture, on_request) => open_session_ws(options, fixture, {
97
+ admitted: false,
98
+ on_request,
99
+ default_timeout_ms: FRAME_TIMEOUT_MS
100
+ });
107
101
  const send_ping = (ws, id, nonce) => ws.send(create_jsonrpc_request(PEER_PING_METHOD, { nonce }, id));
108
102
  const send_heartbeat = (ws, id) => ws.send(create_jsonrpc_request(HEARTBEAT_METHOD, undefined, id));
109
103
  const assert_duplicate_refusal = (frame, id) => {
@@ -117,7 +111,7 @@ export const describe_ws_inbound_dispatch_cross_tests = (options) => {
117
111
  test_if(capabilities.peer_request, 'a request reusing a live id is refused with that id; the first keeps it, then the id frees', async () => {
118
112
  const fixture = await setup_test();
119
113
  const { responder, release, pinged } = create_holding_responder();
120
- const ws = await open_ws(fixture.create_session_headers().cookie, responder);
114
+ const ws = await open_ws(fixture, responder);
121
115
  try {
122
116
  const id = 'held';
123
117
  await send_ping(ws, id, 41);
@@ -147,7 +141,7 @@ export const describe_ws_inbound_dispatch_cross_tests = (options) => {
147
141
  test_if(capabilities.peer_request, 'past the in-flight ceiling a request is shed with queue_overflow and the socket keeps serving', async () => {
148
142
  const fixture = await setup_test();
149
143
  const { responder, release } = create_holding_responder();
150
- const ws = await open_ws(fixture.create_session_headers().cookie, responder);
144
+ const ws = await open_ws(fixture, responder);
151
145
  try {
152
146
  for (let n = 0; n < MAX_INBOUND_DISPATCHES; n++) {
153
147
  await send_ping(ws, n, n);
@@ -1,18 +1,13 @@
1
1
  import '../assert_dev_env.js';
2
2
  import { type BackendCapabilities } from './capabilities.js';
3
3
  import type { SetupTest } from './setup.js';
4
+ import { type WsEndpointTarget } from './ws_endpoint.js';
4
5
  /** Options for the outbound-overflow parity suite. */
5
- export interface WsOutboundOverflowCrossTestOptions {
6
+ export interface WsOutboundOverflowCrossTestOptions extends WsEndpointTarget {
6
7
  /** Per-test fixture producer (`default_cross_process_setup(handle)`). */
7
8
  readonly setup_test: SetupTest;
8
9
  /** Backend capability flags; every WebSocket case gates on `capabilities.ws`. */
9
10
  readonly capabilities: BackendCapabilities;
10
- /** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
11
- readonly base_url: string;
12
- /** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
13
- readonly ws_path: string;
14
- /** Origin for the session socket's upgrade. Defaults to `base_url`. */
15
- readonly origin?: string;
16
11
  /**
17
12
  * The backend's RPC endpoint path, for the over-HTTP refusal. Omitted, that
18
13
  * case is skipped.
@@ -1 +1 @@
1
- {"version":3,"file":"ws_outbound_overflow.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_outbound_overflow.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAoFzD,sDAAsD;AACtD,MAAM,WAAW,kCAAkC;IAClD,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,kCAAkC,KACzC,IA0NF,CAAC"}
1
+ {"version":3,"file":"ws_outbound_overflow.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_outbound_overflow.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwE9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AACzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAoF1E,sDAAsD;AACtD,MAAM,WAAW,kCAAmC,SAAQ,gBAAgB;IAC3E,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,yCAAyC,GACrD,SAAS,kCAAkC,KACzC,IAqNF,CAAC"}
@@ -62,7 +62,8 @@ import { create_jsonrpc_request } from '../../http/jsonrpc_helpers.js';
62
62
  import { create_admitted_ws_transport } from '../transports/ws_transport.js';
63
63
  import { is_notification, is_response_for } from '../transports/ws_client.js';
64
64
  import { test_if } from './capabilities.js';
65
- import { cross_rpc_call } from './cell_cross_helpers.js';
65
+ import { cross_rpc_call } from './cross_rpc.js';
66
+ import { open_session_ws } from './ws_endpoint.js';
66
67
  /** The close code both spines send on an outbound overflow. */
67
68
  const WS_CLOSE_OUTBOUND_OVERFLOW = 4005;
68
69
  /** The close reason both spines send with it. */
@@ -139,7 +140,7 @@ const CLOSE_TIMEOUT_MS = 15_000;
139
140
  * connection is unaffected.
140
141
  */
141
142
  export const describe_ws_outbound_overflow_cross_tests = (options) => {
142
- const { setup_test, capabilities, base_url, ws_path, origin, rpc_path } = options;
143
+ const { setup_test, capabilities, base_url, ws_path, rpc_path } = options;
143
144
  /** A daemon-token socket the backend has admitted — the one that floods itself. */
144
145
  const open_flooder = (fixture) => create_admitted_ws_transport({
145
146
  base_url,
@@ -156,7 +157,7 @@ export const describe_ws_outbound_overflow_cross_tests = (options) => {
156
157
  assert.deepStrictEqual(await client.request(id, HEARTBEAT_METHOD, {}), {}, message);
157
158
  assert.strictEqual(client.close_code, null, message);
158
159
  };
159
- describe('websocket outbound overflow parity', { timeout: 30_000 }, () => {
160
+ describe('websocket outbound overflow parity', () => {
160
161
  test_if(capabilities.ws, 'a burst within the budget arrives in order, ahead of its response', async () => {
161
162
  const fixture = await setup_test();
162
163
  const client = await open_flooder(fixture);
@@ -206,12 +207,7 @@ export const describe_ws_outbound_overflow_cross_tests = (options) => {
206
207
  test_if(capabilities.ws, 'a burst past the budget closes the socket with WS_CLOSE_OUTBOUND_OVERFLOW and no response', async () => {
207
208
  const fixture = await setup_test();
208
209
  // a second connection on the same account (the keeper's session)
209
- const bystander = await create_admitted_ws_transport({
210
- base_url,
211
- ws_path,
212
- cookies: fixture.transport.cookies(),
213
- origin
214
- });
210
+ const bystander = await open_session_ws(options, fixture, { admitted: true });
215
211
  const client = await open_flooder(fixture);
216
212
  try {
217
213
  await client.send(create_jsonrpc_request(FLOOD_METHOD, OVERFLOWS, 'overflows'));
@@ -1,16 +1,15 @@
1
1
  import '../assert_dev_env.js';
2
2
  import { type BackendCapabilities } from './capabilities.js';
3
3
  import type { SetupTest } from './setup.js';
4
+ import { type WsEndpointTarget } from './ws_endpoint.js';
4
5
  /** Configuration for `describe_ws_role_gated_cross_tests`. */
5
- export interface WsRoleGatedCrossTestOptions {
6
+ export interface WsRoleGatedCrossTestOptions extends WsEndpointTarget {
7
+ /** The ungated WebSocket endpoint path (e.g. `/api/ws`). */
8
+ readonly ws_path: string;
6
9
  /** Per-test fixture producer (`default_cross_process_setup(handle)`). */
7
10
  readonly setup_test: SetupTest;
8
11
  /** Backend capability flags; every case gates on `ws` and `ws_role_gated`. */
9
12
  readonly capabilities: BackendCapabilities;
10
- /** Base URL the backend is reachable at. */
11
- readonly base_url: string;
12
- /** The ungated WebSocket endpoint path (e.g. `/api/ws`). */
13
- readonly ws_path: string;
14
13
  /** The role-gated WebSocket endpoint path. */
15
14
  readonly gated_ws_path: string;
16
15
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ws_role_gated.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_role_gated.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwC9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAKtE,OAAO,KAAK,EAAE,SAAS,EAAmC,MAAM,YAAY,CAAC;AAM7E,8DAA8D;AAC9D,MAAM,WAAW,2BAA2B;IAC3C,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,GAAI,SAAS,2BAA2B,KAAG,IA2IzF,CAAC"}
1
+ {"version":3,"file":"ws_role_gated.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_role_gated.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAuC9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAMtE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAK1E,8DAA8D;AAC9D,MAAM,WAAW,2BAA4B,SAAQ,gBAAgB;IACpE,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,8CAA8C;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,GAAI,SAAS,2BAA2B,KAAG,IA+HzF,CAAC"}
@@ -31,10 +31,10 @@ import { ROLE_ADMIN } from '../../auth/role_schema.js';
31
31
  import { ROLE_GRANT_OFFER_RECEIVED_NOTIFICATION_METHOD } from '../../auth/role_grant_offer_notifications.js';
32
32
  import { rpc_call } from '../rpc_helpers.js';
33
33
  import { is_notification_with } from '../transports/ws_client.js';
34
- import { create_admitted_ws_transport, create_ws_transport } from '../transports/ws_transport.js';
35
34
  import { test_if } from './capabilities.js';
36
35
  import { create_active_role_grant, create_pending_role_grant_offer } from './role_grant_cross_helpers.js';
37
36
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
37
+ import { open_session_ws } from './ws_endpoint.js';
38
38
  /** How long a socket that must stay open is watched for a close it must not get. */
39
39
  const STAYS_OPEN_MS = 1500;
40
40
  /**
@@ -49,30 +49,26 @@ const STAYS_OPEN_MS = 1500;
49
49
  * it: each spine's own transport tests pin it.
50
50
  */
51
51
  export const describe_ws_role_gated_cross_tests = (options) => {
52
- const { setup_test, capabilities, base_url, ws_path, gated_ws_path, gated_roles, scoped_role, rpc_path = SPINE_RPC_PATH } = options;
52
+ const { setup_test, capabilities, gated_ws_path, gated_roles, scoped_role, rpc_path = SPINE_RPC_PATH } = options;
53
53
  const enabled = capabilities.ws && capabilities.ws_role_gated;
54
54
  if (enabled && (!gated_roles.includes(ROLE_ADMIN) || !gated_roles.includes(scoped_role))) {
55
55
  throw new Error(`describe_ws_role_gated_cross_tests: gated_roles must include ${ROLE_ADMIN} and ${scoped_role}`);
56
56
  }
57
57
  const rpc = (fixture, method, params, headers = fixture.create_session_headers()) => rpc_call({ app: fixture.transport, path: rpc_path, method, params, headers });
58
- const cookie_of = (account) => {
59
- const { cookie } = account.create_session_headers();
60
- assert.ok(cookie, 'expected a session cookie for the WS upgrade');
61
- return cookie;
58
+ /** The role-gated endpoint, on the ungated one's backend and origin. */
59
+ const gated_target = {
60
+ base_url: options.base_url,
61
+ ws_path: gated_ws_path,
62
+ ...(options.origin !== undefined && { origin: options.origin })
62
63
  };
63
- const open = (path, account) => create_admitted_ws_transport({
64
- base_url,
65
- ws_path: path,
66
- cookies: [cookie_of(account)],
67
- origin: base_url
68
- });
64
+ const open = (target, account) => open_session_ws(target, account, { admitted: true });
69
65
  describe('role-gated websocket (cross-process)', () => {
70
66
  test_if(enabled, 'revoking a global grant of a gating role closes the gated socket with WS_CLOSE_PERMISSION_REVOKED', async () => {
71
67
  const fixture = await setup_test();
72
68
  const target = await fixture.create_account({ roles: [ROLE_ADMIN] });
73
69
  const role_grant_id = await create_active_role_grant(fixture, target, { rpc_path });
74
- const gated = await open(gated_ws_path, target);
75
- const ungated = await open(ws_path, target);
70
+ const gated = await open(gated_target, target);
71
+ const ungated = await open(options, target);
76
72
  try {
77
73
  const revoked = await rpc(fixture, 'role_grant_revoke', {
78
74
  actor_id: target.actor.id,
@@ -92,12 +88,7 @@ export const describe_ws_role_gated_cross_tests = (options) => {
92
88
  assert.strictEqual(ungated.close_code, null);
93
89
  assert.deepStrictEqual(await ungated.request(2, heartbeat_action_spec.method, {}), {});
94
90
  // and the gate refuses the account a new gated socket
95
- await assert_rejects(() => create_ws_transport({
96
- base_url,
97
- ws_path: gated_ws_path,
98
- cookies: [cookie_of(target)],
99
- origin: base_url
100
- }), /403/);
91
+ await assert_rejects(() => open_session_ws(gated_target, target, { admitted: false }), /403/);
101
92
  }
102
93
  finally {
103
94
  await gated.close();
@@ -124,7 +115,7 @@ export const describe_ws_role_gated_cross_tests = (options) => {
124
115
  scope_kind: 'space',
125
116
  scope_id: '33333333-3333-4333-8333-333333333333'
126
117
  });
127
- const gated = await open(gated_ws_path, target);
118
+ const gated = await open(gated_target, target);
128
119
  try {
129
120
  const revoked = await rpc(fixture, 'role_grant_revoke', {
130
121
  actor_id: target.actor.id,
@@ -1,22 +1,17 @@
1
1
  import '../assert_dev_env.js';
2
2
  import { type BackendCapabilities } from './capabilities.js';
3
3
  import type { SetupTest } from './setup.js';
4
+ import { type WsEndpointTarget } from './ws_endpoint.js';
4
5
  /** Configuration for `describe_cross_process_ws_tests`. */
5
- export interface CrossProcessWsTestOptions {
6
+ export interface CrossProcessWsTestOptions extends WsEndpointTarget {
6
7
  /**
7
8
  * Per-test fixture producer (`default_cross_process_setup(handle)`).
8
- * The authenticated case reads the fresh-per-test keeper's session
9
- * cookies from `fixture.transport.cookies()` to thread onto the upgrade.
9
+ * The authenticated cases upgrade on the fresh-per-test keeper's session
10
+ * cookie (`open_session_ws`).
10
11
  */
11
12
  readonly setup_test: SetupTest;
12
13
  /** Backend capability flags; every case gates on `capabilities.ws`. */
13
14
  readonly capabilities: BackendCapabilities;
14
- /** Base URL the backend is reachable at (e.g. `http://localhost:1178`). */
15
- readonly base_url: string;
16
- /** WebSocket endpoint path on the backend (e.g. `/api/ws`). */
17
- readonly ws_path: string;
18
- /** Origin for the authenticated upgrade. Defaults to `base_url`. */
19
- readonly origin?: string;
20
15
  /**
21
16
  * RPC endpoint path (e.g. `/api/rpc`) used by the close-on-revoke case to
22
17
  * fire `account_session_revoke_all` over the keeper's session channel.
@@ -41,6 +36,14 @@ export interface CrossProcessWsTestOptions {
41
36
  * cap disabled) skips the case.
42
37
  */
43
38
  readonly max_tokens?: number | null;
39
+ /**
40
+ * Roles each second account whose socket a revocation case watches is created
41
+ * with, for a
42
+ * backend whose WS upgrade requires a role (an admin-gated `/api/ws`
43
+ * refuses a roleless account's upgrade). Each must be admin-grantable — the
44
+ * fixture offers it. Default none.
45
+ */
46
+ readonly ws_account_roles?: ReadonlyArray<string>;
44
47
  }
45
48
  /**
46
49
  * Register the cross-process WS round-trip suite over a real upgrade: authed
@@ -1 +1 @@
1
- {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAyH9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAGtE,OAAO,KAAK,EAAE,SAAS,EAAmC,MAAM,YAAY,CAAC;AAK7E,2DAA2D;AAC3D,MAAM,WAAW,yBAAyB;IACzC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,wFAAwF;IACxF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,4FAA4F;IAC5F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,yBAAyB,KAAG,IA4epF,CAAC"}
1
+ {"version":3,"file":"ws_round_trip.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/ws_round_trip.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsH9B,OAAO,EAAE,KAAK,mBAAmB,EAAW,MAAM,mBAAmB,CAAC;AAItE,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC;AAKzD,OAAO,EAAmB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAK1E,2DAA2D;AAC3D,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IAClE;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,wFAAwF;IACxF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,4FAA4F;IAC5F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAClD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,+BAA+B,GAAI,SAAS,yBAAyB,KAAG,IAsdpF,CAAC"}
@@ -85,16 +85,19 @@ import { heartbeat_action_spec } from '../../actions/heartbeat.js';
85
85
  import { DEFAULT_WS_MAX_MESSAGE_BYTES, WS_CLOSE_MESSAGE_TOO_BIG, WS_CLOSE_SESSION_REVOKED, WS_CLOSE_SESSION_REVOKED_REASON } from '../../actions/transports.js';
86
86
  import { account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_revoke_action_spec, TokenCreateOutput, TokenRevokeOutput } from '../../auth/account_action_specs.js';
87
87
  import { DEFAULT_MAX_TOKENS } from '../../auth/account_route_schema.js';
88
- import { account_delete_action_spec, audit_log_list_action_spec, AuditLogListOutput } from '../../auth/admin_action_specs.js';
88
+ import { account_delete_action_spec } from '../../auth/admin_action_specs.js';
89
89
  import { JSONRPC_ERROR_CODES } from '../../http/jsonrpc_errors.js';
90
90
  import { is_response_for } from '../transports/ws_client.js';
91
91
  import { create_admitted_ws_transport, create_ws_transport } from '../transports/ws_transport.js';
92
92
  import { connect_raw_ws, is_raw_ws_close, RAW_WS_OPCODE_TEXT } from '../transports/ws_raw_client.js';
93
93
  import { create_rpc_post_init } from '../rpc_helpers.js';
94
94
  import { DEFAULT_TEST_PASSWORD } from '../test_credentials.js';
95
+ import { list_audit_events } from './audit_cross_helpers.js';
95
96
  import { test_if } from './capabilities.js';
96
- import { cross_rpc_call, expect_output } from './cell_cross_helpers.js';
97
+ import { cross_rpc_call, expect_output } from './cross_rpc.js';
97
98
  import { create_account_revocations } from './revocation_drivers.js';
99
+ import { SPINE_ACCOUNT_LOGOUT_PATH, SPINE_ACCOUNT_PASSWORD_PATH } from './spine_surface_constants.js';
100
+ import { open_session_ws } from './ws_endpoint.js';
98
101
  /** Origin guaranteed to fail the `http://localhost:*` allowlist the test backends run with. */
99
102
  const DISALLOWED_ORIGIN = 'http://disallowed.example';
100
103
  /**
@@ -109,11 +112,11 @@ const DISALLOWED_ORIGIN = 'http://disallowed.example';
109
112
  * audit row read back after `_testing_drain_effects`.
110
113
  */
111
114
  export const describe_cross_process_ws_tests = (options) => {
112
- const { setup_test, capabilities, base_url, ws_path, origin, rpc_path, max_message_bytes = DEFAULT_WS_MAX_MESSAGE_BYTES, logout_path = '/api/account/logout', password_path = '/api/account/password' } = options;
115
+ const { setup_test, capabilities, base_url, ws_path, origin, rpc_path, max_message_bytes = DEFAULT_WS_MAX_MESSAGE_BYTES, logout_path = SPINE_ACCOUNT_LOGOUT_PATH, password_path = SPINE_ACCOUNT_PASSWORD_PATH, ws_account_roles = [] } = options;
113
116
  const max_tokens = options.max_tokens === undefined ? DEFAULT_MAX_TOKENS : options.max_tokens;
114
117
  const open_authed = async () => {
115
118
  const fixture = await setup_test();
116
- return create_ws_transport({ base_url, ws_path, cookies: fixture.transport.cookies(), origin });
119
+ return open_session_ws(options, fixture, { admitted: false });
117
120
  };
118
121
  describe('cross-process websocket', () => {
119
122
  test_if(capabilities.ws, 'authenticated upgrade round-trips heartbeat', async () => {
@@ -260,11 +263,7 @@ export const describe_cross_process_ws_tests = (options) => {
260
263
  origin: null,
261
264
  headers: { Authorization: `Bearer ${token}` }
262
265
  });
263
- const open_session = (account) => {
264
- const { cookie } = account.create_session_headers();
265
- assert.ok(cookie, 'expected a session cookie');
266
- return create_admitted_ws_transport({ base_url, ws_path, cookies: [cookie], origin });
267
- };
266
+ const open_session = (account) => open_session_ws(options, account, { admitted: true });
268
267
  /**
269
268
  * Open each socket in turn, run `fn` over them, and close every socket
270
269
  * that opened — also when a later open fails, so a refused upgrade leaves
@@ -299,12 +298,7 @@ export const describe_cross_process_ws_tests = (options) => {
299
298
  // account actions on the RPC endpoint).
300
299
  test_if(capabilities.ws && rpc_path !== undefined, 'session revocation closes the live socket', async () => {
301
300
  const fixture = await setup_test();
302
- const client = await create_ws_transport({
303
- base_url,
304
- ws_path,
305
- cookies: fixture.transport.cookies(),
306
- origin
307
- });
301
+ const client = await open_session_ws(options, fixture, { admitted: false });
308
302
  try {
309
303
  // Confirm the socket dispatches before revoking, so a failed
310
304
  // close assertion can't be confused with a dead connection.
@@ -323,10 +317,11 @@ export const describe_cross_process_ws_tests = (options) => {
323
317
  // committed. The keeper deletes a second account whose socket is open.
324
318
  test_if(capabilities.ws && capabilities.account_lifecycle && rpc_path !== undefined, 'an account delete closes the live socket of the deleted account', async () => {
325
319
  const fixture = await setup_test();
326
- const target = await fixture.create_account({ username: 'ws_delete_target', roles: [] });
327
- const cookie = target.create_session_headers().cookie;
328
- assert.ok(cookie, 'expected a session cookie for the target account');
329
- const client = await create_ws_transport({ base_url, ws_path, cookies: [cookie], origin });
320
+ const target = await fixture.create_account({
321
+ username: 'ws_delete_target',
322
+ roles: [...ws_account_roles]
323
+ });
324
+ const client = await open_session_ws(options, target, { admitted: false });
330
325
  try {
331
326
  // admitted and dispatching before the delete, so a failed close
332
327
  // assertion can't be confused with a dead connection
@@ -350,7 +345,10 @@ export const describe_cross_process_ws_tests = (options) => {
350
345
  // others: a socket on the account's other token stays open.
351
346
  test_if(capabilities.ws && rpc_path !== undefined, "account_token_revoke closes the revoked token's bearer socket only", async () => {
352
347
  const fixture = await setup_test();
353
- const target = await fixture.create_account({ username: 'ws_token_revoke' });
348
+ const target = await fixture.create_account({
349
+ username: 'ws_token_revoke',
350
+ roles: [...ws_account_roles]
351
+ });
354
352
  const minted = await mint_token(fixture, target, 'ws revoke probe');
355
353
  await with_sockets([() => open_bearer(minted.token), () => open_bearer(target.api_token)], async (revoked, other) => {
356
354
  const res = expect_output(await cross_rpc_call(fixture.fresh_transport(), rpc_path, account_token_revoke_action_spec.method, { token_id: minted.id }, target.create_session_headers()), TokenRevokeOutput);
@@ -377,7 +375,8 @@ export const describe_cross_process_ws_tests = (options) => {
377
375
  const fixture = await setup_test();
378
376
  const target = await fixture.create_account({
379
377
  username: `ws_revoke_${slug}`,
380
- password_value: DEFAULT_TEST_PASSWORD
378
+ password_value: DEFAULT_TEST_PASSWORD,
379
+ roles: [...ws_account_roles]
381
380
  });
382
381
  await with_sockets([() => open_bearer(target.api_token), () => open_session(target)], async (bearer, session) => {
383
382
  await revoke(fixture, target);
@@ -391,7 +390,10 @@ export const describe_cross_process_ws_tests = (options) => {
391
390
  // before it, and its socket closes once the evicting mint commits.
392
391
  test_if(capabilities.ws && rpc_path !== undefined && max_tokens !== null, 'a token the cap evicts has its bearer socket closed', async () => {
393
392
  const fixture = await setup_test();
394
- const target = await fixture.create_account({ username: 'ws_token_cap' });
393
+ const target = await fixture.create_account({
394
+ username: 'ws_token_cap',
395
+ roles: [...ws_account_roles]
396
+ });
395
397
  const watched = await mint_token(fixture, target, 'ws cap victim');
396
398
  const client = await open_bearer(watched.token);
397
399
  try {
@@ -437,12 +439,7 @@ export const describe_cross_process_ws_tests = (options) => {
437
439
  // it returns, the row is visible on either spine.
438
440
  test_if(capabilities.ws && capabilities.ws_account_actions && rpc_path !== undefined, "a WS mutation's audit row is visible after _testing_drain_effects", async () => {
439
441
  const fixture = await setup_test();
440
- const client = await create_ws_transport({
441
- base_url,
442
- ws_path,
443
- cookies: fixture.transport.cookies(),
444
- origin
445
- });
442
+ const client = await open_session_ws(options, fixture, { admitted: false });
446
443
  try {
447
444
  await client.request(1, account_token_create_action_spec.method, {
448
445
  name: 'ws drain probe',
@@ -453,14 +450,12 @@ export const describe_cross_process_ws_tests = (options) => {
453
450
  finally {
454
451
  await client.close();
455
452
  }
456
- const drained = await cross_rpc_call(fixture.fresh_transport({ origin: null }), rpc_path, '_testing_drain_effects', undefined, fixture.create_daemon_token_headers());
457
- assert.ok(drained.ok, `_testing_drain_effects failed: ${JSON.stringify(drained.error)}`);
458
453
  // `_testing_reset` wiped audit_log at setup, so the mint is the only
459
454
  // token_create event
460
- const listed = expect_output(await cross_rpc_call(fixture.transport, rpc_path, audit_log_list_action_spec.method, { event_type: 'token_create' }, fixture.create_session_headers()), AuditLogListOutput);
461
- assert.strictEqual(listed.events.length, 1, JSON.stringify(listed.events));
462
- assert.strictEqual(listed.events[0].outcome, 'success');
463
- assert.strictEqual(listed.events[0].account_id, fixture.account.id);
455
+ const events = await list_audit_events(fixture, { event_type: 'token_create' }, rpc_path);
456
+ assert.strictEqual(events.length, 1, JSON.stringify(events));
457
+ assert.strictEqual(events[0].outcome, 'success');
458
+ assert.strictEqual(events[0].account_id, fixture.account.id);
464
459
  });
465
460
  });
466
461
  };
@@ -43,9 +43,22 @@ export declare const create_test_role_grant_direct: (db: Db, input: CreateRoleGr
43
43
  /**
44
44
  * Add a second `actor` to an existing test account — the multi-actor edge.
45
45
  *
46
- * Wraps the production `query_create_actor`; the TS twin of the Rust
47
- * `tests/common` `seed_extra_actor` helper. Lets a `.db.test.ts` suite build a
48
- * multi-actor account without reimplementing the raw insert.
46
+ * Its `created_at` is read from the wall clock (`clock_timestamp()`) rather
47
+ * than the transaction-fixed `NOW()` default the production
48
+ * `query_create_actor` relies on. Actors listed for an account sort by
49
+ * `created_at ASC, id ASC`, so an extra actor seeded in the transaction that
50
+ * created the account's first one would share its `created_at` and order by
51
+ * random id, and the multi-actor `actor_required` `available` list would come
52
+ * out in a random order. Seeded through here, the first actor sorts first and
53
+ * the extras in insert order. The bootstrap-cradle seeding of `extra_actors`
54
+ * uses it — the `_testing_reset` action and the in-process setup — as the Rust
55
+ * reset seeds them with `query_create_actor_with_clock_timestamp`.
56
+ *
57
+ * @param db - the database, or the transaction the account was seeded in
58
+ * @param account_id - the account the actor joins
59
+ * @param name - the actor's name
60
+ * @returns the created actor
61
+ * @mutates `actor` table - inserts the new row
49
62
  */
50
63
  export declare const create_test_extra_actor: (db: Db, account_id: string, name: string) => Promise<Actor>;
51
64
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"db_entities.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db_entities.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA0B7B,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACjG,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,IAAI,EAAE,EACN,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,KACnD,OAAO,CAAC,oBAAoB,CAI7B,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,6BAA6B,GACzC,IAAI,EAAE,EACN,OAAO,oBAAoB,KACzB,OAAO,CAAC,SAAS,CAA2C,CAAC;AAEhE;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GACnC,IAAI,EAAE,EACN,YAAY,MAAM,EAClB,MAAM,MAAM,KACV,OAAO,CAAC,KAAK,CAAiD,CAAC;AAElE;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GAAU,IAAI,EAAE,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,OAAO,CACvC,CAAC"}
1
+ {"version":3,"file":"db_entities.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/db_entities.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA0B7B,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACjG,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;CACb;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,8BAA8B,GAC1C,IAAI,EAAE,EACN,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,KACnD,OAAO,CAAC,oBAAoB,CAI7B,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,6BAA6B,GACzC,IAAI,EAAE,EACN,OAAO,oBAAoB,KACzB,OAAO,CAAC,SAAS,CAA2C,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,uBAAuB,GACnC,IAAI,EAAE,EACN,YAAY,MAAM,EAClB,MAAM,MAAM,KACV,OAAO,CAAC,KAAK,CAWf,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GAAU,IAAI,EAAE,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,OAAO,CACvC,CAAC"}
@@ -16,7 +16,7 @@ import './assert_dev_env.js';
16
16
  *
17
17
  * @module
18
18
  */
19
- import { query_create_account_with_actor, query_create_actor, query_actor_soft_delete } from '../auth/account_queries.js';
19
+ import { query_create_account_with_actor, query_actor_by_id, query_actor_soft_delete } from '../auth/account_queries.js';
20
20
  import { query_create_role_grant } from '../auth/role_grant_queries.js';
21
21
  /**
22
22
  * Create an `account` + `actor` row pair in the database for tests.
@@ -52,11 +52,34 @@ export const create_test_role_grant_direct = async (db, input) => query_create_r
52
52
  /**
53
53
  * Add a second `actor` to an existing test account — the multi-actor edge.
54
54
  *
55
- * Wraps the production `query_create_actor`; the TS twin of the Rust
56
- * `tests/common` `seed_extra_actor` helper. Lets a `.db.test.ts` suite build a
57
- * multi-actor account without reimplementing the raw insert.
55
+ * Its `created_at` is read from the wall clock (`clock_timestamp()`) rather
56
+ * than the transaction-fixed `NOW()` default the production
57
+ * `query_create_actor` relies on. Actors listed for an account sort by
58
+ * `created_at ASC, id ASC`, so an extra actor seeded in the transaction that
59
+ * created the account's first one would share its `created_at` and order by
60
+ * random id, and the multi-actor `actor_required` `available` list would come
61
+ * out in a random order. Seeded through here, the first actor sorts first and
62
+ * the extras in insert order. The bootstrap-cradle seeding of `extra_actors`
63
+ * uses it — the `_testing_reset` action and the in-process setup — as the Rust
64
+ * reset seeds them with `query_create_actor_with_clock_timestamp`.
65
+ *
66
+ * @param db - the database, or the transaction the account was seeded in
67
+ * @param account_id - the account the actor joins
68
+ * @param name - the actor's name
69
+ * @returns the created actor
70
+ * @mutates `actor` table - inserts the new row
58
71
  */
59
- export const create_test_extra_actor = async (db, account_id, name) => query_create_actor({ db }, account_id, name);
72
+ export const create_test_extra_actor = async (db, account_id, name) => {
73
+ // two steps: the actor projection's timestamp expression is private to
74
+ // `account_queries.ts`, so the row is read back through `query_actor_by_id`
75
+ const inserted = await db.query_one(`INSERT INTO actor (account_id, name, created_at) VALUES ($1, $2, clock_timestamp()) RETURNING id`, [account_id, name]);
76
+ if (!inserted)
77
+ throw new Error('create_test_extra_actor: INSERT returned no row');
78
+ const actor = await query_actor_by_id({ db }, inserted.id);
79
+ if (!actor)
80
+ throw new Error('create_test_extra_actor: inserted actor not found');
81
+ return actor;
82
+ };
60
83
  /**
61
84
  * Soft-delete (tombstone) one actor via the production `query_actor_soft_delete`
62
85
  * — returns `true` when an active row flipped. The TS twin of the Rust