@geekmidas/testkit 3.1.1 → 10.0.0-alpha.0
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/dist/{Factory-CUM2767q.d.cts → Factory-C6W78ulZ.d.mts} +2 -2
- package/dist/{Factory-SFupxRC2.d.mts.map → Factory-C6W78ulZ.d.mts.map} +1 -1
- package/dist/{Factory-SFupxRC2.d.mts → Factory-DSEADOaF.d.cts} +2 -2
- package/dist/{Factory-CUM2767q.d.cts.map → Factory-DSEADOaF.d.cts.map} +1 -1
- package/dist/Factory.d.cts +2 -2
- package/dist/Factory.d.mts +2 -2
- package/dist/{KyselyFactory-vAxYodck.d.mts → KyselyFactory-Cc2UmOJk.d.mts} +3 -3
- package/dist/{KyselyFactory-OMwcuAX_.d.cts.map → KyselyFactory-Cc2UmOJk.d.mts.map} +1 -1
- package/dist/{KyselyFactory-OMwcuAX_.d.cts → KyselyFactory-DzH0N1zr.d.cts} +3 -3
- package/dist/{KyselyFactory-vAxYodck.d.mts.map → KyselyFactory-DzH0N1zr.d.cts.map} +1 -1
- package/dist/KyselyFactory.d.cts +3 -3
- package/dist/KyselyFactory.d.mts +3 -3
- package/dist/{ObjectionFactory-BWjB49-i.d.mts → ObjectionFactory-BYnPr9ZP.d.mts} +3 -3
- package/dist/{ObjectionFactory-BWjB49-i.d.mts.map → ObjectionFactory-BYnPr9ZP.d.mts.map} +1 -1
- package/dist/{ObjectionFactory-DvmZVHhe.d.cts → ObjectionFactory-BmKdG0nT.d.cts} +3 -3
- package/dist/{ObjectionFactory-DvmZVHhe.d.cts.map → ObjectionFactory-BmKdG0nT.d.cts.map} +1 -1
- package/dist/ObjectionFactory.d.cts +3 -3
- package/dist/ObjectionFactory.d.mts +3 -3
- package/dist/{VitestKyselyTransactionIsolator-CfSMOFlN.mjs → VitestKyselyTransactionIsolator-1Saieke7.mjs} +10 -3
- package/dist/VitestKyselyTransactionIsolator-1Saieke7.mjs.map +1 -0
- package/dist/{VitestKyselyTransactionIsolator-0CqOsuc9.cjs → VitestKyselyTransactionIsolator-BjJSXryR.cjs} +10 -3
- package/dist/VitestKyselyTransactionIsolator-BjJSXryR.cjs.map +1 -0
- package/dist/{VitestKyselyTransactionIsolator-CduJlHoT.d.cts → VitestKyselyTransactionIsolator-CwlTo6yf.d.mts} +8 -3
- package/dist/VitestKyselyTransactionIsolator-CwlTo6yf.d.mts.map +1 -0
- package/dist/{VitestKyselyTransactionIsolator-Cswnnj0k.d.mts → VitestKyselyTransactionIsolator-DJasVuIZ.d.cts} +8 -3
- package/dist/VitestKyselyTransactionIsolator-DJasVuIZ.d.cts.map +1 -0
- package/dist/VitestKyselyTransactionIsolator.cjs +2 -2
- package/dist/VitestKyselyTransactionIsolator.d.cts +2 -2
- package/dist/VitestKyselyTransactionIsolator.d.mts +2 -2
- package/dist/VitestKyselyTransactionIsolator.mjs +2 -2
- package/dist/{VitestObjectionTransactionIsolator-CNo40EdC.mjs → VitestObjectionTransactionIsolator-B_VSWFum.mjs} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-CNo40EdC.mjs.map → VitestObjectionTransactionIsolator-B_VSWFum.mjs.map} +1 -1
- package/dist/{VitestObjectionTransactionIsolator-BXoR6xdG.d.cts → VitestObjectionTransactionIsolator-CTTjvTPO.d.cts} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-BXoR6xdG.d.cts.map → VitestObjectionTransactionIsolator-CTTjvTPO.d.cts.map} +1 -1
- package/dist/{VitestObjectionTransactionIsolator-x6hY5j4u.d.mts → VitestObjectionTransactionIsolator-CyZG6nq4.d.mts} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-x6hY5j4u.d.mts.map → VitestObjectionTransactionIsolator-CyZG6nq4.d.mts.map} +1 -1
- package/dist/{VitestObjectionTransactionIsolator-B5J9jjKq.cjs → VitestObjectionTransactionIsolator-DDoJTu7e.cjs} +2 -2
- package/dist/{VitestObjectionTransactionIsolator-B5J9jjKq.cjs.map → VitestObjectionTransactionIsolator-DDoJTu7e.cjs.map} +1 -1
- package/dist/VitestObjectionTransactionIsolator.cjs +2 -2
- package/dist/VitestObjectionTransactionIsolator.d.cts +2 -2
- package/dist/VitestObjectionTransactionIsolator.d.mts +2 -2
- package/dist/VitestObjectionTransactionIsolator.mjs +2 -2
- package/dist/{VitestTransactionIsolator-BNWJqh9f.d.mts → VitestTransactionIsolator-BLaw80cx.d.mts} +20 -4
- package/dist/VitestTransactionIsolator-BLaw80cx.d.mts.map +1 -0
- package/dist/{VitestTransactionIsolator-D6ZxLWys.mjs → VitestTransactionIsolator-CvwPecpl.mjs} +17 -4
- package/dist/VitestTransactionIsolator-CvwPecpl.mjs.map +1 -0
- package/dist/{VitestTransactionIsolator-CtZaOUjH.cjs → VitestTransactionIsolator-DyiX-QtK.cjs} +17 -4
- package/dist/VitestTransactionIsolator-DyiX-QtK.cjs.map +1 -0
- package/dist/{VitestTransactionIsolator-CSroc7Df.d.cts → VitestTransactionIsolator-glcxyS_R.d.cts} +20 -4
- package/dist/VitestTransactionIsolator-glcxyS_R.d.cts.map +1 -0
- package/dist/VitestTransactionIsolator.cjs +1 -1
- package/dist/VitestTransactionIsolator.d.cts +2 -2
- package/dist/VitestTransactionIsolator.d.mts +2 -2
- package/dist/VitestTransactionIsolator.mjs +1 -1
- package/dist/better-auth.d.cts +2 -2
- package/dist/better-auth.d.mts +2 -2
- package/dist/{directory-Bpz5c5pj.d.cts → directory-DGOcVlKD.d.cts} +3 -3
- package/dist/{directory-Bpz5c5pj.d.cts.map → directory-DGOcVlKD.d.cts.map} +1 -1
- package/dist/{faker-DHh7xs4u.d.mts → faker-D9gz7KjY.d.mts} +3 -3
- package/dist/{faker-DHh7xs4u.d.mts.map → faker-D9gz7KjY.d.mts.map} +1 -1
- package/dist/{faker-zVCm31nU.d.cts → faker-km9UhOS6.d.cts} +3 -3
- package/dist/{faker-zVCm31nU.d.cts.map → faker-km9UhOS6.d.cts.map} +1 -1
- package/dist/faker.d.cts +1 -1
- package/dist/faker.d.mts +1 -1
- package/dist/kysely.cjs +2 -2
- package/dist/kysely.d.cts +5 -5
- package/dist/kysely.d.mts +5 -5
- package/dist/kysely.mjs +2 -2
- package/dist/objection.cjs +2 -2
- package/dist/objection.d.cts +5 -5
- package/dist/objection.d.mts +5 -5
- package/dist/objection.mjs +2 -2
- package/dist/os/directory.d.cts +1 -1
- package/dist/os/index.d.cts +1 -1
- package/dist/requestContext.cjs +1 -1
- package/dist/requestContext.d.cts +1 -1
- package/dist/requestContext.d.mts +1 -1
- package/dist/requestContext.mjs +1 -1
- package/package.json +10 -7
- package/CHANGELOG.md +0 -128
- package/dist/VitestKyselyTransactionIsolator-0CqOsuc9.cjs.map +0 -1
- package/dist/VitestKyselyTransactionIsolator-CduJlHoT.d.cts.map +0 -1
- package/dist/VitestKyselyTransactionIsolator-CfSMOFlN.mjs.map +0 -1
- package/dist/VitestKyselyTransactionIsolator-Cswnnj0k.d.mts.map +0 -1
- package/dist/VitestTransactionIsolator-BNWJqh9f.d.mts.map +0 -1
- package/dist/VitestTransactionIsolator-CSroc7Df.d.cts.map +0 -1
- package/dist/VitestTransactionIsolator-CtZaOUjH.cjs.map +0 -1
- package/dist/VitestTransactionIsolator-D6ZxLWys.mjs.map +0 -1
- package/src/Factory.ts +0 -174
- package/src/KyselyFactory.ts +0 -391
- package/src/ObjectionFactory.ts +0 -412
- package/src/PostgresKyselyMigrator.ts +0 -104
- package/src/PostgresMigrator.ts +0 -193
- package/src/PostgresObjectionMigrator.ts +0 -138
- package/src/VitestKyselyTransactionIsolator.ts +0 -72
- package/src/VitestObjectionTransactionIsolator.ts +0 -75
- package/src/VitestTransactionIsolator.ts +0 -348
- package/src/__tests__/Factory.spec.ts +0 -178
- package/src/__tests__/KyselyFactory.spec.ts +0 -458
- package/src/__tests__/ObjectionFactory.spec.ts +0 -586
- package/src/__tests__/PostgresKyselyMigrator.spec.ts +0 -794
- package/src/__tests__/PostgresMigrator.spec.ts +0 -471
- package/src/__tests__/PostgresObjectionMigrator.spec.ts +0 -634
- package/src/__tests__/VitestObjectionTransactionIsolator.spec.ts +0 -138
- package/src/__tests__/benchmark.spec.ts +0 -140
- package/src/__tests__/better-auth.spec.ts +0 -21
- package/src/__tests__/faker.spec.ts +0 -231
- package/src/__tests__/initScript.spec.ts +0 -332
- package/src/__tests__/integration.spec.ts +0 -610
- package/src/__tests__/requestContext.spec.ts +0 -113
- package/src/__tests__/utilities.spec.ts +0 -211
- package/src/aws.ts +0 -131
- package/src/benchmark.ts +0 -48
- package/src/better-auth.ts +0 -310
- package/src/faker.ts +0 -349
- package/src/helpers.ts +0 -45
- package/src/initScript.ts +0 -122
- package/src/kysely.ts +0 -166
- package/src/logger.ts +0 -18
- package/src/objection.ts +0 -157
- package/src/os/directory.ts +0 -26
- package/src/os/index.ts +0 -1
- package/src/requestContext.ts +0 -119
- package/src/timer.ts +0 -3
- package/test/globalSetup.ts +0 -54
- package/test/helpers.ts +0 -268
- package/test/migrations/1749664623372_user.ts +0 -22
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -8
package/src/objection.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import type { Knex } from 'knex';
|
|
2
|
-
import type { TestAPI } from 'vitest';
|
|
3
|
-
import { VitestObjectionTransactionIsolator } from './VitestObjectionTransactionIsolator';
|
|
4
|
-
import {
|
|
5
|
-
extendWithFixtures as baseExtendWithFixtures,
|
|
6
|
-
type DatabaseConnection,
|
|
7
|
-
type FixtureCreators,
|
|
8
|
-
type IsolationLevel,
|
|
9
|
-
} from './VitestTransactionIsolator';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Objection.js-specific exports for test utilities.
|
|
13
|
-
* Provides factory implementation for creating test data with Objection.js ORM
|
|
14
|
-
* and transaction isolation for test suites.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export type { ExtractSeedAttrs, FactorySeed } from './Factory';
|
|
18
|
-
export { ObjectionFactory } from './ObjectionFactory';
|
|
19
|
-
export { PostgresObjectionMigrator } from './PostgresObjectionMigrator';
|
|
20
|
-
export { VitestObjectionTransactionIsolator } from './VitestObjectionTransactionIsolator';
|
|
21
|
-
export type {
|
|
22
|
-
DatabaseFixtures,
|
|
23
|
-
ExtendedDatabaseFixtures,
|
|
24
|
-
FixtureCreators,
|
|
25
|
-
TestWithExtendedFixtures,
|
|
26
|
-
TransactionWrapperOptions,
|
|
27
|
-
} from './VitestTransactionIsolator';
|
|
28
|
-
export { IsolationLevel } from './VitestTransactionIsolator';
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Objection.js-specific options for transaction wrapping.
|
|
32
|
-
*/
|
|
33
|
-
export interface ObjectionTransactionOptions<
|
|
34
|
-
Extended extends Record<string, unknown> = {},
|
|
35
|
-
> {
|
|
36
|
-
/** Function that creates or returns a Knex database connection */
|
|
37
|
-
connection: DatabaseConnection<Knex>;
|
|
38
|
-
/** Optional setup function to run within the transaction before each test */
|
|
39
|
-
setup?: (trx: Knex.Transaction) => Promise<void>;
|
|
40
|
-
/** Transaction isolation level (defaults to REPEATABLE_READ) */
|
|
41
|
-
isolationLevel?: IsolationLevel;
|
|
42
|
-
/** Additional fixtures that depend on the transaction */
|
|
43
|
-
fixtures?: FixtureCreators<Knex.Transaction, Extended>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Re-export faker and FakerFactory for type portability in declaration files
|
|
47
|
-
export { type FakerFactory, faker } from './faker';
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Creates a wrapped Vitest test API with automatic transaction rollback for Objection.js.
|
|
51
|
-
* Each test runs in an isolated database transaction that is rolled back after completion.
|
|
52
|
-
* This ensures tests don't affect each other's data and run faster than truncating tables.
|
|
53
|
-
*
|
|
54
|
-
* @template Extended - Additional fixtures to provide
|
|
55
|
-
* @param api - The Vitest test API (usually `test` from vitest)
|
|
56
|
-
* @param options - Configuration options for transaction wrapping
|
|
57
|
-
* @returns A wrapped test API that provides transaction isolation
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```typescript
|
|
61
|
-
* import { test } from 'vitest';
|
|
62
|
-
* import { wrapVitestObjectionTransaction } from '@geekmidas/testkit/objection';
|
|
63
|
-
* import { knex } from './database';
|
|
64
|
-
* import { User } from './models';
|
|
65
|
-
*
|
|
66
|
-
* // Create isolated test with automatic rollback
|
|
67
|
-
* const isolatedTest = wrapVitestObjectionTransaction(test, {
|
|
68
|
-
* connection: knex,
|
|
69
|
-
* });
|
|
70
|
-
*
|
|
71
|
-
* // Use in tests - each test gets its own transaction
|
|
72
|
-
* isolatedTest('should create user', async ({ trx }) => {
|
|
73
|
-
* const user = await User.query(trx)
|
|
74
|
-
* .insert({ name: 'Test User', email: 'test@example.com' });
|
|
75
|
-
*
|
|
76
|
-
* expect(user).toBeDefined();
|
|
77
|
-
* });
|
|
78
|
-
*
|
|
79
|
-
* // With fixtures for factories
|
|
80
|
-
* const it = wrapVitestObjectionTransaction<{ factory: Factory }>(test, {
|
|
81
|
-
* connection: knex,
|
|
82
|
-
* fixtures: {
|
|
83
|
-
* factory: (trx) => new Factory(trx),
|
|
84
|
-
* },
|
|
85
|
-
* });
|
|
86
|
-
*
|
|
87
|
-
* it('should create user with factory', async ({ trx, factory }) => {
|
|
88
|
-
* const user = await factory.insert('user', { name: 'Test' });
|
|
89
|
-
* expect(user.id).toBeDefined();
|
|
90
|
-
* });
|
|
91
|
-
* ```
|
|
92
|
-
*/
|
|
93
|
-
export function wrapVitestObjectionTransaction<
|
|
94
|
-
Extended extends Record<string, unknown> = {},
|
|
95
|
-
>(api: TestAPI, options: ObjectionTransactionOptions<Extended>) {
|
|
96
|
-
const wrapper = new VitestObjectionTransactionIsolator(api);
|
|
97
|
-
|
|
98
|
-
return wrapper.wrapVitestWithTransaction(options);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Extends an Objection.js transaction-wrapped test with additional fixtures.
|
|
103
|
-
* Each fixture receives the transaction and can create dependencies like factories or repositories.
|
|
104
|
-
*
|
|
105
|
-
* @template Extended - The type of additional fixtures to provide
|
|
106
|
-
* @param wrappedTest - The base wrapped test from wrapVitestObjectionTransaction
|
|
107
|
-
* @param fixtures - Object mapping fixture names to creator functions
|
|
108
|
-
* @returns An extended test API with both trx and the additional fixtures
|
|
109
|
-
*
|
|
110
|
-
* @example
|
|
111
|
-
* ```typescript
|
|
112
|
-
* import { test } from 'vitest';
|
|
113
|
-
* import { wrapVitestObjectionTransaction, extendWithFixtures, ObjectionFactory } from '@geekmidas/testkit/objection';
|
|
114
|
-
* import { User } from './models';
|
|
115
|
-
*
|
|
116
|
-
* // Define your builders
|
|
117
|
-
* const builders = {
|
|
118
|
-
* user: ObjectionFactory.createBuilder(User, ({ faker }) => ({
|
|
119
|
-
* name: faker.person.fullName(),
|
|
120
|
-
* email: faker.internet.email(),
|
|
121
|
-
* })),
|
|
122
|
-
* };
|
|
123
|
-
*
|
|
124
|
-
* // Create base wrapped test
|
|
125
|
-
* const baseTest = wrapVitestObjectionTransaction(test, {
|
|
126
|
-
* connection: knex,
|
|
127
|
-
* setup: createTestTables,
|
|
128
|
-
* });
|
|
129
|
-
*
|
|
130
|
-
* // Extend with fixtures - each fixture receives the transaction
|
|
131
|
-
* const it = extendWithFixtures<{ factory: ObjectionFactory<typeof builders, {}> }>(
|
|
132
|
-
* baseTest,
|
|
133
|
-
* {
|
|
134
|
-
* factory: (trx) => new ObjectionFactory(builders, {}, trx),
|
|
135
|
-
* }
|
|
136
|
-
* );
|
|
137
|
-
*
|
|
138
|
-
* // Use in tests - both trx and factory are available
|
|
139
|
-
* it('should create user with factory', async ({ trx, factory }) => {
|
|
140
|
-
* const user = await factory.insert('user', { name: 'Test User' });
|
|
141
|
-
* expect(user.id).toBeDefined();
|
|
142
|
-
*
|
|
143
|
-
* // Verify in database
|
|
144
|
-
* const found = await User.query(trx).findById(user.id);
|
|
145
|
-
* expect(found?.name).toBe('Test User');
|
|
146
|
-
* });
|
|
147
|
-
* ```
|
|
148
|
-
*/
|
|
149
|
-
export function extendWithFixtures<
|
|
150
|
-
Extended extends Record<string, unknown>,
|
|
151
|
-
T extends ReturnType<TestAPI['extend']> = ReturnType<TestAPI['extend']>,
|
|
152
|
-
>(wrappedTest: T, fixtures: FixtureCreators<Knex.Transaction, Extended>) {
|
|
153
|
-
return baseExtendWithFixtures<Knex.Transaction, Extended, T>(
|
|
154
|
-
wrappedTest,
|
|
155
|
-
fixtures,
|
|
156
|
-
);
|
|
157
|
-
}
|
package/src/os/directory.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { it } from 'vitest';
|
|
6
|
-
|
|
7
|
-
export const itWithDir = it.extend<DirectoryFixtures>({
|
|
8
|
-
// This fixture automatically provides a transaction to each test
|
|
9
|
-
// biome-ignore lint/correctness/noEmptyPattern: this has to be like this to satisfy Biome
|
|
10
|
-
dir: async ({}, use) => {
|
|
11
|
-
const tempDir = os.tmpdir();
|
|
12
|
-
const directoryName = crypto.randomUUID().replace(/-/g, '').toUpperCase();
|
|
13
|
-
const dir = path.join(tempDir, directoryName);
|
|
14
|
-
await fs.mkdir(dir, { recursive: true });
|
|
15
|
-
await use(dir);
|
|
16
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
17
|
-
|
|
18
|
-
// Clean up keystore directory that may have been created at ~/.gkm/{directoryName}
|
|
19
|
-
const keystoreDir = path.join(os.homedir(), '.gkm', directoryName);
|
|
20
|
-
await fs.rm(keystoreDir, { recursive: true, force: true });
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export interface DirectoryFixtures {
|
|
25
|
-
dir: string;
|
|
26
|
-
}
|
package/src/os/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { itWithDir } from './directory';
|
package/src/requestContext.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import type { Logger } from '@geekmidas/logger';
|
|
3
|
-
import { ConsoleLogger } from '@geekmidas/logger/console';
|
|
4
|
-
import {
|
|
5
|
-
enterRequestContext,
|
|
6
|
-
exitRequestContext,
|
|
7
|
-
runWithRequestContext,
|
|
8
|
-
type ServiceContext,
|
|
9
|
-
serviceContext,
|
|
10
|
-
} from '@geekmidas/services';
|
|
11
|
-
import type { TestAPI } from 'vitest';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Options for setting up a request context in tests. Any field omitted
|
|
15
|
-
* is filled with a test-friendly default (ConsoleLogger, random UUID,
|
|
16
|
-
* Date.now()).
|
|
17
|
-
*/
|
|
18
|
-
export interface RequestContextOptions {
|
|
19
|
-
/** Logger exposed via serviceContext.getLogger(). */
|
|
20
|
-
logger?: Logger;
|
|
21
|
-
/** Request id exposed via serviceContext.getRequestId(). */
|
|
22
|
-
requestId?: string;
|
|
23
|
-
/** Start time exposed via serviceContext.getRequestStartTime(). */
|
|
24
|
-
startTime?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function resolveContext(opts: RequestContextOptions = {}) {
|
|
28
|
-
return {
|
|
29
|
-
logger: opts.logger ?? new ConsoleLogger({ app: 'test' }),
|
|
30
|
-
requestId: opts.requestId ?? randomUUID(),
|
|
31
|
-
startTime: opts.startTime ?? Date.now(),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Run a function inside a request context with test-friendly defaults.
|
|
37
|
-
* Useful when you need `serviceContext` to be available for a single call
|
|
38
|
-
* (e.g. invoking `Service.register({ context })` outside a wrapped test).
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* await runInRequestContext(() =>
|
|
43
|
-
* discovery.register([databaseService]),
|
|
44
|
-
* );
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export function runInRequestContext<T>(
|
|
48
|
-
fn: () => T | Promise<T>,
|
|
49
|
-
opts: RequestContextOptions = {},
|
|
50
|
-
): T | Promise<T> {
|
|
51
|
-
return runWithRequestContext(resolveContext(opts), fn);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Vitest fixture creator. Spread the result into `.extend()` and every test
|
|
56
|
-
* runs inside `runWithRequestContext`, even tests that don't destructure
|
|
57
|
-
* `requestContext` directly (the fixture is registered as `auto`).
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```ts
|
|
61
|
-
* const it = test.extend({ ...requestContextFixture() });
|
|
62
|
-
*
|
|
63
|
-
* it('registers services with context', async ({ requestContext }) => {
|
|
64
|
-
* await discovery.register([databaseService]);
|
|
65
|
-
* expect(requestContext.getRequestId()).toBeDefined();
|
|
66
|
-
* });
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
export function requestContextFixture(opts: RequestContextOptions = {}) {
|
|
70
|
-
return {
|
|
71
|
-
requestContext: [
|
|
72
|
-
async (
|
|
73
|
-
// biome-ignore lint/correctness/noEmptyPattern: vitest fixture signature
|
|
74
|
-
{}: {},
|
|
75
|
-
use: (value: ServiceContext) => Promise<void>,
|
|
76
|
-
) => {
|
|
77
|
-
// Vitest suspends the fixture on `await use(...)` and runs the test
|
|
78
|
-
// body in a continuation of the runner's async context. A scoped
|
|
79
|
-
// `runWithRequestContext` callback ends as soon as we hand control
|
|
80
|
-
// to the runner, so the test body would see no context. Instead,
|
|
81
|
-
// mutate the current task's store with `enterRequestContext` —
|
|
82
|
-
// async/await preserves that store across the suspension, so the
|
|
83
|
-
// test body sees it. We restore on the way out so subsequent tests
|
|
84
|
-
// don't inherit the value.
|
|
85
|
-
enterRequestContext(resolveContext(opts));
|
|
86
|
-
try {
|
|
87
|
-
await use(serviceContext);
|
|
88
|
-
} finally {
|
|
89
|
-
exitRequestContext();
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
{ auto: true },
|
|
93
|
-
] as const,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Wrap any Vitest `TestAPI` (including the one returned by
|
|
99
|
-
* `wrapVitestKyselyTransaction`) so every test runs inside
|
|
100
|
-
* `runWithRequestContext`. Compose with the transaction wrappers:
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* ```ts
|
|
104
|
-
* import { wrapVitestKyselyTransaction } from '@geekmidas/testkit/kysely';
|
|
105
|
-
* import { withRequestContext } from '@geekmidas/testkit/request-context';
|
|
106
|
-
*
|
|
107
|
-
* const baseIt = wrapVitestKyselyTransaction<DB>(it, { connection: db });
|
|
108
|
-
* const itWithCtx = withRequestContext(baseIt);
|
|
109
|
-
*
|
|
110
|
-
* itWithCtx('runs inside trx + context', async ({ trx, requestContext }) => {
|
|
111
|
-
* // serviceContext is set up; Service.register({ context }) works here.
|
|
112
|
-
* });
|
|
113
|
-
* ```
|
|
114
|
-
*/
|
|
115
|
-
export function withRequestContext<
|
|
116
|
-
T extends TestAPI | ReturnType<TestAPI['extend']>,
|
|
117
|
-
>(testApi: T, opts: RequestContextOptions = {}): T {
|
|
118
|
-
return (testApi as any).extend(requestContextFixture(opts));
|
|
119
|
-
}
|
package/src/timer.ts
DELETED
package/test/globalSetup.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Client } from 'pg';
|
|
2
|
-
|
|
3
|
-
const TEST_DATABASE_NAME = 'geekmidas_test';
|
|
4
|
-
|
|
5
|
-
export const TEST_DATABASE_CONFIG = {
|
|
6
|
-
host: 'localhost',
|
|
7
|
-
port: 5432,
|
|
8
|
-
user: 'geekmidas',
|
|
9
|
-
password: 'geekmidas',
|
|
10
|
-
database: TEST_DATABASE_NAME,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export default async function globalSetup() {
|
|
14
|
-
const adminConfig = {
|
|
15
|
-
host: TEST_DATABASE_CONFIG.host,
|
|
16
|
-
port: TEST_DATABASE_CONFIG.port,
|
|
17
|
-
user: TEST_DATABASE_CONFIG.user,
|
|
18
|
-
password: TEST_DATABASE_CONFIG.password,
|
|
19
|
-
database: 'postgres', // Connect to default postgres database
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const client = new Client(adminConfig);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
await client.connect();
|
|
26
|
-
|
|
27
|
-
// Check if test database exists
|
|
28
|
-
const result = await client.query(
|
|
29
|
-
`SELECT * FROM pg_catalog.pg_database WHERE datname = $1`,
|
|
30
|
-
[TEST_DATABASE_NAME],
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
// Create test database if it doesn't exist
|
|
34
|
-
if (result.rowCount === 0) {
|
|
35
|
-
await client.query(`CREATE DATABASE "${TEST_DATABASE_NAME}"`);
|
|
36
|
-
} else {
|
|
37
|
-
}
|
|
38
|
-
} finally {
|
|
39
|
-
await client.end();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Return cleanup function that drops the database
|
|
43
|
-
return async () => {
|
|
44
|
-
const cleanupClient = new Client(adminConfig);
|
|
45
|
-
try {
|
|
46
|
-
await cleanupClient.connect();
|
|
47
|
-
await cleanupClient.query(
|
|
48
|
-
`DROP DATABASE IF EXISTS "${TEST_DATABASE_NAME}"`,
|
|
49
|
-
);
|
|
50
|
-
} finally {
|
|
51
|
-
await cleanupClient.end();
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
}
|
package/test/helpers.ts
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import type { Knex } from 'knex';
|
|
2
|
-
import knex from 'knex';
|
|
3
|
-
import {
|
|
4
|
-
CamelCasePlugin,
|
|
5
|
-
type ControlledTransaction,
|
|
6
|
-
Kysely,
|
|
7
|
-
PostgresDialect,
|
|
8
|
-
sql,
|
|
9
|
-
} from 'kysely';
|
|
10
|
-
// `Migrator` moved to the 'kysely/migration' subpath in kysely 0.29+.
|
|
11
|
-
import type { Migrator } from 'kysely/migration';
|
|
12
|
-
import pg from 'pg';
|
|
13
|
-
import { TEST_DATABASE_CONFIG } from './globalSetup';
|
|
14
|
-
|
|
15
|
-
export interface TestDatabase {
|
|
16
|
-
users: {
|
|
17
|
-
id: number;
|
|
18
|
-
name: string;
|
|
19
|
-
email: string;
|
|
20
|
-
role?: 'admin' | 'user';
|
|
21
|
-
createdAt: Date;
|
|
22
|
-
updatedAt?: Date;
|
|
23
|
-
};
|
|
24
|
-
posts: {
|
|
25
|
-
id: number;
|
|
26
|
-
title: string;
|
|
27
|
-
content: string;
|
|
28
|
-
userId: number;
|
|
29
|
-
published?: boolean;
|
|
30
|
-
createdAt: Date;
|
|
31
|
-
updatedAt?: Date;
|
|
32
|
-
};
|
|
33
|
-
comments: {
|
|
34
|
-
id: number;
|
|
35
|
-
content: string;
|
|
36
|
-
postId: number;
|
|
37
|
-
userId: number;
|
|
38
|
-
createdAt: Date;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Creates a Kysely database instance for testing
|
|
44
|
-
*/
|
|
45
|
-
export function createKyselyDb(): Kysely<TestDatabase> {
|
|
46
|
-
return new Kysely({
|
|
47
|
-
dialect: new PostgresDialect({
|
|
48
|
-
pool: new pg.Pool(TEST_DATABASE_CONFIG),
|
|
49
|
-
}),
|
|
50
|
-
plugins: [new CamelCasePlugin()],
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Creates a Knex database instance for testing
|
|
56
|
-
*/
|
|
57
|
-
export function createKnexDb(): Knex {
|
|
58
|
-
return knex({
|
|
59
|
-
client: 'pg',
|
|
60
|
-
connection: TEST_DATABASE_CONFIG,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Test setup helper that creates tables within a transaction and returns cleanup
|
|
66
|
-
*/
|
|
67
|
-
export async function setupKyselyTest(db: Kysely<TestDatabase>): Promise<{
|
|
68
|
-
db: Kysely<TestDatabase>;
|
|
69
|
-
trx: ControlledTransaction<TestDatabase, []>;
|
|
70
|
-
cleanup: () => Promise<void>;
|
|
71
|
-
}> {
|
|
72
|
-
const trx = await db.startTransaction().execute();
|
|
73
|
-
|
|
74
|
-
// Create tables within the transaction
|
|
75
|
-
await createTestTables(db);
|
|
76
|
-
|
|
77
|
-
const cleanup = async () => {
|
|
78
|
-
await trx.rollback().execute();
|
|
79
|
-
await db.destroy();
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
return { db, trx, cleanup };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Test setup helper for Knex/Objection that creates tables within a transaction
|
|
87
|
-
*/
|
|
88
|
-
export async function setupKnexTest(): Promise<{
|
|
89
|
-
db: Knex;
|
|
90
|
-
trx: Knex.Transaction;
|
|
91
|
-
cleanup: () => Promise<void>;
|
|
92
|
-
}> {
|
|
93
|
-
const db = createKnexDb();
|
|
94
|
-
const trx = await db.transaction();
|
|
95
|
-
|
|
96
|
-
// Create tables within the transaction
|
|
97
|
-
await createTestTablesKnex(trx);
|
|
98
|
-
|
|
99
|
-
const cleanup = async () => {
|
|
100
|
-
await trx.rollback();
|
|
101
|
-
await db.destroy();
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
return { db, trx, cleanup };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Creates test tables using Kysely
|
|
109
|
-
*/
|
|
110
|
-
export async function createTestTables(
|
|
111
|
-
db: Kysely<TestDatabase> | ControlledTransaction<TestDatabase, []>,
|
|
112
|
-
): Promise<void> {
|
|
113
|
-
// Create users table
|
|
114
|
-
await db.schema
|
|
115
|
-
.createTable('users')
|
|
116
|
-
.addColumn('id', 'bigserial', (col) => col.primaryKey())
|
|
117
|
-
.addColumn('name', 'varchar', (col) => col.notNull())
|
|
118
|
-
.addColumn('email', 'varchar', (col) => col.notNull().unique())
|
|
119
|
-
.addColumn('role', 'varchar', (col) => col.defaultTo('user'))
|
|
120
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
121
|
-
col.defaultTo(sql`now()`).notNull(),
|
|
122
|
-
)
|
|
123
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
124
|
-
col.defaultTo(sql`now()`).notNull(),
|
|
125
|
-
)
|
|
126
|
-
.execute();
|
|
127
|
-
|
|
128
|
-
// Create posts table
|
|
129
|
-
await db.schema
|
|
130
|
-
.createTable('posts')
|
|
131
|
-
.addColumn('id', 'bigserial', (col) => col.primaryKey())
|
|
132
|
-
.addColumn('title', 'varchar', (col) => col.notNull())
|
|
133
|
-
.addColumn('content', 'text', (col) => col.notNull())
|
|
134
|
-
.addColumn('user_id', 'bigint', (col) =>
|
|
135
|
-
col.notNull().references('users.id').onDelete('cascade'),
|
|
136
|
-
)
|
|
137
|
-
.addColumn('published', 'boolean', (col) => col.defaultTo(false))
|
|
138
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
139
|
-
col.defaultTo(sql`now()`).notNull(),
|
|
140
|
-
)
|
|
141
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
142
|
-
col.defaultTo(sql`now()`).notNull(),
|
|
143
|
-
)
|
|
144
|
-
.execute();
|
|
145
|
-
|
|
146
|
-
// Create comments table
|
|
147
|
-
await db.schema
|
|
148
|
-
.createTable('comments')
|
|
149
|
-
.addColumn('id', 'bigserial', (col) => col.primaryKey())
|
|
150
|
-
.addColumn('content', 'text', (col) => col.notNull())
|
|
151
|
-
.addColumn('post_id', 'bigint', (col) =>
|
|
152
|
-
col.notNull().references('posts.id').onDelete('cascade'),
|
|
153
|
-
)
|
|
154
|
-
.addColumn('user_id', 'bigint', (col) =>
|
|
155
|
-
col.notNull().references('users.id').onDelete('cascade'),
|
|
156
|
-
)
|
|
157
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
158
|
-
col.defaultTo(sql`now()`).notNull(),
|
|
159
|
-
)
|
|
160
|
-
.execute();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Creates test tables using Knex
|
|
165
|
-
*/
|
|
166
|
-
export async function createTestTablesKnex(
|
|
167
|
-
trx: Knex.Transaction,
|
|
168
|
-
): Promise<void> {
|
|
169
|
-
// Create simple users table for testing factory
|
|
170
|
-
await trx.raw(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
|
|
171
|
-
await trx.schema.createTableIfNotExists('users', (table) => {
|
|
172
|
-
table.uuid('id').primary().defaultTo(trx.raw('uuid_generate_v4()'));
|
|
173
|
-
table.string('name').notNullable();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
// Create simple posts table for testing factory
|
|
177
|
-
await trx.schema.createTableIfNotExists('posts', (table) => {
|
|
178
|
-
table.uuid('id').primary().defaultTo(trx.raw('uuid_generate_v4()'));
|
|
179
|
-
table.string('title').notNullable();
|
|
180
|
-
table
|
|
181
|
-
.uuid('user_id')
|
|
182
|
-
.notNullable()
|
|
183
|
-
.references('id')
|
|
184
|
-
.inTable('users')
|
|
185
|
-
.onDelete('cascade');
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Create simple comments table for testing factory
|
|
189
|
-
await trx.schema.createTableIfNotExists('comments', (table) => {
|
|
190
|
-
table.uuid('id').primary().defaultTo(trx.raw('uuid_generate_v4()'));
|
|
191
|
-
table.text('content').notNullable();
|
|
192
|
-
table
|
|
193
|
-
.uuid('post_id')
|
|
194
|
-
.notNullable()
|
|
195
|
-
.references('id')
|
|
196
|
-
.inTable('posts')
|
|
197
|
-
.onDelete('cascade');
|
|
198
|
-
table
|
|
199
|
-
.uuid('user_id')
|
|
200
|
-
.notNullable()
|
|
201
|
-
.references('id')
|
|
202
|
-
.inTable('users')
|
|
203
|
-
.onDelete('cascade');
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Helper for PostgresMigrator tests - creates a separate test database
|
|
209
|
-
*/
|
|
210
|
-
export async function createTestDatabase(
|
|
211
|
-
dbName: string,
|
|
212
|
-
): Promise<() => Promise<void>> {
|
|
213
|
-
const adminConfig = {
|
|
214
|
-
host: TEST_DATABASE_CONFIG.host,
|
|
215
|
-
port: TEST_DATABASE_CONFIG.port,
|
|
216
|
-
user: TEST_DATABASE_CONFIG.user,
|
|
217
|
-
password: TEST_DATABASE_CONFIG.password,
|
|
218
|
-
database: 'postgres',
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const client = new pg.Client(adminConfig);
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
await client.connect();
|
|
225
|
-
|
|
226
|
-
// Drop database if it exists, then create it
|
|
227
|
-
await client.query(`DROP DATABASE IF EXISTS "${dbName}"`);
|
|
228
|
-
await client.query(`CREATE DATABASE "${dbName}"`);
|
|
229
|
-
} finally {
|
|
230
|
-
await client.end();
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Return cleanup function
|
|
234
|
-
return async () => {
|
|
235
|
-
const cleanupClient = new pg.Client(adminConfig);
|
|
236
|
-
try {
|
|
237
|
-
await cleanupClient.connect();
|
|
238
|
-
await cleanupClient.query(`DROP DATABASE IF EXISTS "${dbName}"`);
|
|
239
|
-
} finally {
|
|
240
|
-
await cleanupClient.end();
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Creates a Kysely migrator for testing
|
|
247
|
-
*/
|
|
248
|
-
export function createTestMigrator(
|
|
249
|
-
db: Kysely<any>,
|
|
250
|
-
migrations: Record<
|
|
251
|
-
string,
|
|
252
|
-
{
|
|
253
|
-
up: (db: Kysely<any>) => Promise<void>;
|
|
254
|
-
down: (db: Kysely<any>) => Promise<void>;
|
|
255
|
-
}
|
|
256
|
-
>,
|
|
257
|
-
): Migrator {
|
|
258
|
-
const { Migrator } = require('kysely/migration');
|
|
259
|
-
|
|
260
|
-
return new Migrator({
|
|
261
|
-
db,
|
|
262
|
-
provider: {
|
|
263
|
-
async getMigrations() {
|
|
264
|
-
return migrations;
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type Kysely, sql } from 'kysely';
|
|
2
|
-
|
|
3
|
-
// `any` is required here since migrations should be frozen in time. alternatively, keep a "snapshot" db interface.
|
|
4
|
-
export async function up(db: Kysely<any>): Promise<void> {
|
|
5
|
-
await db.schema
|
|
6
|
-
.createTable('users')
|
|
7
|
-
.addColumn('id', 'bigserial', (col) => col.primaryKey())
|
|
8
|
-
.addColumn('email', 'varchar', (col) => col.notNull().unique())
|
|
9
|
-
.addColumn('name', 'varchar', (col) => col.notNull())
|
|
10
|
-
.addColumn('created_at', 'timestamp', (col) =>
|
|
11
|
-
col.defaultTo(sql`now()`).notNull(),
|
|
12
|
-
)
|
|
13
|
-
.addColumn('updated_at', 'timestamp', (col) =>
|
|
14
|
-
col.defaultTo(sql`now()`).notNull(),
|
|
15
|
-
)
|
|
16
|
-
.execute();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// `any` is required here since migrations should be frozen in time. alternatively, keep a "snapshot" db interface.
|
|
20
|
-
export async function down(db: Kysely<any>): Promise<void> {
|
|
21
|
-
await db.schema.dropTable('users').execute();
|
|
22
|
-
}
|
package/tsconfig.json
DELETED