@blamejs/blamejs-shop 0.5.12 → 0.5.14

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 (522) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/sales-tax-filings.js +143 -0
  4. package/lib/vendor/MANIFEST.json +632 -416
  5. package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +21 -10
  6. package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +30 -9
  7. package/lib/vendor/blamejs/.github/dependabot.yml +8 -0
  8. package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
  9. package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +7 -2
  10. package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +7 -2
  11. package/lib/vendor/blamejs/.github/workflows/ci.yml +21 -21
  12. package/lib/vendor/blamejs/.github/workflows/codeql.yml +3 -3
  13. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +4 -4
  14. package/lib/vendor/blamejs/.github/workflows/release-container.yml +7 -7
  15. package/lib/vendor/blamejs/.github/workflows/scorecard.yml +3 -3
  16. package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
  17. package/lib/vendor/blamejs/.gitignore +2 -0
  18. package/lib/vendor/blamejs/CHANGELOG.md +122 -0
  19. package/lib/vendor/blamejs/NOTICE +26 -1
  20. package/lib/vendor/blamejs/SECURITY.md +1 -1
  21. package/lib/vendor/blamejs/api-snapshot.json +207 -5
  22. package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +29 -0
  23. package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +1 -0
  24. package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +31 -0
  25. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +109 -0
  26. package/lib/vendor/blamejs/examples/wiki/nginx.conf +106 -0
  27. package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
  28. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +92 -0
  29. package/lib/vendor/blamejs/fuzz/asn1-der.fuzz.js +28 -0
  30. package/lib/vendor/blamejs/fuzz/cms-codec.fuzz.js +17 -0
  31. package/lib/vendor/blamejs/fuzz/link-header.fuzz.js +18 -0
  32. package/lib/vendor/blamejs/index.js +6 -0
  33. package/lib/vendor/blamejs/lib/a2a.js +4 -4
  34. package/lib/vendor/blamejs/lib/agent-idempotency.js +18 -2
  35. package/lib/vendor/blamejs/lib/agent-saga.js +36 -8
  36. package/lib/vendor/blamejs/lib/agent-snapshot.js +63 -6
  37. package/lib/vendor/blamejs/lib/app-shutdown.js +23 -20
  38. package/lib/vendor/blamejs/lib/archive-read.js +8 -0
  39. package/lib/vendor/blamejs/lib/archive-tar.js +12 -2
  40. package/lib/vendor/blamejs/lib/arg-parser.js +49 -5
  41. package/lib/vendor/blamejs/lib/asn1-der.js +12 -0
  42. package/lib/vendor/blamejs/lib/audit-sign.js +47 -1
  43. package/lib/vendor/blamejs/lib/audit.js +71 -8
  44. package/lib/vendor/blamejs/lib/auth/ciba.js +14 -2
  45. package/lib/vendor/blamejs/lib/auth/fido-mds3.js +58 -47
  46. package/lib/vendor/blamejs/lib/auth/jwt.js +34 -0
  47. package/lib/vendor/blamejs/lib/auth/oauth.js +90 -13
  48. package/lib/vendor/blamejs/lib/auth/oid4vci.js +20 -17
  49. package/lib/vendor/blamejs/lib/auth/openid-federation.js +214 -23
  50. package/lib/vendor/blamejs/lib/auth/saml.js +38 -20
  51. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +10 -4
  52. package/lib/vendor/blamejs/lib/backup/bundle.js +13 -3
  53. package/lib/vendor/blamejs/lib/backup/index.js +16 -5
  54. package/lib/vendor/blamejs/lib/backup/manifest.js +18 -5
  55. package/lib/vendor/blamejs/lib/base32.js +21 -0
  56. package/lib/vendor/blamejs/lib/break-glass.js +19 -10
  57. package/lib/vendor/blamejs/lib/bundler.js +5 -1
  58. package/lib/vendor/blamejs/lib/cache-status.js +3 -7
  59. package/lib/vendor/blamejs/lib/calendar.js +21 -1
  60. package/lib/vendor/blamejs/lib/cbor.js +12 -1
  61. package/lib/vendor/blamejs/lib/cert.js +29 -11
  62. package/lib/vendor/blamejs/lib/cli.js +94 -24
  63. package/lib/vendor/blamejs/lib/cloud-events.js +2 -2
  64. package/lib/vendor/blamejs/lib/cluster-provider-db.js +13 -3
  65. package/lib/vendor/blamejs/lib/codepoint-class.js +85 -0
  66. package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +8 -4
  67. package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +19 -4
  68. package/lib/vendor/blamejs/lib/compliance-ai-act.js +6 -8
  69. package/lib/vendor/blamejs/lib/content-credentials.js +1 -1
  70. package/lib/vendor/blamejs/lib/content-digest.js +2 -1
  71. package/lib/vendor/blamejs/lib/cookies.js +13 -3
  72. package/lib/vendor/blamejs/lib/cose.js +31 -16
  73. package/lib/vendor/blamejs/lib/crypto-field.js +32 -0
  74. package/lib/vendor/blamejs/lib/crypto-oprf.js +11 -2
  75. package/lib/vendor/blamejs/lib/crypto-xwing.js +22 -4
  76. package/lib/vendor/blamejs/lib/crypto.js +314 -19
  77. package/lib/vendor/blamejs/lib/csp.js +11 -3
  78. package/lib/vendor/blamejs/lib/daemon.js +270 -24
  79. package/lib/vendor/blamejs/lib/dark-patterns.js +5 -2
  80. package/lib/vendor/blamejs/lib/db-declare-view.js +2 -2
  81. package/lib/vendor/blamejs/lib/db-query.js +34 -7
  82. package/lib/vendor/blamejs/lib/db.js +18 -6
  83. package/lib/vendor/blamejs/lib/dbsc.js +17 -0
  84. package/lib/vendor/blamejs/lib/dev.js +21 -2
  85. package/lib/vendor/blamejs/lib/dsr.js +157 -102
  86. package/lib/vendor/blamejs/lib/external-db.js +41 -2
  87. package/lib/vendor/blamejs/lib/file-upload.js +25 -7
  88. package/lib/vendor/blamejs/lib/flag-providers.js +27 -15
  89. package/lib/vendor/blamejs/lib/forms.js +113 -27
  90. package/lib/vendor/blamejs/lib/gate-contract.js +23 -10
  91. package/lib/vendor/blamejs/lib/gdpr-ropa.js +8 -1
  92. package/lib/vendor/blamejs/lib/guard-agent-registry.js +1 -1
  93. package/lib/vendor/blamejs/lib/guard-archive.js +2 -2
  94. package/lib/vendor/blamejs/lib/guard-cidr.js +27 -1
  95. package/lib/vendor/blamejs/lib/guard-graphql.js +30 -11
  96. package/lib/vendor/blamejs/lib/guard-html.js +26 -53
  97. package/lib/vendor/blamejs/lib/guard-image.js +24 -5
  98. package/lib/vendor/blamejs/lib/guard-imap-command.js +10 -2
  99. package/lib/vendor/blamejs/lib/guard-jmap.js +19 -3
  100. package/lib/vendor/blamejs/lib/guard-jwt.js +18 -2
  101. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -2
  102. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +10 -2
  103. package/lib/vendor/blamejs/lib/guard-markdown.js +11 -24
  104. package/lib/vendor/blamejs/lib/guard-pdf.js +18 -2
  105. package/lib/vendor/blamejs/lib/guard-pop3-command.js +10 -2
  106. package/lib/vendor/blamejs/lib/guard-regex.js +8 -5
  107. package/lib/vendor/blamejs/lib/guard-svg.js +31 -32
  108. package/lib/vendor/blamejs/lib/guard-time.js +1 -1
  109. package/lib/vendor/blamejs/lib/guard-uuid.js +2 -2
  110. package/lib/vendor/blamejs/lib/html-balance.js +3 -3
  111. package/lib/vendor/blamejs/lib/http-client-cache.js +82 -4
  112. package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +16 -1
  113. package/lib/vendor/blamejs/lib/http-client.js +39 -7
  114. package/lib/vendor/blamejs/lib/http-message-signature.js +35 -1
  115. package/lib/vendor/blamejs/lib/i18n-messageformat.js +31 -6
  116. package/lib/vendor/blamejs/lib/i18n.js +35 -21
  117. package/lib/vendor/blamejs/lib/incident-report.js +8 -1
  118. package/lib/vendor/blamejs/lib/ip-utils.js +11 -1
  119. package/lib/vendor/blamejs/lib/json-path.js +23 -1
  120. package/lib/vendor/blamejs/lib/jwk.js +20 -3
  121. package/lib/vendor/blamejs/lib/link-header.js +2 -1
  122. package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +5 -1
  123. package/lib/vendor/blamejs/lib/log-stream-otlp.js +5 -1
  124. package/lib/vendor/blamejs/lib/log-stream.js +8 -3
  125. package/lib/vendor/blamejs/lib/mail-auth.js +123 -22
  126. package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
  127. package/lib/vendor/blamejs/lib/mail-bounce.js +8 -0
  128. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +46 -10
  129. package/lib/vendor/blamejs/lib/mail-dav.js +22 -3
  130. package/lib/vendor/blamejs/lib/mail-dkim.js +96 -30
  131. package/lib/vendor/blamejs/lib/mail-helo.js +6 -1
  132. package/lib/vendor/blamejs/lib/mail-scan.js +23 -16
  133. package/lib/vendor/blamejs/lib/mail-send-deliver.js +34 -8
  134. package/lib/vendor/blamejs/lib/mail-server-imap.js +3 -4
  135. package/lib/vendor/blamejs/lib/mail-server-managesieve.js +7 -11
  136. package/lib/vendor/blamejs/lib/mail-server-mx.js +93 -7
  137. package/lib/vendor/blamejs/lib/mail-server-registry.js +8 -2
  138. package/lib/vendor/blamejs/lib/mail.js +15 -5
  139. package/lib/vendor/blamejs/lib/metrics.js +268 -63
  140. package/lib/vendor/blamejs/lib/middleware/age-gate.js +7 -1
  141. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +1 -1
  142. package/lib/vendor/blamejs/lib/middleware/body-parser.js +19 -4
  143. package/lib/vendor/blamejs/lib/middleware/bot-guard.js +5 -9
  144. package/lib/vendor/blamejs/lib/middleware/csp-report.js +40 -8
  145. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +24 -10
  146. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -5
  147. package/lib/vendor/blamejs/lib/middleware/index.js +6 -1
  148. package/lib/vendor/blamejs/lib/middleware/nel.js +1 -1
  149. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +1 -1
  150. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +12 -1
  151. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +1 -1
  152. package/lib/vendor/blamejs/lib/middleware/scim-server.js +1 -1
  153. package/lib/vendor/blamejs/lib/middleware/tus-upload.js +6 -2
  154. package/lib/vendor/blamejs/lib/mtls-ca.js +18 -2
  155. package/lib/vendor/blamejs/lib/network-dns-resolver.js +11 -1
  156. package/lib/vendor/blamejs/lib/network-dns.js +21 -2
  157. package/lib/vendor/blamejs/lib/network-tls.js +69 -7
  158. package/lib/vendor/blamejs/lib/nonce-store.js +9 -1
  159. package/lib/vendor/blamejs/lib/object-store/gcs.js +7 -0
  160. package/lib/vendor/blamejs/lib/object-store/sigv4.js +31 -0
  161. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +1 -1
  162. package/lib/vendor/blamejs/lib/outbox.js +1 -1
  163. package/lib/vendor/blamejs/lib/parsers/safe-ini.js +9 -1
  164. package/lib/vendor/blamejs/lib/parsers/safe-toml.js +10 -1
  165. package/lib/vendor/blamejs/lib/permissions.js +6 -3
  166. package/lib/vendor/blamejs/lib/pid-probe.js +55 -0
  167. package/lib/vendor/blamejs/lib/pqc-agent.js +8 -1
  168. package/lib/vendor/blamejs/lib/pqc-software.js +3 -3
  169. package/lib/vendor/blamejs/lib/pubsub.js +20 -9
  170. package/lib/vendor/blamejs/lib/queue-local.js +8 -1
  171. package/lib/vendor/blamejs/lib/queue-redis.js +5 -0
  172. package/lib/vendor/blamejs/lib/redact.js +54 -0
  173. package/lib/vendor/blamejs/lib/restore-bundle.js +10 -2
  174. package/lib/vendor/blamejs/lib/restore.js +19 -0
  175. package/lib/vendor/blamejs/lib/retention.js +12 -4
  176. package/lib/vendor/blamejs/lib/router.js +60 -6
  177. package/lib/vendor/blamejs/lib/safe-buffer.js +69 -0
  178. package/lib/vendor/blamejs/lib/safe-decompress.js +8 -2
  179. package/lib/vendor/blamejs/lib/safe-dns.js +25 -20
  180. package/lib/vendor/blamejs/lib/safe-ical.js +7 -3
  181. package/lib/vendor/blamejs/lib/safe-mime.js +12 -1
  182. package/lib/vendor/blamejs/lib/safe-object.js +80 -0
  183. package/lib/vendor/blamejs/lib/safe-path.js +52 -4
  184. package/lib/vendor/blamejs/lib/safe-redirect.js +10 -2
  185. package/lib/vendor/blamejs/lib/safe-schema.js +4 -1
  186. package/lib/vendor/blamejs/lib/safe-sieve.js +33 -1
  187. package/lib/vendor/blamejs/lib/safe-sql.js +88 -0
  188. package/lib/vendor/blamejs/lib/safe-vcard.js +7 -3
  189. package/lib/vendor/blamejs/lib/scheduler.js +8 -1
  190. package/lib/vendor/blamejs/lib/security-assert.js +6 -6
  191. package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +74 -27
  192. package/lib/vendor/blamejs/lib/self-update.js +517 -87
  193. package/lib/vendor/blamejs/lib/server-timing.js +2 -4
  194. package/lib/vendor/blamejs/lib/session.js +34 -12
  195. package/lib/vendor/blamejs/lib/sql.js +15 -1
  196. package/lib/vendor/blamejs/lib/ssrf-guard.js +52 -0
  197. package/lib/vendor/blamejs/lib/static.js +55 -13
  198. package/lib/vendor/blamejs/lib/storage.js +8 -2
  199. package/lib/vendor/blamejs/lib/subject.js +6 -1
  200. package/lib/vendor/blamejs/lib/tenant-quota.js +52 -13
  201. package/lib/vendor/blamejs/lib/tls-exporter.js +3 -3
  202. package/lib/vendor/blamejs/lib/tsa.js +34 -13
  203. package/lib/vendor/blamejs/lib/uri-template.js +8 -1
  204. package/lib/vendor/blamejs/lib/vault/index.js +13 -0
  205. package/lib/vendor/blamejs/lib/vc.js +44 -5
  206. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +26 -26
  207. package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +792 -1
  208. package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +3443 -1
  209. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +2737 -1
  210. package/lib/vendor/blamejs/lib/vendor/pki.cjs +39550 -13
  211. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +55 -53
  212. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5551 -5554
  213. package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +22459 -32
  214. package/lib/vendor/blamejs/lib/watcher.js +89 -17
  215. package/lib/vendor/blamejs/lib/webhook-dispatcher.js +25 -3
  216. package/lib/vendor/blamejs/lib/webhook.js +12 -2
  217. package/lib/vendor/blamejs/lib/ws-client.js +32 -1
  218. package/lib/vendor/blamejs/lib/xml-c14n.js +29 -1
  219. package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +1 -1
  220. package/lib/vendor/blamejs/package-lock.json +2 -2
  221. package/lib/vendor/blamejs/package.json +1 -1
  222. package/lib/vendor/blamejs/release-notes/v0.16.x.json +1462 -0
  223. package/lib/vendor/blamejs/release-notes/v0.17.0.json +31 -0
  224. package/lib/vendor/blamejs/release-notes/v0.17.1.json +31 -0
  225. package/lib/vendor/blamejs/release-notes/v0.17.10.json +30 -0
  226. package/lib/vendor/blamejs/release-notes/v0.17.11.json +22 -0
  227. package/lib/vendor/blamejs/release-notes/v0.17.12.json +18 -0
  228. package/lib/vendor/blamejs/release-notes/v0.17.13.json +77 -0
  229. package/lib/vendor/blamejs/release-notes/v0.17.14.json +40 -0
  230. package/lib/vendor/blamejs/release-notes/v0.17.15.json +18 -0
  231. package/lib/vendor/blamejs/release-notes/v0.17.16.json +27 -0
  232. package/lib/vendor/blamejs/release-notes/v0.17.17.json +31 -0
  233. package/lib/vendor/blamejs/release-notes/v0.17.18.json +18 -0
  234. package/lib/vendor/blamejs/release-notes/v0.17.19.json +18 -0
  235. package/lib/vendor/blamejs/release-notes/v0.17.2.json +26 -0
  236. package/lib/vendor/blamejs/release-notes/v0.17.20.json +43 -0
  237. package/lib/vendor/blamejs/release-notes/v0.17.21.json +26 -0
  238. package/lib/vendor/blamejs/release-notes/v0.17.22.json +18 -0
  239. package/lib/vendor/blamejs/release-notes/v0.17.3.json +43 -0
  240. package/lib/vendor/blamejs/release-notes/v0.17.4.json +35 -0
  241. package/lib/vendor/blamejs/release-notes/v0.17.5.json +26 -0
  242. package/lib/vendor/blamejs/release-notes/v0.17.6.json +26 -0
  243. package/lib/vendor/blamejs/release-notes/v0.17.7.json +35 -0
  244. package/lib/vendor/blamejs/release-notes/v0.17.8.json +22 -0
  245. package/lib/vendor/blamejs/release-notes/v0.17.9.json +26 -0
  246. package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +4 -0
  247. package/lib/vendor/blamejs/scripts/vendor-update.sh +293 -9
  248. package/lib/vendor/blamejs/socket.yml +94 -0
  249. package/lib/vendor/blamejs/test/00-primitives.js +605 -0
  250. package/lib/vendor/blamejs/test/helpers/drivers.js +12 -9
  251. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +90 -1
  252. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +122 -1
  253. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +30 -0
  254. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +29 -0
  255. package/lib/vendor/blamejs/test/integration/dsr-cluster-mysql.test.js +479 -0
  256. package/lib/vendor/blamejs/test/integration/dsr-cluster-pg.test.js +487 -0
  257. package/lib/vendor/blamejs/test/integration/federation-auth.test.js +313 -5
  258. package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +80 -0
  259. package/lib/vendor/blamejs/test/integration/openid-federation-chain.test.js +275 -0
  260. package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +96 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +401 -0
  262. package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +1649 -2
  263. package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +33 -0
  264. package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +60 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +524 -0
  267. package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +891 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +128 -1
  269. package/lib/vendor/blamejs/test/layer-0-primitives/archive-adapters.test.js +271 -0
  270. package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +628 -1
  271. package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-read.test.js +0 -0
  272. package/lib/vendor/blamejs/test/layer-0-primitives/archive-wrap.test.js +227 -0
  273. package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +15 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +73 -0
  275. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file.test.js +114 -0
  276. package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +141 -0
  277. package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools.test.js +624 -0
  278. package/lib/vendor/blamejs/test/layer-0-primitives/audit.test.js +769 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +436 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth.test.js +1697 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oid4vp.test.js +495 -0
  282. package/lib/vendor/blamejs/test/layer-0-primitives/auth-password.test.js +500 -0
  283. package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml.test.js +1809 -0
  284. package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +243 -1
  285. package/lib/vendor/blamejs/test/layer-0-primitives/backup-index.test.js +2226 -0
  286. package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +183 -0
  287. package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +105 -0
  288. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +693 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +603 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +557 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/case-insensitive-security-token-guard.test.js +214 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/cbor.test.js +25 -0
  293. package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +843 -6
  294. package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +77 -2
  295. package/lib/vendor/blamejs/test/layer-0-primitives/ciba.test.js +866 -0
  296. package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +18 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +19 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/cli.test.js +2790 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/cluster-lease-renewal-ttl.test.js +114 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/cluster.test.js +1032 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +575 -0
  302. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +739 -1
  303. package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +8 -0
  304. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +194 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-lifecycle-proto-key.test.js +104 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +254 -0
  307. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +613 -3
  308. package/lib/vendor/blamejs/test/layer-0-primitives/cookies.test.js +140 -0
  309. package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +407 -0
  310. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-adversarial.test.js +352 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +32 -0
  312. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field.test.js +575 -0
  313. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +10 -0
  314. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +24 -0
  315. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +17 -0
  316. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-spki-pin.test.js +183 -0
  317. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +48 -0
  318. package/lib/vendor/blamejs/test/layer-0-primitives/crypto.test.js +345 -0
  319. package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +58 -0
  320. package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +48 -0
  321. package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +459 -1
  322. package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +19 -0
  323. package/lib/vendor/blamejs/test/layer-0-primitives/db-query.test.js +1019 -0
  324. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +77 -0
  325. package/lib/vendor/blamejs/test/layer-0-primitives/db.test.js +1621 -0
  326. package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +462 -3
  327. package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +323 -0
  328. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-verify.test.js +562 -0
  329. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +901 -1
  330. package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +249 -0
  331. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +561 -0
  332. package/lib/vendor/blamejs/test/layer-0-primitives/external-db.test.js +1214 -0
  333. package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +21 -0
  334. package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +46 -20
  335. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +147 -3
  336. package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +907 -38
  337. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +29 -0
  338. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload.test.js +1135 -0
  339. package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +30 -0
  340. package/lib/vendor/blamejs/test/layer-0-primitives/forms.test.js +747 -0
  341. package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract.test.js +1855 -0
  342. package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +30 -0
  343. package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +86 -0
  344. package/lib/vendor/blamejs/test/layer-0-primitives/guard-auth.test.js +117 -0
  345. package/lib/vendor/blamejs/test/layer-0-primitives/guard-cidr.test.js +151 -0
  346. package/lib/vendor/blamejs/test/layer-0-primitives/guard-domain.test.js +422 -0
  347. package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +424 -0
  348. package/lib/vendor/blamejs/test/layer-0-primitives/{guard-gate-disposition-coverage.test.js → guard-gate-disposition.test.js} +1 -1
  349. package/lib/vendor/blamejs/test/layer-0-primitives/guard-graphql.test.js +340 -0
  350. package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +29 -0
  351. package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +28 -0
  352. package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +519 -45
  353. package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
  354. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +51 -0
  355. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jsonpath.test.js +66 -0
  356. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jwt.test.js +436 -0
  357. package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +25 -0
  358. package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +16 -0
  359. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mime.test.js +86 -0
  360. package/lib/vendor/blamejs/test/layer-0-primitives/guard-oauth.test.js +140 -0
  361. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +80 -0
  362. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +27 -0
  363. package/lib/vendor/blamejs/test/layer-0-primitives/guard-regex.test.js +99 -0
  364. package/lib/vendor/blamejs/test/layer-0-primitives/guard-shell.test.js +76 -0
  365. package/lib/vendor/blamejs/test/layer-0-primitives/{guard-sql-coverage.test.js → guard-sql.test.js} +3 -3
  366. package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +524 -0
  367. package/lib/vendor/blamejs/test/layer-0-primitives/guard-template.test.js +71 -0
  368. package/lib/vendor/blamejs/test/layer-0-primitives/guard-time.test.js +92 -0
  369. package/lib/vendor/blamejs/test/layer-0-primitives/guard-uuid.test.js +86 -0
  370. package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +38 -0
  371. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache-authorization.test.js +347 -0
  372. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +908 -0
  373. package/lib/vendor/blamejs/test/layer-0-primitives/http-client.test.js +2874 -0
  374. package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +643 -0
  375. package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +38 -0
  376. package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +1153 -1
  377. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +494 -0
  378. package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +30 -0
  379. package/lib/vendor/blamejs/test/layer-0-primitives/jsdoc-example-execution.test.js +177 -0
  380. package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +103 -0
  381. package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +214 -0
  382. package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +41 -0
  383. package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +276 -0
  384. package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
  385. package/lib/vendor/blamejs/test/layer-0-primitives/log-stream.test.js +241 -0
  386. package/lib/vendor/blamejs/test/layer-0-primitives/log.test.js +106 -0
  387. package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +441 -0
  388. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +2136 -4
  389. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +759 -12
  390. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +811 -1
  391. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +401 -3
  392. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +751 -8
  393. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +864 -0
  394. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto.test.js +110 -0
  395. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +830 -1
  396. package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +635 -86
  397. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +818 -0
  398. package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +27 -0
  399. package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +199 -0
  400. package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +686 -2
  401. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +778 -0
  402. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +1438 -0
  403. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +449 -6
  404. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +773 -2
  405. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +623 -3
  406. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +55 -0
  407. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +13 -0
  408. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +877 -1
  409. package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +326 -0
  410. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +55 -0
  411. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +242 -0
  412. package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +1596 -2
  413. package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +440 -4
  414. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +89 -0
  415. package/lib/vendor/blamejs/test/layer-0-primitives/metrics.test.js +1230 -0
  416. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-assetlinks.test.js +113 -0
  417. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-body-parser.test.js +1169 -0
  418. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-bot-disclose.test.js +123 -0
  419. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-cookies.test.js +108 -0
  420. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-gpc.test.js +121 -0
  421. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-headers.test.js +163 -0
  422. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-host-allowlist.test.js +154 -0
  423. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-require-content-type.test.js +127 -0
  424. package/lib/vendor/blamejs/test/layer-0-primitives/{middleware-tus-upload-coverage.test.js → middleware-tus-upload.test.js} +45 -0
  425. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-web-app-manifest.test.js +132 -0
  426. package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-crl-fingerprint-only.test.js +92 -0
  427. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +65 -0
  428. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +1727 -1
  429. package/lib/vendor/blamejs/test/layer-0-primitives/network-nts.test.js +699 -0
  430. package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy.test.js +1181 -0
  431. package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +1876 -3
  432. package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +32 -0
  433. package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-enforce-replay-nonboolean.test.js +81 -0
  434. package/lib/vendor/blamejs/test/layer-0-primitives/ntp-check.test.js +122 -0
  435. package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +378 -0
  436. package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +77 -0
  437. package/lib/vendor/blamejs/test/layer-0-primitives/oid4vci.test.js +1016 -0
  438. package/lib/vendor/blamejs/test/layer-0-primitives/openid-federation.test.js +946 -0
  439. package/lib/vendor/blamejs/test/layer-0-primitives/outbox.test.js +818 -0
  440. package/lib/vendor/blamejs/test/layer-0-primitives/own-property-membership-guards.test.js +109 -0
  441. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-toml.test.js +543 -0
  442. package/lib/vendor/blamejs/test/layer-0-primitives/{parsers-safe-yaml-coverage.test.js → parsers-safe-yaml.test.js} +2 -2
  443. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +73 -0
  444. package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +649 -0
  445. package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +524 -0
  446. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +22 -0
  447. package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +295 -0
  448. package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +36 -0
  449. package/lib/vendor/blamejs/test/layer-0-primitives/queue.test.js +83 -0
  450. package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +34 -0
  451. package/lib/vendor/blamejs/test/layer-0-primitives/redact.test.js +1031 -0
  452. package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +94 -0
  453. package/lib/vendor/blamejs/test/layer-0-primitives/require-bound-key.test.js +401 -0
  454. package/lib/vendor/blamejs/test/layer-0-primitives/restore-empty-manifest-wipe.test.js +154 -0
  455. package/lib/vendor/blamejs/test/layer-0-primitives/retention.test.js +600 -0
  456. package/lib/vendor/blamejs/test/layer-0-primitives/router-host-path-injection.test.js +231 -0
  457. package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +21 -0
  458. package/lib/vendor/blamejs/test/layer-0-primitives/router.test.js +1277 -0
  459. package/lib/vendor/blamejs/test/layer-0-primitives/safe-async.test.js +133 -0
  460. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +14 -0
  461. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer.test.js +80 -0
  462. package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +28 -0
  463. package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +245 -0
  464. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +32 -0
  465. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ini.test.js +376 -0
  466. package/lib/vendor/blamejs/test/layer-0-primitives/safe-json.test.js +175 -0
  467. package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +518 -0
  468. package/lib/vendor/blamejs/test/layer-0-primitives/safe-object.test.js +62 -0
  469. package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +30 -0
  470. package/lib/vendor/blamejs/test/layer-0-primitives/safe-schema.test.js +104 -0
  471. package/lib/vendor/blamejs/test/layer-0-primitives/safe-sql.test.js +142 -0
  472. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url.test.js +112 -0
  473. package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +26 -0
  474. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler.test.js +157 -0
  475. package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +396 -0
  476. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +930 -0
  477. package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +755 -82
  478. package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +1053 -0
  479. package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +200 -0
  480. package/lib/vendor/blamejs/test/layer-0-primitives/session-strict-binding-missing-failclosed.test.js +110 -0
  481. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +636 -0
  482. package/lib/vendor/blamejs/test/layer-0-primitives/sql-offset-without-limit.test.js +94 -0
  483. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +1190 -0
  484. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +21 -0
  485. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +850 -0
  486. package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +146 -0
  487. package/lib/vendor/blamejs/test/layer-0-primitives/storage.test.js +621 -0
  488. package/lib/vendor/blamejs/test/layer-0-primitives/subject.test.js +47 -0
  489. package/lib/vendor/blamejs/test/layer-0-primitives/template.test.js +582 -0
  490. package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +587 -25
  491. package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +11 -0
  492. package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +524 -63
  493. package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +27 -0
  494. package/lib/vendor/blamejs/test/layer-0-primitives/vault-rotate.test.js +866 -0
  495. package/lib/vendor/blamejs/test/layer-0-primitives/vault.test.js +74 -0
  496. package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +215 -0
  497. package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +550 -0
  498. package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +388 -0
  499. package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +262 -0
  500. package/lib/vendor/blamejs/test/layer-0-primitives/websocket.test.js +1294 -0
  501. package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +629 -0
  502. package/lib/vendor/blamejs/test/layer-0-primitives/xml-c14n-attr-normalization.test.js +164 -0
  503. package/lib/vendor/blamejs/test/smoke.js +2 -0
  504. package/package.json +1 -1
  505. package/lib/vendor/blamejs/release-notes/v0.16.0.json +0 -44
  506. package/lib/vendor/blamejs/release-notes/v0.16.1.json +0 -36
  507. package/lib/vendor/blamejs/release-notes/v0.16.2.json +0 -71
  508. package/lib/vendor/blamejs/release-notes/v0.16.3.json +0 -71
  509. package/lib/vendor/blamejs/test/layer-0-primitives/acme-coverage.test.js +0 -441
  510. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth-coverage.test.js +0 -482
  511. package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml-coverage.test.js +0 -671
  512. package/lib/vendor/blamejs/test/layer-0-primitives/backup-index-coverage.test.js +0 -690
  513. package/lib/vendor/blamejs/test/layer-0-primitives/cli-coverage.test.js +0 -238
  514. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +0 -319
  515. package/lib/vendor/blamejs/test/layer-0-primitives/keychain-coverage.test.js +0 -0
  516. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth-coverage.test.js +0 -437
  517. package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +0 -443
  518. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +0 -395
  519. package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy-coverage.test.js +0 -565
  520. package/lib/vendor/blamejs/test/layer-0-primitives/router-coverage.test.js +0 -592
  521. package/lib/vendor/blamejs/test/layer-0-primitives/sql-coverage.test.js +0 -422
  522. package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +0 -573
@@ -6,6 +6,8 @@ var helpers = require("../helpers");
6
6
  var check = helpers.check;
7
7
  var b = helpers.b;
8
8
 
9
+ var nodeHttp = require("node:http");
10
+
9
11
  function testSurface() {
10
12
  check("namespace", typeof b.mail.server.jmap === "object");
11
13
  check("create fn", typeof b.mail.server.jmap.create === "function");
@@ -749,6 +751,931 @@ function testWebSocketHandlerRefusesUnauthenticated() {
749
751
  check("unauth → socket destroyed", destroyed === true);
750
752
  }
751
753
 
754
+ // ---- Handlers driven over REAL localhost connections ----
755
+ //
756
+ // JMAP is HTTP-mounted (RFC 8620), so the Express-style handlers
757
+ // (apiHandler / sessionHandler / discoveryHandler / eventSourceHandler /
758
+ // uploadHandler / downloadHandler) are mounted on a real node:http server
759
+ // and driven with genuine HTTP requests + a real WebSocket client
760
+ // (b.wsClient) for the RFC 8887 webSocketHandler — exercising the
761
+ // wrong-state / malformed / backend-failure / resource-limit / cross-tenant
762
+ // refusals over the wire.
763
+
764
+ // ---- global handle tracking (WS clients + upgrade sockets + servers) ----
765
+ var _httpServers = [];
766
+ var _wsClients = [];
767
+ var _wsSockets = [];
768
+
769
+ function _actorFrom(req, cfg) {
770
+ if (cfg.noActor) return null;
771
+ var h = req.headers["x-actor"];
772
+ if (h === "none") return null;
773
+ if (h === "idonly") return { id: "uid-9" };
774
+ if (h === "empty") return {};
775
+ return cfg.actor || { id: "u1", username: "alice" };
776
+ }
777
+
778
+ // Mount every JMAP handler on a real node:http server. Body-consuming
779
+ // apiHandler reads the request body first (simulating b.middleware.bodyParser);
780
+ // the streaming upload/download/eventsource handlers own their own req stream.
781
+ function _startHttp(jmap, cfg) {
782
+ cfg = cfg || {};
783
+ var server = nodeHttp.createServer(function (req, res) {
784
+ req.user = _actorFrom(req, cfg);
785
+ if (cfg.params) req.params = cfg.params;
786
+ var path = String(req.url || "").split("?")[0];
787
+ if (cfg.forceDownload) { jmap.downloadHandler(req, res); return; }
788
+ if (cfg.forceUpload) { jmap.uploadHandler(req, res); return; }
789
+ if (path === "/jmap/session") { jmap.sessionHandler(req, res); return; }
790
+ if (path === "/.well-known/jmap") { jmap.discoveryHandler(req, res); return; }
791
+ if (path === "/jmap/eventsource") { jmap.eventSourceHandler(req, res); return; }
792
+ if (path.indexOf("/jmap/upload/") === 0) { jmap.uploadHandler(req, res); return; }
793
+ if (path.indexOf("/jmap/download/") === 0) { jmap.downloadHandler(req, res); return; }
794
+ if (path === "/jmap/api") {
795
+ var chunks = [];
796
+ req.on("data", function (c) { chunks.push(c); });
797
+ req.on("end", function () {
798
+ var raw = Buffer.concat(chunks).toString("utf8");
799
+ // Leave req.body UNDEFINED on an empty body so the "body missing"
800
+ // branch is reachable; otherwise hand apiHandler the parsed object.
801
+ if (raw.length > 0) { try { req.body = JSON.parse(raw); } catch (_e) { req.body = raw; } }
802
+ jmap.apiHandler(req, res);
803
+ });
804
+ return;
805
+ }
806
+ res.statusCode = 404; res.end();
807
+ });
808
+ server.on("upgrade", function (req, socket, head) {
809
+ _wsSockets.push(socket);
810
+ req.user = _actorFrom(req, cfg);
811
+ jmap.webSocketHandler(req, socket, head);
812
+ });
813
+ _httpServers.push(server);
814
+ return new Promise(function (resolve) {
815
+ server.listen(0, "127.0.0.1", function () { resolve({ server: server, port: server.address().port }); });
816
+ });
817
+ }
818
+
819
+ function _stop(server) {
820
+ return new Promise(function (resolve) { try { server.close(function () { resolve(); }); } catch (_e) { resolve(); } });
821
+ }
822
+
823
+ // One HTTP request → { status, headers, body(string) }. agent:false so each
824
+ // request uses a fresh socket that closes (no keep-alive handle leak).
825
+ function _req(port, opts) {
826
+ opts = opts || {};
827
+ return new Promise(function (resolve, reject) {
828
+ var r = nodeHttp.request({
829
+ host: "127.0.0.1", port: port, method: opts.method || "GET",
830
+ path: opts.path, headers: opts.headers || {}, agent: false,
831
+ }, function (res) {
832
+ var chunks = [];
833
+ res.on("data", function (c) { chunks.push(c); });
834
+ res.on("end", function () {
835
+ resolve({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks).toString("utf8") });
836
+ });
837
+ });
838
+ r.on("error", reject);
839
+ if (opts.body != null) r.write(opts.body);
840
+ r.end();
841
+ });
842
+ }
843
+
844
+ // Tolerant request — resolves { refused:true } if the socket resets (the
845
+ // oversize-upload responder writes 413 then destroys the request stream, a
846
+ // respond-then-reset race). Either a 413 read OR a reset proves refusal.
847
+ function _reqSafe(port, opts) {
848
+ return _req(port, opts).then(
849
+ function (r) { return r; },
850
+ function (_e) { return { refused: true, status: 0, headers: {}, body: "" }; }
851
+ );
852
+ }
853
+
854
+ // Open an SSE / streaming response; resolve on the response head so we can
855
+ // read status + a live-growing buffer, then abort.
856
+ function _openSse(port, path, headers) {
857
+ return new Promise(function (resolve, reject) {
858
+ var r = nodeHttp.request({
859
+ host: "127.0.0.1", port: port, method: "GET", path: path, headers: headers || {}, agent: false,
860
+ }, function (res) {
861
+ var buf = ""; var closed = false;
862
+ res.on("data", function (c) { buf += c.toString("utf8"); });
863
+ res.on("end", function () { closed = true; });
864
+ res.on("close", function () { closed = true; });
865
+ res.on("error", function () { closed = true; });
866
+ resolve({
867
+ status: res.statusCode, headers: res.headers,
868
+ read: function () { return buf; },
869
+ isClosed: function () { return closed; },
870
+ abort: function () { try { r.destroy(); } catch (_e) { /* best-effort */ } },
871
+ });
872
+ });
873
+ r.on("error", reject);
874
+ r.end();
875
+ });
876
+ }
877
+
878
+ function _wsConnect(port, opts) {
879
+ var client = b.wsClient.connect("ws://127.0.0.1:" + port + "/jmap/ws",
880
+ Object.assign({ subprotocols: ["jmap"], reconnect: false, audit: false, allowInternal: true }, opts || {}));
881
+ _wsClients.push(client);
882
+ return client;
883
+ }
884
+
885
+ var DEFAULT_ACCOUNTS = async function () {
886
+ return { primaryAccounts: { core: "A1" }, accounts: { A1: { name: "tenant-a" } } };
887
+ };
888
+
889
+ // ==========================================================================
890
+ // 1. sessionHandler (RFC 8620 §2)
891
+ // ==========================================================================
892
+ async function testSessionHandler() {
893
+ // 1a. default caps inject the websocket transport (no operator ws cap)
894
+ var jmap = b.mail.server.jmap.create({
895
+ mailStore: { appendMessage: function () {} },
896
+ accountsFor: DEFAULT_ACCOUNTS,
897
+ methods: {},
898
+ });
899
+ var s = await _startHttp(jmap, {});
900
+ try {
901
+ var r = await _req(s.port, { path: "/jmap/session" });
902
+ check("session → 200", r.status === 200);
903
+ check("session content-type json", /application\/json/.test(r.headers["content-type"] || ""));
904
+ var sess = JSON.parse(r.body);
905
+ check("session advertises default websocket cap",
906
+ sess.capabilities["urn:ietf:params:jmap:websocket"] &&
907
+ sess.capabilities["urn:ietf:params:jmap:websocket"].url === "/jmap/ws");
908
+ check("session core cap present", !!sess.capabilities["urn:ietf:params:jmap:core"]);
909
+ check("session accounts echoed", sess.accounts.A1 && sess.accounts.A1.name === "tenant-a");
910
+ check("session primaryAccounts echoed", sess.primaryAccounts.core === "A1");
911
+ check("session apiUrl default", sess.apiUrl === "/jmap/api");
912
+ check("session username from actor.username", sess.username === "alice");
913
+ check("session state present", typeof sess.state === "string" && sess.state.length > 0);
914
+
915
+ // 1b. username falls back to actor.id, then "unknown"
916
+ var rId = await _req(s.port, { path: "/jmap/session", headers: { "x-actor": "idonly" } });
917
+ check("session username falls back to actor.id", JSON.parse(rId.body).username === "uid-9");
918
+ var rEmpty = await _req(s.port, { path: "/jmap/session", headers: { "x-actor": "empty" } });
919
+ check("session username falls back to 'unknown'", JSON.parse(rEmpty.body).username === "unknown");
920
+
921
+ // 1c. unauthenticated → 401 forbidden
922
+ var rNo = await _req(s.port, { path: "/jmap/session", headers: { "x-actor": "none" } });
923
+ check("session unauth → 401", rNo.status === 401);
924
+ check("session unauth → forbidden type", /jmap:error:forbidden/.test(rNo.body));
925
+ } finally { await _stop(s.server); }
926
+
927
+ // 1d. operator-supplied websocket cap → NOT overwritten + urlEndpointResolution set
928
+ var jmapWs = b.mail.server.jmap.create({
929
+ mailStore: { appendMessage: function () {} },
930
+ accountsFor: DEFAULT_ACCOUNTS,
931
+ methods: {},
932
+ serverCapabilities: { "urn:ietf:params:jmap:websocket": { url: "/operator/ws", supportsPush: false } },
933
+ });
934
+ var sWs = await _startHttp(jmapWs, {});
935
+ try {
936
+ var rw = await _req(sWs.port, { path: "/jmap/session" });
937
+ var sessw = JSON.parse(rw.body);
938
+ check("session keeps operator websocket cap",
939
+ sessw.capabilities["urn:ietf:params:jmap:websocket"].url === "/operator/ws");
940
+ check("session urlEndpointResolution present when operator ws cap set",
941
+ sessw.urlEndpointResolution && sessw.urlEndpointResolution.useEndpoint === "/jmap/ws");
942
+ } finally { await _stop(sWs.server); }
943
+
944
+ // 1e. accountsFor returns null → defaults; and accountsFor rejects → 500
945
+ var jmapNull = b.mail.server.jmap.create({
946
+ mailStore: { appendMessage: function () {} },
947
+ accountsFor: async function () { return null; },
948
+ methods: {},
949
+ });
950
+ var sNull = await _startHttp(jmapNull, {});
951
+ try {
952
+ var rn = await _req(sNull.port, { path: "/jmap/session" });
953
+ check("session accountInfo null → 200 with empty accounts",
954
+ rn.status === 200 && JSON.stringify(JSON.parse(rn.body).accounts) === "{}");
955
+ } finally { await _stop(sNull.server); }
956
+
957
+ var jmapThrow = b.mail.server.jmap.create({
958
+ mailStore: { appendMessage: function () {} },
959
+ accountsFor: async function () { throw new Error("accountsFor boom"); },
960
+ methods: {},
961
+ });
962
+ var sThrow = await _startHttp(jmapThrow, {});
963
+ try {
964
+ var rt = await _req(sThrow.port, { path: "/jmap/session" });
965
+ check("session accountsFor throw → 500", rt.status === 500);
966
+ check("session accountsFor throw → serverFail", /jmap:error:serverFail/.test(rt.body));
967
+ } finally { await _stop(sThrow.server); }
968
+ }
969
+
970
+ // ==========================================================================
971
+ // 2. discoveryHandler (RFC 8620 §2.2) — 302 redirect
972
+ // ==========================================================================
973
+ async function testDiscoveryHandler() {
974
+ var jmap = b.mail.server.jmap.create({
975
+ mailStore: { appendMessage: function () {} }, accountsFor: DEFAULT_ACCOUNTS, methods: {},
976
+ });
977
+ var s = await _startHttp(jmap, {});
978
+ try {
979
+ var r = await _req(s.port, { path: "/.well-known/jmap" });
980
+ check("discovery → 302", r.status === 302);
981
+ check("discovery → Location /jmap/session", r.headers["location"] === "/jmap/session");
982
+ } finally { await _stop(s.server); }
983
+
984
+ var jmapCustom = b.mail.server.jmap.create({
985
+ mailStore: { appendMessage: function () {} }, accountsFor: DEFAULT_ACCOUNTS, methods: {},
986
+ sessionUrl: "/custom/session",
987
+ });
988
+ var sc = await _startHttp(jmapCustom, {});
989
+ try {
990
+ var rc = await _req(sc.port, { path: "/.well-known/jmap" });
991
+ check("discovery custom sessionUrl honored", rc.headers["location"] === "/custom/session");
992
+ } finally { await _stop(sc.server); }
993
+ }
994
+
995
+ // ==========================================================================
996
+ // 3. apiHandler (RFC 8620 §3.3) over HTTP — refusal / error status mapping
997
+ // ==========================================================================
998
+ async function testApiHandler() {
999
+ var jmap = b.mail.server.jmap.create({
1000
+ mailStore: { appendMessage: function () {} },
1001
+ accountsFor: DEFAULT_ACCOUNTS,
1002
+ methods: {
1003
+ "Core/echo": async function (actor, args) { return { hi: args.hi }; },
1004
+ "Op/err": async function () { return { type: "urn:ietf:params:jmap:error:invalidArguments", description: "operator says no" }; },
1005
+ },
1006
+ });
1007
+ var s = await _startHttp(jmap, {});
1008
+ try {
1009
+ // 3a. body missing (bodyParser not run) → 400
1010
+ var rNoBody = await _req(s.port, { method: "POST", path: "/jmap/api" });
1011
+ check("api missing body → 400", rNoBody.status === 400);
1012
+ check("api missing body → invalidArguments", /request body missing/.test(rNoBody.body));
1013
+
1014
+ // 3b. happy 200
1015
+ var rOk = await _req(s.port, {
1016
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1017
+ body: JSON.stringify({ using: [], methodCalls: [["Core/echo", { hi: 7 }, "c0"]] }),
1018
+ });
1019
+ check("api happy → 200", rOk.status === 200);
1020
+ var okBody = JSON.parse(rOk.body);
1021
+ check("api happy → result echoed", okBody.methodResponses[0][1].hi === 7);
1022
+ check("api happy → sessionState", typeof okBody.sessionState === "string");
1023
+
1024
+ // 3c. guard refusal (no `using`) → 400 invalidArguments
1025
+ var rBad = await _req(s.port, {
1026
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1027
+ body: JSON.stringify({ methodCalls: [["Core/echo", {}, "c0"]] }),
1028
+ });
1029
+ check("api guard-refusal → 400", rBad.status === 400);
1030
+ check("api guard-refusal → invalidArguments", /jmap:error:invalidArguments/.test(rBad.body));
1031
+
1032
+ // 3d. no actor → forbidden mapped to 401
1033
+ var rForbidden = await _req(s.port, {
1034
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json", "x-actor": "none" },
1035
+ body: JSON.stringify({ using: [], methodCalls: [["Core/echo", {}, "c0"]] }),
1036
+ });
1037
+ check("api no-actor → 401", rForbidden.status === 401);
1038
+ check("api no-actor → forbidden", /jmap:error:forbidden/.test(rForbidden.body));
1039
+
1040
+ // 3e. operator-emitted error shape → preserved (200, error inside)
1041
+ var rOpErr = await _req(s.port, {
1042
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1043
+ body: JSON.stringify({ using: [], methodCalls: [["Op/err", {}, "c0"]] }),
1044
+ });
1045
+ check("api operator-error-shape → 200", rOpErr.status === 200);
1046
+ var opErrBody = JSON.parse(rOpErr.body);
1047
+ check("api operator-error-shape preserved",
1048
+ opErrBody.methodResponses[0][0] === "error" &&
1049
+ opErrBody.methodResponses[0][1].type === "urn:ietf:params:jmap:error:invalidArguments");
1050
+ } finally { await _stop(s.server); }
1051
+
1052
+ // 3f. accountsFor throws inside dispatch → serverFail refusal (mapped 400)
1053
+ var jmapAcctThrow = b.mail.server.jmap.create({
1054
+ mailStore: { appendMessage: function () {} },
1055
+ accountsFor: async function () { throw new Error("acct boom"); },
1056
+ methods: { "Core/echo": async function () { return {}; } },
1057
+ });
1058
+ var sat = await _startHttp(jmapAcctThrow, {});
1059
+ try {
1060
+ var rat = await _req(sat.port, {
1061
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1062
+ body: JSON.stringify({ using: [], methodCalls: [["Core/echo", {}, "c0"]] }),
1063
+ });
1064
+ check("api accountsFor-throw → 400 (serverFail refusal)", rat.status === 400);
1065
+ check("api accountsFor-throw → serverFail", /jmap:error:serverFail/.test(rat.body));
1066
+ check("api accountsFor-throw → account authorization unavailable",
1067
+ /account authorization unavailable/.test(rat.body));
1068
+ } finally { await _stop(sat.server); }
1069
+ }
1070
+
1071
+ // ==========================================================================
1072
+ // 4. Back-reference resolution + JSON-Pointer edge cases (RFC 8620 §3.7 /
1073
+ // RFC 6901) — driven through apiHandler over HTTP
1074
+ // ==========================================================================
1075
+ async function testBackRefsAndPointer() {
1076
+ var jmap = b.mail.server.jmap.create({
1077
+ mailStore: { appendMessage: function () {} },
1078
+ accountsFor: DEFAULT_ACCOUNTS,
1079
+ methods: {
1080
+ "First/get": async function () {
1081
+ return { list: [{ id: "x1" }, { id: "x2" }], name: "n", s: "str", "a/b": "slash", "c~d": "tilde" };
1082
+ },
1083
+ "Second/use": async function (actor, args) { return { received: args }; },
1084
+ },
1085
+ });
1086
+ var s = await _startHttp(jmap, {});
1087
+ try {
1088
+ // 4a. pointer edges that SUCCEED (whole result / array-* / escapes / nested)
1089
+ var rv = await _req(s.port, {
1090
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1091
+ body: JSON.stringify({ using: [], methodCalls: [
1092
+ ["First/get", {}, "c0"],
1093
+ ["Second/use", {
1094
+ "#whole": { resultOf: "c0", name: "First/get", path: "" },
1095
+ "#arr": { resultOf: "c0", name: "First/get", path: "/list/*" },
1096
+ "#first": { resultOf: "c0", name: "First/get", path: "/list/0/id" },
1097
+ "#sl": { resultOf: "c0", name: "First/get", path: "/a~1b" },
1098
+ "#ti": { resultOf: "c0", name: "First/get", path: "/c~0d" },
1099
+ }, "c1"],
1100
+ ] }),
1101
+ });
1102
+ var got = JSON.parse(rv.body).methodResponses[1][1].received;
1103
+ check("backref path='' resolves whole result", got.whole && got.whole.name === "n");
1104
+ check("backref path='/list/*' resolves array", Array.isArray(got.arr) && got.arr.length === 2);
1105
+ check("backref path='/list/0/id' resolves scalar", got.first === "x1");
1106
+ check("backref ~1 escape → '/'", got.sl === "slash");
1107
+ check("backref ~0 escape → '~'", got.ti === "tilde");
1108
+
1109
+ // 4b. pointer into a NON-object (string) → undefined → invalidResultReference
1110
+ var rNon = await _req(s.port, {
1111
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1112
+ body: JSON.stringify({ using: [], methodCalls: [
1113
+ ["First/get", {}, "c0"],
1114
+ ["Second/use", { "#x": { resultOf: "c0", name: "First/get", path: "/s/foo" } }, "c1"],
1115
+ ] }),
1116
+ });
1117
+ check("backref into non-object → invalidResultReference",
1118
+ JSON.parse(rNon.body).methodResponses[1][1].type === "urn:ietf:params:jmap:error:invalidResultReference");
1119
+
1120
+ // 4c. malformed back-ref value (missing `path`) → invalidResultReference
1121
+ var rMissing = await _req(s.port, {
1122
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1123
+ body: JSON.stringify({ using: [], methodCalls: [
1124
+ ["First/get", {}, "c0"],
1125
+ ["Second/use", { "#bad": { resultOf: "c0", name: "First/get" } }, "c1"],
1126
+ ] }),
1127
+ });
1128
+ check("backref missing path → invalidResultReference",
1129
+ JSON.parse(rMissing.body).methodResponses[1][1].type === "urn:ietf:params:jmap:error:invalidResultReference");
1130
+
1131
+ // 4d. back-ref value is an ARRAY (not the { resultOf, name, path } object)
1132
+ var rArr = await _req(s.port, {
1133
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1134
+ body: JSON.stringify({ using: [], methodCalls: [
1135
+ ["First/get", {}, "c0"],
1136
+ ["Second/use", { "#bad": [1, 2, 3] }, "c1"],
1137
+ ] }),
1138
+ });
1139
+ check("backref value-is-array → invalidResultReference",
1140
+ JSON.parse(rArr.body).methodResponses[1][1].type === "urn:ietf:params:jmap:error:invalidResultReference");
1141
+
1142
+ // 4e. back-ref name mismatch (prior clientId produced a different method)
1143
+ var rName = await _req(s.port, {
1144
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1145
+ body: JSON.stringify({ using: [], methodCalls: [
1146
+ ["First/get", {}, "c0"],
1147
+ ["Second/use", { "#bad": { resultOf: "c0", name: "Other/get", path: "/list/0/id" } }, "c1"],
1148
+ ] }),
1149
+ });
1150
+ check("backref name-mismatch → invalidResultReference",
1151
+ JSON.parse(rName.body).methodResponses[1][1].type === "urn:ietf:params:jmap:error:invalidResultReference");
1152
+
1153
+ // 4e2. back-ref array index is non-numeric → undefined → invalidResultReference
1154
+ var rNaN = await _req(s.port, {
1155
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1156
+ body: JSON.stringify({ using: [], methodCalls: [
1157
+ ["First/get", {}, "c0"],
1158
+ ["Second/use", { "#bad": { resultOf: "c0", name: "First/get", path: "/list/notanum/id" } }, "c1"],
1159
+ ] }),
1160
+ });
1161
+ check("backref non-numeric array index → invalidResultReference",
1162
+ JSON.parse(rNaN.body).methodResponses[1][1].type === "urn:ietf:params:jmap:error:invalidResultReference");
1163
+
1164
+ // 4f. unknown method → unknownMethod
1165
+ var rUnknown = await _req(s.port, {
1166
+ method: "POST", path: "/jmap/api", headers: { "content-type": "application/json" },
1167
+ body: JSON.stringify({ using: [], methodCalls: [["Nope/nope", {}, "c0"]] }),
1168
+ });
1169
+ check("unknown method → unknownMethod",
1170
+ JSON.parse(rUnknown.body).methodResponses[0][1].type === "urn:ietf:params:jmap:error:unknownMethod");
1171
+ } finally { await _stop(s.server); }
1172
+ }
1173
+
1174
+ // ==========================================================================
1175
+ // 5. uploadHandler (RFC 8620 §6.1) over HTTP
1176
+ // ==========================================================================
1177
+ async function testUploadHandler() {
1178
+ // 5a. happy 201 + meta defaults (uploadBlob returns no type/size → defaults)
1179
+ var jmap = b.mail.server.jmap.create({
1180
+ mailStore: {
1181
+ appendMessage: function () {},
1182
+ uploadBlob: function (actor, accountId, type, bytes) { return Promise.resolve({ blobId: "blob_1" }); },
1183
+ },
1184
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1185
+ });
1186
+ var s = await _startHttp(jmap, {});
1187
+ try {
1188
+ var rOk = await _req(s.port, {
1189
+ method: "POST", path: "/jmap/upload/A1", headers: { "content-type": "text/plain" }, body: "hello",
1190
+ });
1191
+ check("upload happy → 201", rOk.status === 201);
1192
+ var meta = JSON.parse(rOk.body);
1193
+ check("upload meta type defaults to content-type", meta.type === "text/plain");
1194
+ check("upload meta size defaults to byte length", meta.size === 5);
1195
+ check("upload echoes accountId", meta.accountId === "A1");
1196
+
1197
+ // 5b. default content-type when no header
1198
+ var rNoCt = await _req(s.port, { method: "POST", path: "/jmap/upload/A1", body: "x" });
1199
+ check("upload no content-type → octet-stream default", JSON.parse(rNoCt.body).type === "application/octet-stream");
1200
+
1201
+ // 5c. malformed accountId (path-traversal shape) → 400
1202
+ var rBadId = await _req(s.port, { method: "POST", path: "/jmap/upload/..%2Fevil", body: "x" });
1203
+ check("upload bad accountId → 400", rBadId.status === 400);
1204
+ check("upload bad accountId → invalidArguments", /jmap:error:invalidArguments/.test(rBadId.body));
1205
+
1206
+ // 5d. over-long URL (> 8 KiB) → segments empty → 400
1207
+ var longSeg = "a"; for (var i = 0; i < 14; i += 1) longSeg += longSeg; // ~16 KiB
1208
+ var rLong = await _req(s.port, { method: "POST", path: "/jmap/upload/" + longSeg.slice(0, 8300), body: "x" });
1209
+ check("upload over-long URL → 400", rLong.status === 400);
1210
+ check("upload over-long URL → cap message", /exceeds the/.test(rLong.body));
1211
+
1212
+ // 5e. foreign accountId → 404 accountNotFound
1213
+ var rForeign = await _req(s.port, { method: "POST", path: "/jmap/upload/B9", body: "x" });
1214
+ check("upload foreign accountId → 404", rForeign.status === 404);
1215
+ check("upload foreign accountId → accountNotFound", /jmap:error:accountNotFound/.test(rForeign.body));
1216
+
1217
+ // 5f. unauthenticated → 401
1218
+ var rUnauth = await _req(s.port, { method: "POST", path: "/jmap/upload/A1", headers: { "x-actor": "none" }, body: "x" });
1219
+ check("upload unauth → 401", rUnauth.status === 401);
1220
+ } finally { await _stop(s.server); }
1221
+
1222
+ // 5g. no uploadBlob backend → 503
1223
+ var jmapNoBackend = b.mail.server.jmap.create({
1224
+ mailStore: { appendMessage: function () {} }, accountsFor: DEFAULT_ACCOUNTS, methods: {},
1225
+ });
1226
+ var sNb = await _startHttp(jmapNoBackend, {});
1227
+ try {
1228
+ var rNb = await _req(sNb.port, { method: "POST", path: "/jmap/upload/A1", body: "x" });
1229
+ check("upload no-backend → 503", rNb.status === 503);
1230
+ check("upload no-backend → serverUnavailable", /jmap:error:serverUnavailable/.test(rNb.body));
1231
+ } finally { await _stop(sNb.server); }
1232
+
1233
+ // 5h. oversize body → refused (413 branch runs server-side; client sees 413 or reset)
1234
+ var jmapCap = b.mail.server.jmap.create({
1235
+ mailStore: { appendMessage: function () {}, uploadBlob: function () { return Promise.resolve({ blobId: "x" }); } },
1236
+ accountsFor: DEFAULT_ACCOUNTS, methods: {}, maxBlobBytes: b.constants.BYTES.bytes(16),
1237
+ });
1238
+ var sCap = await _startHttp(jmapCap, {});
1239
+ try {
1240
+ var rOver = await _reqSafe(sCap.port, { method: "POST", path: "/jmap/upload/A1", body: Buffer.alloc(64, 0x41) });
1241
+ check("upload oversize → not accepted (refused)", rOver.status !== 201);
1242
+ check("upload oversize → 413 or connection reset",
1243
+ rOver.status === 413 || rOver.refused === true);
1244
+ } finally { await _stop(sCap.server); }
1245
+
1246
+ // 5i. uploadBlob returns bad meta (no blobId) → 500 serverFail
1247
+ var jmapBadMeta = b.mail.server.jmap.create({
1248
+ mailStore: { appendMessage: function () {}, uploadBlob: function () { return Promise.resolve({}); } },
1249
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1250
+ });
1251
+ var sBad = await _startHttp(jmapBadMeta, {});
1252
+ try {
1253
+ var rBadMeta = await _req(sBad.port, { method: "POST", path: "/jmap/upload/A1", body: "x" });
1254
+ check("upload bad-meta → 500", rBadMeta.status === 500);
1255
+ check("upload bad-meta → serverFail", /jmap:error:serverFail/.test(rBadMeta.body));
1256
+ } finally { await _stop(sBad.server); }
1257
+
1258
+ // 5j. uploadBlob throws → 500 serverFail
1259
+ var jmapThrow = b.mail.server.jmap.create({
1260
+ mailStore: { appendMessage: function () {}, uploadBlob: function () { return Promise.reject(new Error("upload boom")); } },
1261
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1262
+ });
1263
+ var sTh = await _startHttp(jmapThrow, {});
1264
+ try {
1265
+ var rTh = await _req(sTh.port, { method: "POST", path: "/jmap/upload/A1", body: "x" });
1266
+ check("upload backend-throw → 500", rTh.status === 500);
1267
+ check("upload backend-throw → serverFail", /jmap:error:serverFail/.test(rTh.body));
1268
+ } finally { await _stop(sTh.server); }
1269
+
1270
+ // 5k. router-supplied req.params.accountId path is honored
1271
+ var uploadedAcct = null;
1272
+ var jmapParams = b.mail.server.jmap.create({
1273
+ mailStore: {
1274
+ appendMessage: function () {},
1275
+ uploadBlob: function (actor, accountId) { uploadedAcct = accountId; return Promise.resolve({ blobId: "x" }); },
1276
+ },
1277
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1278
+ });
1279
+ var sParam = await _startHttp(jmapParams, { forceUpload: true, params: { accountId: "A1" } });
1280
+ try {
1281
+ var rp = await _req(sParam.port, { method: "POST", path: "/anything/here", body: "x" });
1282
+ check("upload router-supplied params.accountId honored → 201", rp.status === 201 && uploadedAcct === "A1");
1283
+ } finally { await _stop(sParam.server); }
1284
+ }
1285
+
1286
+ // ==========================================================================
1287
+ // 6. downloadHandler (RFC 8620 §6.2) over HTTP
1288
+ // ==========================================================================
1289
+ async function testDownloadHandler() {
1290
+ // 6a. happy 5-seg path (+ Content-Disposition) and raw-Buffer / accept branch
1291
+ var jmap = b.mail.server.jmap.create({
1292
+ mailStore: {
1293
+ appendMessage: function () {},
1294
+ downloadBlob: function (actor, accountId, blobId) {
1295
+ if (blobId === "rawbuf") return Promise.resolve(Buffer.from("rawbytes"));
1296
+ if (blobId === "notbuf") return Promise.resolve({ bytes: "not-a-buffer" });
1297
+ if (blobId === "missing") return Promise.resolve(null);
1298
+ if (blobId === "boom") return Promise.reject(new Error("download boom"));
1299
+ return Promise.resolve({ bytes: Buffer.from("hello blob"), type: "text/plain" });
1300
+ },
1301
+ },
1302
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1303
+ });
1304
+ var s = await _startHttp(jmap, {});
1305
+ try {
1306
+ var rOk = await _req(s.port, { path: "/jmap/download/A1/blob_1/note.txt" });
1307
+ check("download happy → 200", rOk.status === 200);
1308
+ check("download content-type from backend", (rOk.headers["content-type"] || "") === "text/plain");
1309
+ check("download body bytes", rOk.body === "hello blob");
1310
+ check("download Content-Disposition attachment",
1311
+ /attachment; filename="note\.txt"/.test(rOk.headers["content-disposition"] || ""));
1312
+
1313
+ // 6b. invalid filename segment → no Content-Disposition
1314
+ var rNoDisp = await _req(s.port, { path: "/jmap/download/A1/blob_1/no,te" });
1315
+ check("download invalid filename → no disposition", !rNoDisp.headers["content-disposition"]);
1316
+
1317
+ // 6c. raw Buffer result + ?accept type
1318
+ var rRaw = await _req(s.port, { path: "/jmap/download/A1/rawbuf/f.bin?accept=application/x-test" });
1319
+ check("download raw Buffer + accept → 200", rRaw.status === 200);
1320
+ check("download raw Buffer uses accept type", (rRaw.headers["content-type"] || "") === "application/x-test");
1321
+ check("download raw Buffer body", rRaw.body === "rawbytes");
1322
+
1323
+ // 6d. malformed %-encoded accept is drop-silent (still 200 octet-stream)
1324
+ var rBadAccept = await _req(s.port, { path: "/jmap/download/A1/rawbuf/f.bin?accept=%zz" });
1325
+ check("download malformed accept drop-silent → 200", rBadAccept.status === 200);
1326
+ check("download malformed accept → octet-stream default",
1327
+ (rBadAccept.headers["content-type"] || "") === "application/octet-stream");
1328
+
1329
+ // 6e. non-Buffer backend body → 500
1330
+ var rNotBuf = await _req(s.port, { path: "/jmap/download/A1/notbuf/f.bin" });
1331
+ check("download non-Buffer body → 500", rNotBuf.status === 500);
1332
+ check("download non-Buffer → serverFail", /non-Buffer body/.test(rNotBuf.body));
1333
+
1334
+ // 6f. null backend result → 404 blob-not-found
1335
+ var rMissing = await _req(s.port, { path: "/jmap/download/A1/missing/f.bin" });
1336
+ check("download null result → 404", rMissing.status === 404);
1337
+ check("download null result → Blob not found", /Blob not found/.test(rMissing.body));
1338
+
1339
+ // 6g. backend throws → 500
1340
+ var rBoom = await _req(s.port, { path: "/jmap/download/A1/boom/f.bin" });
1341
+ check("download backend-throw → 500", rBoom.status === 500);
1342
+ check("download backend-throw → serverFail", /jmap:error:serverFail/.test(rBoom.body));
1343
+
1344
+ // 6h. malformed accountId segment → 400
1345
+ var rBadAcct = await _req(s.port, { path: "/jmap/download/..%2Fx/blob_1/f.bin" });
1346
+ check("download malformed accountId → 400", rBadAcct.status === 400);
1347
+ check("download malformed accountId → invalidArguments", /malformed accountId/.test(rBadAcct.body));
1348
+
1349
+ // 6i. malformed blobId segment → 400
1350
+ var rBadBlob = await _req(s.port, { path: "/jmap/download/A1/..%2Fx/f.bin" });
1351
+ check("download malformed blobId → 400", rBadBlob.status === 400);
1352
+ check("download malformed blobId → invalidArguments", /malformed blobId/.test(rBadBlob.body));
1353
+
1354
+ // 6j. missing name segment (only 4 real segments → not 3, not 5+) → 400
1355
+ var rShort = await _req(s.port, { path: "/jmap/download/A1/blob_1" });
1356
+ check("download missing name segment → 400", rShort.status === 400);
1357
+
1358
+ // 6k. over-long URL → 400
1359
+ var lp = "a"; for (var i = 0; i < 14; i += 1) lp += lp;
1360
+ var rLong = await _req(s.port, { path: "/jmap/download/A1/blob_1/" + lp.slice(0, 8300) });
1361
+ check("download over-long URL → 400", rLong.status === 400);
1362
+ check("download over-long URL → cap message", /exceeds the/.test(rLong.body));
1363
+
1364
+ // 6l. foreign accountId → 404 accountNotFound
1365
+ var rForeign = await _req(s.port, { path: "/jmap/download/B9/blob_1/f.bin" });
1366
+ check("download foreign accountId → 404", rForeign.status === 404);
1367
+ check("download foreign accountId → accountNotFound", /jmap:error:accountNotFound/.test(rForeign.body));
1368
+
1369
+ // 6m. unauthenticated → 401
1370
+ var rUnauth = await _req(s.port, { path: "/jmap/download/A1/blob_1/f.bin", headers: { "x-actor": "none" } });
1371
+ check("download unauth → 401", rUnauth.status === 401);
1372
+ } finally { await _stop(s.server); }
1373
+
1374
+ // 6n. no downloadBlob backend → 503
1375
+ var jmapNb = b.mail.server.jmap.create({
1376
+ mailStore: { appendMessage: function () {} }, accountsFor: DEFAULT_ACCOUNTS, methods: {},
1377
+ });
1378
+ var sNb = await _startHttp(jmapNb, {});
1379
+ try {
1380
+ var rNb = await _req(sNb.port, { path: "/jmap/download/A1/blob_1/f.bin" });
1381
+ check("download no-backend → 503", rNb.status === 503);
1382
+ } finally { await _stop(sNb.server); }
1383
+
1384
+ // 6o. router-stripped 3-segment path ({accountId}/{blobId}/{name})
1385
+ var strippedCall = null;
1386
+ var jmapStrip = b.mail.server.jmap.create({
1387
+ mailStore: {
1388
+ appendMessage: function () {},
1389
+ downloadBlob: function (actor, accountId, blobId) {
1390
+ strippedCall = { accountId: accountId, blobId: blobId };
1391
+ return Promise.resolve({ bytes: Buffer.from("s"), type: "text/plain" });
1392
+ },
1393
+ },
1394
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1395
+ });
1396
+ var sStrip = await _startHttp(jmapStrip, { forceDownload: true });
1397
+ try {
1398
+ var rStrip = await _req(sStrip.port, { path: "/A1/blob_9/note.txt" });
1399
+ check("download router-stripped 3-seg → 200", rStrip.status === 200);
1400
+ check("download router-stripped mapped segments",
1401
+ strippedCall && strippedCall.accountId === "A1" && strippedCall.blobId === "blob_9");
1402
+ } finally { await _stop(sStrip.server); }
1403
+ }
1404
+
1405
+ // ==========================================================================
1406
+ // 7. eventSourceHandler (RFC 8620 §7.3, SSE) over HTTP
1407
+ // ==========================================================================
1408
+ async function testEventSource() {
1409
+ // 7a. no subscribePush backend → 503
1410
+ var jmapNb = b.mail.server.jmap.create({
1411
+ mailStore: { appendMessage: function () {} }, accountsFor: DEFAULT_ACCOUNTS, methods: {},
1412
+ });
1413
+ var sNb = await _startHttp(jmapNb, {});
1414
+ try {
1415
+ var rNb = await _req(sNb.port, { path: "/jmap/eventsource" });
1416
+ check("eventsource no-backend → 503", rNb.status === 503);
1417
+ check("eventsource no-backend → serverUnavailable", /jmap:error:serverUnavailable/.test(rNb.body));
1418
+
1419
+ // 7b. unauth → 401
1420
+ var rUn = await _req(sNb.port, { path: "/jmap/eventsource", headers: { "x-actor": "none" } });
1421
+ check("eventsource unauth → 401", rUn.status === 401);
1422
+ } finally { await _stop(sNb.server); }
1423
+
1424
+ // 7c. invalid closeafter → 400 (subscribePush present so we pass the 503 gate)
1425
+ var jmapBadCa = b.mail.server.jmap.create({
1426
+ mailStore: { appendMessage: function () {}, subscribePush: function () { return Promise.resolve(); } },
1427
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1428
+ });
1429
+ var sCa = await _startHttp(jmapBadCa, {});
1430
+ try {
1431
+ var rCa = await _req(sCa.port, { path: "/jmap/eventsource?closeafter=banana" });
1432
+ check("eventsource bad closeafter → 400", rCa.status === 400);
1433
+ check("eventsource bad closeafter → invalidArguments", /jmap:error:invalidArguments/.test(rCa.body));
1434
+ } finally { await _stop(sCa.server); }
1435
+
1436
+ // 7d. full SSE stream: headers + connected + StateChange emit + unsubscribe
1437
+ var captured = {};
1438
+ var jmap = b.mail.server.jmap.create({
1439
+ mailStore: {
1440
+ appendMessage: function () {},
1441
+ subscribePush: function (actor, types, emitFn) {
1442
+ captured.types = types; captured.emit = emitFn;
1443
+ return Promise.resolve(function () { captured.unsub = true; });
1444
+ },
1445
+ },
1446
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1447
+ });
1448
+ var s = await _startHttp(jmap, {});
1449
+ try {
1450
+ // ping=1000 exercises the >900 clamp; %zz key exercises decode drop-silent;
1451
+ // `flag` (no '=') exercises the eq===-1 branch.
1452
+ var sse = await _openSse(s.port, "/jmap/eventsource?types=Email,Mailbox&ping=1000&%zz=bad&flag");
1453
+ check("eventsource → 200", sse.status === 200);
1454
+ check("eventsource content-type event-stream", /text\/event-stream/.test(sse.headers["content-type"] || ""));
1455
+ check("eventsource cache-control no-cache", sse.headers["cache-control"] === "no-cache");
1456
+ check("eventsource X-Accel-Buffering no", sse.headers["x-accel-buffering"] === "no");
1457
+ await helpers.waitUntil(function () { return typeof captured.emit === "function"; },
1458
+ { timeoutMs: 5000, label: "eventsource: subscribePush emitFn captured" });
1459
+ check("eventsource connected comment present", /: connected/.test(sse.read()));
1460
+ check("eventsource types forwarded", captured.types && captured.types[0] === "Email");
1461
+ // push a StateChange over the real stream
1462
+ captured.emit({ kind: "StateChange", changed: { A1: { Email: "s1" } } });
1463
+ await helpers.waitUntil(function () { return /event: state/.test(sse.read()); },
1464
+ { timeoutMs: 5000, label: "eventsource: state event delivered" });
1465
+ check("eventsource StateChange delivered",
1466
+ /event: state/.test(sse.read()) && /"@type":"StateChange"/.test(sse.read()));
1467
+ // non-StateChange event is ignored (no crash)
1468
+ captured.emit({ kind: "Other" });
1469
+ captured.emit(null);
1470
+ // abort → req 'close' → cleanup → unsubscribe invoked
1471
+ sse.abort();
1472
+ await helpers.waitUntil(function () { return captured.unsub === true; },
1473
+ { timeoutMs: 5000, label: "eventsource: unsubscribe after client close" });
1474
+ check("eventsource unsubscribe called on client close", captured.unsub === true);
1475
+ } finally { await _stop(s.server); }
1476
+
1477
+ // 7e. closeafter=state → stream closes after first StateChange
1478
+ var cap2 = {};
1479
+ var jmap2 = b.mail.server.jmap.create({
1480
+ mailStore: {
1481
+ appendMessage: function () {},
1482
+ subscribePush: function (actor, types, emitFn) { cap2.emit = emitFn; return Promise.resolve(); },
1483
+ },
1484
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1485
+ });
1486
+ var s2 = await _startHttp(jmap2, {});
1487
+ try {
1488
+ var sse2 = await _openSse(s2.port, "/jmap/eventsource?closeafter=state&ping=0");
1489
+ await helpers.waitUntil(function () { return typeof cap2.emit === "function"; },
1490
+ { timeoutMs: 5000, label: "eventsource closeafter: emitFn captured" });
1491
+ cap2.emit({ kind: "StateChange", changed: { A1: { Email: "s2" } } });
1492
+ await helpers.waitUntil(function () { return sse2.isClosed(); },
1493
+ { timeoutMs: 5000, label: "eventsource closeafter=state: stream closed" });
1494
+ check("eventsource closeafter=state → closed after event",
1495
+ sse2.isClosed() && /event: state/.test(sse2.read()));
1496
+ } finally { await _stop(s2.server); }
1497
+
1498
+ // 7f. subscribePush rejects → handler cleans up (stream closes)
1499
+ var jmap3 = b.mail.server.jmap.create({
1500
+ mailStore: {
1501
+ appendMessage: function () {},
1502
+ subscribePush: function () { return Promise.reject(new Error("subscribe boom")); },
1503
+ },
1504
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1505
+ });
1506
+ var s3 = await _startHttp(jmap3, {});
1507
+ try {
1508
+ var sse3 = await _openSse(s3.port, "/jmap/eventsource");
1509
+ check("eventsource subscribe-reject: 200 headers already sent", sse3.status === 200);
1510
+ await helpers.waitUntil(function () { return sse3.isClosed(); },
1511
+ { timeoutMs: 5000, label: "eventsource subscribe-reject: stream cleaned up" });
1512
+ check("eventsource subscribe-reject → stream closed", sse3.isClosed());
1513
+ } finally { await _stop(s3.server); }
1514
+ }
1515
+
1516
+ // ==========================================================================
1517
+ // 8. webSocketHandler (RFC 8887) over a REAL b.wsClient connection
1518
+ // ==========================================================================
1519
+ function _wsWait(client, until, label) {
1520
+ return helpers.waitUntil(until, { timeoutMs: 5000, label: label });
1521
+ }
1522
+
1523
+ async function testWebSocket() {
1524
+ var captured = {};
1525
+ var jmap = b.mail.server.jmap.create({
1526
+ mailStore: {
1527
+ appendMessage: function () {},
1528
+ subscribePush: function (actor, dataTypes, emitFn) {
1529
+ captured.emit = emitFn; return Promise.resolve(function () { captured.unsub = true; });
1530
+ },
1531
+ },
1532
+ accountsFor: DEFAULT_ACCOUNTS,
1533
+ methods: { "Core/echo": async function (actor, args) { return { hi: args.hi }; } },
1534
+ });
1535
+ var s = await _startHttp(jmap, {});
1536
+ try {
1537
+ var client = _wsConnect(s.port);
1538
+ var msgs = [];
1539
+ var errSeen = null;
1540
+ client.on("message", function (d) { msgs.push(typeof d === "string" ? d : d.toString("utf8")); });
1541
+ client.on("error", function (e) { errSeen = e; });
1542
+ await _wsWait(client, function () { return client.readyState === "open"; }, "ws: open");
1543
+ check("ws negotiated jmap subprotocol", client.subprotocol === "jmap");
1544
+
1545
+ // 8a. valid Request → Response
1546
+ client.send(JSON.stringify({ "@type": "Request", id: "r1", using: [], methodCalls: [["Core/echo", { hi: 9 }, "c0"]] }));
1547
+ await _wsWait(client, function () { return msgs.length >= 1; }, "ws: response received");
1548
+ var resp = JSON.parse(msgs[0]);
1549
+ check("ws Request → Response @type", resp["@type"] === "Response");
1550
+ check("ws Response echoes requestId", resp.requestId === "r1");
1551
+ check("ws Response carries methodResponses", resp.methodResponses[0][1].hi === 9);
1552
+
1553
+ // 8b. binary frame → RequestError notJSON
1554
+ msgs.length = 0;
1555
+ client.send(Buffer.from([0x00, 0x01, 0x02]));
1556
+ await _wsWait(client, function () { return msgs.length >= 1; }, "ws: binary refused");
1557
+ check("ws binary frame → RequestError notJSON",
1558
+ JSON.parse(msgs[0])["@type"] === "RequestError" && /notJSON/.test(msgs[0]));
1559
+
1560
+ // 8c. non-JSON text frame → RequestError notJSON
1561
+ msgs.length = 0;
1562
+ client.send("this is not json {");
1563
+ await _wsWait(client, function () { return msgs.length >= 1; }, "ws: bad json refused");
1564
+ check("ws non-JSON text → RequestError notJSON",
1565
+ JSON.parse(msgs[0])["@type"] === "RequestError" && /not valid JSON/.test(msgs[0]));
1566
+
1567
+ // 8d. Request that fails envelope validation → RequestError (not empty Response)
1568
+ msgs.length = 0;
1569
+ client.send(JSON.stringify({ "@type": "Request", id: "r2", using: [], methodCalls: [] }));
1570
+ await _wsWait(client, function () { return msgs.length >= 1; }, "ws: invalid request refused");
1571
+ check("ws invalid Request → RequestError",
1572
+ JSON.parse(msgs[0])["@type"] === "RequestError");
1573
+ check("ws invalid Request → requestId echoed", JSON.parse(msgs[0]).requestId === "r2");
1574
+
1575
+ // 8e. unknown @type → RequestError unknownDataType
1576
+ msgs.length = 0;
1577
+ client.send(JSON.stringify({ "@type": "Bogus", id: "r3" }));
1578
+ await _wsWait(client, function () { return msgs.length >= 1; }, "ws: unknown type refused");
1579
+ check("ws unknown @type → unknownDataType",
1580
+ /unknownDataType/.test(msgs[0]) && JSON.parse(msgs[0]).requestId === "r3");
1581
+
1582
+ // 8f. WebSocketPushEnable → StateChange push; duplicate enable is a no-op
1583
+ client.send(JSON.stringify({ "@type": "WebSocketPushEnable", dataTypes: ["Email"] }));
1584
+ await _wsWait(client, function () { return typeof captured.emit === "function"; }, "ws: push enabled");
1585
+ client.send(JSON.stringify({ "@type": "WebSocketPushEnable" })); // duplicate no-op
1586
+ msgs.length = 0;
1587
+ captured.emit({ kind: "StateChange", changed: { A1: { Email: "sc" } } });
1588
+ captured.emit(null); // guard: !event
1589
+ captured.emit({ kind: "Nope" }); // non-StateChange ignored
1590
+ await _wsWait(client, function () { return msgs.length >= 1; }, "ws: statechange pushed");
1591
+ check("ws StateChange pushed", JSON.parse(msgs[0])["@type"] === "StateChange");
1592
+
1593
+ // 8g. WebSocketPushDisable → unsubscribe invoked
1594
+ client.send(JSON.stringify({ "@type": "WebSocketPushDisable" }));
1595
+ await _wsWait(client, function () { return captured.unsub === true; }, "ws: push disabled/unsub");
1596
+ check("ws PushDisable → unsubscribe called", captured.unsub === true);
1597
+
1598
+ check("ws no client error over the exchange", errSeen === null);
1599
+ client.close(1000, "bye");
1600
+ await _wsWait(client, function () { return client.readyState === "closed"; }, "ws: closed");
1601
+ } finally { await _stop(s.server); }
1602
+
1603
+ // 8h. WebSocketPushEnable with no subscribePush backend → serverUnavailable
1604
+ var jmapNoPush = b.mail.server.jmap.create({
1605
+ mailStore: { appendMessage: function () {} }, accountsFor: DEFAULT_ACCOUNTS, methods: {},
1606
+ });
1607
+ var sNp = await _startHttp(jmapNoPush, {});
1608
+ try {
1609
+ var c2 = _wsConnect(sNp.port);
1610
+ var m2 = [];
1611
+ c2.on("message", function (d) { m2.push(typeof d === "string" ? d : d.toString("utf8")); });
1612
+ c2.on("error", function () {});
1613
+ await _wsWait(c2, function () { return c2.readyState === "open"; }, "ws(no-push): open");
1614
+ c2.send(JSON.stringify({ "@type": "WebSocketPushEnable" }));
1615
+ await _wsWait(c2, function () { return m2.length >= 1; }, "ws(no-push): serverUnavailable");
1616
+ check("ws PushEnable no-backend → serverUnavailable", /serverUnavailable/.test(m2[0]));
1617
+ c2.close(1000, "bye");
1618
+ } finally { await _stop(sNp.server); }
1619
+
1620
+ // 8i. WebSocketPushEnable whose subscribePush REJECTS → serverFail + rollback
1621
+ var jmapReject = b.mail.server.jmap.create({
1622
+ mailStore: { appendMessage: function () {}, subscribePush: function () { return Promise.reject(new Error("sub reject")); } },
1623
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
1624
+ });
1625
+ var sRj = await _startHttp(jmapReject, {});
1626
+ try {
1627
+ var c3 = _wsConnect(sRj.port);
1628
+ var m3 = [];
1629
+ c3.on("message", function (d) { m3.push(typeof d === "string" ? d : d.toString("utf8")); });
1630
+ c3.on("error", function () {});
1631
+ await _wsWait(c3, function () { return c3.readyState === "open"; }, "ws(reject): open");
1632
+ c3.send(JSON.stringify({ "@type": "WebSocketPushEnable" }));
1633
+ await _wsWait(c3, function () { return m3.length >= 1; }, "ws(reject): serverFail");
1634
+ check("ws PushEnable subscribe-reject → serverFail",
1635
+ JSON.parse(m3[0])["@type"] === "RequestError" && /serverFail/.test(m3[0]));
1636
+ c3.close(1000, "bye");
1637
+ } finally { await _stop(sRj.server); }
1638
+
1639
+ // 8j. unauthenticated upgrade → 401 handshake refusal (client sees error)
1640
+ var jmapUn = b.mail.server.jmap.create({
1641
+ mailStore: { appendMessage: function () {} }, accountsFor: DEFAULT_ACCOUNTS, methods: {},
1642
+ });
1643
+ var sUn = await _startHttp(jmapUn, { noActor: true });
1644
+ try {
1645
+ var c4 = _wsConnect(sUn.port);
1646
+ var err4 = null; var closed4 = false;
1647
+ c4.on("error", function (e) { err4 = e; });
1648
+ c4.on("close", function () { closed4 = true; });
1649
+ await _wsWait(c4, function () { return err4 !== null || closed4; }, "ws(unauth): handshake refused");
1650
+ check("ws unauth handshake → client error/close", err4 !== null || closed4);
1651
+ } finally { await _stop(sUn.server); }
1652
+ }
1653
+
1654
+ // ---- teardown: force every WS client + socket down, wait for TCP drain ----
1655
+ async function _drainTcpHandles() {
1656
+ _wsClients.forEach(function (c) {
1657
+ try { c.cancelReconnect(); } catch (_e) { /* best-effort */ }
1658
+ try { c.close(); } catch (_e) { /* best-effort */ }
1659
+ try { c._teardown(b.wsClient.CLOSE_NORMAL, "", false); } catch (_e) { /* best-effort */ }
1660
+ });
1661
+ _wsSockets.forEach(function (sock) {
1662
+ try { if (sock && !sock.destroyed) sock.destroy(); } catch (_e) { /* best-effort */ }
1663
+ });
1664
+ await Promise.all(_httpServers.map(function (srv) {
1665
+ return new Promise(function (res) {
1666
+ try { if (typeof srv.closeAllConnections === "function") srv.closeAllConnections(); } catch (_e) { /* best-effort */ }
1667
+ try { srv.close(function () { res(); }); } catch (_e) { res(); }
1668
+ });
1669
+ }));
1670
+ _wsClients = []; _wsSockets = []; _httpServers = [];
1671
+ if (typeof process.getActiveResourcesInfo !== "function") return;
1672
+ await helpers.waitUntil(function () {
1673
+ return process.getActiveResourcesInfo().filter(function (t) {
1674
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
1675
+ }).length === 0;
1676
+ }, { timeoutMs: 5000, label: "mail-server-jmap: TCP handle drain after run" });
1677
+ }
1678
+
752
1679
  async function run() {
753
1680
  testSurface();
754
1681
  testBadOptsRefused();
@@ -764,6 +1691,10 @@ async function run() {
764
1691
  await testDispatchOwnFromAccountAllowed();
765
1692
  await testDispatchOwnAccountAllowed();
766
1693
  await testDispatchAccountAgnosticMethodAllowed();
1694
+ await testDispatchArrayArgsRecursion();
1695
+ await testDispatchMethodEntryNonFunctionSkipped();
1696
+ await testDispatchAccountGateEdges();
1697
+ await testDispatchBackRefMissingObjectKey();
767
1698
  await testUploadForeignAccountRefused();
768
1699
  await testDownloadForeignAccountRefused();
769
1700
  // v0.11.29 — JMAP Push (EventSource SSE per RFC 8620 §7.3)
@@ -775,12 +1706,16 @@ async function run() {
775
1706
  await testEventSourceStateChangeAndCloseAfter();
776
1707
  await testEventSourcePingZeroDisables();
777
1708
  testEventSourceBadCloseAfter();
1709
+ await testEventSourceSendThrowCleansUp();
1710
+ await testEventSourceSubscribeAfterClose();
1711
+ await testEventSourcePingTickEmits();
778
1712
  // v0.11.30 — blob upload + download
779
1713
  await testUploadHandlerHappyPath();
780
1714
  await testUploadHandlerOversizeRefused();
781
1715
  testUploadHandlerRefusesUnauth();
782
1716
  testUploadHandlerWithoutBackend();
783
1717
  testUploadHandlerRefusesBadAccountId();
1718
+ await testUploadHandlerReqError();
784
1719
  await testDownloadHandlerHappyPath();
785
1720
  await testDownloadHandlerNotFound();
786
1721
  testDownloadHandlerRefusesUnauth();
@@ -802,6 +1737,33 @@ async function run() {
802
1737
  await testEmailSubmissionSetUpdateCannotUnsendWithoutOnCancel();
803
1738
  await testEmailSubmissionSetDestroy();
804
1739
  testEmailSubmissionSetRefusesBadOpts();
1740
+ await testEmailSubmissionSetBadMaxRecipients();
1741
+ await testEmailSubmissionSetMissingAccountId();
1742
+ await testEmailSubmissionSetCreateValidationErrors();
1743
+ await testEmailSubmissionSetUpdatePatchNotObject();
1744
+ await testEmailSubmissionSetUpdateOnCancelOutcomes();
1745
+ await testEmailSubmissionSetDestroyEdges();
1746
+ await testEmailSubmissionSetDeliveryStatusVariants();
1747
+ await testEmailSubmissionSetCreateDeliverThrows();
1748
+ await testEmailSubmissionSetOnCreatedThrows();
1749
+ await testEmailSubmissionSetIdentitiesReturnsNull();
1750
+ // Handlers driven over real localhost HTTP + WebSocket connections
1751
+ var wtt = helpers.withTestTimeout;
1752
+ try {
1753
+ await wtt("session handler", testSessionHandler);
1754
+ await wtt("discovery handler", testDiscoveryHandler);
1755
+ await wtt("api handler", testApiHandler);
1756
+ await wtt("backrefs+pointer", testBackRefsAndPointer);
1757
+ await wtt("upload handler", testUploadHandler);
1758
+ await wtt("download handler", testDownloadHandler);
1759
+ await wtt("event source", testEventSource);
1760
+ await wtt("web socket", testWebSocket);
1761
+ await wtt("download router params", testDownloadRouterSuppliedParams);
1762
+ await wtt("ws non-jmap subprotocol", testWebSocketNonJmapSubprotocol);
1763
+ await wtt("ws push lifecycle edges", testWebSocketPushLifecycleEdges);
1764
+ } finally {
1765
+ await _drainTcpHandles();
1766
+ }
805
1767
  }
806
1768
 
807
1769
  function _makeESHandler(overrides) {
@@ -999,6 +1961,482 @@ function testEmailSubmissionSetRefusesBadOpts() {
999
1961
  "mail-server-jmap/no-identities");
1000
1962
  }
1001
1963
 
1964
+ // ==========================================================================
1965
+ // Dispatch-level branches: array-arg recursion, non-fn method skip, account
1966
+ // gate edge shapes, JSON-Pointer missing-key (RFC 8620 §3.6.1 / §3.7)
1967
+ // ==========================================================================
1968
+ async function testDispatchArrayArgsRecursion() {
1969
+ // Array-valued args force _resolveBackRefs down its Array.isArray branch,
1970
+ // recursing element-by-element (including nested objects/arrays).
1971
+ var received = null;
1972
+ var jmap = b.mail.server.jmap.create({
1973
+ mailStore: { appendMessage: function () {} },
1974
+ accountsFor: async function () { return { primaryAccounts: {}, accounts: {} }; },
1975
+ methods: { "Bulk/set": async function (actor, args) { received = args; return { ok: true }; } },
1976
+ });
1977
+ var rv = await jmap.dispatch({ id: "a" }, {
1978
+ using: [], methodCalls: [["Bulk/set", { ids: ["x1", "x2", { nested: ["deep"] }] }, "c0"]],
1979
+ });
1980
+ check("array-valued args resolved intact through recursion",
1981
+ received && Array.isArray(received.ids) && received.ids[0] === "x1" &&
1982
+ received.ids[2] && received.ids[2].nested[0] === "deep");
1983
+ check("array-args method → non-error response", rv.methodResponses[0][0] === "Bulk/set");
1984
+ }
1985
+
1986
+ async function testDispatchMethodEntryNonFunctionSkipped() {
1987
+ // A methods map entry whose value is not a function is skipped at registry
1988
+ // build time — it never registers, so a call to it is unknownMethod.
1989
+ var jmap = b.mail.server.jmap.create({
1990
+ mailStore: { appendMessage: function () {} },
1991
+ accountsFor: async function () { return { primaryAccounts: {}, accounts: {} }; },
1992
+ methods: { "Good/x": async function () { return { ok: 1 }; }, "Bad/y": 12345 },
1993
+ });
1994
+ var rv = await jmap.dispatch({ id: "a" }, {
1995
+ using: [], methodCalls: [["Good/x", {}, "c0"], ["Bad/y", {}, "c1"]],
1996
+ });
1997
+ check("non-function method entry skipped (Good/x dispatches)",
1998
+ rv.methodResponses[0][0] === "Good/x" && rv.methodResponses[0][1].ok === 1);
1999
+ check("non-function method entry unregistered → unknownMethod",
2000
+ rv.methodResponses[1][0] === "error" &&
2001
+ rv.methodResponses[1][1].type === "urn:ietf:params:jmap:error:unknownMethod");
2002
+ }
2003
+
2004
+ async function testDispatchAccountGateEdges() {
2005
+ // accountsFor returns null → empty permitted set → any named accountId is
2006
+ // rejected fail-closed (_permittedAccountIds info/accounts fallbacks).
2007
+ var jmapNull = b.mail.server.jmap.create({
2008
+ mailStore: { appendMessage: function () {} },
2009
+ accountsFor: async function () { return null; },
2010
+ methods: { "Mailbox/get": async function () { return { list: [] }; } },
2011
+ });
2012
+ var rvNull = await jmapNull.dispatch({ id: "a" }, {
2013
+ using: [], methodCalls: [["Mailbox/get", { accountId: "A1" }, "c0"]],
2014
+ });
2015
+ check("accountsFor null → accountNotFound (fail-closed)",
2016
+ rvNull.methodResponses[0][1].type === "urn:ietf:params:jmap:error:accountNotFound");
2017
+
2018
+ var reached = { nullId: false };
2019
+ var jmap = b.mail.server.jmap.create({
2020
+ mailStore: { appendMessage: function () {} },
2021
+ accountsFor: async function () { return { primaryAccounts: {}, accounts: { A1: { name: "x" } } }; },
2022
+ methods: { "Mailbox/get": async function (actor, args) { reached.nullId = true; return { got: args.accountId }; } },
2023
+ });
2024
+ // accountId:null → the gate skips a null-valued *AccountId key (method runs).
2025
+ var rvNullAcc = await jmap.dispatch({ id: "a" }, {
2026
+ using: [], methodCalls: [["Mailbox/get", { accountId: null }, "c0"]],
2027
+ });
2028
+ check("accountId:null skips gate → method runs",
2029
+ reached.nullId === true && rvNullAcc.methodResponses[0][0] === "Mailbox/get");
2030
+ // non-string accountId (number) → denied with deniedAccountId coerced to null.
2031
+ var rvNum = await jmap.dispatch({ id: "a" }, {
2032
+ using: [], methodCalls: [["Mailbox/get", { accountId: 999 }, "c0"]],
2033
+ });
2034
+ check("non-string accountId → accountNotFound (denied)",
2035
+ rvNum.methodResponses[0][1].type === "urn:ietf:params:jmap:error:accountNotFound");
2036
+ }
2037
+
2038
+ async function testDispatchBackRefMissingObjectKey() {
2039
+ // JSON-Pointer walk into an object that lacks the final segment → undefined
2040
+ // → invalidResultReference (the hasOwnProperty guard, not the array guard).
2041
+ var jmap = b.mail.server.jmap.create({
2042
+ mailStore: { appendMessage: function () {} },
2043
+ accountsFor: async function () { return { primaryAccounts: {}, accounts: {} }; },
2044
+ methods: {
2045
+ "First/get": async function () { return { list: [{ id: "x1" }] }; },
2046
+ "Second/use": async function (actor, args) { return { received: args }; },
2047
+ },
2048
+ });
2049
+ var rv = await jmap.dispatch({ id: "a" }, {
2050
+ using: [], methodCalls: [
2051
+ ["First/get", {}, "c0"],
2052
+ ["Second/use", { "#miss": { resultOf: "c0", name: "First/get", path: "/list/0/nope" } }, "c1"],
2053
+ ],
2054
+ });
2055
+ check("back-ref to missing object key → invalidResultReference",
2056
+ rv.methodResponses[1][1].type === "urn:ietf:params:jmap:error:invalidResultReference");
2057
+ }
2058
+
2059
+ // ==========================================================================
2060
+ // eventSourceHandler internal branches driven through a mock req/res:
2061
+ // _send write-throw cleanup, subscribe-resolves-after-close, ping keepalive
2062
+ // ==========================================================================
2063
+ async function testEventSourceSendThrowCleansUp() {
2064
+ // A res.write that throws while emitting a `state` frame drives _send's
2065
+ // catch → _cleanup (the socket-torn-down path).
2066
+ var emitFn = null;
2067
+ var jmap = b.mail.server.jmap.create({
2068
+ mailStore: {
2069
+ appendMessage: function () {},
2070
+ subscribePush: function (actor, types, fn) { emitFn = fn; return Promise.resolve(function () {}); },
2071
+ },
2072
+ accountsFor: async function () { return {}; },
2073
+ methods: {},
2074
+ });
2075
+ var status = 200; var ended = false; var listeners = {};
2076
+ var req = {
2077
+ url: "/jmap/eventsource?ping=0", user: { id: "u1" },
2078
+ on: function (ev, fn) { (listeners[ev] = listeners[ev] || []).push(fn); },
2079
+ _fire: function (ev) { (listeners[ev] || []).forEach(function (fn) { try { fn(); } catch (_e) { /* ignore */ } }); },
2080
+ };
2081
+ var res = {
2082
+ get statusCode() { return status; }, set statusCode(v) { status = v; },
2083
+ setHeader: function () {},
2084
+ write: function (chunk) { if (String(chunk).indexOf("event: state") === 0) throw new Error("socket gone"); },
2085
+ end: function () { ended = true; },
2086
+ };
2087
+ jmap.eventSourceHandler(req, res);
2088
+ await helpers.waitUntil(function () { return typeof emitFn === "function"; },
2089
+ { timeoutMs: 5000, label: "es-send-throw: subscribePush resolved" });
2090
+ emitFn({ kind: "StateChange", changed: { A1: { Email: "s" } } });
2091
+ check("SSE _send write-throw triggers cleanup (res.end called)", ended === true);
2092
+ }
2093
+
2094
+ async function testEventSourceSubscribeAfterClose() {
2095
+ // Client disconnects (req 'close' → _cleanup, closed=true) BEFORE
2096
+ // subscribePush resolves → the late-resolving handle is unsubscribed.
2097
+ var resolveSub = null; var unsubCalled = false;
2098
+ var jmap = b.mail.server.jmap.create({
2099
+ mailStore: {
2100
+ appendMessage: function () {},
2101
+ subscribePush: function () { return new Promise(function (r) { resolveSub = r; }); },
2102
+ },
2103
+ accountsFor: async function () { return {}; },
2104
+ methods: {},
2105
+ });
2106
+ var mr = _makeMockReqRes("/jmap/eventsource?ping=0");
2107
+ jmap.eventSourceHandler(mr.req, mr.res);
2108
+ await helpers.waitUntil(function () { return typeof resolveSub === "function"; },
2109
+ { timeoutMs: 5000, label: "es-after-close: subscribePush invoked" });
2110
+ mr.req._fire("close");
2111
+ resolveSub(function () { unsubCalled = true; });
2112
+ await helpers.waitUntil(function () { return unsubCalled === true; },
2113
+ { timeoutMs: 5000, label: "es-after-close: late unsubscribe invoked" });
2114
+ check("SSE subscribe resolves after close → unsubscribe invoked", unsubCalled === true);
2115
+ }
2116
+
2117
+ async function testEventSourcePingTickEmits() {
2118
+ // ping=5 is the RFC 8620 §7.3 floor; the keepalive interval fires an
2119
+ // `event: ping` frame carrying the negotiated interval. Genuine timer
2120
+ // latency (5 s min) — polled via waitUntil.
2121
+ var jmap = b.mail.server.jmap.create({
2122
+ mailStore: {
2123
+ appendMessage: function () {},
2124
+ subscribePush: function () { return Promise.resolve(function () {}); },
2125
+ },
2126
+ accountsFor: async function () { return {}; },
2127
+ methods: {},
2128
+ });
2129
+ var mr = _makeMockReqRes("/jmap/eventsource?ping=5");
2130
+ jmap.eventSourceHandler(mr.req, mr.res);
2131
+ try {
2132
+ await helpers.waitUntil(function () { return /event: ping/.test(mr.res._buf()); },
2133
+ { timeoutMs: 8000, label: "es-ping: keepalive ping frame emitted" });
2134
+ check("SSE ping keepalive frame emitted with interval",
2135
+ /event: ping/.test(mr.res._buf()) && /"interval":5/.test(mr.res._buf()));
2136
+ } finally {
2137
+ mr.req._fire("close"); // clears the ping interval
2138
+ }
2139
+ }
2140
+
2141
+ // ==========================================================================
2142
+ // uploadHandler req 'error' event → 400 abort
2143
+ // ==========================================================================
2144
+ async function testUploadHandlerReqError() {
2145
+ var jmap = b.mail.server.jmap.create({
2146
+ mailStore: { appendMessage: function () {}, uploadBlob: function () { return Promise.resolve({ blobId: "x" }); } },
2147
+ accountsFor: async function () { return { accounts: { A1: { name: "x" } } }; },
2148
+ methods: {},
2149
+ });
2150
+ var status = 200; var ended = false; var listeners = {};
2151
+ var req = {
2152
+ url: "/jmap/upload/A1", user: { id: "u1" }, headers: { "content-type": "text/plain" },
2153
+ on: function (ev, fn) { (listeners[ev] = listeners[ev] || []).push(fn); },
2154
+ _fire: function (ev, a) { (listeners[ev] || []).forEach(function (fn) { try { fn(a); } catch (_e) { /* ignore */ } }); },
2155
+ };
2156
+ var res = {
2157
+ get statusCode() { return status; }, set statusCode(v) { status = v; },
2158
+ setHeader: function () {}, write: function () {}, end: function () { ended = true; },
2159
+ };
2160
+ jmap.uploadHandler(req, res);
2161
+ req._fire("error", new Error("socket error mid-upload"));
2162
+ check("upload req 'error' → 400", status === 400);
2163
+ check("upload req 'error' → response ended", ended === true);
2164
+ }
2165
+
2166
+ // ==========================================================================
2167
+ // downloadHandler router-supplied req.params path (RFC 8620 §6.2)
2168
+ // ==========================================================================
2169
+ async function testDownloadRouterSuppliedParams() {
2170
+ var call = null;
2171
+ var jmap = b.mail.server.jmap.create({
2172
+ mailStore: {
2173
+ appendMessage: function () {},
2174
+ downloadBlob: function (a, accountId, blobId) {
2175
+ call = { accountId: accountId, blobId: blobId };
2176
+ return Promise.resolve({ bytes: Buffer.from("rp"), type: "text/plain" });
2177
+ },
2178
+ },
2179
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
2180
+ });
2181
+ var s = await _startHttp(jmap, { forceDownload: true, params: { accountId: "A1", blobId: "blob_7", name: "n.txt" } });
2182
+ try {
2183
+ var r = await _req(s.port, { path: "/whatever/the/router/mounted" });
2184
+ check("download router-supplied params → 200", r.status === 200);
2185
+ check("download router-supplied maps params to backend",
2186
+ call && call.accountId === "A1" && call.blobId === "blob_7");
2187
+ } finally { await _stop(s.server); }
2188
+ }
2189
+
2190
+ // ==========================================================================
2191
+ // webSocketHandler: non-jmap subprotocol refusal (RFC 8887 §3.1)
2192
+ // ==========================================================================
2193
+ async function testWebSocketNonJmapSubprotocol() {
2194
+ var jmap = b.mail.server.jmap.create({
2195
+ mailStore: { appendMessage: function () {} }, accountsFor: DEFAULT_ACCOUNTS, methods: {},
2196
+ });
2197
+ var s = await _startHttp(jmap, {});
2198
+ try {
2199
+ var client = _wsConnect(s.port, { subprotocols: ["not-jmap"] });
2200
+ var closed = false; var errSeen = null;
2201
+ client.on("close", function () { closed = true; });
2202
+ client.on("error", function (e) { errSeen = e; });
2203
+ await _wsWait(client, function () { return closed || errSeen !== null; },
2204
+ "ws non-jmap: server refused the connection");
2205
+ check("ws non-jmap subprotocol → server refused (closed/errored)", closed || errSeen !== null);
2206
+ check("ws non-jmap → never negotiated jmap", client.subprotocol !== "jmap");
2207
+ } finally { await _stop(s.server); }
2208
+ }
2209
+
2210
+ // ==========================================================================
2211
+ // webSocketHandler push lifecycle edges: late-cleanup when disabled during
2212
+ // subscribe setup; unsubscribe on connection close (RFC 8887 §5)
2213
+ // ==========================================================================
2214
+ async function testWebSocketPushLifecycleEdges() {
2215
+ // (a) PushEnable, PushDisable, THEN subscribePush resolves → the deferred
2216
+ // handle is torn down by the late-cleanup path (!pushEnabled).
2217
+ var cap = {};
2218
+ var jmapLate = b.mail.server.jmap.create({
2219
+ mailStore: {
2220
+ appendMessage: function () {},
2221
+ subscribePush: function () {
2222
+ cap.enableCalls = (cap.enableCalls || 0) + 1;
2223
+ return new Promise(function (r) { cap.resolveSub = r; });
2224
+ },
2225
+ },
2226
+ accountsFor: DEFAULT_ACCOUNTS,
2227
+ methods: { "Core/echo": async function (a, args) { return { hi: args.hi }; } },
2228
+ });
2229
+ var s = await _startHttp(jmapLate, {});
2230
+ try {
2231
+ var client = _wsConnect(s.port);
2232
+ var msgs = [];
2233
+ client.on("message", function (d) { msgs.push(typeof d === "string" ? d : d.toString("utf8")); });
2234
+ client.on("error", function () {});
2235
+ await _wsWait(client, function () { return client.readyState === "open"; }, "ws-late: open");
2236
+ client.send(JSON.stringify({ "@type": "WebSocketPushEnable" }));
2237
+ await _wsWait(client, function () { return typeof cap.resolveSub === "function"; }, "ws-late: subscribePush called");
2238
+ client.send(JSON.stringify({ "@type": "WebSocketPushDisable" }));
2239
+ // A Request AFTER the Disable: receiving its Response proves the Disable
2240
+ // was processed in-order (pushEnabled=false) before we resolve the setup.
2241
+ client.send(JSON.stringify({ "@type": "Request", id: "q1", using: [], methodCalls: [["Core/echo", { hi: 1 }, "c0"]] }));
2242
+ await _wsWait(client, function () { return msgs.some(function (m) { return /"requestId":"q1"/.test(m); }); },
2243
+ "ws-late: post-disable response received");
2244
+ cap.resolveSub(function () { cap.unsub = true; });
2245
+ await _wsWait(client, function () { return cap.unsub === true; }, "ws-late: deferred unsubscribe ran");
2246
+ check("ws push late-cleanup unsubscribes a handle that resolved after disable", cap.unsub === true);
2247
+ client.close(1000, "bye");
2248
+ await _wsWait(client, function () { return client.readyState === "closed"; }, "ws-late: closed");
2249
+ } finally { await _stop(s.server); }
2250
+
2251
+ // (b) PushEnable resolves (pushUnsubscribe set), THEN the connection closes
2252
+ // → conn 'close' runs the active unsubscribe.
2253
+ var cap2 = {};
2254
+ var jmapClose = b.mail.server.jmap.create({
2255
+ mailStore: {
2256
+ appendMessage: function () {},
2257
+ subscribePush: function (a, dt, emitFn) { cap2.emit = emitFn; return Promise.resolve(function () { cap2.unsub = true; }); },
2258
+ },
2259
+ accountsFor: DEFAULT_ACCOUNTS, methods: {},
2260
+ });
2261
+ var s2 = await _startHttp(jmapClose, {});
2262
+ try {
2263
+ var c2 = _wsConnect(s2.port);
2264
+ c2.on("error", function () {});
2265
+ await _wsWait(c2, function () { return c2.readyState === "open"; }, "ws-close: open");
2266
+ c2.send(JSON.stringify({ "@type": "WebSocketPushEnable" }));
2267
+ await _wsWait(c2, function () { return typeof cap2.emit === "function"; }, "ws-close: push enabled");
2268
+ cap2.emit(null); // guard: falsy event ignored
2269
+ cap2.emit({ kind: "StateChange" }); // no `changed` → default {} fallback
2270
+ cap2.emit({ kind: "Nope" }); // non-StateChange ignored
2271
+ c2.close(1000, "bye");
2272
+ await _wsWait(c2, function () { return cap2.unsub === true; }, "ws-close: unsub on conn close");
2273
+ check("ws push conn-close unsubscribes the active subscription", cap2.unsub === true);
2274
+ } finally { await _stop(s2.server); }
2275
+ }
2276
+
2277
+ // ==========================================================================
2278
+ // emailSubmissionSetHandler — option-default + create/update/destroy branches
2279
+ // (RFC 8621 §7.5)
2280
+ // ==========================================================================
2281
+ function testEmailSubmissionSetBadMaxRecipients() {
2282
+ function expectThrow(label, maxRecipients) {
2283
+ var threw = null;
2284
+ try {
2285
+ b.mail.server.jmap.emailSubmissionSetHandler({
2286
+ deliver: function () {}, lookupEmail: function () {}, identities: function () {},
2287
+ maxRecipients: maxRecipients,
2288
+ });
2289
+ } catch (e) { threw = e; }
2290
+ check(label, threw && (threw.code || "").indexOf("mail-server-jmap/bad-max-recipients") !== -1);
2291
+ }
2292
+ expectThrow("negative maxRecipients rejected", -5);
2293
+ expectThrow("non-number maxRecipients rejected", "lots");
2294
+ expectThrow("non-finite maxRecipients rejected", Infinity);
2295
+ }
2296
+
2297
+ async function testEmailSubmissionSetMissingAccountId() {
2298
+ var es = _makeESHandler();
2299
+ var threw = null;
2300
+ try { await es.handler({}, {}, {}); } catch (e) { threw = e; }
2301
+ check("EmailSubmission/set missing accountId → invalidArguments throw",
2302
+ threw && (threw.code || "").indexOf("invalidArguments") !== -1);
2303
+ var threw2 = null;
2304
+ try { await es.handler({}, null, {}); } catch (e) { threw2 = e; }
2305
+ check("EmailSubmission/set null args → invalidArguments throw",
2306
+ threw2 && (threw2.code || "").indexOf("invalidArguments") !== -1);
2307
+ }
2308
+
2309
+ async function testEmailSubmissionSetCreateValidationErrors() {
2310
+ var es = _makeESHandler();
2311
+ async function notCreated(sub) {
2312
+ var rv = await es.handler({}, { accountId: "A1", create: { c1: sub } }, {});
2313
+ return rv.notCreated && rv.notCreated.c1;
2314
+ }
2315
+ var eNull = await notCreated(null);
2316
+ check("create sub null → invalidArguments", eNull && eNull.type === "invalidArguments");
2317
+ var eArr = await notCreated([1, 2]);
2318
+ check("create sub array → invalidArguments", eArr && eArr.type === "invalidArguments");
2319
+ var eNoId = await notCreated({ emailId: "E1", envelope: {} });
2320
+ check("create no identityId → invalidProperties(identityId)",
2321
+ eNoId && eNoId.type === "invalidProperties" && eNoId.properties.indexOf("identityId") !== -1);
2322
+ var eNoEmail = await notCreated({ identityId: "I1", envelope: {} });
2323
+ check("create no emailId → invalidProperties(emailId)",
2324
+ eNoEmail && eNoEmail.type === "invalidProperties" && eNoEmail.properties.indexOf("emailId") !== -1);
2325
+ var eNoEnv = await notCreated({ identityId: "I1", emailId: "E1" });
2326
+ check("create no envelope → invalidProperties(envelope)",
2327
+ eNoEnv && eNoEnv.type === "invalidProperties" && eNoEnv.properties.indexOf("envelope") !== -1);
2328
+ var eNoMailFrom = await notCreated({ identityId: "I1", emailId: "E1", envelope: { rcptTo: [{ email: "a@x.com" }] } });
2329
+ check("create no mailFrom.email → invalidProperties(envelope/mailFrom)",
2330
+ eNoMailFrom && eNoMailFrom.type === "invalidProperties" &&
2331
+ eNoMailFrom.properties.indexOf("envelope/mailFrom") !== -1);
2332
+ }
2333
+
2334
+ async function testEmailSubmissionSetUpdatePatchNotObject() {
2335
+ var es = _makeESHandler();
2336
+ var rv = await es.handler({}, { accountId: "A1", update: { S1: "notanobject", S2: [1, 2] } }, {});
2337
+ check("update patch string → invalidPatch", rv.notUpdated && rv.notUpdated.S1.type === "invalidPatch");
2338
+ check("update patch array → invalidPatch", rv.notUpdated && rv.notUpdated.S2.type === "invalidPatch");
2339
+ }
2340
+
2341
+ async function testEmailSubmissionSetUpdateOnCancelOutcomes() {
2342
+ var esFalse = _makeESHandler({ onCancel: async function () { return false; } });
2343
+ var rvF = await esFalse.handler({}, { accountId: "A1", update: { S1: { undoStatus: "canceled" } } }, {});
2344
+ check("onCancel false → cannotUnsend", rvF.notUpdated && rvF.notUpdated.S1.type === "cannotUnsend");
2345
+
2346
+ var esThrow = _makeESHandler({ onCancel: async function () { throw new Error("cancel boom"); } });
2347
+ var rvT = await esThrow.handler({}, { accountId: "A1", update: { S1: { undoStatus: "canceled" } } }, {});
2348
+ check("onCancel throws (plain Error) → notUpdated serverFail shape",
2349
+ rvT.notUpdated && rvT.notUpdated.S1.type === "serverFail");
2350
+ }
2351
+
2352
+ async function testEmailSubmissionSetDestroyEdges() {
2353
+ var esNoOp = _makeESHandler(); // no onDestroyed configured
2354
+ var rv = await esNoOp.handler({}, { accountId: "A1", destroy: [123, "", "S3"] }, {});
2355
+ check("destroy non-string id → notDestroyed invalidArguments",
2356
+ rv.notDestroyed && rv.notDestroyed["123"] && rv.notDestroyed["123"].type === "invalidArguments");
2357
+ check("destroy empty id → notDestroyed invalidArguments",
2358
+ rv.notDestroyed && rv.notDestroyed[""] && rv.notDestroyed[""].type === "invalidArguments");
2359
+ check("destroy valid id with no onDestroyed → noop accepted",
2360
+ Array.isArray(rv.destroyed) && rv.destroyed.indexOf("S3") !== -1);
2361
+
2362
+ var esThrow = _makeESHandler({ onDestroyed: async function () { throw new Error("destroy boom"); } });
2363
+ var rvT = await esThrow.handler({}, { accountId: "A1", destroy: ["S9"] }, {});
2364
+ check("onDestroyed throws → notDestroyed serverFail",
2365
+ rvT.notDestroyed && rvT.notDestroyed.S9 && rvT.notDestroyed.S9.type === "serverFail");
2366
+ }
2367
+
2368
+ async function testEmailSubmissionSetDeliveryStatusVariants() {
2369
+ // deferred + failed + smtpReply-default fallbacks across all three loops.
2370
+ var es = _makeESHandler({
2371
+ deliver: async function () {
2372
+ return {
2373
+ delivered: [{ recipient: "d@x.com" }], // no smtpReply → "250 Accepted"
2374
+ deferred: [{ recipient: "q@x.com" }], // no smtpReply → "451 ..."
2375
+ failed: [{ recipient: "f@x.com" }], // no smtpReply → "550 ..."
2376
+ };
2377
+ },
2378
+ });
2379
+ var rv = await es.handler({}, {
2380
+ accountId: "A1",
2381
+ create: { c1: { identityId: "I1", emailId: "E1",
2382
+ envelope: { mailFrom: { email: "ops@example.com" },
2383
+ rcptTo: [{ email: "d@x.com" }, { email: "q@x.com" }, { email: "f@x.com" }] } } },
2384
+ }, {});
2385
+ var ds = rv.created.c1.deliveryStatus;
2386
+ check("delivered → yes + default 250 smtpReply", ds["d@x.com"].delivered === "yes" && /^250/.test(ds["d@x.com"].smtpReply));
2387
+ check("deferred → queued + default 451 smtpReply", ds["q@x.com"].delivered === "queued" && /^451/.test(ds["q@x.com"].smtpReply));
2388
+ check("failed → no + default 550 smtpReply", ds["f@x.com"].delivered === "no" && /^550/.test(ds["f@x.com"].smtpReply));
2389
+
2390
+ // deliver returns {} → every deliveryStatus loop's source array falls back
2391
+ // to [] (empty deliveryStatus).
2392
+ var esEmpty = _makeESHandler({ deliver: async function () { return {}; } });
2393
+ var rvE = await esEmpty.handler({}, {
2394
+ accountId: "A1",
2395
+ create: { c1: { identityId: "I1", emailId: "E1",
2396
+ envelope: { mailFrom: { email: "ops@example.com" }, rcptTo: [{ email: "a@x.com" }] } } },
2397
+ }, {});
2398
+ check("empty deliver result → empty deliveryStatus",
2399
+ rvE.created.c1.deliveryStatus && Object.keys(rvE.created.c1.deliveryStatus).length === 0);
2400
+ }
2401
+
2402
+ async function testEmailSubmissionSetCreateDeliverThrows() {
2403
+ // deliver throwing a plain Error (no _jmapType) surfaces as serverFail via
2404
+ // the create-loop catch + _jmapErrorShape fallback.
2405
+ var es = _makeESHandler({ deliver: async function () { throw new Error("mta down"); } });
2406
+ var rv = await es.handler({}, {
2407
+ accountId: "A1",
2408
+ create: { c1: { identityId: "I1", emailId: "E1",
2409
+ envelope: { mailFrom: { email: "ops@example.com" }, rcptTo: [{ email: "a@x.com" }] } } },
2410
+ }, {});
2411
+ check("deliver throws plain Error → notCreated serverFail",
2412
+ rv.notCreated && rv.notCreated.c1 && rv.notCreated.c1.type === "serverFail");
2413
+ }
2414
+
2415
+ async function testEmailSubmissionSetOnCreatedThrows() {
2416
+ // onCreated is an operator persistence side-effect — a throw there is
2417
+ // drop-silent and the create still succeeds.
2418
+ var es = _makeESHandler({ onCreated: async function () { throw new Error("persist boom"); } });
2419
+ var rv = await es.handler({}, {
2420
+ accountId: "A1",
2421
+ create: { c1: { identityId: "I1", emailId: "E1",
2422
+ envelope: { mailFrom: { email: "ops@example.com" }, rcptTo: [{ email: "a@x.com" }] } } },
2423
+ }, {});
2424
+ check("onCreated throw is drop-silent → create still succeeds",
2425
+ rv.created && rv.created.c1 && typeof rv.created.c1.id === "string");
2426
+ }
2427
+
2428
+ async function testEmailSubmissionSetIdentitiesReturnsNull() {
2429
+ // identities() returning null falls back to an empty list → identityNotFound.
2430
+ var es = _makeESHandler({ identities: function () { return null; } });
2431
+ var rv = await es.handler({}, {
2432
+ accountId: "A1",
2433
+ create: { c1: { identityId: "I1", emailId: "E1",
2434
+ envelope: { mailFrom: { email: "ops@example.com" }, rcptTo: [{ email: "a@x.com" }] } } },
2435
+ }, {});
2436
+ check("identities()→null → identityNotFound (empty-list fallback)",
2437
+ rv.notCreated && rv.notCreated.c1 && rv.notCreated.c1.type === "identityNotFound");
2438
+ }
2439
+
1002
2440
  module.exports = { run: run };
1003
2441
 
1004
2442
  if (require.main === module) {