@tstdl/base 0.93.162 → 0.93.164
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/api/server/gateway.d.ts +1 -0
- package/api/server/gateway.js +11 -4
- package/authentication/tests/authentication.api-controller.test.js +24 -0
- package/circuit-breaker/postgres/circuit-breaker.d.ts +0 -3
- package/circuit-breaker/postgres/circuit-breaker.js +9 -12
- package/circuit-breaker/tests/circuit-breaker.test.js +7 -2
- package/object-storage/s3/tests/s3.object-storage.integration.test.js +2 -2
- package/orm/tests/build-jsonb.test.js +5 -5
- package/orm/tests/query-converter-complex.test.js +5 -5
- package/orm/tests/repository-cti-complex.test.js +54 -73
- package/orm/tests/repository-cti-embedded.test.js +9 -19
- package/orm/tests/repository-cti-search.test.js +12 -23
- package/orm/tests/repository-edge-cases.test.js +81 -119
- package/orm/tests/repository-mapping.test.js +3 -9
- package/orm/tests/repository-search-coverage.test.js +52 -74
- package/orm/tests/repository-transactions-nested.test.js +96 -120
- package/orm/tests/transactional.test.js +5 -14
- package/package.json +11 -11
- package/task-queue/tests/optimization-edge-cases.test.js +11 -14
- package/task-queue/tests/queue.test.js +29 -2
- package/testing/README.md +23 -35
- package/testing/integration-setup.d.ts +54 -7
- package/testing/integration-setup.js +147 -21
|
@@ -8,11 +8,11 @@ import { type S3ObjectStorageProviderConfig } from '../object-storage/s3/index.j
|
|
|
8
8
|
import type { EntityType } from '../orm/entity.js';
|
|
9
9
|
import { Database } from '../orm/server/index.js';
|
|
10
10
|
import type { Type } from '../types/index.js';
|
|
11
|
-
import { type ValueOrProvider } from '../utils/value-or-provider.js';
|
|
12
11
|
export type IntegrationTestOptions = {
|
|
13
12
|
dbConfig?: Partial<PoolConfig>;
|
|
14
13
|
orm?: {
|
|
15
14
|
schema?: string;
|
|
15
|
+
encryptionSecret?: Uint8Array;
|
|
16
16
|
};
|
|
17
17
|
api?: {
|
|
18
18
|
baseUrl?: string;
|
|
@@ -37,23 +37,70 @@ export type IntegrationTestOptions = {
|
|
|
37
37
|
webServer?: boolean;
|
|
38
38
|
};
|
|
39
39
|
authenticationAncillaryService?: Type<AuthenticationAncillaryService>;
|
|
40
|
+
/**
|
|
41
|
+
* If true, a fresh context will be created and it will be disposed after the test.
|
|
42
|
+
* If false (default), the context is shared across tests with the same options and is NOT disposed automatically.
|
|
43
|
+
*/
|
|
44
|
+
isolated?: boolean;
|
|
40
45
|
};
|
|
41
46
|
export type TestContext = {
|
|
42
47
|
injector: Injector;
|
|
43
48
|
database: Database;
|
|
44
49
|
};
|
|
50
|
+
export type IntegrationTestFixtures = {
|
|
51
|
+
options: IntegrationTestOptions;
|
|
52
|
+
context: TestContext;
|
|
53
|
+
injector: Injector;
|
|
54
|
+
database: Database;
|
|
55
|
+
};
|
|
45
56
|
/**
|
|
46
57
|
* Standard setup for integration tests.
|
|
58
|
+
* By default, this caches the context based on the provided options to improve performance.
|
|
59
|
+
* For a fresh context, use {@link setupIsolatedIntegrationTest} or set `options.isolated` to true.
|
|
47
60
|
*/
|
|
48
61
|
export declare function setupIntegrationTest(options?: IntegrationTestOptions): Promise<TestContext>;
|
|
49
62
|
/**
|
|
50
|
-
*
|
|
51
|
-
* @param name The name of the test.
|
|
52
|
-
* @param injector The injector to use for the context.
|
|
53
|
-
* @param fn The test function.
|
|
54
|
-
* @param options Vitest test options.
|
|
63
|
+
* Creates a fresh, non-cached integration test context.
|
|
55
64
|
*/
|
|
56
|
-
export declare function
|
|
65
|
+
export declare function setupIsolatedIntegrationTest(options?: IntegrationTestOptions): Promise<TestContext>;
|
|
66
|
+
export declare const integrationTest: import("vitest").TestAPI<{
|
|
67
|
+
injector: Injector;
|
|
68
|
+
options: IntegrationTestOptions;
|
|
69
|
+
context: TestContext;
|
|
70
|
+
database: Database;
|
|
71
|
+
}>;
|
|
72
|
+
export declare function getIntegrationTest(options: IntegrationTestOptions): {
|
|
73
|
+
(name: string, fn: import("vitest").TestFunction<{
|
|
74
|
+
injector: Injector;
|
|
75
|
+
options: IntegrationTestOptions;
|
|
76
|
+
context: TestContext;
|
|
77
|
+
database: Database;
|
|
78
|
+
}> | undefined, options?: number | TestOptions): void;
|
|
79
|
+
beforeEach: (fn: import("@vitest/runner").BeforeEachListener<{
|
|
80
|
+
injector: Injector;
|
|
81
|
+
options: IntegrationTestOptions;
|
|
82
|
+
context: TestContext;
|
|
83
|
+
database: Database;
|
|
84
|
+
}>, timeout?: number) => void;
|
|
85
|
+
afterEach: (fn: import("@vitest/runner").AfterEachListener<{
|
|
86
|
+
injector: Injector;
|
|
87
|
+
options: IntegrationTestOptions;
|
|
88
|
+
context: TestContext;
|
|
89
|
+
database: Database;
|
|
90
|
+
}>, timeout?: number) => void;
|
|
91
|
+
beforeAll: (this: unknown, fn: import("@vitest/runner").BeforeAllListener<{
|
|
92
|
+
injector: Injector;
|
|
93
|
+
options: IntegrationTestOptions;
|
|
94
|
+
context: TestContext;
|
|
95
|
+
database: Database;
|
|
96
|
+
}>, timeout?: number) => void;
|
|
97
|
+
afterAll: (this: unknown, fn: import("@vitest/runner").AfterAllListener<{
|
|
98
|
+
injector: Injector;
|
|
99
|
+
options: IntegrationTestOptions;
|
|
100
|
+
context: TestContext;
|
|
101
|
+
database: Database;
|
|
102
|
+
}>, timeout?: number) => void;
|
|
103
|
+
};
|
|
57
104
|
/**
|
|
58
105
|
* Helper to truncate specific tables in a schema.
|
|
59
106
|
* Useful in beforeEach() to reset state.
|
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
/** biome-ignore-all lint/nursery/useExpect: helper file */
|
|
2
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
3
|
+
if (value !== null && value !== void 0) {
|
|
4
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
5
|
+
var dispose, inner;
|
|
6
|
+
if (async) {
|
|
7
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
8
|
+
dispose = value[Symbol.asyncDispose];
|
|
9
|
+
}
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.dispose];
|
|
13
|
+
if (async) inner = dispose;
|
|
14
|
+
}
|
|
15
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
16
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
17
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
18
|
+
}
|
|
19
|
+
else if (async) {
|
|
20
|
+
env.stack.push({ async: true });
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
25
|
+
return function (env) {
|
|
26
|
+
function fail(e) {
|
|
27
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
28
|
+
env.hasError = true;
|
|
29
|
+
}
|
|
30
|
+
var r, s = 0;
|
|
31
|
+
function next() {
|
|
32
|
+
while (r = env.stack.pop()) {
|
|
33
|
+
try {
|
|
34
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
35
|
+
if (r.dispose) {
|
|
36
|
+
var result = r.dispose.call(r.value);
|
|
37
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
38
|
+
}
|
|
39
|
+
else s |= 1;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
fail(e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
46
|
+
if (env.hasError) throw env.error;
|
|
47
|
+
}
|
|
48
|
+
return next();
|
|
49
|
+
};
|
|
50
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
51
|
+
var e = new Error(message);
|
|
52
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
|
+
});
|
|
2
54
|
import { sql } from 'drizzle-orm';
|
|
3
|
-
import {
|
|
55
|
+
import { test as baseTest } from 'vitest';
|
|
4
56
|
import { configureApiServer } from '../api/server/index.js';
|
|
5
57
|
import { configureAudit } from '../audit/index.js';
|
|
6
58
|
import { AuthenticationApiClient } from '../authentication/client/api.client.js';
|
|
@@ -25,14 +77,36 @@ import { getEntitySchema, getEntityTableName } from '../orm/utils.js';
|
|
|
25
77
|
import { configurePostgresRateLimiter } from '../rate-limit/postgres/module.js';
|
|
26
78
|
import { configureDefaultSignalsImplementation } from '../signals/implementation/configure.js';
|
|
27
79
|
import { configurePostgresTaskQueue } from '../task-queue/postgres/index.js';
|
|
80
|
+
import { encodeBase64 } from '../utils/base64.js';
|
|
28
81
|
import * as configParser from '../utils/config-parser.js';
|
|
29
82
|
import { objectEntries } from '../utils/object/object.js';
|
|
30
|
-
import { assertDefinedPass, isDefined, isNotNull, isNumber, isString, isUndefined } from '../utils/type-guards.js';
|
|
31
|
-
|
|
83
|
+
import { assertDefinedPass, isDefined, isFunction, isNotNull, isNumber, isString, isUint8Array, isUndefined } from '../utils/type-guards.js';
|
|
84
|
+
const sharedContexts = new Map();
|
|
32
85
|
/**
|
|
33
86
|
* Standard setup for integration tests.
|
|
87
|
+
* By default, this caches the context based on the provided options to improve performance.
|
|
88
|
+
* For a fresh context, use {@link setupIsolatedIntegrationTest} or set `options.isolated` to true.
|
|
34
89
|
*/
|
|
35
90
|
export async function setupIntegrationTest(options = {}) {
|
|
91
|
+
if (options.isolated == true) {
|
|
92
|
+
return await setupIsolatedIntegrationTest(options);
|
|
93
|
+
}
|
|
94
|
+
const key = getOptionsKey(options);
|
|
95
|
+
let contextPromise = sharedContexts.get(key);
|
|
96
|
+
if (isUndefined(contextPromise)) {
|
|
97
|
+
contextPromise = setupIsolatedIntegrationTest(options);
|
|
98
|
+
sharedContexts.set(key, contextPromise);
|
|
99
|
+
}
|
|
100
|
+
const context = await contextPromise;
|
|
101
|
+
if (!context.injector.disposed) {
|
|
102
|
+
context.injector.addDisposeHandler(() => sharedContexts.delete(key));
|
|
103
|
+
}
|
|
104
|
+
return context;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Creates a fresh, non-cached integration test context.
|
|
108
|
+
*/
|
|
109
|
+
export async function setupIsolatedIntegrationTest(options = {}) {
|
|
36
110
|
const injector = new Injector('TestInjector');
|
|
37
111
|
// 1. Logger Setup
|
|
38
112
|
injector.register(LogFormatter, { useToken: PrettyPrintLogFormatter });
|
|
@@ -60,6 +134,7 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
60
134
|
configureOrm({
|
|
61
135
|
repositoryConfig: { schema: options.orm?.schema ?? 'test' },
|
|
62
136
|
connection: dbConfig,
|
|
137
|
+
encryptionSecret: options.orm?.encryptionSecret,
|
|
63
138
|
injector,
|
|
64
139
|
});
|
|
65
140
|
// 5. Database Resolution
|
|
@@ -165,27 +240,67 @@ export async function setupIntegrationTest(options = {}) {
|
|
|
165
240
|
}
|
|
166
241
|
}
|
|
167
242
|
});
|
|
168
|
-
afterAll(async () => await injector.dispose());
|
|
169
243
|
return { injector, database };
|
|
170
244
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
245
|
+
export const integrationTest = baseTest.extend({
|
|
246
|
+
options: {},
|
|
247
|
+
context: async ({ options }, use) => {
|
|
248
|
+
const context = await setupIntegrationTest(options);
|
|
249
|
+
if (options.isolated == true) {
|
|
250
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
251
|
+
try {
|
|
252
|
+
const stack = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
253
|
+
stack.use(context.injector);
|
|
254
|
+
await use(context);
|
|
255
|
+
}
|
|
256
|
+
catch (e_1) {
|
|
257
|
+
env_1.error = e_1;
|
|
258
|
+
env_1.hasError = true;
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
const result_1 = __disposeResources(env_1);
|
|
262
|
+
if (result_1)
|
|
263
|
+
await result_1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
await use(context);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
injector: async ({ context }, use) => {
|
|
271
|
+
await use(context.injector);
|
|
272
|
+
},
|
|
273
|
+
database: async ({ context }, use) => {
|
|
274
|
+
await use(context.database);
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
export function getIntegrationTest(options) {
|
|
278
|
+
const customTest = integrationTest.extend({ options });
|
|
279
|
+
/**
|
|
280
|
+
* A wrapper for vitest's `test` that automatically runs the test function in the provided injector's context.
|
|
281
|
+
* @param name The name of the test.
|
|
282
|
+
* @param injector The injector to use for the context.
|
|
283
|
+
* @param fn The test function.
|
|
284
|
+
* @param options Vitest test options.
|
|
285
|
+
*/
|
|
286
|
+
function test(name, fn, options) {
|
|
287
|
+
if (isUndefined(options) || isNumber(options)) {
|
|
288
|
+
customTest(name, async ({ options, context, injector, database, task, signal, skip, annotate, expect, _local, onTestFailed, onTestFinished }) => {
|
|
289
|
+
await runInInjectionContext(injector, () => fn?.({ options, context, injector, database, task, signal, skip, annotate, expect, _local, onTestFailed, onTestFinished })); // eslint-disable-line @typescript-eslint/no-unsafe-return
|
|
290
|
+
}, options);
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
customTest(name, options, async ({ options, context, injector, database, task, signal, skip, annotate, expect, _local, onTestFailed, onTestFinished }) => {
|
|
294
|
+
await runInInjectionContext(injector, () => fn?.({ options, context, injector, database, task, signal, skip, annotate, expect, _local, onTestFailed, onTestFinished })); // eslint-disable-line @typescript-eslint/no-unsafe-return
|
|
295
|
+
});
|
|
296
|
+
}
|
|
188
297
|
}
|
|
298
|
+
;
|
|
299
|
+
test.beforeEach = customTest.beforeEach;
|
|
300
|
+
test.afterEach = customTest.afterEach;
|
|
301
|
+
test.beforeAll = customTest.beforeAll;
|
|
302
|
+
test.afterAll = customTest.afterAll;
|
|
303
|
+
return test;
|
|
189
304
|
}
|
|
190
305
|
/**
|
|
191
306
|
* Helper to truncate specific tables in a schema.
|
|
@@ -270,3 +385,14 @@ async function runWithAdvisoryLock(database, lockName, fn) {
|
|
|
270
385
|
await new Promise((resolve) => setTimeout(resolve, 10 + Math.random() * 90));
|
|
271
386
|
}
|
|
272
387
|
}
|
|
388
|
+
function getOptionsKey(options) {
|
|
389
|
+
return JSON.stringify(options, (_key, value) => {
|
|
390
|
+
if (isFunction(value)) {
|
|
391
|
+
return value.name;
|
|
392
|
+
}
|
|
393
|
+
if (isUint8Array(value)) {
|
|
394
|
+
return encodeBase64(value);
|
|
395
|
+
}
|
|
396
|
+
return String(value);
|
|
397
|
+
});
|
|
398
|
+
}
|