@tstdl/base 0.93.144 → 0.93.146
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/authentication/authentication.api.d.ts +9 -0
- package/authentication/authentication.api.js +3 -0
- package/authentication/client/authentication.service.js +5 -5
- package/authentication/client/http-client.middleware.js +6 -2
- package/authentication/tests/authentication.client-middleware.test.js +35 -0
- package/authentication/tests/authentication.client-service-refresh.test.js +7 -0
- package/authentication/tests/authentication.client-service.test.js +15 -19
- package/authentication/tests/authentication.service.test.js +92 -119
- package/notification/tests/notification-client.test.js +39 -50
- package/notification/tests/notification-flow.test.js +204 -238
- package/notification/tests/notification-sse.service.test.js +20 -27
- package/notification/tests/notification-type.service.test.js +17 -20
- package/orm/tests/query-complex.test.js +80 -111
- package/orm/tests/repository-advanced.test.js +100 -143
- package/orm/tests/repository-attributes.test.js +30 -39
- package/orm/tests/repository-compound-primary-key.test.js +67 -75
- package/orm/tests/repository-comprehensive.test.js +76 -101
- package/orm/tests/repository-coverage.test.d.ts +1 -0
- package/orm/tests/repository-coverage.test.js +88 -149
- package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
- package/orm/tests/repository-cti-extensive.test.js +118 -147
- package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
- package/orm/tests/repository-cti-mapping.test.js +29 -42
- package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-cti-soft-delete.test.js +25 -37
- package/orm/tests/repository-cti-transactions.test.js +19 -33
- package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
- package/orm/tests/repository-cti-upsert-many.test.js +38 -50
- package/orm/tests/repository-cti.test.d.ts +1 -0
- package/orm/tests/repository-cti.test.js +195 -247
- package/orm/tests/repository-expiration.test.d.ts +1 -0
- package/orm/tests/repository-expiration.test.js +46 -59
- package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
- package/orm/tests/repository-extra-coverage.test.js +195 -337
- package/orm/tests/repository-mapping.test.d.ts +1 -0
- package/orm/tests/repository-mapping.test.js +20 -20
- package/orm/tests/repository-regression.test.js +124 -163
- package/orm/tests/repository-search.test.js +30 -44
- package/orm/tests/repository-soft-delete.test.js +54 -79
- package/orm/tests/repository-types.test.js +77 -111
- package/package.json +1 -1
- package/task-queue/tests/worker.test.js +5 -5
- package/testing/README.md +38 -16
- package/testing/integration-setup.d.ts +11 -0
- package/testing/integration-setup.js +57 -30
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
/** biome-ignore-all lint/nursery/useExpect: helper file */
|
|
1
2
|
import { sql } from 'drizzle-orm';
|
|
2
|
-
import { afterAll } from 'vitest';
|
|
3
|
+
import { afterAll, test } from 'vitest';
|
|
3
4
|
import { configureApiServer } from '../api/server/index.js';
|
|
4
5
|
import { configureAudit } from '../audit/index.js';
|
|
5
6
|
import { AuthenticationApiClient } from '../authentication/client/api.client.js';
|
|
@@ -26,7 +27,8 @@ import { configureDefaultSignalsImplementation } from '../signals/implementation
|
|
|
26
27
|
import { configurePostgresTaskQueue } from '../task-queue/postgres/index.js';
|
|
27
28
|
import * as configParser from '../utils/config-parser.js';
|
|
28
29
|
import { objectEntries } from '../utils/object/object.js';
|
|
29
|
-
import { assertDefinedPass, isDefined, isNotNull, isString } from '../utils/type-guards.js';
|
|
30
|
+
import { assertDefinedPass, isDefined, isNotNull, isNumber, isString, isUndefined } from '../utils/type-guards.js';
|
|
31
|
+
import { resolveValueOrProvider } from '../utils/value-or-provider.js';
|
|
30
32
|
/**
|
|
31
33
|
* Standard setup for integration tests.
|
|
32
34
|
*/
|
|
@@ -167,16 +169,22 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
167
169
|
return { injector, database };
|
|
168
170
|
}
|
|
169
171
|
/**
|
|
170
|
-
*
|
|
172
|
+
* A wrapper for vitest's `test` that automatically runs the test function in the provided injector's context.
|
|
173
|
+
* @param name The name of the test.
|
|
174
|
+
* @param injector The injector to use for the context.
|
|
175
|
+
* @param fn The test function.
|
|
176
|
+
* @param options Vitest test options.
|
|
171
177
|
*/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
export function testInInjector(name, injector, fn, options) {
|
|
179
|
+
if (isUndefined(options) || isNumber(options)) {
|
|
180
|
+
test(name, async () => {
|
|
181
|
+
await runInInjectionContext(resolveValueOrProvider(injector), fn);
|
|
182
|
+
}, options);
|
|
177
183
|
}
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
else {
|
|
185
|
+
test(name, options, async () => {
|
|
186
|
+
await runInInjectionContext(resolveValueOrProvider(injector), fn);
|
|
187
|
+
});
|
|
180
188
|
}
|
|
181
189
|
}
|
|
182
190
|
/**
|
|
@@ -187,19 +195,15 @@ export async function truncateTables(database, schema, tables) {
|
|
|
187
195
|
if (tables.length == 0) {
|
|
188
196
|
return;
|
|
189
197
|
}
|
|
190
|
-
const
|
|
191
|
-
await database
|
|
192
|
-
try {
|
|
198
|
+
const lockName = `tstdl:maintenance:${schema}`;
|
|
199
|
+
await runWithAdvisoryLock(database, lockName, async () => {
|
|
193
200
|
// Using CASCADE to handle foreign keys automatically
|
|
194
201
|
for (const table of tables) {
|
|
195
202
|
const tableName = isString(table) ? table : getEntityTableName(table);
|
|
196
203
|
const tableSchema = isString(table) ? schema : getEntitySchema(table, schema);
|
|
197
204
|
await database.execute(sql `TRUNCATE TABLE ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} CASCADE`);
|
|
198
205
|
}
|
|
199
|
-
}
|
|
200
|
-
finally {
|
|
201
|
-
await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
|
|
202
|
-
}
|
|
206
|
+
});
|
|
203
207
|
}
|
|
204
208
|
/**
|
|
205
209
|
* Helper to delete data for a specific tenant from specific tables.
|
|
@@ -209,18 +213,15 @@ export async function clearTenantData(database, schema, tables, tenantId) {
|
|
|
209
213
|
if (tables.length == 0) {
|
|
210
214
|
return;
|
|
211
215
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
216
|
+
// Use a lock that is specific to both the schema AND the tenant
|
|
217
|
+
const lockName = `tstdl:maintenance:${schema ?? 'default'}:${tenantId}`;
|
|
218
|
+
await runWithAdvisoryLock(database, lockName, async () => {
|
|
215
219
|
for (const table of tables) {
|
|
216
220
|
const tableName = isString(table) ? table : getEntityTableName(table);
|
|
217
221
|
const tableSchema = isString(table) ? assertDefinedPass(schema, 'Schema not provided') : getEntitySchema(table, schema);
|
|
218
222
|
await database.execute(sql `DELETE FROM ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} WHERE tenant_id = ${tenantId}`);
|
|
219
223
|
}
|
|
220
|
-
}
|
|
221
|
-
finally {
|
|
222
|
-
await database.execute(sql `SELECT pg_advisory_unlock(${lockId})`);
|
|
223
|
-
}
|
|
224
|
+
});
|
|
224
225
|
}
|
|
225
226
|
/**
|
|
226
227
|
* Helper to drop specific tables.
|
|
@@ -230,16 +231,42 @@ export async function dropTables(database, schema, tables) {
|
|
|
230
231
|
if (tables.length == 0) {
|
|
231
232
|
return;
|
|
232
233
|
}
|
|
233
|
-
const
|
|
234
|
-
await database
|
|
235
|
-
try {
|
|
234
|
+
const lockName = `tstdl:maintenance:${schema ?? 'default'}`;
|
|
235
|
+
await runWithAdvisoryLock(database, lockName, async () => {
|
|
236
236
|
for (const table of tables) {
|
|
237
237
|
const tableName = isString(table) ? table : getEntityTableName(table);
|
|
238
238
|
const tableSchema = isString(table) ? assertDefinedPass(schema, 'Schema not provided') : getEntitySchema(table, schema);
|
|
239
239
|
await database.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(tableSchema)}.${sql.identifier(tableName)} CASCADE`);
|
|
240
240
|
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Helper to run a migration safely with a database advisory lock to prevent race conditions.
|
|
245
|
+
*/
|
|
246
|
+
async function runMigrationSafely(database, migration) {
|
|
247
|
+
const lockName = 'tstdl:migration';
|
|
248
|
+
await runWithAdvisoryLock(database, lockName, migration);
|
|
249
|
+
}
|
|
250
|
+
async function runWithAdvisoryLock(database, lockName, fn) {
|
|
251
|
+
// We use pg_try_advisory_lock in a loop with small random delays to avoid deadlocks
|
|
252
|
+
// which sometimes happen with pg_advisory_lock under very high parallelism in Vitest
|
|
253
|
+
const start = Date.now();
|
|
254
|
+
const timeout = 30000; // 30s timeout for maintenance tasks
|
|
255
|
+
while (true) {
|
|
256
|
+
const { rows: [result] } = await database.execute(sql `SELECT pg_try_advisory_lock(hashtext(${lockName})) as locked`);
|
|
257
|
+
if (result?.locked) {
|
|
258
|
+
try {
|
|
259
|
+
await fn();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
await database.execute(sql `SELECT pg_advisory_unlock(hashtext(${lockName}))`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (Date.now() - start > timeout) {
|
|
267
|
+
throw new Error(`Timeout while waiting for advisory lock: ${lockName}`);
|
|
268
|
+
}
|
|
269
|
+
// Small random delay between 10ms and 100ms
|
|
270
|
+
await new Promise((resolve) => setTimeout(resolve, 10 + Math.random() * 90));
|
|
244
271
|
}
|
|
245
272
|
}
|