@geekmidas/testkit 0.0.6 → 0.0.8
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 +74 -0
- package/src/VitestTransactionIsolator.ts +95 -0
- package/src/__tests__/VitestObjectionTransactionIsolator.spec.ts +144 -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/test/helpers.ts +3 -1
- 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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Model } from 'objection';
|
|
2
|
+
import { it as base, describe, expect } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { createKnexDb, createTestTablesKnex } from '../../test/helpers';
|
|
5
|
+
import { wrapVitestObjectionTransaction } from '../objection';
|
|
6
|
+
|
|
7
|
+
// Define Objection models for testing
|
|
8
|
+
class User extends Model {
|
|
9
|
+
static get tableName() {
|
|
10
|
+
return 'users';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
id!: number;
|
|
14
|
+
name!: string;
|
|
15
|
+
email!: string;
|
|
16
|
+
role?: string;
|
|
17
|
+
createdAt!: Date;
|
|
18
|
+
updatedAt?: Date;
|
|
19
|
+
|
|
20
|
+
static get relationMappings() {
|
|
21
|
+
return {
|
|
22
|
+
posts: {
|
|
23
|
+
relation: Model.HasManyRelation,
|
|
24
|
+
modelClass: Post,
|
|
25
|
+
join: {
|
|
26
|
+
from: 'users.id',
|
|
27
|
+
to: 'posts.user_id',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
comments: {
|
|
31
|
+
relation: Model.HasManyRelation,
|
|
32
|
+
modelClass: Comment,
|
|
33
|
+
join: {
|
|
34
|
+
from: 'users.id',
|
|
35
|
+
to: 'comments.user_id',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class Post extends Model {
|
|
43
|
+
static get tableName() {
|
|
44
|
+
return 'posts';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
id!: number;
|
|
48
|
+
title!: string;
|
|
49
|
+
content!: string;
|
|
50
|
+
userId!: number;
|
|
51
|
+
published?: boolean;
|
|
52
|
+
createdAt!: Date;
|
|
53
|
+
updatedAt?: Date;
|
|
54
|
+
|
|
55
|
+
static get relationMappings() {
|
|
56
|
+
return {
|
|
57
|
+
user: {
|
|
58
|
+
relation: Model.BelongsToOneRelation,
|
|
59
|
+
modelClass: User,
|
|
60
|
+
join: {
|
|
61
|
+
from: 'posts.user_id',
|
|
62
|
+
to: 'users.id',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
comments: {
|
|
66
|
+
relation: Model.HasManyRelation,
|
|
67
|
+
modelClass: Comment,
|
|
68
|
+
join: {
|
|
69
|
+
from: 'posts.id',
|
|
70
|
+
to: 'comments.post_id',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class Comment extends Model {
|
|
78
|
+
static get tableName() {
|
|
79
|
+
return 'comments';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
id!: number;
|
|
83
|
+
content!: string;
|
|
84
|
+
postId!: number;
|
|
85
|
+
userId!: number;
|
|
86
|
+
createdAt!: Date;
|
|
87
|
+
|
|
88
|
+
static get relationMappings() {
|
|
89
|
+
return {
|
|
90
|
+
post: {
|
|
91
|
+
relation: Model.BelongsToOneRelation,
|
|
92
|
+
modelClass: Post,
|
|
93
|
+
join: {
|
|
94
|
+
from: 'comments.post_id',
|
|
95
|
+
to: 'posts.id',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
user: {
|
|
99
|
+
relation: Model.BelongsToOneRelation,
|
|
100
|
+
modelClass: User,
|
|
101
|
+
join: {
|
|
102
|
+
from: 'comments.user_id',
|
|
103
|
+
to: 'users.id',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Create database connection
|
|
111
|
+
const knex = createKnexDb();
|
|
112
|
+
|
|
113
|
+
// Bind models to Knex instance
|
|
114
|
+
Model.knex(knex);
|
|
115
|
+
|
|
116
|
+
// Create wrapped test with transaction isolation
|
|
117
|
+
const it = wrapVitestObjectionTransaction(base, knex, async (trx) => {
|
|
118
|
+
// Create tables in the transaction
|
|
119
|
+
await createTestTablesKnex(trx);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('VitestObjectionTransactionIsolator', () => {
|
|
123
|
+
describe('Transaction Isolation', () => {
|
|
124
|
+
it('should rollback data after test completes', async ({ trx }) => {
|
|
125
|
+
// Create a user within the transaction
|
|
126
|
+
const user = await User.query(trx).insert({
|
|
127
|
+
name: 'Test User',
|
|
128
|
+
email: 'test@example.com',
|
|
129
|
+
role: 'user',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(user).toBeDefined();
|
|
133
|
+
expect(user.id).toBeDefined();
|
|
134
|
+
expect(user.name).toBe('Test User');
|
|
135
|
+
|
|
136
|
+
// Verify user exists in transaction
|
|
137
|
+
const foundUser = await User.query(trx).findById(user.id);
|
|
138
|
+
expect(foundUser).toBeDefined();
|
|
139
|
+
expect(foundUser?.email).toBe(user.email);
|
|
140
|
+
|
|
141
|
+
// Data will be rolled back after this test
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
package/src/faker.ts
CHANGED
|
@@ -3,15 +3,39 @@ import { faker as baseFaker } from '@faker-js/faker';
|
|
|
3
3
|
// NOTE: This is a simple way to extend `faker` with additional methods
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Atomic counter implementation for thread-safe sequence generation
|
|
6
|
+
* Atomic counter implementation for thread-safe sequence generation.
|
|
7
|
+
* Provides a clean abstraction for generating sequential numbers in tests.
|
|
8
|
+
* While JavaScript is single-threaded, this class makes the intent explicit.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const counter = new AtomicCounter(100);
|
|
13
|
+
* console.log(counter.increment()); // 101
|
|
14
|
+
* console.log(counter.increment()); // 102
|
|
15
|
+
* console.log(counter.get()); // 102
|
|
16
|
+
* counter.reset(200);
|
|
17
|
+
* console.log(counter.increment()); // 201
|
|
18
|
+
* ```
|
|
7
19
|
*/
|
|
8
20
|
class AtomicCounter {
|
|
21
|
+
/**
|
|
22
|
+
* The current counter value.
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
9
25
|
private value: number;
|
|
10
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new atomic counter.
|
|
29
|
+
* @param initialValue - The starting value (default: 0)
|
|
30
|
+
*/
|
|
11
31
|
constructor(initialValue = 0) {
|
|
12
32
|
this.value = initialValue;
|
|
13
33
|
}
|
|
14
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Increments the counter and returns the new value.
|
|
37
|
+
* @returns The incremented value
|
|
38
|
+
*/
|
|
15
39
|
increment(): number {
|
|
16
40
|
// In Node.js, JavaScript is single-threaded within the event loop,
|
|
17
41
|
// so this operation is already atomic. However, this class provides
|
|
@@ -19,17 +43,42 @@ class AtomicCounter {
|
|
|
19
43
|
return ++this.value;
|
|
20
44
|
}
|
|
21
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Gets the current counter value without incrementing.
|
|
48
|
+
* @returns The current value
|
|
49
|
+
*/
|
|
22
50
|
get(): number {
|
|
23
51
|
return this.value;
|
|
24
52
|
}
|
|
25
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Resets the counter to a specific value.
|
|
56
|
+
* @param value - The new value (default: 0)
|
|
57
|
+
*/
|
|
26
58
|
reset(value = 0): void {
|
|
27
59
|
this.value = value;
|
|
28
60
|
}
|
|
29
61
|
}
|
|
30
62
|
|
|
31
63
|
/**
|
|
32
|
-
*
|
|
64
|
+
* Generates random timestamp fields for database records.
|
|
65
|
+
* Creates a createdAt date in the past and an updatedAt date between creation and now.
|
|
66
|
+
* Milliseconds are set to 0 for cleaner database storage.
|
|
67
|
+
*
|
|
68
|
+
* @returns Object with createdAt and updatedAt Date fields
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* const { createdAt, updatedAt } = timestamps();
|
|
73
|
+
* console.log(createdAt); // 2023-05-15T10:30:00.000Z
|
|
74
|
+
* console.log(updatedAt); // 2023-11-20T14:45:00.000Z
|
|
75
|
+
*
|
|
76
|
+
* // Use in factory
|
|
77
|
+
* const user = {
|
|
78
|
+
* name: 'John Doe',
|
|
79
|
+
* ...timestamps()
|
|
80
|
+
* };
|
|
81
|
+
* ```
|
|
33
82
|
*/
|
|
34
83
|
export function timestamps(): Timestamps {
|
|
35
84
|
const createdAt = faker.date.past();
|
|
@@ -45,7 +94,18 @@ export function timestamps(): Timestamps {
|
|
|
45
94
|
}
|
|
46
95
|
|
|
47
96
|
/**
|
|
48
|
-
*
|
|
97
|
+
* Generates a reverse domain name identifier.
|
|
98
|
+
* Useful for creating unique identifiers that follow domain naming conventions.
|
|
99
|
+
*
|
|
100
|
+
* @param suffix - Optional suffix to append to the identifier
|
|
101
|
+
* @returns A reverse domain name string (e.g., "com.example.feature123")
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* console.log(identifier()); // "com.example.widget1"
|
|
106
|
+
* console.log(identifier('user')); // "org.acme.user"
|
|
107
|
+
* console.log(identifier('api')); // "net.demo.api"
|
|
108
|
+
* ```
|
|
49
109
|
*/
|
|
50
110
|
export function identifier(suffix?: string): string {
|
|
51
111
|
return [
|
|
@@ -55,9 +115,33 @@ export function identifier(suffix?: string): string {
|
|
|
55
115
|
].join('.');
|
|
56
116
|
}
|
|
57
117
|
|
|
58
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Storage for named sequence counters.
|
|
120
|
+
* Each sequence maintains its own independent counter.
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
59
123
|
const sequences = new Map<string, AtomicCounter>();
|
|
60
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Generates sequential numbers for a named sequence.
|
|
127
|
+
* Useful for creating unique IDs or numbered test data.
|
|
128
|
+
* Each named sequence maintains its own counter.
|
|
129
|
+
*
|
|
130
|
+
* @param name - The sequence name (default: 'default')
|
|
131
|
+
* @returns The next number in the sequence
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* console.log(sequence()); // 1
|
|
136
|
+
* console.log(sequence()); // 2
|
|
137
|
+
* console.log(sequence('user')); // 1
|
|
138
|
+
* console.log(sequence('user')); // 2
|
|
139
|
+
* console.log(sequence()); // 3
|
|
140
|
+
*
|
|
141
|
+
* // Use in factories
|
|
142
|
+
* const email = `user${sequence('email')}@example.com`;
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
61
145
|
export function sequence(name = 'default'): number {
|
|
62
146
|
if (!sequences.has(name)) {
|
|
63
147
|
sequences.set(name, new AtomicCounter());
|
|
@@ -68,7 +152,22 @@ export function sequence(name = 'default'): number {
|
|
|
68
152
|
}
|
|
69
153
|
|
|
70
154
|
/**
|
|
71
|
-
* Resets a sequence counter to a specific value
|
|
155
|
+
* Resets a named sequence counter to a specific value.
|
|
156
|
+
* Useful for resetting sequences between test suites.
|
|
157
|
+
*
|
|
158
|
+
* @param name - The sequence name to reset (default: 'default')
|
|
159
|
+
* @param value - The new starting value (default: 0)
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* sequence('user'); // 1
|
|
164
|
+
* sequence('user'); // 2
|
|
165
|
+
* resetSequence('user');
|
|
166
|
+
* sequence('user'); // 1
|
|
167
|
+
*
|
|
168
|
+
* resetSequence('order', 1000);
|
|
169
|
+
* sequence('order'); // 1001
|
|
170
|
+
* ```
|
|
72
171
|
*/
|
|
73
172
|
export function resetSequence(name = 'default', value = 0): void {
|
|
74
173
|
if (sequences.has(name)) {
|
|
@@ -80,19 +179,61 @@ export function resetSequence(name = 'default', value = 0): void {
|
|
|
80
179
|
}
|
|
81
180
|
|
|
82
181
|
/**
|
|
83
|
-
* Resets all sequence counters
|
|
182
|
+
* Resets all sequence counters.
|
|
183
|
+
* Useful for cleaning up between test suites to ensure predictable sequences.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* // In test setup
|
|
188
|
+
* beforeEach(() => {
|
|
189
|
+
* resetAllSequences();
|
|
190
|
+
* });
|
|
191
|
+
*
|
|
192
|
+
* it('starts sequences from 1', () => {
|
|
193
|
+
* expect(sequence()).toBe(1);
|
|
194
|
+
* expect(sequence('user')).toBe(1);
|
|
195
|
+
* });
|
|
196
|
+
* ```
|
|
84
197
|
*/
|
|
85
198
|
export function resetAllSequences(): void {
|
|
86
199
|
sequences.clear();
|
|
87
200
|
}
|
|
88
201
|
|
|
89
202
|
/**
|
|
90
|
-
*
|
|
203
|
+
* Generates a random price as a number.
|
|
204
|
+
* Converts faker's string price to a numeric value.
|
|
205
|
+
*
|
|
206
|
+
* @returns A random price number
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```typescript
|
|
210
|
+
* const productPrice = price(); // 29.99
|
|
211
|
+
* const total = price() * quantity; // Numeric calculation
|
|
212
|
+
* ```
|
|
91
213
|
*/
|
|
92
214
|
function price(): number {
|
|
93
215
|
return +faker.commerce.price();
|
|
94
216
|
}
|
|
95
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Enhanced faker instance with additional utility methods for testing.
|
|
220
|
+
* Extends @faker-js/faker with custom methods for common test data generation patterns.
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```typescript
|
|
224
|
+
* import { faker } from '@geekmidas/testkit';
|
|
225
|
+
*
|
|
226
|
+
* // Use standard faker methods
|
|
227
|
+
* const name = faker.person.fullName();
|
|
228
|
+
* const email = faker.internet.email();
|
|
229
|
+
*
|
|
230
|
+
* // Use custom extensions
|
|
231
|
+
* const { createdAt, updatedAt } = faker.timestamps();
|
|
232
|
+
* const id = faker.identifier('user');
|
|
233
|
+
* const orderNumber = faker.sequence('order');
|
|
234
|
+
* const productPrice = faker.price();
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
96
237
|
export const faker = Object.freeze(
|
|
97
238
|
Object.assign({}, baseFaker, {
|
|
98
239
|
timestamps,
|
|
@@ -104,9 +245,19 @@ export const faker = Object.freeze(
|
|
|
104
245
|
}),
|
|
105
246
|
);
|
|
106
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Type definition for timestamp fields.
|
|
250
|
+
* Used by the timestamps() function to generate date fields.
|
|
251
|
+
*/
|
|
107
252
|
export type Timestamps = {
|
|
253
|
+
/** The creation date */
|
|
108
254
|
createdAt: Date;
|
|
255
|
+
/** The last update date */
|
|
109
256
|
updatedAt: Date;
|
|
110
257
|
};
|
|
111
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Type definition for the enhanced faker factory.
|
|
261
|
+
* Includes all standard faker methods plus custom extensions.
|
|
262
|
+
*/
|
|
112
263
|
export type FakerFactory = typeof faker;
|
package/src/helpers.ts
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import { CamelCasePlugin, Kysely, PostgresDialect } from 'kysely';
|
|
2
2
|
import pg from 'pg';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Kysely database instance with PostgreSQL dialect and camelCase plugin.
|
|
6
|
+
* This is a convenience function for quickly setting up a Kysely connection for testing.
|
|
7
|
+
*
|
|
8
|
+
* @template Database - The database schema type
|
|
9
|
+
* @param config - PostgreSQL connection configuration (pg.Pool config)
|
|
10
|
+
* @returns A configured Kysely instance
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* interface Database {
|
|
15
|
+
* users: UsersTable;
|
|
16
|
+
* posts: PostsTable;
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* // Create from connection string
|
|
20
|
+
* const db = createKyselyDb<Database>({
|
|
21
|
+
* connectionString: 'postgresql://user:pass@localhost:5432/testdb'
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Create with detailed config
|
|
25
|
+
* const db = createKyselyDb<Database>({
|
|
26
|
+
* host: 'localhost',
|
|
27
|
+
* port: 5432,
|
|
28
|
+
* database: 'testdb',
|
|
29
|
+
* user: 'testuser',
|
|
30
|
+
* password: 'testpass',
|
|
31
|
+
* max: 10 // connection pool size
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Use in tests
|
|
35
|
+
* const users = await db.selectFrom('users').selectAll().execute();
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
4
38
|
export function createKyselyDb<Database>(config: any): Kysely<Database> {
|
|
5
39
|
return new Kysely({
|
|
6
40
|
dialect: new PostgresDialect({
|
package/src/kysely.ts
CHANGED
|
@@ -3,9 +3,72 @@ import type { TestAPI } from 'vitest';
|
|
|
3
3
|
import { VitestKyselyTransactionIsolator } from './VitestKyselyTransactionIsolator';
|
|
4
4
|
import { IsolationLevel } from './VitestTransactionIsolator';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Kysely-specific exports for test utilities.
|
|
8
|
+
* Provides factories, migrators, and transaction isolators for Kysely ORM.
|
|
9
|
+
*/
|
|
10
|
+
|
|
6
11
|
export { KyselyFactory } from './KyselyFactory';
|
|
7
12
|
export { PostgresKyselyMigrator } from './PostgresKyselyMigrator';
|
|
13
|
+
export { VitestKyselyTransactionIsolator } from './VitestKyselyTransactionIsolator';
|
|
14
|
+
export { IsolationLevel } from './VitestTransactionIsolator';
|
|
8
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Creates a wrapped Vitest test API with automatic transaction rollback for Kysely.
|
|
18
|
+
* Each test runs in an isolated database transaction that is rolled back after completion.
|
|
19
|
+
* This ensures tests don't affect each other's data and run faster than truncating tables.
|
|
20
|
+
*
|
|
21
|
+
* @template Database - The database schema type
|
|
22
|
+
* @param api - The Vitest test API (usually `test` from vitest)
|
|
23
|
+
* @param db - The Kysely database instance
|
|
24
|
+
* @param setup - Optional setup function to run before each test in the transaction
|
|
25
|
+
* @param level - Transaction isolation level (defaults to REPEATABLE_READ)
|
|
26
|
+
* @returns A wrapped test API that provides transaction isolation
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { test } from 'vitest';
|
|
31
|
+
* import { wrapVitestKyselyTransaction } from '@geekmidas/testkit/kysely';
|
|
32
|
+
* import { db } from './database';
|
|
33
|
+
*
|
|
34
|
+
* // Create isolated test with automatic rollback
|
|
35
|
+
* const isolatedTest = wrapVitestKyselyTransaction(test, db);
|
|
36
|
+
*
|
|
37
|
+
* // Use in tests - each test gets its own transaction
|
|
38
|
+
* isolatedTest('should create user', async ({ trx }) => {
|
|
39
|
+
* const user = await trx
|
|
40
|
+
* .insertInto('users')
|
|
41
|
+
* .values({ name: 'Test User', email: 'test@example.com' })
|
|
42
|
+
* .returningAll()
|
|
43
|
+
* .executeTakeFirst();
|
|
44
|
+
*
|
|
45
|
+
* expect(user).toBeDefined();
|
|
46
|
+
* // User is automatically rolled back after test
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* // With setup function for common test data
|
|
50
|
+
* const testWithSetup = wrapVitestKyselyTransaction(
|
|
51
|
+
* test,
|
|
52
|
+
* db,
|
|
53
|
+
* async (trx) => {
|
|
54
|
+
* // Create common test data
|
|
55
|
+
* await trx.insertInto('settings')
|
|
56
|
+
* .values({ key: 'test_mode', value: 'true' })
|
|
57
|
+
* .execute();
|
|
58
|
+
* }
|
|
59
|
+
* );
|
|
60
|
+
*
|
|
61
|
+
* testWithSetup('should have test settings', async ({ trx }) => {
|
|
62
|
+
* const setting = await trx
|
|
63
|
+
* .selectFrom('settings')
|
|
64
|
+
* .where('key', '=', 'test_mode')
|
|
65
|
+
* .selectAll()
|
|
66
|
+
* .executeTakeFirst();
|
|
67
|
+
*
|
|
68
|
+
* expect(setting?.value).toBe('true');
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
9
72
|
export function wrapVitestKyselyTransaction<Database>(
|
|
10
73
|
api: TestAPI,
|
|
11
74
|
db: Kysely<Database>,
|
package/src/objection.ts
CHANGED
|
@@ -1 +1,96 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import type { TestAPI } from 'vitest';
|
|
3
|
+
import { VitestObjectionTransactionIsolator } from './VitestObjectionTransactionIsolator';
|
|
4
|
+
import { IsolationLevel } from './VitestTransactionIsolator';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Objection.js-specific exports for test utilities.
|
|
8
|
+
* Provides factory implementation for creating test data with Objection.js ORM
|
|
9
|
+
* and transaction isolation for test suites.
|
|
10
|
+
*/
|
|
11
|
+
|
|
1
12
|
export { ObjectionFactory } from './ObjectionFactory';
|
|
13
|
+
export { VitestObjectionTransactionIsolator } from './VitestObjectionTransactionIsolator';
|
|
14
|
+
export { IsolationLevel } from './VitestTransactionIsolator';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a wrapped Vitest test API with automatic transaction rollback for Objection.js.
|
|
18
|
+
* Each test runs in an isolated database transaction that is rolled back after completion.
|
|
19
|
+
* This ensures tests don't affect each other's data and run faster than truncating tables.
|
|
20
|
+
*
|
|
21
|
+
* @param api - The Vitest test API (usually `test` from vitest)
|
|
22
|
+
* @param knex - The Knex database connection instance
|
|
23
|
+
* @param setup - Optional setup function to run before each test in the transaction
|
|
24
|
+
* @param level - Transaction isolation level (defaults to REPEATABLE_READ)
|
|
25
|
+
* @returns A wrapped test API that provides transaction isolation
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { test } from 'vitest';
|
|
30
|
+
* import { wrapVitestObjectionTransaction } from '@geekmidas/testkit/objection';
|
|
31
|
+
* import { knex } from './database';
|
|
32
|
+
* import { User, Post } from './models';
|
|
33
|
+
*
|
|
34
|
+
* // Create isolated test with automatic rollback
|
|
35
|
+
* const isolatedTest = wrapVitestObjectionTransaction(test, knex);
|
|
36
|
+
*
|
|
37
|
+
* // Use in tests - each test gets its own transaction
|
|
38
|
+
* isolatedTest('should create user', async ({ trx }) => {
|
|
39
|
+
* const user = await User.query(trx)
|
|
40
|
+
* .insert({ name: 'Test User', email: 'test@example.com' });
|
|
41
|
+
*
|
|
42
|
+
* expect(user).toBeDefined();
|
|
43
|
+
* // User is automatically rolled back after test
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // With setup function for common test data
|
|
47
|
+
* const testWithSetup = wrapVitestObjectionTransaction(
|
|
48
|
+
* test,
|
|
49
|
+
* knex,
|
|
50
|
+
* async (trx) => {
|
|
51
|
+
* // Create common test data
|
|
52
|
+
* await knex('settings')
|
|
53
|
+
* .transacting(trx)
|
|
54
|
+
* .insert({ key: 'test_mode', value: 'true' });
|
|
55
|
+
* }
|
|
56
|
+
* );
|
|
57
|
+
*
|
|
58
|
+
* testWithSetup('should have test settings', async ({ trx }) => {
|
|
59
|
+
* const setting = await knex('settings')
|
|
60
|
+
* .transacting(trx)
|
|
61
|
+
* .where('key', 'test_mode')
|
|
62
|
+
* .first();
|
|
63
|
+
*
|
|
64
|
+
* expect(setting?.value).toBe('true');
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* // Example with factory and transaction
|
|
68
|
+
* const isolatedTest = wrapVitestObjectionTransaction(test, knex);
|
|
69
|
+
* const factory = new ObjectionFactory(builders, seeds, knex);
|
|
70
|
+
*
|
|
71
|
+
* isolatedTest('creates related data', async ({ trx }) => {
|
|
72
|
+
* // Factory can use the transaction
|
|
73
|
+
* const user = await User.query(trx).insert({ name: 'Author' });
|
|
74
|
+
* const posts = await Post.query(trx).insert([
|
|
75
|
+
* { title: 'Post 1', userId: user.id },
|
|
76
|
+
* { title: 'Post 2', userId: user.id }
|
|
77
|
+
* ]);
|
|
78
|
+
*
|
|
79
|
+
* const userWithPosts = await User.query(trx)
|
|
80
|
+
* .findById(user.id)
|
|
81
|
+
* .withGraphFetched('posts');
|
|
82
|
+
*
|
|
83
|
+
* expect(userWithPosts.posts).toHaveLength(2);
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function wrapVitestObjectionTransaction(
|
|
88
|
+
api: TestAPI,
|
|
89
|
+
knex: Knex,
|
|
90
|
+
setup?: (trx: Knex.Transaction) => Promise<void>,
|
|
91
|
+
level: IsolationLevel = IsolationLevel.REPEATABLE_READ,
|
|
92
|
+
) {
|
|
93
|
+
const wrapper = new VitestObjectionTransactionIsolator(api);
|
|
94
|
+
|
|
95
|
+
return wrapper.wrapVitestWithTransaction(knex, setup, level);
|
|
96
|
+
}
|
package/test/helpers.ts
CHANGED
|
@@ -162,7 +162,9 @@ export async function createTestTables(
|
|
|
162
162
|
/**
|
|
163
163
|
* Creates test tables using Knex
|
|
164
164
|
*/
|
|
165
|
-
async function createTestTablesKnex(
|
|
165
|
+
export async function createTestTablesKnex(
|
|
166
|
+
trx: Knex.Transaction,
|
|
167
|
+
): Promise<void> {
|
|
166
168
|
// Create users table
|
|
167
169
|
await trx.schema.createTable('users', (table) => {
|
|
168
170
|
table.bigIncrements('id').primary();
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const require_Factory = require('./Factory-DREHoms3.cjs');
|
|
2
|
-
const require_faker = require('./faker-caz-8zt8.cjs');
|
|
3
|
-
|
|
4
|
-
//#region src/KyselyFactory.ts
|
|
5
|
-
var KyselyFactory = class extends require_Factory.Factory {
|
|
6
|
-
static createSeed(seedFn) {
|
|
7
|
-
return require_Factory.Factory.createSeed(seedFn);
|
|
8
|
-
}
|
|
9
|
-
constructor(builders, seeds, db) {
|
|
10
|
-
super();
|
|
11
|
-
this.builders = builders;
|
|
12
|
-
this.seeds = seeds;
|
|
13
|
-
this.db = db;
|
|
14
|
-
}
|
|
15
|
-
static createBuilder(table, item, autoInsert) {
|
|
16
|
-
return async (attrs, factory, db, faker$1) => {
|
|
17
|
-
let data = { ...attrs };
|
|
18
|
-
if (item) {
|
|
19
|
-
const defaults = await item(attrs, factory, db, faker$1);
|
|
20
|
-
data = {
|
|
21
|
-
...defaults,
|
|
22
|
-
...data
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
if (autoInsert !== false) {
|
|
26
|
-
const result = await db.insertInto(table).values(data).returningAll().executeTakeFirst();
|
|
27
|
-
if (!result) throw new Error(`Failed to insert into ${table}`);
|
|
28
|
-
return result;
|
|
29
|
-
} else return {
|
|
30
|
-
table,
|
|
31
|
-
data
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
async insert(builderName, attrs) {
|
|
36
|
-
if (!(builderName in this.builders)) throw new Error(`Factory "${builderName}" does not exist. Make sure it is correct and registered in src/test/setup.ts`);
|
|
37
|
-
const result = await this.builders[builderName](attrs || {}, this, this.db, require_faker.faker);
|
|
38
|
-
if (result && typeof result === "object" && "table" in result && "data" in result) {
|
|
39
|
-
const inserted = await this.db.insertInto(result.table).values(result.data).returningAll().executeTakeFirst();
|
|
40
|
-
return inserted;
|
|
41
|
-
}
|
|
42
|
-
return result;
|
|
43
|
-
}
|
|
44
|
-
async insertMany(count, builderName, attrs) {
|
|
45
|
-
if (!(builderName in this.builders)) throw new Error(`Builder "${builderName}" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`);
|
|
46
|
-
const promises = [];
|
|
47
|
-
for (let i = 0; i < count; i++) {
|
|
48
|
-
const newAttrs = typeof attrs === "function" ? attrs(i, require_faker.faker) : attrs;
|
|
49
|
-
promises.push(this.insert(builderName, newAttrs));
|
|
50
|
-
}
|
|
51
|
-
return Promise.all(promises);
|
|
52
|
-
}
|
|
53
|
-
seed(seedName, attrs) {
|
|
54
|
-
if (!(seedName in this.seeds)) throw new Error(`Seed "${seedName}" is not registered in this factory. Make sure it is correct and registered in src/test/setup.ts`);
|
|
55
|
-
return this.seeds[seedName](attrs || {}, this, this.db);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
//#endregion
|
|
60
|
-
Object.defineProperty(exports, 'KyselyFactory', {
|
|
61
|
-
enumerable: true,
|
|
62
|
-
get: function () {
|
|
63
|
-
return KyselyFactory;
|
|
64
|
-
}
|
|
65
|
-
});
|