velocious 1.0.643 → 1.0.645

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 (279) hide show
  1. package/README.md +21 -10
  2. package/build/authorization/ability.js +36 -13
  3. package/build/background-jobs/adapter.js +9 -0
  4. package/build/background-jobs/main.js +35 -10
  5. package/build/background-jobs/store.js +217 -38
  6. package/build/background-jobs/types.js +15 -0
  7. package/build/configuration-types.js +1 -1
  8. package/build/configuration.js +45 -9
  9. package/build/database/drivers/base-column.js +8 -0
  10. package/build/database/drivers/mssql/sql/update.js +1 -3
  11. package/build/database/drivers/mysql/sql/update.js +1 -3
  12. package/build/database/drivers/pgsql/column.js +22 -0
  13. package/build/database/drivers/pgsql/sql/update.js +1 -3
  14. package/build/database/drivers/sqlite/sql/alter-table.js +11 -1
  15. package/build/database/drivers/sqlite/sql/update.js +1 -3
  16. package/build/database/query/index.js +2 -2
  17. package/build/database/query/model-class-query.js +26 -10
  18. package/build/database/query/preloader/has-many.js +4 -1
  19. package/build/database/query/query-data.js +2 -1
  20. package/build/database/query/update-base.js +14 -0
  21. package/build/database/query/with-count.js +3 -1
  22. package/build/database/record/acts-as-list.js +37 -13
  23. package/build/database/record/attachments/store.js +336 -26
  24. package/build/database/record/auditing.js +6 -3
  25. package/build/database/record/index.js +197 -42
  26. package/build/database/record/instance-relationships/belongs-to.js +2 -1
  27. package/build/database/record/validators/uniqueness.js +8 -5
  28. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
  29. package/build/frontend-model-controller.js +136 -53
  30. package/build/frontend-model-resource/base-resource.js +87 -37
  31. package/build/frontend-model-resource/velocious-attachment-resource.js +98 -31
  32. package/build/frontend-models/base.js +307 -59
  33. package/build/frontend-models/preloader.js +6 -3
  34. package/build/frontend-models/query.js +15 -6
  35. package/build/frontend-models/resource-definition.js +21 -2
  36. package/build/frontend-models/use-destroyed-event.js +1 -1
  37. package/build/frontend-models/use-model-class-event.js +2 -2
  38. package/build/frontend-models/use-updated-event.js +1 -1
  39. package/build/frontend-models/websocket-channel.js +256 -93
  40. package/build/frontend-models/websocket-publishers.js +218 -18
  41. package/build/http-server/client/websocket-session.js +13 -1
  42. package/build/http-server/websocket-channel.js +15 -2
  43. package/build/src/authorization/ability.d.ts +9 -3
  44. package/build/src/authorization/ability.d.ts.map +1 -1
  45. package/build/src/authorization/ability.js +33 -14
  46. package/build/src/background-jobs/adapter.d.ts +6 -0
  47. package/build/src/background-jobs/adapter.d.ts.map +1 -1
  48. package/build/src/background-jobs/adapter.js +9 -1
  49. package/build/src/background-jobs/main.d.ts +6 -0
  50. package/build/src/background-jobs/main.d.ts.map +1 -1
  51. package/build/src/background-jobs/main.js +34 -11
  52. package/build/src/background-jobs/store.d.ts +66 -12
  53. package/build/src/background-jobs/store.d.ts.map +1 -1
  54. package/build/src/background-jobs/store.js +191 -35
  55. package/build/src/background-jobs/types.d.ts +55 -0
  56. package/build/src/background-jobs/types.d.ts.map +1 -1
  57. package/build/src/background-jobs/types.js +16 -1
  58. package/build/src/configuration-types.d.ts +3 -3
  59. package/build/src/configuration-types.d.ts.map +1 -1
  60. package/build/src/configuration-types.js +2 -2
  61. package/build/src/configuration.d.ts +18 -8
  62. package/build/src/configuration.d.ts.map +1 -1
  63. package/build/src/configuration.js +38 -7
  64. package/build/src/database/drivers/base-column.d.ts +5 -0
  65. package/build/src/database/drivers/base-column.d.ts.map +1 -1
  66. package/build/src/database/drivers/base-column.js +8 -1
  67. package/build/src/database/drivers/mssql/sql/update.d.ts.map +1 -1
  68. package/build/src/database/drivers/mssql/sql/update.js +2 -4
  69. package/build/src/database/drivers/mysql/sql/update.d.ts.map +1 -1
  70. package/build/src/database/drivers/mysql/sql/update.js +2 -4
  71. package/build/src/database/drivers/pgsql/column.d.ts +5 -0
  72. package/build/src/database/drivers/pgsql/column.d.ts.map +1 -1
  73. package/build/src/database/drivers/pgsql/column.js +19 -1
  74. package/build/src/database/drivers/pgsql/sql/update.d.ts.map +1 -1
  75. package/build/src/database/drivers/pgsql/sql/update.js +2 -4
  76. package/build/src/database/drivers/sqlite/sql/alter-table.d.ts.map +1 -1
  77. package/build/src/database/drivers/sqlite/sql/alter-table.js +6 -2
  78. package/build/src/database/drivers/sqlite/sql/update.d.ts.map +1 -1
  79. package/build/src/database/drivers/sqlite/sql/update.js +2 -4
  80. package/build/src/database/query/index.d.ts +4 -4
  81. package/build/src/database/query/index.d.ts.map +1 -1
  82. package/build/src/database/query/index.js +3 -3
  83. package/build/src/database/query/model-class-query.d.ts +8 -2
  84. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  85. package/build/src/database/query/model-class-query.js +25 -11
  86. package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
  87. package/build/src/database/query/preloader/has-many.js +4 -2
  88. package/build/src/database/query/query-data.d.ts.map +1 -1
  89. package/build/src/database/query/query-data.js +3 -2
  90. package/build/src/database/query/update-base.d.ts +7 -0
  91. package/build/src/database/query/update-base.d.ts.map +1 -1
  92. package/build/src/database/query/update-base.js +13 -1
  93. package/build/src/database/query/with-count.d.ts.map +1 -1
  94. package/build/src/database/query/with-count.js +3 -2
  95. package/build/src/database/record/acts-as-list.d.ts.map +1 -1
  96. package/build/src/database/record/acts-as-list.js +35 -14
  97. package/build/src/database/record/attachments/store.d.ts +67 -0
  98. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  99. package/build/src/database/record/attachments/store.js +301 -25
  100. package/build/src/database/record/auditing.d.ts.map +1 -1
  101. package/build/src/database/record/auditing.js +7 -4
  102. package/build/src/database/record/index.d.ts +78 -26
  103. package/build/src/database/record/index.d.ts.map +1 -1
  104. package/build/src/database/record/index.js +171 -42
  105. package/build/src/database/record/instance-relationships/belongs-to.d.ts.map +1 -1
  106. package/build/src/database/record/instance-relationships/belongs-to.js +3 -2
  107. package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
  108. package/build/src/database/record/validators/uniqueness.js +8 -6
  109. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +4 -4
  110. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  111. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -12
  112. package/build/src/frontend-model-controller.d.ts +29 -8
  113. package/build/src/frontend-model-controller.d.ts.map +1 -1
  114. package/build/src/frontend-model-controller.js +122 -51
  115. package/build/src/frontend-model-resource/base-resource.d.ts +51 -24
  116. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  117. package/build/src/frontend-model-resource/base-resource.js +83 -37
  118. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +45 -26
  119. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
  120. package/build/src/frontend-model-resource/velocious-attachment-resource.js +89 -31
  121. package/build/src/frontend-models/base.d.ts +53 -20
  122. package/build/src/frontend-models/base.d.ts.map +1 -1
  123. package/build/src/frontend-models/base.js +275 -59
  124. package/build/src/frontend-models/preloader.d.ts.map +1 -1
  125. package/build/src/frontend-models/preloader.js +6 -4
  126. package/build/src/frontend-models/query.d.ts +2 -2
  127. package/build/src/frontend-models/query.d.ts.map +1 -1
  128. package/build/src/frontend-models/query.js +14 -7
  129. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  130. package/build/src/frontend-models/resource-definition.js +20 -3
  131. package/build/src/frontend-models/use-destroyed-event.d.ts +1 -1
  132. package/build/src/frontend-models/use-destroyed-event.d.ts.map +1 -1
  133. package/build/src/frontend-models/use-destroyed-event.js +2 -2
  134. package/build/src/frontend-models/use-model-class-event.d.ts +2 -2
  135. package/build/src/frontend-models/use-model-class-event.d.ts.map +1 -1
  136. package/build/src/frontend-models/use-model-class-event.js +3 -3
  137. package/build/src/frontend-models/use-updated-event.d.ts +1 -1
  138. package/build/src/frontend-models/use-updated-event.d.ts.map +1 -1
  139. package/build/src/frontend-models/use-updated-event.js +2 -2
  140. package/build/src/frontend-models/websocket-channel.d.ts +60 -41
  141. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  142. package/build/src/frontend-models/websocket-channel.js +225 -85
  143. package/build/src/frontend-models/websocket-publishers.d.ts +9 -0
  144. package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
  145. package/build/src/frontend-models/websocket-publishers.js +190 -19
  146. package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
  147. package/build/src/http-server/client/websocket-session.js +11 -2
  148. package/build/src/http-server/websocket-channel.d.ts +20 -8
  149. package/build/src/http-server/websocket-channel.d.ts.map +1 -1
  150. package/build/src/http-server/websocket-channel.js +15 -3
  151. package/build/src/sync/assets/cache.d.ts +288 -0
  152. package/build/src/sync/assets/cache.d.ts.map +1 -0
  153. package/build/src/sync/assets/cache.js +883 -0
  154. package/build/src/sync/assets/types.d.ts +199 -0
  155. package/build/src/sync/assets/types.d.ts.map +1 -0
  156. package/build/src/sync/assets/types.js +56 -0
  157. package/build/src/sync/sync-api-client.d.ts.map +1 -1
  158. package/build/src/sync/sync-api-client.js +4 -3
  159. package/build/src/sync/sync-client.d.ts.map +1 -1
  160. package/build/src/sync/sync-client.js +3 -2
  161. package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
  162. package/build/src/sync/sync-envelope-replay-service.js +6 -5
  163. package/build/src/sync/sync-publisher.d.ts.map +1 -1
  164. package/build/src/sync/sync-publisher.js +4 -3
  165. package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts +2 -2
  166. package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts.map +1 -1
  167. package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +3 -3
  168. package/build/src/testing/test-profiler.d.ts +4 -4
  169. package/build/src/testing/test-profiler.d.ts.map +1 -1
  170. package/build/src/testing/test-profiler.js +3 -3
  171. package/build/src/testing/test-runner.d.ts +279 -117
  172. package/build/src/testing/test-runner.d.ts.map +1 -1
  173. package/build/src/testing/test-runner.js +503 -933
  174. package/build/src/testing/test.d.ts +11 -72
  175. package/build/src/testing/test.d.ts.map +1 -1
  176. package/build/src/testing/test.js +131 -284
  177. package/build/src/testing/velocious-attempt-executor.d.ts +54 -0
  178. package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -0
  179. package/build/src/testing/velocious-attempt-executor.js +408 -0
  180. package/build/src/testing/velocious-runner-reporter.d.ts +67 -0
  181. package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -0
  182. package/build/src/testing/velocious-runner-reporter.js +240 -0
  183. package/build/src/testing/velocious-suite-hook-executor.d.ts +19 -0
  184. package/build/src/testing/velocious-suite-hook-executor.d.ts.map +1 -0
  185. package/build/src/testing/velocious-suite-hook-executor.js +41 -0
  186. package/build/src/testing/velocious-test-arguments.d.ts +39 -0
  187. package/build/src/testing/velocious-test-arguments.d.ts.map +1 -0
  188. package/build/src/testing/velocious-test-arguments.js +55 -0
  189. package/build/src/utils/model-primary-key.d.ts +73 -0
  190. package/build/src/utils/model-primary-key.d.ts.map +1 -0
  191. package/build/src/utils/model-primary-key.js +144 -0
  192. package/build/src/utils/sha256-bytes-hex.d.ts +7 -0
  193. package/build/src/utils/sha256-bytes-hex.d.ts.map +1 -0
  194. package/build/src/utils/sha256-bytes-hex.js +117 -0
  195. package/build/src/utils/sha256-hex.d.ts +2 -4
  196. package/build/src/utils/sha256-hex.d.ts.map +1 -1
  197. package/build/src/utils/sha256-hex.js +6 -120
  198. package/build/sync/assets/cache.js +998 -0
  199. package/build/sync/assets/types.js +62 -0
  200. package/build/sync/sync-api-client.js +3 -2
  201. package/build/sync/sync-client.js +2 -1
  202. package/build/sync/sync-envelope-replay-service.js +5 -4
  203. package/build/sync/sync-publisher.js +3 -2
  204. package/build/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
  205. package/build/testing/test-profiler.js +2 -2
  206. package/build/testing/test-runner.js +496 -990
  207. package/build/testing/test.js +164 -306
  208. package/build/testing/velocious-attempt-executor.js +441 -0
  209. package/build/testing/velocious-runner-reporter.js +264 -0
  210. package/build/testing/velocious-suite-hook-executor.js +43 -0
  211. package/build/testing/velocious-test-arguments.js +64 -0
  212. package/build/tsconfig.tsbuildinfo +1 -1
  213. package/build/utils/model-primary-key.js +171 -0
  214. package/build/utils/sha256-bytes-hex.js +132 -0
  215. package/build/utils/sha256-hex.js +7 -135
  216. package/package.json +5 -4
  217. package/src/authorization/ability.js +36 -13
  218. package/src/background-jobs/adapter.js +9 -0
  219. package/src/background-jobs/main.js +35 -10
  220. package/src/background-jobs/store.js +217 -38
  221. package/src/background-jobs/types.js +15 -0
  222. package/src/configuration-types.js +1 -1
  223. package/src/configuration.js +45 -9
  224. package/src/database/drivers/base-column.js +8 -0
  225. package/src/database/drivers/mssql/sql/update.js +1 -3
  226. package/src/database/drivers/mysql/sql/update.js +1 -3
  227. package/src/database/drivers/pgsql/column.js +22 -0
  228. package/src/database/drivers/pgsql/sql/update.js +1 -3
  229. package/src/database/drivers/sqlite/sql/alter-table.js +11 -1
  230. package/src/database/drivers/sqlite/sql/update.js +1 -3
  231. package/src/database/query/index.js +2 -2
  232. package/src/database/query/model-class-query.js +26 -10
  233. package/src/database/query/preloader/has-many.js +4 -1
  234. package/src/database/query/query-data.js +2 -1
  235. package/src/database/query/update-base.js +14 -0
  236. package/src/database/query/with-count.js +3 -1
  237. package/src/database/record/acts-as-list.js +37 -13
  238. package/src/database/record/attachments/store.js +336 -26
  239. package/src/database/record/auditing.js +6 -3
  240. package/src/database/record/index.js +197 -42
  241. package/src/database/record/instance-relationships/belongs-to.js +2 -1
  242. package/src/database/record/validators/uniqueness.js +8 -5
  243. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
  244. package/src/frontend-model-controller.js +136 -53
  245. package/src/frontend-model-resource/base-resource.js +87 -37
  246. package/src/frontend-model-resource/velocious-attachment-resource.js +98 -31
  247. package/src/frontend-models/base.js +307 -59
  248. package/src/frontend-models/preloader.js +6 -3
  249. package/src/frontend-models/query.js +15 -6
  250. package/src/frontend-models/resource-definition.js +21 -2
  251. package/src/frontend-models/use-destroyed-event.js +1 -1
  252. package/src/frontend-models/use-model-class-event.js +2 -2
  253. package/src/frontend-models/use-updated-event.js +1 -1
  254. package/src/frontend-models/websocket-channel.js +256 -93
  255. package/src/frontend-models/websocket-publishers.js +218 -18
  256. package/src/http-server/client/websocket-session.js +13 -1
  257. package/src/http-server/websocket-channel.js +15 -2
  258. package/src/sync/assets/cache.js +998 -0
  259. package/src/sync/assets/types.js +62 -0
  260. package/src/sync/sync-api-client.js +3 -2
  261. package/src/sync/sync-client.js +2 -1
  262. package/src/sync/sync-envelope-replay-service.js +5 -4
  263. package/src/sync/sync-publisher.js +3 -2
  264. package/src/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
  265. package/src/testing/test-profiler.js +2 -2
  266. package/src/testing/test-runner.js +496 -990
  267. package/src/testing/test.js +164 -306
  268. package/src/testing/velocious-attempt-executor.js +441 -0
  269. package/src/testing/velocious-runner-reporter.js +264 -0
  270. package/src/testing/velocious-suite-hook-executor.js +43 -0
  271. package/src/testing/velocious-test-arguments.js +64 -0
  272. package/src/utils/model-primary-key.js +171 -0
  273. package/src/utils/sha256-bytes-hex.js +132 -0
  274. package/src/utils/sha256-hex.js +7 -135
  275. package/build/src/testing/testing-package-adapter.d.ts +0 -9
  276. package/build/src/testing/testing-package-adapter.d.ts.map +0 -1
  277. package/build/src/testing/testing-package-adapter.js +0 -69
  278. package/build/testing/testing-package-adapter.js +0 -76
  279. package/src/testing/testing-package-adapter.js +0 -76
@@ -0,0 +1,441 @@
1
+ // @ts-check
2
+
3
+ import { TestDatabaseAccessRevokedError } from "../environment-handlers/base.js"
4
+ import { clearDeliveries } from "../mailer.js"
5
+ import restArgsError from "../utils/rest-args-error.js"
6
+ import {clearTimeout as realClearTimeout, setTimeout as realSetTimeout} from "node:timers"
7
+
8
+ /** @typedef {import("@velocious/testing/runner").AttemptExecutorInput["beforeEach"][number]} PackageHookDeclaration */
9
+
10
+ /**
11
+ * Marks one whole-lifecycle timeout while its underlying promise keeps running.
12
+ * @typedef {Error & {velociousTestTimeout?: true}} TestTimeoutError
13
+ */
14
+
15
+ /**
16
+ * Runs one promise with a lifecycle timeout.
17
+ * @param {Promise<ReturnType<typeof JSON.parse>> | ReturnType<typeof JSON.parse>} promise - Promise or value.
18
+ * @param {number} timeoutMs - Timeout in milliseconds.
19
+ * @param {string} testDescription - Test description.
20
+ * @returns {Promise<ReturnType<typeof JSON.parse>>} - Lifecycle result.
21
+ */
22
+ function runWithTimeout(promise, timeoutMs, testDescription) {
23
+ const timeoutSeconds = (timeoutMs / 1000).toFixed(3).replace(/\.?0+$/, "")
24
+ /** @type {TestTimeoutError} */
25
+ const timeoutError = new Error(`Timed out after ${timeoutSeconds}s: ${testDescription}`)
26
+ timeoutError.velociousTestTimeout = true
27
+
28
+ return new Promise((resolve, reject) => {
29
+ const timeout = realSetTimeout(() => reject(timeoutError), timeoutMs)
30
+
31
+ Promise.resolve(promise).then((result) => {
32
+ realClearTimeout(timeout)
33
+ resolve(result)
34
+ }).catch((error) => {
35
+ realClearTimeout(timeout)
36
+ reject(error)
37
+ })
38
+ })
39
+ }
40
+
41
+ /**
42
+ * Waits for detached lifecycle cleanup up to the timeout grace period.
43
+ * @param {Promise<ReturnType<typeof JSON.parse>>} lifecycle - Detached lifecycle promise.
44
+ * @param {number} graceMs - Maximum wait.
45
+ * @returns {Promise<{settled: false} | {settled: true, status: "fulfilled"} | {settled: true, status: "rejected", reason: ReturnType<typeof JSON.parse>}>} - Settlement outcome.
46
+ */
47
+ function awaitSettledOrGrace(lifecycle, graceMs) {
48
+ return new Promise((resolve) => {
49
+ let settled = false
50
+ const graceTimer = realSetTimeout(() => {
51
+ if (settled) return
52
+
53
+ settled = true
54
+ resolve({settled: false})
55
+ }, graceMs)
56
+
57
+ Promise.resolve(lifecycle).then(
58
+ () => {
59
+ if (settled) return
60
+
61
+ settled = true
62
+ realClearTimeout(graceTimer)
63
+ resolve({settled: true, status: "fulfilled"})
64
+ },
65
+ (reason) => {
66
+ if (settled) return
67
+
68
+ settled = true
69
+ realClearTimeout(graceTimer)
70
+ resolve({settled: true, status: "rejected", reason})
71
+ }
72
+ )
73
+ })
74
+ }
75
+
76
+ /**
77
+ * Checks whether a late lifecycle stopped only because its attempt access was revoked.
78
+ * @param {ReturnType<typeof JSON.parse>} error - Lifecycle rejection.
79
+ * @returns {boolean} - Whether every contained error is expected revocation.
80
+ */
81
+ function isTestDatabaseAccessRevocation(error) {
82
+ if (error instanceof TestDatabaseAccessRevokedError) return true
83
+ if (error instanceof AggregateError) {
84
+ return error.errors.length > 0 && error.errors.every((nestedError) => isTestDatabaseAccessRevocation(nestedError))
85
+ }
86
+
87
+ return false
88
+ }
89
+
90
+ export default class VelociousAttemptExecutor {
91
+ /**
92
+ * Creates an executor for framework-owned attempt lifecycle work.
93
+ * @param {object} args - Constructor arguments.
94
+ * @param {import("./test-runner.js").default} args.testRunner - Owning Velocious runner.
95
+ */
96
+ constructor({testRunner, ...restArgs}) {
97
+ restArgsError(restArgs)
98
+ this.testRunner = testRunner
99
+ }
100
+
101
+ /**
102
+ * Normalizes the legacy timeout contract at the framework adapter boundary.
103
+ * @param {number | undefined} timeoutMs - Declared package timeout.
104
+ * @returns {number | undefined} - Positive finite timeout, or no timeout.
105
+ */
106
+ normalizeTimeoutMs(timeoutMs) {
107
+ return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined
108
+ }
109
+
110
+ /**
111
+ * Executes exactly one complete Velocious-owned test attempt.
112
+ * @param {import("@velocious/testing/runner").AttemptExecutorInput} input - Package attempt.
113
+ * @returns {Promise<void>} - Resolves after one complete framework attempt.
114
+ */
115
+ async execute({afterEach, args, attemptNumber, beforeEach, context, defaultExecute, fullName, suite, test, timeoutMs, ...restArgs}) {
116
+ restArgsError(restArgs)
117
+ void context
118
+ void defaultExecute
119
+ void suite
120
+ const testRunner = this.testRunner
121
+ const effectiveTimeoutMs = this.normalizeTimeoutMs(timeoutMs)
122
+ const compatibility = await testRunner.testCompatibility(test)
123
+ const {testArgs, testData} = compatibility
124
+ const metadata = testRunner.testMetadata(test)
125
+ const {descriptions, testDescription} = metadata
126
+ /** @type {ReturnType<typeof JSON.parse>} */
127
+ let caughtError
128
+ let failed = false
129
+ /** @type {Promise<ReturnType<typeof JSON.parse>> | undefined} */
130
+ let testLifecycle
131
+ /** @type {{pool: import("../database/pool/base.js").default, registration: import("../database/pool/base.js").TestSharedConnectionRegistration}[]} */
132
+ let testSharedConnectionRegistrations = []
133
+ let testSharedConnectionsActive = false
134
+ /** @type {import("./test-runner.js").SharedTransactionBrokerRegistration | undefined} */
135
+ let sharedTransactionBrokerRegistration
136
+ /** @type {import("./test-runner.js").SharedTransactionBrokerRegistration | undefined} */
137
+ let sharedTransactionBrokerPreparation
138
+ /** @type {import("./test-runner.js").TransactionalTenantRegistration[]} */
139
+ const transactionalTenantRegistrations = []
140
+ /** @type {import("./test-runner.js").BrowserDummyConnectionRegistration[]} */
141
+ const browserDummyConnectionRegistrations = []
142
+ const testDatabaseAccessScope = {revoked: false}
143
+ /** @type {Set<Error>} */
144
+ const recordedTimeoutCleanupErrors = new Set()
145
+ let abortRemainingTests = false
146
+ let attemptTimedOut = false
147
+ testArgs.registerTransactionalTenant = async (args) => {
148
+ await testRunner.registerTransactionalTenant(args, transactionalTenantRegistrations)
149
+ }
150
+ const profiler = testRunner._profiler
151
+ const profileAttempt = profiler?.startAttempt({
152
+ descriptions,
153
+ attemptNumber,
154
+ testData,
155
+ testDescription
156
+ })
157
+
158
+ try {
159
+ const runLifecycleCallback = async () => await testRunner.runWithDummyIfNeeded(testArgs, async () => {
160
+ const useTransaction = testArgs.databaseCleaning?.transaction === true
161
+ const shouldTruncate = testArgs.databaseCleaning?.truncate ?? !useTransaction
162
+ const useSharedTestConnections = useTransaction || testArgs.type == "request"
163
+ const useTestConnections = useSharedTestConnections || shouldTruncate
164
+ const runTestAttempt = async () => {
165
+ if (useSharedTestConnections) {
166
+ testSharedConnectionRegistrations = testRunner.activateTestSharedConnections()
167
+ testSharedConnectionsActive = true
168
+ }
169
+ /** @type {ReturnType<typeof JSON.parse>[]} */
170
+ const lifecycleErrors = []
171
+ let runCleanupHooks = false
172
+
173
+ try {
174
+ if (useSharedTestConnections) {
175
+ sharedTransactionBrokerPreparation = await testRunner.prepareSharedTransactionBroker()
176
+ }
177
+ runCleanupHooks = true
178
+
179
+ clearDeliveries()
180
+ await this.runBeforeEaches({beforeEaches: beforeEach, testArgs, testData})
181
+
182
+ if (useSharedTestConnections) {
183
+ const activeConnections = testRunner.sharedTransactionConnections({transactionsOnly: true})
184
+ if (sharedTransactionBrokerPreparation && !testRunner.sharedTransactionBrokerMatchesConnections(sharedTransactionBrokerPreparation, activeConnections)) {
185
+ testRunner.clearTestSharedConnections(testSharedConnectionRegistrations)
186
+ testSharedConnectionRegistrations = []
187
+ testSharedConnectionsActive = false
188
+ }
189
+
190
+ sharedTransactionBrokerRegistration = await testRunner.startSharedTransactionBroker(sharedTransactionBrokerPreparation, activeConnections)
191
+ sharedTransactionBrokerPreparation = undefined
192
+ if (sharedTransactionBrokerRegistration && !testSharedConnectionsActive) {
193
+ testSharedConnectionRegistrations = testRunner.activateTestSharedConnections()
194
+ testSharedConnectionsActive = true
195
+ }
196
+ }
197
+
198
+ testRunner._lastTestContext = {
199
+ fullDescription: fullName,
200
+ filePath: testData.filePath ?? "<unknown>",
201
+ line: testData.line ?? 0
202
+ }
203
+ await testRunner.runProfileSpan({phase: "test body", filePath: testData.ownerFilePath ?? testData.filePath}, async () => {
204
+ await test.callback(...args)
205
+ })
206
+ } catch (error) {
207
+ lifecycleErrors.push(error)
208
+ }
209
+
210
+ if (runCleanupHooks) {
211
+ try {
212
+ await testRunner.getConfiguration().awaitPendingBroadcasts()
213
+ } catch (error) {
214
+ lifecycleErrors.push(error)
215
+ }
216
+
217
+ try {
218
+ if (testSharedConnectionsActive) {
219
+ testRunner.clearTestSharedConnections(testSharedConnectionRegistrations)
220
+ testSharedConnectionRegistrations = []
221
+ testSharedConnectionsActive = false
222
+ }
223
+ } catch (error) {
224
+ lifecycleErrors.push(error)
225
+ }
226
+
227
+ try {
228
+ await testRunner.stopSharedTransactionBroker(sharedTransactionBrokerRegistration || sharedTransactionBrokerPreparation)
229
+ sharedTransactionBrokerRegistration = undefined
230
+ sharedTransactionBrokerPreparation = undefined
231
+ } catch (error) {
232
+ lifecycleErrors.push(error)
233
+ }
234
+
235
+ try {
236
+ await this.runAfterEaches({afterEaches: [...afterEach].reverse(), testArgs, testData})
237
+ } catch (error) {
238
+ lifecycleErrors.push(error)
239
+ }
240
+
241
+ try {
242
+ await testRunner.cleanupTransactionalTenants(transactionalTenantRegistrations)
243
+ } catch (error) {
244
+ lifecycleErrors.push(error)
245
+ }
246
+ }
247
+
248
+ if (testSharedConnectionsActive) {
249
+ try {
250
+ testRunner.clearTestSharedConnections(testSharedConnectionRegistrations)
251
+ } catch (error) {
252
+ lifecycleErrors.push(error)
253
+ }
254
+ testSharedConnectionsActive = false
255
+ }
256
+
257
+ if (lifecycleErrors.length == 1) throw lifecycleErrors[0]
258
+ if (lifecycleErrors.length > 1) {
259
+ throw new AggregateError(lifecycleErrors, "Test lifecycle and cleanup failed", {cause: lifecycleErrors[0]})
260
+ }
261
+ }
262
+
263
+ if (useTestConnections) {
264
+ await testRunner.getConfiguration().ensureConnections({name: `Test: ${testDescription}`}, runTestAttempt)
265
+ } else {
266
+ await runTestAttempt()
267
+ }
268
+ }, browserDummyConnectionRegistrations)
269
+ const lifecycleCallback = async () => await testRunner.getConfiguration().runWithTestDatabaseAccessScope(testDatabaseAccessScope, runLifecycleCallback)
270
+ testLifecycle = profileAttempt && profiler
271
+ ? profiler.runAttempt(profileAttempt, lifecycleCallback)
272
+ : lifecycleCallback()
273
+
274
+ if (effectiveTimeoutMs !== undefined) {
275
+ await runWithTimeout(testLifecycle, effectiveTimeoutMs, testDescription)
276
+ } else {
277
+ await testLifecycle
278
+ }
279
+ } catch (error) {
280
+ failed = true
281
+ caughtError = error
282
+ const timedOut = Boolean(/** @type {TestTimeoutError} */ (error)?.velociousTestTimeout)
283
+ attemptTimedOut = timedOut
284
+
285
+ if (timedOut && testLifecycle) {
286
+ const emergencyCleanupErrors = []
287
+
288
+ if (profileAttempt && profiler) profiler.finishAttempt(profileAttempt, "timed-out")
289
+ const lifecycleOutcome = await awaitSettledOrGrace(testLifecycle, effectiveTimeoutMs ?? 60000)
290
+
291
+ if (lifecycleOutcome.settled && lifecycleOutcome.status === "rejected") {
292
+ emergencyCleanupErrors.push(lifecycleOutcome.reason)
293
+ }
294
+
295
+ if (!lifecycleOutcome.settled) {
296
+ testDatabaseAccessScope.revoked = true
297
+ void testLifecycle.catch((cleanupError) => {
298
+ if (isTestDatabaseAccessRevocation(cleanupError)) return
299
+ testRunner.recordTimeoutCleanupFailure(cleanupError, "test lifecycle", recordedTimeoutCleanupErrors)
300
+ })
301
+ const quarantine = testRunner.quarantineBrowserDummyConnections(browserDummyConnectionRegistrations)
302
+ const quarantineOutcome = await awaitSettledOrGrace(quarantine, effectiveTimeoutMs ?? 60000)
303
+ const usesBrowserTransactions = testArgs.databaseCleaning?.transaction === true
304
+ const usesBrowserTruncation = testArgs.databaseCleaning?.truncate ?? !usesBrowserTransactions
305
+
306
+ abortRemainingTests = testRunner.isBrowserTestMode()
307
+ && testRunner.hasTag(testArgs, "dummy")
308
+ && (usesBrowserTransactions || usesBrowserTruncation)
309
+
310
+ if (quarantineOutcome.settled && quarantineOutcome.status === "rejected") {
311
+ emergencyCleanupErrors.push(quarantineOutcome.reason)
312
+ } else if (!quarantineOutcome.settled) {
313
+ void quarantine.catch((cleanupError) => {
314
+ testRunner.recordTimeoutCleanupFailure(cleanupError, "browser dummy connection quarantine", recordedTimeoutCleanupErrors)
315
+ })
316
+ }
317
+ }
318
+
319
+ try {
320
+ if (testSharedConnectionsActive) {
321
+ testRunner.clearTestSharedConnections(testSharedConnectionRegistrations)
322
+ testSharedConnectionRegistrations = []
323
+ testSharedConnectionsActive = false
324
+ }
325
+ } catch (cleanupError) {
326
+ emergencyCleanupErrors.push(cleanupError)
327
+ }
328
+
329
+ const brokerCleanup = testRunner.stopSharedTransactionBroker(sharedTransactionBrokerRegistration || sharedTransactionBrokerPreparation)
330
+ const brokerCleanupOutcome = await awaitSettledOrGrace(brokerCleanup, effectiveTimeoutMs ?? 60000)
331
+
332
+ if (brokerCleanupOutcome.settled && brokerCleanupOutcome.status === "rejected") {
333
+ emergencyCleanupErrors.push(brokerCleanupOutcome.reason)
334
+ } else if (!brokerCleanupOutcome.settled) {
335
+ void brokerCleanup.catch((cleanupError) => {
336
+ testRunner.recordTimeoutCleanupFailure(cleanupError, "shared transaction broker", recordedTimeoutCleanupErrors)
337
+ })
338
+ }
339
+ sharedTransactionBrokerRegistration = undefined
340
+ sharedTransactionBrokerPreparation = undefined
341
+ const emergencyCleanup = testRunner.cleanupTransactionalTenants(transactionalTenantRegistrations, {discard: true})
342
+ const emergencyCleanupOutcome = await awaitSettledOrGrace(emergencyCleanup, effectiveTimeoutMs ?? 60000)
343
+
344
+ if (emergencyCleanupOutcome.settled && emergencyCleanupOutcome.status === "rejected") {
345
+ emergencyCleanupErrors.push(emergencyCleanupOutcome.reason)
346
+ } else if (!emergencyCleanupOutcome.settled) {
347
+ void emergencyCleanup.catch((cleanupError) => {
348
+ testRunner.recordTimeoutCleanupFailure(cleanupError, "transactional tenant", recordedTimeoutCleanupErrors)
349
+ })
350
+ }
351
+
352
+ if (emergencyCleanupErrors.length > 0) {
353
+ caughtError = new AggregateError(
354
+ [caughtError, ...emergencyCleanupErrors],
355
+ "Test timeout and emergency cleanup failed",
356
+ {cause: caughtError}
357
+ )
358
+ }
359
+ }
360
+
361
+ if (browserDummyConnectionRegistrations.some((registration) => registration.quarantined)) {
362
+ testDatabaseAccessScope.revoked = true
363
+ abortRemainingTests = true
364
+ }
365
+ } finally {
366
+ testDatabaseAccessScope.revoked = true
367
+
368
+ if (profileAttempt && profiler) {
369
+ profiler.finishAttempt(profileAttempt, failed
370
+ ? (attemptTimedOut ? "timed-out" : "failed")
371
+ : "passed")
372
+ }
373
+ }
374
+
375
+ testRunner.recordAttemptOutcome(test, attemptNumber, {
376
+ abortRemainingTests,
377
+ error: caughtError,
378
+ failed
379
+ })
380
+
381
+ if (failed) throw caughtError
382
+ }
383
+
384
+ /**
385
+ * Runs before-each hooks in inherited declaration order.
386
+ * @param {object} args - Hook arguments.
387
+ * @param {PackageHookDeclaration[]} args.beforeEaches - Setup hooks.
388
+ * @param {import("./velocious-test-arguments.js").TestArgs} args.testArgs - Stable test arguments.
389
+ * @param {import("./velocious-test-arguments.js").TestData} args.testData - Test registration.
390
+ * @returns {Promise<void>} - Resolves after all setup hooks complete.
391
+ */
392
+ async runBeforeEaches({beforeEaches, testArgs, testData}) {
393
+ for (const hook of beforeEaches) {
394
+ const metadata = this.testRunner.hookMetadata(hook)
395
+
396
+ await this.testRunner.runProfileSpan({
397
+ phase: "beforeEach",
398
+ declarationIndex: metadata.declarationIndex,
399
+ declarationScopeId: metadata.declarationScopeId,
400
+ filePath: metadata.ownerFilePath
401
+ }, async () => {
402
+ await hook.callback({configuration: this.testRunner.getConfiguration(), testArgs, testData})
403
+ })
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Runs every after-each hook while preserving all failures.
409
+ * @param {object} args - Hook arguments.
410
+ * @param {PackageHookDeclaration[]} args.afterEaches - Cleanup hooks.
411
+ * @param {import("./velocious-test-arguments.js").TestArgs} args.testArgs - Stable test arguments.
412
+ * @param {import("./velocious-test-arguments.js").TestData} args.testData - Test registration.
413
+ * @returns {Promise<void>} - Resolves after every cleanup hook settles.
414
+ */
415
+ async runAfterEaches({afterEaches, testArgs, testData}) {
416
+ /** @type {ReturnType<typeof JSON.parse>[]} */
417
+ const errors = []
418
+
419
+ for (const hook of afterEaches) {
420
+ const metadata = this.testRunner.hookMetadata(hook)
421
+
422
+ try {
423
+ await this.testRunner.runProfileSpan({
424
+ phase: "afterEach",
425
+ declarationIndex: metadata.declarationIndex,
426
+ declarationScopeId: metadata.declarationScopeId,
427
+ filePath: metadata.ownerFilePath
428
+ }, async () => {
429
+ await hook.callback({configuration: this.testRunner.getConfiguration(), testArgs, testData})
430
+ })
431
+ } catch (error) {
432
+ errors.push(error)
433
+ }
434
+ }
435
+
436
+ if (errors.length == 1) throw errors[0]
437
+ if (errors.length > 1) {
438
+ throw new AggregateError(errors, "Multiple afterEach hooks failed", {cause: errors[0]})
439
+ }
440
+ }
441
+ }