velocious 1.0.698 → 1.0.700
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.
- package/README.md +2 -2
- package/build/configuration-types.js +23 -0
- package/build/configuration.js +136 -1
- package/build/database/advisory-lock-backend-policy.js +204 -0
- package/build/database/advisory-lock-file-backend.js +348 -0
- package/build/database/advisory-lock-runner.js +239 -16
- package/build/database/drivers/base.js +318 -15
- package/build/database/drivers/mssql/index.js +1 -1
- package/build/database/drivers/mysql/index.js +4 -2
- package/build/database/drivers/pgsql/index.js +4 -2
- package/build/database/drivers/sqlite/base.js +13 -129
- package/build/database/drivers/sqlite/index.js +41 -62
- package/build/database/migrator.js +120 -50
- package/build/database/record/attachments/store.js +84 -1
- package/build/environment-handlers/base.js +13 -0
- package/build/environment-handlers/node.js +11 -0
- package/build/http-server/server-lock.js +21 -115
- package/build/src/configuration-types.d.ts +68 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +21 -1
- package/build/src/configuration.d.ts +100 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +124 -2
- package/build/src/database/advisory-lock-backend-policy.d.ts +145 -0
- package/build/src/database/advisory-lock-backend-policy.d.ts.map +1 -0
- package/build/src/database/advisory-lock-backend-policy.js +184 -0
- package/build/src/database/advisory-lock-file-backend.d.ts +194 -0
- package/build/src/database/advisory-lock-file-backend.d.ts.map +1 -0
- package/build/src/database/advisory-lock-file-backend.js +313 -0
- package/build/src/database/advisory-lock-runner.d.ts +116 -15
- package/build/src/database/advisory-lock-runner.d.ts.map +1 -1
- package/build/src/database/advisory-lock-runner.js +190 -17
- package/build/src/database/drivers/base.d.ts +125 -4
- package/build/src/database/drivers/base.d.ts.map +1 -1
- package/build/src/database/drivers/base.js +282 -17
- package/build/src/database/drivers/mssql/index.d.ts +1 -1
- package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/index.js +2 -2
- package/build/src/database/drivers/mysql/index.d.ts +4 -2
- package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mysql/index.js +5 -3
- package/build/src/database/drivers/pgsql/index.d.ts +4 -2
- package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
- package/build/src/database/drivers/pgsql/index.js +5 -3
- package/build/src/database/drivers/sqlite/base.d.ts +11 -49
- package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/base.js +14 -114
- package/build/src/database/drivers/sqlite/index.d.ts +28 -19
- package/build/src/database/drivers/sqlite/index.d.ts.map +1 -1
- package/build/src/database/drivers/sqlite/index.js +41 -59
- package/build/src/database/migrator.d.ts.map +1 -1
- package/build/src/database/migrator.js +110 -56
- package/build/src/database/record/attachments/store.d.ts +0 -3
- package/build/src/database/record/attachments/store.d.ts.map +1 -1
- package/build/src/database/record/attachments/store.js +71 -2
- package/build/src/environment-handlers/base.d.ts +14 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +13 -1
- package/build/src/environment-handlers/node.d.ts +11 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +11 -1
- package/build/src/http-server/server-lock.d.ts +13 -37
- package/build/src/http-server/server-lock.d.ts.map +1 -1
- package/build/src/http-server/server-lock.js +22 -109
- package/build/src/testing/test-runner.d.ts +74 -0
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +140 -1
- package/build/src/testing/test-tenant-database-pool.d.ts +183 -0
- package/build/src/testing/test-tenant-database-pool.d.ts.map +1 -0
- package/build/src/testing/test-tenant-database-pool.js +388 -0
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -1
- package/build/src/testing/velocious-attempt-executor.js +43 -1
- package/build/testing/test-runner.js +152 -0
- package/build/testing/test-tenant-database-pool.js +438 -0
- package/build/testing/velocious-attempt-executor.js +44 -0
- package/package.json +1 -1
- package/src/configuration-types.js +23 -0
- package/src/configuration.js +136 -1
- package/src/database/advisory-lock-backend-policy.js +204 -0
- package/src/database/advisory-lock-file-backend.js +348 -0
- package/src/database/advisory-lock-runner.js +239 -16
- package/src/database/drivers/base.js +318 -15
- package/src/database/drivers/mssql/index.js +1 -1
- package/src/database/drivers/mysql/index.js +4 -2
- package/src/database/drivers/pgsql/index.js +4 -2
- package/src/database/drivers/sqlite/base.js +13 -129
- package/src/database/drivers/sqlite/index.js +41 -62
- package/src/database/migrator.js +120 -50
- package/src/database/record/attachments/store.js +84 -1
- package/src/environment-handlers/base.js +13 -0
- package/src/environment-handlers/node.js +11 -0
- package/src/http-server/server-lock.js +21 -115
- package/src/testing/test-runner.js +152 -0
- package/src/testing/test-tenant-database-pool.js +438 -0
- package/src/testing/velocious-attempt-executor.js +44 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import os from "node:os"
|
|
3
|
+
import AdvisoryLockFileBackend from "../database/advisory-lock-file-backend.js"
|
|
5
4
|
import path from "node:path"
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
|
-
* Directory-based lock that allows only one HTTP server process per app
|
|
7
|
+
* Directory-based lock that allows only one HTTP server process per app
|
|
8
|
+
* directory. Thin wrapper over the shared file-lock backend
|
|
9
|
+
* (`src/database/advisory-lock-file-backend.js` — the STC-210 `file` tier
|
|
10
|
+
* primitive generalized from this lock's original hand-rolled directory
|
|
11
|
+
* protocol), so the server lock and the advisory-lock file tier share one
|
|
12
|
+
* acquisition, owner-metadata, lease-renewal, and staleness implementation.
|
|
9
13
|
*/
|
|
10
14
|
export default class VelociousHttpServerLock {
|
|
11
15
|
/**
|
|
@@ -20,53 +24,26 @@ export default class VelociousHttpServerLock {
|
|
|
20
24
|
this.host = host
|
|
21
25
|
this.port = port
|
|
22
26
|
this.lockPath = path.join(configuration.getDirectory(), "tmp", "server.lock")
|
|
27
|
+
this.backend = new AdvisoryLockFileBackend({
|
|
28
|
+
directory: path.join(configuration.getDirectory(), "tmp"),
|
|
29
|
+
extraOwnerFields: {host: this.host, port: this.port},
|
|
30
|
+
pathStyle: "plain"
|
|
31
|
+
})
|
|
23
32
|
this.acquired = false
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
/**
|
|
27
|
-
* Acquires the app-directory HTTP server lock before startup side effects
|
|
36
|
+
* Acquires the app-directory HTTP server lock before startup side effects
|
|
37
|
+
* run. A stale lock (dead local owner or expired lease) is reclaimed by the
|
|
38
|
+
* backend; a live foreign owner fails loudly with the owner details.
|
|
28
39
|
* @returns {Promise<void>} - Resolves after the lock has been acquired.
|
|
29
40
|
*/
|
|
30
41
|
async acquire() {
|
|
31
|
-
await
|
|
42
|
+
const acquired = await this.backend.acquire("server", {timeoutMs: 0})
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
const acquired = await this.tryAcquire()
|
|
44
|
+
if (!acquired) throw new Error(await this.lockHeldMessage())
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (await this.isStale()) {
|
|
41
|
-
await fs.rm(this.lockPath, {recursive: true, force: true})
|
|
42
|
-
continue
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
throw new Error(await this.lockHeldMessage())
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Tries to create the lock directory and write owner metadata.
|
|
51
|
-
* @returns {Promise<boolean>} - Whether the lock was acquired.
|
|
52
|
-
*/
|
|
53
|
-
async tryAcquire() {
|
|
54
|
-
try {
|
|
55
|
-
await fs.mkdir(this.lockPath)
|
|
56
|
-
} catch (error) {
|
|
57
|
-
if (/** @type {{code?: string}} */ (error).code === "EEXIST") return false
|
|
58
|
-
throw error
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
await this.writeOwnerMetadata()
|
|
63
|
-
this.acquired = true
|
|
64
|
-
|
|
65
|
-
return true
|
|
66
|
-
} catch (error) {
|
|
67
|
-
await fs.rm(this.lockPath, {recursive: true, force: true})
|
|
68
|
-
throw error
|
|
69
|
-
}
|
|
46
|
+
this.acquired = true
|
|
70
47
|
}
|
|
71
48
|
|
|
72
49
|
/**
|
|
@@ -77,86 +54,15 @@ export default class VelociousHttpServerLock {
|
|
|
77
54
|
if (!this.acquired) return
|
|
78
55
|
|
|
79
56
|
this.acquired = false
|
|
80
|
-
await
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Writes metadata used to explain or reclaim an existing server lock.
|
|
85
|
-
* @returns {Promise<void>} - Resolves after owner metadata has been written.
|
|
86
|
-
*/
|
|
87
|
-
async writeOwnerMetadata() {
|
|
88
|
-
await fs.writeFile(path.join(this.lockPath, "owner.json"), JSON.stringify({
|
|
89
|
-
acquiredAt: new Date().toISOString(),
|
|
90
|
-
host: this.host,
|
|
91
|
-
hostname: os.hostname(),
|
|
92
|
-
pid: process.pid,
|
|
93
|
-
port: this.port
|
|
94
|
-
}))
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Checks whether the current lock owner is a dead process on this host.
|
|
99
|
-
* @returns {Promise<boolean>} - Whether the existing lock belongs to a dead process.
|
|
100
|
-
*/
|
|
101
|
-
async isStale() {
|
|
102
|
-
const owner = await this.readOwnerMetadata()
|
|
103
|
-
|
|
104
|
-
if (!this.isLocalProcessOwner(owner)) return false
|
|
105
|
-
|
|
106
|
-
return this.processIsDead(/** @type {{pid: number}} */ (owner).pid)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Runs is local process owner.
|
|
111
|
-
* @param {Record<string, ReturnType<typeof JSON.parse>> | null} owner - Existing lock owner metadata.
|
|
112
|
-
* @returns {boolean} - Whether owner metadata names a local process.
|
|
113
|
-
*/
|
|
114
|
-
isLocalProcessOwner(owner) {
|
|
115
|
-
if (!owner) return false
|
|
116
|
-
if (typeof owner.pid !== "number") return false
|
|
117
|
-
|
|
118
|
-
return this.ownerHostnameMatches(owner)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Runs owner hostname matches.
|
|
123
|
-
* @param {Record<string, ReturnType<typeof JSON.parse>>} owner - Existing lock owner metadata.
|
|
124
|
-
* @returns {boolean} - Whether the owner hostname is local or absent.
|
|
125
|
-
*/
|
|
126
|
-
ownerHostnameMatches(owner) {
|
|
127
|
-
if (!owner.hostname) return true
|
|
128
|
-
if (owner.hostname === os.hostname()) return true
|
|
129
|
-
|
|
130
|
-
return false
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Runs process is dead.
|
|
135
|
-
* @param {number} pid - Process id.
|
|
136
|
-
* @returns {boolean} - Whether the process no longer exists.
|
|
137
|
-
*/
|
|
138
|
-
processIsDead(pid) {
|
|
139
|
-
try {
|
|
140
|
-
process.kill(pid, 0)
|
|
141
|
-
|
|
142
|
-
return false
|
|
143
|
-
} catch (error) {
|
|
144
|
-
return /** @type {{code?: string}} */ (error).code === "ESRCH"
|
|
145
|
-
}
|
|
57
|
+
await this.backend.release("server")
|
|
146
58
|
}
|
|
147
59
|
|
|
148
60
|
/**
|
|
149
61
|
* Reads owner metadata from an existing server lock directory.
|
|
150
|
-
* @returns {Promise<
|
|
62
|
+
* @returns {Promise<import("../database/advisory-lock-file-backend.js").AdvisoryLockFileOwner | null>} - Parsed owner metadata, when readable.
|
|
151
63
|
*/
|
|
152
64
|
async readOwnerMetadata() {
|
|
153
|
-
|
|
154
|
-
const rawOwner = await fs.readFile(path.join(this.lockPath, "owner.json"), "utf8")
|
|
155
|
-
|
|
156
|
-
return /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (JSON.parse(rawOwner))
|
|
157
|
-
} catch {
|
|
158
|
-
return null
|
|
159
|
-
}
|
|
65
|
+
return await this.backend.holderInfo("server")
|
|
160
66
|
}
|
|
161
67
|
|
|
162
68
|
/**
|
|
@@ -8,6 +8,7 @@ import { slowestTestResults } from "@velocious/testing/reporters"
|
|
|
8
8
|
import { TestRunner as PackageTestRunner } from "@velocious/testing/runner"
|
|
9
9
|
import Application from "../../src/application.js"
|
|
10
10
|
import RequestClient from "./request-client.js"
|
|
11
|
+
import Tenant from "../tenants/tenant.js"
|
|
11
12
|
import picocolors from "picocolors"
|
|
12
13
|
import restArgsError from "../utils/rest-args-error.js"
|
|
13
14
|
import { testConfig } from "./test.js"
|
|
@@ -17,6 +18,7 @@ import { SHARED_TRANSACTION_BROKER_ENV } from "./shared-transaction-proxy-driver
|
|
|
17
18
|
import VelociousAttemptExecutor from "./velocious-attempt-executor.js"
|
|
18
19
|
import VelociousRunnerReporter, { AbortRemainingTestsError } from "./velocious-runner-reporter.js"
|
|
19
20
|
import VelociousSuiteHookExecutor from "./velocious-suite-hook-executor.js"
|
|
21
|
+
import TestTenantDatabasePool from "./test-tenant-database-pool.js"
|
|
20
22
|
import VelociousTestArguments from "./velocious-test-arguments.js"
|
|
21
23
|
|
|
22
24
|
/** @typedef {typeof defaultTestContext} PackageTestContext */
|
|
@@ -48,6 +50,18 @@ import VelociousTestArguments from "./velocious-test-arguments.js"
|
|
|
48
50
|
* @property {number} [timeoutSeconds] - Timeout in seconds for the test.
|
|
49
51
|
* @property {string} [type] - Test type identifier.
|
|
50
52
|
* @property {(args: {databaseIdentifier: string, tenant: object}) => Promise<void>} [registerTransactionalTenant] - Registers one resolved tenant database transaction for this attempt.
|
|
53
|
+
* @property {(args?: {databaseIdentifier?: string, tenant?: object}) => Promise<TestTenantPoolLease>} [acquireTestTenant] - Leases one member of the configured test tenant database pool for this attempt (opt-in `testTenantPools` configuration).
|
|
54
|
+
* @property {(args: {databaseIdentifier: string, tenant: object}) => void} [registerTenantForCleanup] - Registers one active tenant database for the framework-owned end-of-attempt cleanup (drop + provider hooks) without hand-rolled afterEach bookkeeping.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* TestTenantPoolLease type (re-exported from the pool for TestArgs consumers).
|
|
58
|
+
* @typedef {import("./test-tenant-database-pool.js").TestTenantPoolLease} TestTenantPoolLease
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* TenantCleanupRegistration type.
|
|
62
|
+
* @typedef {object} TenantCleanupRegistration
|
|
63
|
+
* @property {string} databaseIdentifier - Tenant database identifier to drop at attempt end.
|
|
64
|
+
* @property {ReturnType<typeof JSON.parse>} tenant - Tenant descriptor whose database must be dropped.
|
|
51
65
|
*/
|
|
52
66
|
/**
|
|
53
67
|
* BrowserDummyConnectionRegistration type.
|
|
@@ -201,6 +215,10 @@ export default class TestRunner {
|
|
|
201
215
|
this._setupFiles = setupFiles || []
|
|
202
216
|
this._timeoutMs = timeoutMs
|
|
203
217
|
this._abortRemainingTests = false
|
|
218
|
+
/** @type {Map<string, TestTenantDatabasePool>} */
|
|
219
|
+
this._testTenantPools = new Map()
|
|
220
|
+
/** @type {Promise<void> | undefined} */
|
|
221
|
+
this._testTenantPoolTeardownPromise = undefined
|
|
204
222
|
|
|
205
223
|
this._failedTests = 0
|
|
206
224
|
this._successfulTests = 0
|
|
@@ -632,6 +650,81 @@ export default class TestRunner {
|
|
|
632
650
|
return this._application
|
|
633
651
|
}
|
|
634
652
|
|
|
653
|
+
/**
|
|
654
|
+
* Resolves the pool identifier when exactly one `testTenantPools` entry is
|
|
655
|
+
* configured, so `acquireTestTenant()` can omit it. Ambiguous or missing
|
|
656
|
+
* configuration fails loudly instead of guessing.
|
|
657
|
+
* @returns {string} - The single configured pool identifier.
|
|
658
|
+
*/
|
|
659
|
+
singleTestTenantPoolIdentifier() {
|
|
660
|
+
const identifiers = Object.keys(this.getConfiguration().getTestTenantPools() || {})
|
|
661
|
+
|
|
662
|
+
if (identifiers.length === 0) {
|
|
663
|
+
throw new Error("acquireTestTenant requires a databaseIdentifier when no testTenantPools configuration is present")
|
|
664
|
+
}
|
|
665
|
+
if (identifiers.length > 1) {
|
|
666
|
+
throw new Error(`acquireTestTenant requires a databaseIdentifier when multiple testTenantPools are configured: ${identifiers.join(", ")}`)
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return identifiers[0]
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Returns the test tenant database pool for one configured `testTenantPools`
|
|
674
|
+
* identifier, creating it on first use. Throws when the identifier has no pool
|
|
675
|
+
* configuration — the pool stays strictly opt-in.
|
|
676
|
+
* @param {string} databaseIdentifier - Tenant database identifier.
|
|
677
|
+
* @returns {TestTenantDatabasePool} - The runner-owned pool.
|
|
678
|
+
*/
|
|
679
|
+
getTestTenantPool(databaseIdentifier) {
|
|
680
|
+
const existingPool = this._testTenantPools.get(databaseIdentifier)
|
|
681
|
+
|
|
682
|
+
if (existingPool) return existingPool
|
|
683
|
+
|
|
684
|
+
const poolConfiguration = this.getConfiguration().getTestTenantPools()?.[databaseIdentifier]
|
|
685
|
+
|
|
686
|
+
if (!poolConfiguration) {
|
|
687
|
+
throw new Error(`No testTenantPools configuration for database identifier: ${databaseIdentifier}`)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const pool = new TestTenantDatabasePool({
|
|
691
|
+
configuration: this.getConfiguration(),
|
|
692
|
+
databaseIdentifier,
|
|
693
|
+
poolConfiguration
|
|
694
|
+
})
|
|
695
|
+
this._testTenantPools.set(databaseIdentifier, pool)
|
|
696
|
+
|
|
697
|
+
return pool
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Tears down every test tenant database pool created during this run, exactly once.
|
|
702
|
+
* Safe to call from afterAll-style teardown and from the runner's own cleanup; the
|
|
703
|
+
* same promise is shared by concurrent callers.
|
|
704
|
+
* @returns {Promise<void>} - Resolves after all pools dropped their databases.
|
|
705
|
+
*/
|
|
706
|
+
teardownTestTenantPools() {
|
|
707
|
+
this._testTenantPoolTeardownPromise ??= (async () => {
|
|
708
|
+
const pools = [...this._testTenantPools.values()]
|
|
709
|
+
this._testTenantPools.clear()
|
|
710
|
+
|
|
711
|
+
const errors = []
|
|
712
|
+
|
|
713
|
+
for (const pool of pools) {
|
|
714
|
+
try {
|
|
715
|
+
await pool.teardown()
|
|
716
|
+
} catch (error) {
|
|
717
|
+
errors.push(error)
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (errors.length === 1) throw errors[0]
|
|
722
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to tear down test tenant database pools")
|
|
723
|
+
})()
|
|
724
|
+
|
|
725
|
+
return this._testTenantPoolTeardownPromise
|
|
726
|
+
}
|
|
727
|
+
|
|
635
728
|
/**
|
|
636
729
|
* Registers each non-tenant per-test connection as a dynamic candidate for in-process
|
|
637
730
|
* request sharing. The pool evaluates transaction state when each request is dispatched,
|
|
@@ -822,6 +915,52 @@ export default class TestRunner {
|
|
|
822
915
|
if (errors.length > 1) throw new AggregateError(errors, "Failed to clean up a transactional tenant test connection")
|
|
823
916
|
}
|
|
824
917
|
|
|
918
|
+
/**
|
|
919
|
+
* Registers one tenant database for framework-owned end-of-attempt cleanup. The
|
|
920
|
+
* registration drops the tenant's database through the provider's `dropDatabase`
|
|
921
|
+
* hook (or the framework default) after the attempt's afterEach hooks and
|
|
922
|
+
* transactional tenant rollbacks finished, so consuming apps do not hand-roll a
|
|
923
|
+
* Map/afterEach bookkeeping layer for DDL that a test transaction cannot roll back.
|
|
924
|
+
* @param {{databaseIdentifier: string, tenant: object}} args - Tenant database identifier and tenant descriptor to drop at attempt end.
|
|
925
|
+
* @param {TenantCleanupRegistration[]} registrations - Current attempt registrations.
|
|
926
|
+
* @returns {void} - No return value.
|
|
927
|
+
*/
|
|
928
|
+
registerTenantForCleanup({databaseIdentifier, tenant, ...restArgs}, registrations) {
|
|
929
|
+
restArgsError(restArgs)
|
|
930
|
+
if (!databaseIdentifier) throw new Error("registerTenantForCleanup requires a databaseIdentifier")
|
|
931
|
+
if (!tenant) throw new Error("registerTenantForCleanup requires a tenant")
|
|
932
|
+
|
|
933
|
+
const databaseConfiguration = this.getConfiguration().resolveDatabaseConfiguration(databaseIdentifier, tenant)
|
|
934
|
+
|
|
935
|
+
if (!databaseConfiguration.tenantOnly) {
|
|
936
|
+
throw new Error(`registerTenantForCleanup requires a tenantOnly database: ${databaseIdentifier}`)
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
registrations.push({databaseIdentifier, tenant})
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Drops every tenant database registered for this attempt, in registration order.
|
|
944
|
+
* Each drop is attempted even when an earlier one failed; failures are surfaced
|
|
945
|
+
* (single error or AggregateError), never swallowed.
|
|
946
|
+
* @param {TenantCleanupRegistration[]} registrations - Attempt-owned registrations.
|
|
947
|
+
* @returns {Promise<void>} - Resolves after all registered tenants were dropped.
|
|
948
|
+
*/
|
|
949
|
+
async cleanupTenantRegistrations(registrations) {
|
|
950
|
+
const errors = []
|
|
951
|
+
|
|
952
|
+
for (const {databaseIdentifier, tenant} of registrations) {
|
|
953
|
+
try {
|
|
954
|
+
await Tenant.drop({identifier: databaseIdentifier, tenant, configuration: this.getConfiguration()})
|
|
955
|
+
} catch (error) {
|
|
956
|
+
errors.push(error)
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (errors.length === 1) throw errors[0]
|
|
961
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to drop tenant databases registered for cleanup")
|
|
962
|
+
}
|
|
963
|
+
|
|
825
964
|
/**
|
|
826
965
|
* Selects the current non-tenant connections eligible for shared transaction work.
|
|
827
966
|
* @param {{transactionsOnly: boolean}} args - Selection options.
|
|
@@ -1374,6 +1513,12 @@ export default class TestRunner {
|
|
|
1374
1513
|
} finally {
|
|
1375
1514
|
process.off("unhandledRejection", onUnhandledRejection)
|
|
1376
1515
|
process.off("uncaughtException", onUncaughtException)
|
|
1516
|
+
|
|
1517
|
+
try {
|
|
1518
|
+
await this.teardownTestTenantPools()
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
this.recordTimeoutCleanupFailure(error, "test tenant pool teardown")
|
|
1521
|
+
}
|
|
1377
1522
|
}
|
|
1378
1523
|
}
|
|
1379
1524
|
|
|
@@ -1385,6 +1530,13 @@ export default class TestRunner {
|
|
|
1385
1530
|
const failureStart = this._suiteHookFailures.length
|
|
1386
1531
|
|
|
1387
1532
|
await this._packageRunner?.cleanupActiveSuites()
|
|
1533
|
+
|
|
1534
|
+
try {
|
|
1535
|
+
await this.teardownTestTenantPools()
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
this.recordTimeoutCleanupFailure(error, "test tenant pool teardown")
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1388
1540
|
this.throwAfterAllFailures(this._suiteHookFailures.slice(failureStart))
|
|
1389
1541
|
}
|
|
1390
1542
|
|