@geekmidas/testkit 0.0.6 → 0.0.7
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-WMhTNZ9S.cjs +56 -0
- package/dist/Factory-z2m01hMj.mjs +50 -0
- package/dist/Factory.cjs +1 -1
- package/dist/Factory.mjs +1 -1
- package/dist/KyselyFactory-Bdq1s1Go.cjs +215 -0
- package/dist/KyselyFactory-ELiHgHVv.mjs +210 -0
- package/dist/KyselyFactory.cjs +3 -3
- package/dist/KyselyFactory.mjs +3 -3
- package/dist/ObjectionFactory-89p-FFEw.mjs +178 -0
- package/dist/ObjectionFactory-C47B03Ot.cjs +183 -0
- package/dist/ObjectionFactory.cjs +2 -2
- package/dist/ObjectionFactory.mjs +2 -2
- package/dist/PostgresKyselyMigrator-Bs31emFd.cjs +87 -0
- package/dist/PostgresKyselyMigrator-ChIpZFYB.mjs +81 -0
- package/dist/PostgresKyselyMigrator.cjs +2 -2
- package/dist/PostgresKyselyMigrator.mjs +2 -2
- package/dist/PostgresMigrator-BtAWdLss.cjs +151 -0
- package/dist/PostgresMigrator-BzqksJcW.mjs +145 -0
- package/dist/PostgresMigrator.cjs +1 -1
- package/dist/PostgresMigrator.mjs +1 -1
- package/dist/VitestKyselyTransactionIsolator-AfxPJEwR.mjs +58 -0
- package/dist/VitestKyselyTransactionIsolator-YWnSJiIH.cjs +63 -0
- package/dist/VitestKyselyTransactionIsolator.cjs +2 -2
- package/dist/VitestKyselyTransactionIsolator.mjs +2 -2
- package/dist/VitestObjectionTransactionIsolator-0uX6DW5G.cjs +66 -0
- package/dist/VitestObjectionTransactionIsolator-BZRYy8iW.mjs +61 -0
- package/dist/VitestObjectionTransactionIsolator.cjs +4 -0
- package/dist/VitestObjectionTransactionIsolator.mjs +4 -0
- package/dist/VitestTransactionIsolator-DcOz0LZF.cjs +129 -0
- package/dist/VitestTransactionIsolator-kFL36T8x.mjs +117 -0
- package/dist/VitestTransactionIsolator.cjs +1 -1
- package/dist/VitestTransactionIsolator.mjs +1 -1
- package/dist/__tests__/Factory.spec.cjs +1 -1
- package/dist/__tests__/Factory.spec.mjs +1 -1
- package/dist/__tests__/KyselyFactory.spec.cjs +10 -10
- package/dist/__tests__/KyselyFactory.spec.mjs +10 -10
- package/dist/__tests__/ObjectionFactory.spec.cjs +3 -3
- package/dist/__tests__/ObjectionFactory.spec.mjs +3 -3
- package/dist/__tests__/PostgresMigrator.spec.cjs +2 -2
- package/dist/__tests__/PostgresMigrator.spec.mjs +2 -2
- package/dist/__tests__/faker.spec.cjs +1 -1
- package/dist/__tests__/faker.spec.mjs +1 -1
- package/dist/__tests__/integration.spec.cjs +10 -10
- package/dist/__tests__/integration.spec.mjs +10 -10
- package/dist/example.cjs +3 -3
- package/dist/example.mjs +3 -3
- package/dist/faker-CxKkEeYi.mjs +227 -0
- package/dist/faker-SMN4ira4.cjs +263 -0
- package/dist/faker.cjs +1 -1
- package/dist/faker.mjs +1 -1
- package/dist/helpers-CKMlwSYT.mjs +47 -0
- package/dist/helpers-H4hO5SZR.cjs +53 -0
- package/dist/helpers.cjs +1 -1
- package/dist/helpers.mjs +1 -1
- package/dist/kysely-B-GOhABm.cjs +72 -0
- package/dist/kysely-CqfoKVXs.mjs +67 -0
- package/dist/kysely.cjs +10 -8
- package/dist/kysely.mjs +9 -9
- package/dist/objection.cjs +86 -3
- package/dist/objection.mjs +83 -3
- package/package.json +2 -2
- package/src/Factory.ts +97 -0
- package/src/KyselyFactory.ts +180 -0
- package/src/ObjectionFactory.ts +145 -3
- package/src/PostgresKyselyMigrator.ts +54 -0
- package/src/PostgresMigrator.ts +90 -0
- package/src/VitestKyselyTransactionIsolator.ts +46 -0
- package/src/VitestObjectionTransactionIsolator.ts +73 -0
- package/src/VitestTransactionIsolator.ts +95 -0
- package/src/faker.ts +158 -7
- package/src/helpers.ts +34 -0
- package/src/kysely.ts +63 -0
- package/src/objection.ts +95 -0
- package/dist/Factory-DREHoms3.cjs +0 -15
- package/dist/Factory-DlzMkMzb.mjs +0 -9
- package/dist/KyselyFactory-BX7Kv2uP.cjs +0 -65
- package/dist/KyselyFactory-pOMOFQWE.mjs +0 -60
- package/dist/ObjectionFactory-BlkzSEqo.cjs +0 -41
- package/dist/ObjectionFactory-ChuX8sZN.mjs +0 -36
- package/dist/PostgresKyselyMigrator-D8fm35-s.mjs +0 -27
- package/dist/PostgresKyselyMigrator-JTY2LfwD.cjs +0 -33
- package/dist/PostgresMigrator-Bz-tnjB6.cjs +0 -67
- package/dist/PostgresMigrator-CEoRKTdq.mjs +0 -61
- package/dist/VitestKyselyTransactionIsolator-D-qpeVKO.mjs +0 -12
- package/dist/VitestKyselyTransactionIsolator-jF6Ohyu_.cjs +0 -17
- package/dist/VitestTransactionIsolator-BK9UsrKt.cjs +0 -53
- package/dist/VitestTransactionIsolator-e-R3p_X8.mjs +0 -41
- package/dist/faker-BwaXA_RF.mjs +0 -85
- package/dist/faker-caz-8zt8.cjs +0 -121
- package/dist/helpers-DN4sJO4i.mjs +0 -13
- package/dist/helpers-DOtYCEvZ.cjs +0 -19
- package/dist/kysely-C1-aHdnU.mjs +0 -11
- package/dist/kysely-DL3C2eM4.cjs +0 -16
- /package/dist/{helpers-B2CfbaTC.cjs → helpers-Bnm3Jy9X.cjs} +0 -0
- /package/dist/{helpers-Rf5F71r9.mjs → helpers-CukcFAU9.mjs} +0 -0
package/src/ObjectionFactory.ts
CHANGED
|
@@ -1,14 +1,65 @@
|
|
|
1
1
|
import type { Knex } from 'knex';
|
|
2
2
|
import { Factory, type FactorySeed } from './Factory.ts';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Factory implementation for Objection.js ORM, providing test data creation utilities.
|
|
6
|
+
* Extends the base Factory class with Objection.js-specific database operations.
|
|
7
|
+
*
|
|
8
|
+
* @template Builders - Record of builder functions for creating entities
|
|
9
|
+
* @template Seeds - Record of seed functions for complex test scenarios
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // Define your models with Objection.js
|
|
14
|
+
* class User extends Model {
|
|
15
|
+
* static tableName = 'users';
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* // Create builders
|
|
19
|
+
* const builders = {
|
|
20
|
+
* user: (attrs) => User.fromJson({
|
|
21
|
+
* id: faker.string.uuid(),
|
|
22
|
+
* name: faker.person.fullName(),
|
|
23
|
+
* email: faker.internet.email(),
|
|
24
|
+
* ...attrs
|
|
25
|
+
* }),
|
|
26
|
+
* post: (attrs) => Post.fromJson({
|
|
27
|
+
* title: 'Test Post',
|
|
28
|
+
* content: 'Test content',
|
|
29
|
+
* ...attrs
|
|
30
|
+
* })
|
|
31
|
+
* };
|
|
32
|
+
*
|
|
33
|
+
* // Create factory instance
|
|
34
|
+
* const factory = new ObjectionFactory(builders, seeds, knex);
|
|
35
|
+
*
|
|
36
|
+
* // Use in tests
|
|
37
|
+
* const user = await factory.insert('user', { name: 'John Doe' });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
4
40
|
export class ObjectionFactory<
|
|
5
41
|
Builders extends Record<string, any>,
|
|
6
42
|
Seeds extends Record<string, any>,
|
|
7
43
|
> extends Factory<Builders, Seeds> {
|
|
44
|
+
/**
|
|
45
|
+
* Creates a typed seed function with proper type inference.
|
|
46
|
+
* Inherits from the base Factory class implementation.
|
|
47
|
+
*
|
|
48
|
+
* @template Seed - The seed function type
|
|
49
|
+
* @param seedFn - The seed function to wrap
|
|
50
|
+
* @returns The same seed function with proper typing
|
|
51
|
+
*/
|
|
8
52
|
static createSeed<Seed extends FactorySeed>(seedFn: Seed): Seed {
|
|
9
53
|
return Factory.createSeed(seedFn);
|
|
10
54
|
}
|
|
11
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Creates a new ObjectionFactory instance.
|
|
58
|
+
*
|
|
59
|
+
* @param builders - Record of builder functions for creating individual entities
|
|
60
|
+
* @param seeds - Record of seed functions for creating complex test scenarios
|
|
61
|
+
* @param db - Knex database connection instance
|
|
62
|
+
*/
|
|
12
63
|
constructor(
|
|
13
64
|
private builders: Builders,
|
|
14
65
|
private seeds: Seeds,
|
|
@@ -17,7 +68,37 @@ export class ObjectionFactory<
|
|
|
17
68
|
super();
|
|
18
69
|
}
|
|
19
70
|
|
|
20
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Inserts a single record into the database using the specified builder.
|
|
73
|
+
* Uses Objection.js's insertGraph method to handle nested relations.
|
|
74
|
+
*
|
|
75
|
+
* @param factory - The name of the builder to use
|
|
76
|
+
* @param attrs - Optional attributes to override builder defaults
|
|
77
|
+
* @returns A promise resolving to the inserted record with all relations
|
|
78
|
+
* @throws Error if the specified builder doesn't exist
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* // Insert with defaults
|
|
83
|
+
* const user = await factory.insert('user');
|
|
84
|
+
*
|
|
85
|
+
* // Insert with overrides
|
|
86
|
+
* const adminUser = await factory.insert('user', {
|
|
87
|
+
* email: 'admin@example.com',
|
|
88
|
+
* role: 'admin'
|
|
89
|
+
* });
|
|
90
|
+
*
|
|
91
|
+
* // Insert with nested relations
|
|
92
|
+
* const userWithProfile = await factory.insert('user', {
|
|
93
|
+
* name: 'John Doe',
|
|
94
|
+
* profile: {
|
|
95
|
+
* bio: 'Software Developer',
|
|
96
|
+
* avatar: 'avatar.jpg'
|
|
97
|
+
* }
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
insert(factory: keyof Builders, attrs: any = {}) {
|
|
21
102
|
if (!(factory in this.builders)) {
|
|
22
103
|
throw new Error(
|
|
23
104
|
`Factory "${
|
|
@@ -30,7 +111,36 @@ export class ObjectionFactory<
|
|
|
30
111
|
return record.$query(this.db).insertGraph(record).execute();
|
|
31
112
|
}) as any;
|
|
32
113
|
}
|
|
33
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Inserts multiple records into the database using the specified builder.
|
|
116
|
+
* Supports both static attributes and dynamic attribute generation via a function.
|
|
117
|
+
*
|
|
118
|
+
* @param count - The number of records to insert
|
|
119
|
+
* @param builderName - The name of the builder to use
|
|
120
|
+
* @param attrs - Static attributes or a function that generates attributes for each record
|
|
121
|
+
* @returns A promise resolving to an array of inserted records
|
|
122
|
+
* @throws Error if the specified builder doesn't exist
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* // Insert multiple with same attributes
|
|
127
|
+
* const users = await factory.insertMany(5, 'user', { role: 'member' });
|
|
128
|
+
*
|
|
129
|
+
* // Insert multiple with dynamic attributes
|
|
130
|
+
* const posts = await factory.insertMany(10, 'post', (idx) => ({
|
|
131
|
+
* title: `Post ${idx + 1}`,
|
|
132
|
+
* content: `Content for post ${idx + 1}`,
|
|
133
|
+
* publishedAt: new Date()
|
|
134
|
+
* }));
|
|
135
|
+
*
|
|
136
|
+
* // Create users with sequential emails
|
|
137
|
+
* const admins = await factory.insertMany(3, 'user', (idx) => ({
|
|
138
|
+
* email: `admin${idx + 1}@example.com`,
|
|
139
|
+
* role: 'admin'
|
|
140
|
+
* }));
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
insertMany(count: number, builderName: keyof Builders, attrs: any = {}) {
|
|
34
144
|
if (!(builderName in this.builders)) {
|
|
35
145
|
throw new Error(
|
|
36
146
|
`Builder "${
|
|
@@ -52,7 +162,39 @@ export class ObjectionFactory<
|
|
|
52
162
|
|
|
53
163
|
return Promise.all(records);
|
|
54
164
|
}
|
|
55
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Executes a seed function to create complex test scenarios with multiple related records.
|
|
167
|
+
* Seeds are useful for setting up complete test environments with realistic data relationships.
|
|
168
|
+
*
|
|
169
|
+
* @param seedName - The name of the seed to execute
|
|
170
|
+
* @param attrs - Optional configuration attributes for the seed
|
|
171
|
+
* @returns The result of the seed function (typically the primary record created)
|
|
172
|
+
* @throws Error if the specified seed doesn't exist
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* // Execute a simple seed
|
|
177
|
+
* const user = await factory.seed('userWithProfile');
|
|
178
|
+
*
|
|
179
|
+
* // Execute a seed with configuration
|
|
180
|
+
* const author = await factory.seed('authorWithBooks', {
|
|
181
|
+
* bookCount: 5,
|
|
182
|
+
* includeReviews: true
|
|
183
|
+
* });
|
|
184
|
+
*
|
|
185
|
+
* // Use seed result in tests with Objection.js relations
|
|
186
|
+
* const company = await factory.seed('companyWithDepartments', {
|
|
187
|
+
* departmentCount: 3,
|
|
188
|
+
* employeesPerDepartment: 10
|
|
189
|
+
* });
|
|
190
|
+
*
|
|
191
|
+
* // Access eager loaded relations
|
|
192
|
+
* const companyWithRelations = await Company.query()
|
|
193
|
+
* .findById(company.id)
|
|
194
|
+
* .withGraphFetched('[departments.employees]');
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
seed(seedName: keyof Seeds, attrs: any = {}) {
|
|
56
198
|
if (!(seedName in this.seeds)) {
|
|
57
199
|
throw new Error(
|
|
58
200
|
`Seed "${
|
|
@@ -1,9 +1,56 @@
|
|
|
1
1
|
import { type Kysely, type MigrationProvider, Migrator } from 'kysely';
|
|
2
2
|
import { PostgresMigrator } from './PostgresMigrator';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Default logger instance for migration operations.
|
|
6
|
+
*/
|
|
4
7
|
const logger = console;
|
|
5
8
|
|
|
9
|
+
/**
|
|
10
|
+
* PostgreSQL migrator implementation for Kysely ORM.
|
|
11
|
+
* Extends PostgresMigrator to provide Kysely-specific migration functionality.
|
|
12
|
+
* Automatically creates test databases and applies migrations for testing environments.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { FileMigrationProvider } from 'kysely';
|
|
17
|
+
* import { PostgresKyselyMigrator } from '@geekmidas/testkit';
|
|
18
|
+
*
|
|
19
|
+
* // Create migration provider
|
|
20
|
+
* const provider = new FileMigrationProvider({
|
|
21
|
+
* fs: require('fs'),
|
|
22
|
+
* path: require('path'),
|
|
23
|
+
* migrationFolder: path.join(__dirname, 'migrations')
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Create Kysely instance
|
|
27
|
+
* const db = new Kysely<Database>({
|
|
28
|
+
* dialect: new PostgresDialect({
|
|
29
|
+
* pool: new Pool({ connectionString: uri })
|
|
30
|
+
* })
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Create and use migrator
|
|
34
|
+
* const migrator = new PostgresKyselyMigrator({
|
|
35
|
+
* uri: 'postgresql://localhost:5432/test_db',
|
|
36
|
+
* db,
|
|
37
|
+
* provider
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* const cleanup = await migrator.start();
|
|
41
|
+
* // Run tests...
|
|
42
|
+
* await cleanup();
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
6
45
|
export class PostgresKyselyMigrator extends PostgresMigrator {
|
|
46
|
+
/**
|
|
47
|
+
* Creates a new PostgresKyselyMigrator instance.
|
|
48
|
+
*
|
|
49
|
+
* @param options - Configuration options
|
|
50
|
+
* @param options.uri - PostgreSQL connection URI
|
|
51
|
+
* @param options.db - Kysely database instance
|
|
52
|
+
* @param options.provider - Migration provider for locating migration files
|
|
53
|
+
*/
|
|
7
54
|
constructor(
|
|
8
55
|
private options: {
|
|
9
56
|
uri: string;
|
|
@@ -14,6 +61,13 @@ export class PostgresKyselyMigrator extends PostgresMigrator {
|
|
|
14
61
|
super(options.uri);
|
|
15
62
|
}
|
|
16
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Executes Kysely migrations to the latest version.
|
|
66
|
+
* Implements the abstract migrate() method from PostgresMigrator.
|
|
67
|
+
*
|
|
68
|
+
* @throws Error if migrations fail to apply
|
|
69
|
+
* @returns Promise that resolves when all migrations are applied
|
|
70
|
+
*/
|
|
17
71
|
async migrate(): Promise<void> {
|
|
18
72
|
const migrator = new Migrator({
|
|
19
73
|
db: this.options.db,
|
package/src/PostgresMigrator.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { Client } from 'pg';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Creates a PostgreSQL client connected to the 'postgres' database.
|
|
5
|
+
* Extracts connection details from the provided URI.
|
|
6
|
+
*
|
|
7
|
+
* @param uri - PostgreSQL connection URI
|
|
8
|
+
* @returns Object containing the target database name and client instance
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const { database, db } = await setupClient('postgresql://user:pass@localhost:5432/mydb');
|
|
13
|
+
* // database = 'mydb'
|
|
14
|
+
* // db = Client instance connected to 'postgres' database
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
3
17
|
async function setupClient(uri: string) {
|
|
4
18
|
const url = new URL(uri);
|
|
5
19
|
|
|
@@ -18,13 +32,59 @@ async function setupClient(uri: string) {
|
|
|
18
32
|
return { database, db };
|
|
19
33
|
}
|
|
20
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Default logger instance for migration operations.
|
|
37
|
+
*/
|
|
21
38
|
const logger = console;
|
|
22
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Abstract base class for PostgreSQL database migration utilities.
|
|
42
|
+
* Provides database creation, migration, and cleanup functionality for testing.
|
|
43
|
+
* Subclasses must implement the migrate() method to define migration logic.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* class MyMigrator extends PostgresMigrator {
|
|
48
|
+
* async migrate(): Promise<void> {
|
|
49
|
+
* // Run your migrations here
|
|
50
|
+
* await this.runMigrations();
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* // Use in tests
|
|
55
|
+
* const migrator = new MyMigrator('postgresql://localhost:5432/test_db');
|
|
56
|
+
* const cleanup = await migrator.start();
|
|
57
|
+
*
|
|
58
|
+
* // Run tests...
|
|
59
|
+
*
|
|
60
|
+
* // Clean up
|
|
61
|
+
* await cleanup();
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
23
64
|
export abstract class PostgresMigrator {
|
|
65
|
+
/**
|
|
66
|
+
* Creates a new PostgresMigrator instance.
|
|
67
|
+
*
|
|
68
|
+
* @param uri - PostgreSQL connection URI
|
|
69
|
+
*/
|
|
24
70
|
constructor(private uri: string) {}
|
|
25
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Abstract method to be implemented by subclasses.
|
|
74
|
+
* Should contain the migration logic for setting up database schema.
|
|
75
|
+
*
|
|
76
|
+
* @returns Promise that resolves when migrations are complete
|
|
77
|
+
*/
|
|
26
78
|
abstract migrate(): Promise<void>;
|
|
27
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Creates a PostgreSQL database if it doesn't already exist.
|
|
82
|
+
* Connects to the 'postgres' database to check and create the target database.
|
|
83
|
+
*
|
|
84
|
+
* @param uri - PostgreSQL connection URI
|
|
85
|
+
* @returns Object indicating whether the database already existed
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
28
88
|
private static async create(
|
|
29
89
|
uri: string,
|
|
30
90
|
): Promise<{ alreadyExisted: boolean }> {
|
|
@@ -47,6 +107,14 @@ export abstract class PostgresMigrator {
|
|
|
47
107
|
}
|
|
48
108
|
}
|
|
49
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Drops a PostgreSQL database.
|
|
112
|
+
* Used for cleanup after tests are complete.
|
|
113
|
+
*
|
|
114
|
+
* @param uri - PostgreSQL connection URI
|
|
115
|
+
* @throws Error if database cannot be dropped
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
50
118
|
private static async drop(uri: string): Promise<void> {
|
|
51
119
|
const { database, db } = await setupClient(uri);
|
|
52
120
|
try {
|
|
@@ -57,6 +125,28 @@ export abstract class PostgresMigrator {
|
|
|
57
125
|
}
|
|
58
126
|
}
|
|
59
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Starts the migration process by creating the database and running migrations.
|
|
130
|
+
* Returns a cleanup function that will drop the database when called.
|
|
131
|
+
*
|
|
132
|
+
* @returns Async cleanup function that drops the created database
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const migrator = new MyMigrator('postgresql://localhost:5432/test_db');
|
|
137
|
+
*
|
|
138
|
+
* // Start migrations and get cleanup function
|
|
139
|
+
* const cleanup = await migrator.start();
|
|
140
|
+
*
|
|
141
|
+
* try {
|
|
142
|
+
* // Run your tests here
|
|
143
|
+
* await runTests();
|
|
144
|
+
* } finally {
|
|
145
|
+
* // Always clean up
|
|
146
|
+
* await cleanup();
|
|
147
|
+
* }
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
60
150
|
async start() {
|
|
61
151
|
const { database, db } = await setupClient(this.uri);
|
|
62
152
|
try {
|
|
@@ -4,12 +4,58 @@ import {
|
|
|
4
4
|
VitestPostgresTransactionIsolator,
|
|
5
5
|
} from './VitestTransactionIsolator';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Kysely-specific implementation of the Vitest transaction isolator.
|
|
9
|
+
* Provides automatic transaction rollback for test isolation using Kysely's transaction API.
|
|
10
|
+
* Each test runs within a database transaction that is rolled back after completion,
|
|
11
|
+
* ensuring a clean state between tests without the overhead of recreating data.
|
|
12
|
+
*
|
|
13
|
+
* @template Database - The database schema type
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { VitestKyselyTransactionIsolator } from '@geekmidas/testkit';
|
|
18
|
+
* import { db } from './database';
|
|
19
|
+
*
|
|
20
|
+
* // Create isolator instance
|
|
21
|
+
* const isolator = new VitestKyselyTransactionIsolator<Database>();
|
|
22
|
+
*
|
|
23
|
+
* // In your test setup
|
|
24
|
+
* beforeEach(async () => {
|
|
25
|
+
* await isolator.start(db);
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* afterEach(async () => {
|
|
29
|
+
* await isolator.rollback();
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Tests run in isolated transactions
|
|
33
|
+
* it('should create user', async () => {
|
|
34
|
+
* const user = await db.insertInto('users')
|
|
35
|
+
* .values({ name: 'Test User' })
|
|
36
|
+
* .returningAll()
|
|
37
|
+
* .executeTakeFirst();
|
|
38
|
+
*
|
|
39
|
+
* expect(user).toBeDefined();
|
|
40
|
+
* // This data will be rolled back after the test
|
|
41
|
+
* });
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
7
44
|
export class VitestKyselyTransactionIsolator<
|
|
8
45
|
Database,
|
|
9
46
|
> extends VitestPostgresTransactionIsolator<
|
|
10
47
|
Kysely<Database>,
|
|
11
48
|
Transaction<Database>
|
|
12
49
|
> {
|
|
50
|
+
/**
|
|
51
|
+
* Creates a Kysely transaction with the specified isolation level.
|
|
52
|
+
* Implements the abstract transact method from VitestPostgresTransactionIsolator.
|
|
53
|
+
*
|
|
54
|
+
* @param conn - The Kysely database connection
|
|
55
|
+
* @param level - The transaction isolation level
|
|
56
|
+
* @param fn - The function to execute within the transaction
|
|
57
|
+
* @returns Promise that resolves when the transaction completes
|
|
58
|
+
*/
|
|
13
59
|
async transact(
|
|
14
60
|
conn: Kysely<Database>,
|
|
15
61
|
level: IsolationLevel,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import {
|
|
3
|
+
type IsolationLevel,
|
|
4
|
+
VitestPostgresTransactionIsolator,
|
|
5
|
+
} from './VitestTransactionIsolator';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Objection.js-specific implementation of the Vitest transaction isolator.
|
|
9
|
+
* Provides automatic transaction rollback for test isolation using Objection.js and Knex transaction API.
|
|
10
|
+
* Each test runs within a database transaction that is rolled back after completion,
|
|
11
|
+
* ensuring a clean state between tests without the overhead of recreating data.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { VitestObjectionTransactionIsolator } from '@geekmidas/testkit';
|
|
16
|
+
* import { knex } from './database';
|
|
17
|
+
* import { User } from './models';
|
|
18
|
+
* import { test } from 'vitest';
|
|
19
|
+
*
|
|
20
|
+
* // Create isolator instance
|
|
21
|
+
* const isolator = new VitestObjectionTransactionIsolator(test);
|
|
22
|
+
*
|
|
23
|
+
* // Use with wrapped test API
|
|
24
|
+
* const isolatedTest = isolator.wrapVitestWithTransaction(knex);
|
|
25
|
+
*
|
|
26
|
+
* isolatedTest('should create user', async ({ trx }) => {
|
|
27
|
+
* const user = await User.query(trx)
|
|
28
|
+
* .insert({ name: 'Test User' });
|
|
29
|
+
*
|
|
30
|
+
* expect(user).toBeDefined();
|
|
31
|
+
* // This data will be rolled back after the test
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export class VitestObjectionTransactionIsolator extends VitestPostgresTransactionIsolator<
|
|
36
|
+
Knex,
|
|
37
|
+
Knex.Transaction
|
|
38
|
+
> {
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Knex transaction with the specified isolation level.
|
|
41
|
+
* Implements the abstract transact method from VitestPostgresTransactionIsolator.
|
|
42
|
+
* This transaction can be used with Objection.js models via Model.query(trx).
|
|
43
|
+
*
|
|
44
|
+
* @param conn - The Knex database connection
|
|
45
|
+
* @param level - The transaction isolation level
|
|
46
|
+
* @param fn - The function to execute within the transaction
|
|
47
|
+
* @returns Promise that resolves when the transaction completes
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* await isolator.transact(knex, IsolationLevel.REPEATABLE_READ, async (trx) => {
|
|
52
|
+
* // Use transaction with Objection models
|
|
53
|
+
* await User.query(trx).insert({ name: 'Test' });
|
|
54
|
+
* await Post.query(trx).where('userId', user.id).delete();
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
async transact(
|
|
59
|
+
conn: Knex,
|
|
60
|
+
level: IsolationLevel,
|
|
61
|
+
fn: (trx: Knex.Transaction) => Promise<void>,
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
const isolationLevel = level.toUpperCase() as Lowercase<IsolationLevel>;
|
|
64
|
+
await conn.transaction(
|
|
65
|
+
async (trx) => {
|
|
66
|
+
await fn(trx);
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
isolationLevel,
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,28 +1,123 @@
|
|
|
1
1
|
import type { TestAPI } from 'vitest';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Type definition for test fixtures that provide transaction access.
|
|
5
|
+
* Used with Vitest's test.extend() API to inject transactions into tests.
|
|
6
|
+
*
|
|
7
|
+
* @template Transaction - The transaction type specific to the database driver
|
|
8
|
+
*/
|
|
3
9
|
export interface DatabaseFixtures<Transaction> {
|
|
10
|
+
/**
|
|
11
|
+
* The database transaction available to the test.
|
|
12
|
+
* All database operations should use this transaction to ensure proper rollback.
|
|
13
|
+
*/
|
|
4
14
|
trx: Transaction;
|
|
5
15
|
}
|
|
6
16
|
|
|
17
|
+
/**
|
|
18
|
+
* PostgreSQL transaction isolation levels.
|
|
19
|
+
* Controls the visibility of concurrent transactions.
|
|
20
|
+
*
|
|
21
|
+
* @see https://www.postgresql.org/docs/current/transaction-iso.html
|
|
22
|
+
*/
|
|
7
23
|
export enum IsolationLevel {
|
|
24
|
+
/**
|
|
25
|
+
* Lowest isolation level. Allows dirty reads.
|
|
26
|
+
* Not recommended for testing.
|
|
27
|
+
*/
|
|
8
28
|
READ_UNCOMMITTED = 'READ UNCOMMITTED',
|
|
29
|
+
/**
|
|
30
|
+
* Default PostgreSQL isolation level.
|
|
31
|
+
* Prevents dirty reads but allows non-repeatable reads.
|
|
32
|
+
*/
|
|
9
33
|
READ_COMMITTED = 'READ COMMITTED',
|
|
34
|
+
/**
|
|
35
|
+
* Prevents dirty reads and non-repeatable reads.
|
|
36
|
+
* Recommended for most test scenarios.
|
|
37
|
+
*/
|
|
10
38
|
REPEATABLE_READ = 'REPEATABLE READ',
|
|
39
|
+
/**
|
|
40
|
+
* Highest isolation level. Prevents all phenomena.
|
|
41
|
+
* May cause performance overhead in tests.
|
|
42
|
+
*/
|
|
11
43
|
SERIALIZABLE = 'SERIALIZABLE',
|
|
12
44
|
}
|
|
13
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Abstract base class for implementing database transaction isolation in Vitest tests.
|
|
48
|
+
* Provides automatic transaction rollback after each test to maintain test isolation.
|
|
49
|
+
* Subclasses must implement the transact() method for their specific database driver.
|
|
50
|
+
*
|
|
51
|
+
* @template Connection - The database connection type
|
|
52
|
+
* @template Transaction - The transaction type
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // Implement for your database driver
|
|
57
|
+
* class MyDatabaseIsolator extends VitestPostgresTransactionIsolator<MyDB, MyTx> {
|
|
58
|
+
* async transact(conn: MyDB, level: IsolationLevel, fn: (tx: MyTx) => Promise<void>) {
|
|
59
|
+
* await conn.transaction(level, fn);
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
*
|
|
63
|
+
* // Use in tests
|
|
64
|
+
* const isolator = new MyDatabaseIsolator(test);
|
|
65
|
+
* const isolatedTest = isolator.wrapVitestWithTransaction(db);
|
|
66
|
+
*
|
|
67
|
+
* isolatedTest('should create user', async ({ trx }) => {
|
|
68
|
+
* await trx.insert('users', { name: 'Test' });
|
|
69
|
+
* // Data is automatically rolled back after test
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
14
73
|
export abstract class VitestPostgresTransactionIsolator<
|
|
15
74
|
Connection,
|
|
16
75
|
Transaction,
|
|
17
76
|
> {
|
|
77
|
+
/**
|
|
78
|
+
* Abstract method to create a transaction with the specified isolation level.
|
|
79
|
+
* Must be implemented by subclasses for specific database drivers.
|
|
80
|
+
*
|
|
81
|
+
* @param conn - The database connection
|
|
82
|
+
* @param isolationLevel - The transaction isolation level
|
|
83
|
+
* @param fn - The function to execute within the transaction
|
|
84
|
+
* @returns Promise that resolves when the transaction completes
|
|
85
|
+
*/
|
|
18
86
|
abstract transact(
|
|
19
87
|
conn: Connection,
|
|
20
88
|
isolationLevel: IsolationLevel,
|
|
21
89
|
fn: (trx: Transaction) => Promise<void>,
|
|
22
90
|
): Promise<void>;
|
|
23
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Creates a new VitestPostgresTransactionIsolator instance.
|
|
94
|
+
*
|
|
95
|
+
* @param api - The Vitest test API (usually the `test` export from vitest)
|
|
96
|
+
*/
|
|
24
97
|
constructor(private readonly api: TestAPI) {}
|
|
25
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Creates a wrapped version of Vitest's test API that provides transaction isolation.
|
|
101
|
+
* Each test will run within a database transaction that is automatically rolled back.
|
|
102
|
+
*
|
|
103
|
+
* @param conn - The database connection to use
|
|
104
|
+
* @param setup - Optional setup function to run within the transaction before each test
|
|
105
|
+
* @param level - The transaction isolation level (defaults to REPEATABLE_READ)
|
|
106
|
+
* @returns A wrapped test API with transaction support
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const isolatedTest = isolator.wrapVitestWithTransaction(db, async (trx) => {
|
|
111
|
+
* // Optional setup: create common test data
|
|
112
|
+
* await trx.insert('settings', { key: 'test', value: 'true' });
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* isolatedTest('test with transaction', async ({ trx }) => {
|
|
116
|
+
* const user = await trx.insert('users', { name: 'Test' });
|
|
117
|
+
* expect(user).toBeDefined();
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
26
121
|
wrapVitestWithTransaction(
|
|
27
122
|
conn: Connection,
|
|
28
123
|
setup?: (trx: Transaction) => Promise<void>,
|