@geekmidas/testkit 0.0.2 → 0.0.4

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.
Files changed (78) hide show
  1. package/PostgresKyselyMigrator.spec +471 -0
  2. package/dist/Factory.mjs +1 -1
  3. package/dist/{KyselyFactory-DiiWtMYe.cjs → KyselyFactory-BGvSMLtd.cjs} +11 -12
  4. package/dist/{KyselyFactory-DZewtWtJ.mjs → KyselyFactory-ionH4gvk.mjs} +12 -13
  5. package/dist/KyselyFactory.cjs +2 -1
  6. package/dist/KyselyFactory.mjs +3 -2
  7. package/dist/{ObjectionFactory-MAf2m8LI.mjs → ObjectionFactory-CFrtXe7i.mjs} +1 -1
  8. package/dist/ObjectionFactory.cjs +1 -1
  9. package/dist/ObjectionFactory.mjs +2 -2
  10. package/dist/{PostgresKyselyMigrator-ChMJpPrQ.mjs → PostgresKyselyMigrator-CbtiZgfI.mjs} +1 -1
  11. package/dist/{PostgresKyselyMigrator-rY3hO_-1.cjs → PostgresKyselyMigrator-Cxf2Dp9y.cjs} +3 -2
  12. package/dist/PostgresKyselyMigrator.cjs +2 -2
  13. package/dist/PostgresKyselyMigrator.mjs +2 -2
  14. package/dist/{PostgresMigrator-BJ2-5A_b.cjs → PostgresMigrator-eqyAFSf-.cjs} +2 -39
  15. package/dist/PostgresMigrator.cjs +1 -1
  16. package/dist/PostgresMigrator.mjs +1 -1
  17. package/dist/VitestKyselyTransactionIsolator-DXjWQtDN.mjs +12 -0
  18. package/dist/VitestKyselyTransactionIsolator-Dh2AgJDd.cjs +17 -0
  19. package/dist/VitestKyselyTransactionIsolator.cjs +5 -0
  20. package/dist/VitestKyselyTransactionIsolator.mjs +5 -0
  21. package/dist/VitestTransactionIsolator-pLwsDo_A.mjs +40 -0
  22. package/dist/VitestTransactionIsolator-zK5NJ7DQ.cjs +51 -0
  23. package/dist/VitestTransactionIsolator.cjs +5 -0
  24. package/dist/VitestTransactionIsolator.mjs +4 -0
  25. package/dist/__tests__/Factory.spec.cjs +139 -0
  26. package/dist/__tests__/Factory.spec.mjs +139 -0
  27. package/dist/__tests__/KyselyFactory.spec.cjs +221 -15008
  28. package/dist/__tests__/KyselyFactory.spec.mjs +220 -15034
  29. package/dist/__tests__/ObjectionFactory.spec.cjs +387 -0
  30. package/dist/__tests__/ObjectionFactory.spec.mjs +386 -0
  31. package/dist/__tests__/PostgresMigrator.spec.cjs +257 -0
  32. package/dist/__tests__/PostgresMigrator.spec.mjs +256 -0
  33. package/dist/__tests__/faker.spec.cjs +115 -0
  34. package/dist/__tests__/faker.spec.mjs +115 -0
  35. package/dist/__tests__/integration.spec.cjs +279 -0
  36. package/dist/__tests__/integration.spec.mjs +279 -0
  37. package/dist/chunk-DWy1uDak.cjs +39 -0
  38. package/dist/dist-BM2KvLG1.mjs +5618 -0
  39. package/dist/dist-DE3gAxQI.cjs +5736 -0
  40. package/dist/example.cjs +2 -1
  41. package/dist/example.mjs +3 -2
  42. package/dist/faker-cGCFcrj2.mjs +85 -0
  43. package/dist/faker-h6CkRloU.cjs +121 -0
  44. package/dist/faker.cjs +8 -0
  45. package/dist/faker.mjs +3 -0
  46. package/dist/helpers-BnARb5Ap.mjs +19 -0
  47. package/dist/helpers-C2NH7xcz.cjs +135 -0
  48. package/dist/helpers-C_RZk04R.cjs +31 -0
  49. package/dist/helpers-CukcFAU9.mjs +111 -0
  50. package/dist/helpers.cjs +7 -0
  51. package/dist/helpers.mjs +6 -0
  52. package/dist/kysely.cjs +16 -4
  53. package/dist/kysely.mjs +16 -5
  54. package/dist/objection.cjs +1 -1
  55. package/dist/objection.mjs +2 -2
  56. package/dist/vi.bdSIJ99Y-BgRxGeO2.mjs +9382 -0
  57. package/dist/vi.bdSIJ99Y-CFuzUeY6.cjs +9393 -0
  58. package/package.json +11 -6
  59. package/src/Factory.ts +3 -1
  60. package/src/KyselyFactory.ts +30 -36
  61. package/src/VitestKyselyTransactionIsolator.ts +23 -0
  62. package/src/VitestTransactionIsolator.ts +70 -0
  63. package/src/__tests__/Factory.spec.ts +164 -0
  64. package/src/__tests__/KyselyFactory.spec.ts +432 -64
  65. package/src/__tests__/ObjectionFactory.spec.ts +532 -0
  66. package/src/__tests__/PostgresMigrator.spec.ts +366 -0
  67. package/src/__tests__/faker.spec.ts +142 -0
  68. package/src/__tests__/integration.spec.ts +442 -0
  69. package/src/faker.ts +112 -0
  70. package/src/helpers.ts +28 -0
  71. package/src/kysely.ts +14 -0
  72. package/test/globalSetup.ts +41 -40
  73. package/test/helpers.ts +273 -0
  74. /package/dist/{Factory-DlzMkMzb.mjs → Factory-D52Lsc6Z.mjs} +0 -0
  75. /package/dist/{ObjectionFactory-DeFYWbzt.cjs → ObjectionFactory-BlkzSEqo.cjs} +0 -0
  76. /package/dist/{PostgresMigrator-BKaNTth5.mjs → PostgresMigrator-DqeuPy-e.mjs} +0 -0
  77. /package/dist/{magic-string.es-CxbtJGk_.mjs → magic-string.es-C6yzoryu.mjs} +0 -0
  78. /package/dist/{magic-string.es-KiPEzMtt.cjs → magic-string.es-jdtJrR0A.cjs} +0 -0
@@ -0,0 +1,442 @@
1
+ import { beforeAll, describe, expect } from 'vitest';
2
+ import { TEST_DATABASE_CONFIG } from '../../test/globalSetup';
3
+ import { type TestDatabase, createTestTables } from '../../test/helpers';
4
+ import { KyselyFactory } from '../KyselyFactory';
5
+ import { createKyselyDb, wrapVitestKyselyTransaction } from '../helpers';
6
+
7
+ const db = createKyselyDb<TestDatabase>(TEST_DATABASE_CONFIG);
8
+ const it = wrapVitestKyselyTransaction<TestDatabase>(db, createTestTables);
9
+ describe('Testkit Integration Tests', () => {
10
+ beforeAll(async () => {});
11
+ describe('Complex Factory Scenarios', () => {
12
+ it('should handle complex multi-table data creation', async ({ trx }) => {
13
+ // Create builders for all entities
14
+ const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
15
+ 'users',
16
+ async (attrs) => ({
17
+ name: 'John Doe',
18
+ email: `user${Date.now()}-${Math.random()}@example.com`,
19
+ role: 'user' as const,
20
+ createdAt: new Date(),
21
+ updatedAt: new Date(),
22
+ }),
23
+ );
24
+
25
+ const postBuilder = KyselyFactory.createBuilder<TestDatabase, 'posts'>(
26
+ 'posts',
27
+ async (attrs, factory) => {
28
+ // Create a user if no userId provided
29
+ if (!attrs.userId) {
30
+ const user = await factory.insert('user');
31
+ return {
32
+ title: 'Default Post Title',
33
+ content: 'Default post content...',
34
+ userId: user.id,
35
+ published: false,
36
+ createdAt: new Date(),
37
+ updatedAt: new Date(),
38
+ };
39
+ }
40
+ return {
41
+ title: 'Default Post Title',
42
+ content: 'Default post content...',
43
+ published: false,
44
+ createdAt: new Date(),
45
+ updatedAt: new Date(),
46
+ };
47
+ },
48
+ );
49
+
50
+ const commentBuilder = KyselyFactory.createBuilder<
51
+ TestDatabase,
52
+ 'comments'
53
+ >('comments', async (attrs, factory) => {
54
+ let postId = attrs.postId;
55
+ let userId = attrs.userId;
56
+
57
+ // Create post if not provided
58
+ if (!postId) {
59
+ const post = await factory.insert('post');
60
+ postId = post.id;
61
+ }
62
+
63
+ // Create user if not provided
64
+ if (!userId) {
65
+ const user = await factory.insert('user');
66
+ userId = user.id;
67
+ }
68
+
69
+ return {
70
+ content: 'Default comment content',
71
+ postId,
72
+ userId,
73
+ createdAt: new Date(),
74
+ };
75
+ });
76
+
77
+ const builders = {
78
+ user: userBuilder,
79
+ post: postBuilder,
80
+ comment: commentBuilder,
81
+ };
82
+
83
+ const factory = new KyselyFactory<TestDatabase, typeof builders, {}>(
84
+ builders,
85
+ {},
86
+ trx,
87
+ );
88
+
89
+ // Create a complete blog structure
90
+ const author = await factory.insert('user', {
91
+ name: 'Jane Author',
92
+ email: 'jane@author.com',
93
+ role: 'admin',
94
+ });
95
+
96
+ const posts = await factory.insertMany(3, 'post', (idx) => ({
97
+ title: `Post ${idx + 1}`,
98
+ content: `Content for post ${idx + 1}`,
99
+ userId: author.id,
100
+ published: idx < 2, // First two posts are published
101
+ }));
102
+
103
+ // Create comments on the first post
104
+ const comments = await factory.insertMany(5, 'comment', (idx) => ({
105
+ content: `Comment ${idx + 1} on first post`,
106
+ postId: posts[0].id,
107
+ }));
108
+
109
+ // Verify the data structure
110
+ expect(author.name).toBe('Jane Author');
111
+ expect(author.role).toBe('admin');
112
+
113
+ expect(posts).toHaveLength(3);
114
+ expect(posts[0].title).toBe('Post 1');
115
+ expect(posts[0].published).toBe(true);
116
+ expect(posts[2].published).toBe(false);
117
+
118
+ expect(comments).toHaveLength(5);
119
+ comments.forEach((comment, idx) => {
120
+ expect(comment.content).toBe(`Comment ${idx + 1} on first post`);
121
+ expect(comment.postId).toBe(posts[0].id);
122
+ });
123
+
124
+ // Verify relationships in database
125
+ const authorPosts = await trx
126
+ .selectFrom('posts')
127
+ .selectAll()
128
+ .where('userId', '=', author.id)
129
+ .execute();
130
+
131
+ expect(authorPosts).toHaveLength(3);
132
+
133
+ const firstPostComments = await trx
134
+ .selectFrom('comments')
135
+ .selectAll()
136
+ .where('postId', '=', posts[0].id)
137
+ .execute();
138
+
139
+ expect(firstPostComments).toHaveLength(5);
140
+ });
141
+
142
+ it('should handle seeds for complex scenarios', async ({ trx }) => {
143
+ const c = await trx
144
+ .selectFrom('users')
145
+ .select(trx.fn.count('id').as('count'))
146
+ .executeTakeFirst();
147
+
148
+ const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
149
+ 'users',
150
+ async (attrs) => ({
151
+ name: 'Default User',
152
+ email: `user${Date.now()}-${Math.random()}@example.com`,
153
+ role: 'user' as const,
154
+ createdAt: new Date(),
155
+ updatedAt: new Date(),
156
+ }),
157
+ );
158
+
159
+ const postBuilder = KyselyFactory.createBuilder<TestDatabase, 'posts'>(
160
+ 'posts',
161
+ async (attrs, factory) => {
162
+ if (!attrs.userId) {
163
+ const user = await factory.insert('user');
164
+ return {
165
+ title: 'Default Post',
166
+ content: 'Default content',
167
+ userId: user.id,
168
+ published: false,
169
+ createdAt: new Date(),
170
+ updatedAt: new Date(),
171
+ };
172
+ }
173
+ return {
174
+ title: 'Default Post',
175
+ content: 'Default content',
176
+ published: false,
177
+ createdAt: new Date(),
178
+ updatedAt: new Date(),
179
+ };
180
+ },
181
+ );
182
+
183
+ const builders = {
184
+ user: userBuilder,
185
+ post: postBuilder,
186
+ };
187
+
188
+ // Create complex seeds
189
+ const seeds = {
190
+ blogWithAdminAndPosts: KyselyFactory.createSeed(
191
+ async (attrs: { postCount?: number }, factory: any, db: any) => {
192
+ // Create admin user
193
+ const admin = await factory.insert('user', {
194
+ name: 'Blog Admin',
195
+ email: 'admin@blog.com',
196
+ role: 'admin',
197
+ });
198
+
199
+ // Create multiple posts
200
+ const postCount = attrs.postCount || 3;
201
+ const posts = await factory.insertMany(
202
+ postCount,
203
+ 'post',
204
+ (idx) => ({
205
+ title: `Admin Post ${idx + 1}`,
206
+ content: `Content for admin post ${idx + 1}`,
207
+ userId: admin.id,
208
+ published: true,
209
+ }),
210
+ );
211
+
212
+ return {
213
+ admin,
214
+ posts,
215
+ summary: {
216
+ adminId: admin.id,
217
+ postIds: posts.map((p) => p.id),
218
+ totalPosts: posts.length,
219
+ },
220
+ };
221
+ },
222
+ ),
223
+
224
+ usersWithPosts: KyselyFactory.createSeed(
225
+ async (
226
+ attrs: { userCount?: number; postsPerUser?: number },
227
+ factory: any,
228
+ db: any,
229
+ ) => {
230
+ const userCount = attrs.userCount || 2;
231
+ const postsPerUser = attrs.postsPerUser || 2;
232
+
233
+ const results: Array<{ user: any; posts: any[] }> = [];
234
+
235
+ for (let i = 0; i < userCount; i++) {
236
+ const user = await factory.insert('user', {
237
+ name: `User ${i + 1}`,
238
+ email: `user${i + 1}@example.com`,
239
+ });
240
+
241
+ const posts = await factory.insertMany(
242
+ postsPerUser,
243
+ 'post',
244
+ (postIdx) => ({
245
+ title: `User ${i + 1} Post ${postIdx + 1}`,
246
+ content: `Content from user ${i + 1}, post ${postIdx + 1}`,
247
+ userId: user.id,
248
+ published: postIdx === 0, // Only first post is published
249
+ }),
250
+ );
251
+
252
+ results.push({ user, posts });
253
+ }
254
+
255
+ return results;
256
+ },
257
+ ),
258
+ };
259
+
260
+ const factory = new KyselyFactory<
261
+ TestDatabase,
262
+ typeof builders,
263
+ typeof seeds
264
+ >(builders, seeds, trx);
265
+
266
+ // Test first seed
267
+ const blogData = await factory.seed('blogWithAdminAndPosts', {
268
+ postCount: 5,
269
+ });
270
+
271
+ expect(blogData.admin.name).toBe('Blog Admin');
272
+ expect(blogData.admin.role).toBe('admin');
273
+ expect(blogData.posts).toHaveLength(5);
274
+ expect(blogData.summary.totalPosts).toBe(5);
275
+
276
+ // Test second seed
277
+ const userData = await factory.seed('usersWithPosts', {
278
+ userCount: 3,
279
+ postsPerUser: 4,
280
+ });
281
+
282
+ expect(userData).toHaveLength(3);
283
+
284
+ // Verify total counts in database
285
+ const totalUsers = await trx
286
+ .selectFrom('users')
287
+ .select(trx.fn.count('id').as('count'))
288
+ .executeTakeFirst();
289
+
290
+ const totalPosts = await trx
291
+ .selectFrom('posts')
292
+ .select(trx.fn.count('id').as('count'))
293
+ .executeTakeFirst();
294
+
295
+ // 1 admin + 3 users = 4 total users
296
+ expect(Number(totalUsers?.count)).toBe(4);
297
+ // 5 admin posts + (3 users * 4 posts) = 17 total posts
298
+ expect(Number(totalPosts?.count)).toBe(17);
299
+ });
300
+
301
+ it('should handle transaction isolation properly', async ({ trx }) => {
302
+ const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
303
+ 'users',
304
+ async (attrs, factory, db, faker) => ({
305
+ name: 'Test User',
306
+ email: faker.internet.email(),
307
+ role: 'user' as const,
308
+ createdAt: new Date(),
309
+ updatedAt: new Date(),
310
+ }),
311
+ );
312
+
313
+ const builders = { user: userBuilder };
314
+
315
+ const factory1 = new KyselyFactory<TestDatabase, typeof builders, {}>(
316
+ builders,
317
+ {},
318
+ trx,
319
+ );
320
+
321
+ // Create user in transaction
322
+ const user = await factory1.insert('user', {
323
+ name: 'Transaction User',
324
+ email: 'transaction@test.com',
325
+ });
326
+
327
+ // Verify user exists in transaction
328
+ const userInTrx = await trx
329
+ .selectFrom('users')
330
+ .selectAll()
331
+ .where('id', '=', user.id)
332
+ .executeTakeFirst();
333
+
334
+ expect(userInTrx).toBeDefined();
335
+ expect(userInTrx?.name).toBe('Transaction User');
336
+ });
337
+ });
338
+
339
+ describe('Performance and Edge Cases', () => {
340
+ it('should handle creating many records efficiently', async ({ trx }) => {
341
+ const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
342
+ 'users',
343
+ async (attrs, factory, db, faker) => ({
344
+ name: `User ${Math.random()}`,
345
+ email: faker.internet.email().toLowerCase(),
346
+ role: 'user' as const,
347
+ createdAt: new Date(),
348
+ updatedAt: new Date(),
349
+ }),
350
+ );
351
+
352
+ const builders = { user: userBuilder };
353
+ const factory = new KyselyFactory<TestDatabase, typeof builders, {}>(
354
+ builders,
355
+ {},
356
+ trx,
357
+ );
358
+
359
+ const startTime = Date.now();
360
+
361
+ // Create 100 users
362
+ const users = await factory.insertMany(100, 'user');
363
+
364
+ const endTime = Date.now();
365
+ const duration = endTime - startTime;
366
+
367
+ expect(users).toHaveLength(100);
368
+ expect(duration).toBeLessThan(5000); // Should complete in under 5 seconds
369
+
370
+ // Verify all users are unique
371
+ const emails = users.map((u) => u.email);
372
+ const uniqueEmails = new Set(emails);
373
+ expect(uniqueEmails.size).toBe(100);
374
+ });
375
+
376
+ it('should handle complex attribute generation', async ({ trx }) => {
377
+ const userBuilder = KyselyFactory.createBuilder<TestDatabase, 'users'>(
378
+ 'users',
379
+ async (attrs, factory, db, faker) => {
380
+ return {
381
+ name: `Generated User ${attrs.id}`,
382
+ email: faker.internet.email().toLowerCase(),
383
+ role: 'user',
384
+ };
385
+ },
386
+ );
387
+
388
+ const postBuilder = KyselyFactory.createBuilder<TestDatabase, 'posts'>(
389
+ 'posts',
390
+ async (attrs, factory) => {
391
+ let userId = attrs.userId;
392
+ if (!userId) {
393
+ const user = await factory.insert('user');
394
+ userId = user.id;
395
+ }
396
+
397
+ return {
398
+ title: `Auto-generated Post`,
399
+ content: `This is auto-generated content for post. Lorem ipsum dolor sit amet.`,
400
+ published: true,
401
+ userId,
402
+ createdAt: new Date(),
403
+ updatedAt: new Date(),
404
+ };
405
+ },
406
+ );
407
+
408
+ const builders = {
409
+ user: userBuilder,
410
+ post: postBuilder,
411
+ };
412
+
413
+ const factory = new KyselyFactory<TestDatabase, typeof builders, {}>(
414
+ builders,
415
+ {},
416
+ trx,
417
+ );
418
+
419
+ // Create posts which will auto-create users
420
+ const posts = await factory.insertMany(10, 'post', (i) => ({
421
+ published: i % 2 === 0,
422
+ }));
423
+
424
+ expect(posts).toHaveLength(10);
425
+
426
+ // Check email normalization
427
+ const users = await trx.selectFrom('users').selectAll().execute();
428
+
429
+ users.forEach((user) => {
430
+ expect(user.email).toBe(user.email.toLowerCase());
431
+ expect(user.name).not.toMatch(/^\s|\s$/); // No leading/trailing spaces
432
+ });
433
+
434
+ // Check published pattern
435
+ const publishedPosts = posts.filter((p) => p.published);
436
+ const unpublishedPosts = posts.filter((p) => !p.published);
437
+
438
+ expect(publishedPosts).toHaveLength(5); // Every other post
439
+ expect(unpublishedPosts).toHaveLength(5);
440
+ });
441
+ });
442
+ });
package/src/faker.ts CHANGED
@@ -0,0 +1,112 @@
1
+ import { faker as baseFaker } from '@faker-js/faker';
2
+
3
+ // NOTE: This is a simple way to extend `faker` with additional methods
4
+
5
+ /**
6
+ * Atomic counter implementation for thread-safe sequence generation
7
+ */
8
+ class AtomicCounter {
9
+ private value: number;
10
+
11
+ constructor(initialValue = 0) {
12
+ this.value = initialValue;
13
+ }
14
+
15
+ increment(): number {
16
+ // In Node.js, JavaScript is single-threaded within the event loop,
17
+ // so this operation is already atomic. However, this class provides
18
+ // a cleaner abstraction and makes the intent explicit.
19
+ return ++this.value;
20
+ }
21
+
22
+ get(): number {
23
+ return this.value;
24
+ }
25
+
26
+ reset(value = 0): void {
27
+ this.value = value;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Sets the `insertedAt` and `updatedAt` to a random date in the past.
33
+ */
34
+ export function timestamps(): Timestamps {
35
+ const createdAt = faker.date.past();
36
+ const updatedAt = faker.date.between({
37
+ from: createdAt,
38
+ to: new Date(),
39
+ });
40
+
41
+ createdAt.setMilliseconds(0);
42
+ updatedAt.setMilliseconds(0);
43
+
44
+ return { createdAt, updatedAt };
45
+ }
46
+
47
+ /**
48
+ * Returns a reverse domain name identifier.
49
+ */
50
+ export function identifier(suffix?: string): string {
51
+ return [
52
+ faker.internet.domainSuffix(),
53
+ faker.internet.domainWord(),
54
+ suffix ? suffix : faker.internet.domainWord() + sequence('identifier'),
55
+ ].join('.');
56
+ }
57
+
58
+ // Atomic sequences for thread-safe counter generation
59
+ const sequences = new Map<string, AtomicCounter>();
60
+
61
+ export function sequence(name = 'default'): number {
62
+ if (!sequences.has(name)) {
63
+ sequences.set(name, new AtomicCounter());
64
+ }
65
+
66
+ const counter = sequences.get(name) as AtomicCounter;
67
+ return counter.increment();
68
+ }
69
+
70
+ /**
71
+ * Resets a sequence counter to a specific value (default: 0)
72
+ */
73
+ export function resetSequence(name = 'default', value = 0): void {
74
+ if (sequences.has(name)) {
75
+ const counter = sequences.get(name) as AtomicCounter;
76
+ counter.reset(value);
77
+ } else {
78
+ sequences.set(name, new AtomicCounter(value));
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Resets all sequence counters
84
+ */
85
+ export function resetAllSequences(): void {
86
+ sequences.clear();
87
+ }
88
+
89
+ /**
90
+ * Returns a random price number.
91
+ */
92
+ function price(): number {
93
+ return +faker.commerce.price();
94
+ }
95
+
96
+ export const faker = Object.freeze(
97
+ Object.assign({}, baseFaker, {
98
+ timestamps,
99
+ identifier,
100
+ sequence,
101
+ resetSequence,
102
+ resetAllSequences,
103
+ price,
104
+ }),
105
+ );
106
+
107
+ export type Timestamps = {
108
+ createdAt: Date;
109
+ updatedAt: Date;
110
+ };
111
+
112
+ export type FakerFactory = typeof faker;
package/src/helpers.ts ADDED
@@ -0,0 +1,28 @@
1
+ import {
2
+ CamelCasePlugin,
3
+ Kysely,
4
+ PostgresDialect,
5
+ type Transaction,
6
+ } from 'kysely';
7
+ import pg from 'pg';
8
+ import { VitestKyselyTransactionIsolator } from './VitestKyselyTransactionIsolator';
9
+ import { IsolationLevel } from './VitestTransactionIsolator';
10
+
11
+ export function wrapVitestKyselyTransaction<Database>(
12
+ db: Kysely<Database>,
13
+ setup?: (trx: Transaction<Database>) => Promise<void>,
14
+ level: IsolationLevel = IsolationLevel.REPEATABLE_READ,
15
+ ) {
16
+ const wrapper = new VitestKyselyTransactionIsolator<Database>();
17
+
18
+ return wrapper.wrapVitestWithTransaction(db, setup, level);
19
+ }
20
+
21
+ export function createKyselyDb<Database>(config: any): Kysely<Database> {
22
+ return new Kysely({
23
+ dialect: new PostgresDialect({
24
+ pool: new pg.Pool(config),
25
+ }),
26
+ plugins: [new CamelCasePlugin()],
27
+ });
28
+ }
package/src/kysely.ts CHANGED
@@ -1,2 +1,16 @@
1
+ import type { Kysely, Transaction } from 'kysely';
2
+ import { VitestKyselyTransactionIsolator } from './VitestKyselyTransactionIsolator';
3
+ import { IsolationLevel } from './VitestTransactionIsolator';
4
+
1
5
  export { KyselyFactory } from './KyselyFactory';
2
6
  export { PostgresKyselyMigrator } from './PostgresKyselyMigrator';
7
+
8
+ export function wrapVitestKyselyTransaction<Database>(
9
+ db: Kysely<Database>,
10
+ setup?: (trx: Transaction<Database>) => Promise<void>,
11
+ level: IsolationLevel = IsolationLevel.REPEATABLE_READ,
12
+ ) {
13
+ const wrapper = new VitestKyselyTransactionIsolator<Database>();
14
+
15
+ return wrapper.wrapVitestWithTransaction(db, setup, level);
16
+ }
@@ -1,20 +1,7 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import pg from 'pg';
4
-
5
- import {
6
- CamelCasePlugin,
7
- FileMigrationProvider,
8
- Kysely,
9
- PostgresDialect,
10
- } from 'kysely';
11
-
12
- import { PostgresKyselyMigrator } from '../src/PostgresKyselyMigrator';
1
+ import { Client } from 'pg';
13
2
 
14
3
  const TEST_DATABASE_NAME = 'geekmidas_test';
15
4
 
16
- const logger = console;
17
-
18
5
  export const TEST_DATABASE_CONFIG = {
19
6
  host: 'localhost',
20
7
  port: 5432,
@@ -23,31 +10,45 @@ export const TEST_DATABASE_CONFIG = {
23
10
  database: TEST_DATABASE_NAME,
24
11
  };
25
12
 
26
- // password: get('Database.password').string(),
27
- // user: get('Database.username').string(),
28
- // database: get('Database.database').string(),
29
- // host: get('Database.host').string(),
30
- // port: get('Database.port').number().default(5432),
31
-
32
13
  export default async function globalSetup() {
33
- const uri = `postgres://${TEST_DATABASE_CONFIG.user}:${TEST_DATABASE_CONFIG.password}@${TEST_DATABASE_CONFIG.host}:${TEST_DATABASE_CONFIG.port}/${TEST_DATABASE_CONFIG.database}`;
34
-
35
- const migrationFolder = path.resolve(__dirname, './migrations');
36
-
37
- const migrationProcessor = new PostgresKyselyMigrator({
38
- uri,
39
- db: new Kysely({
40
- dialect: new PostgresDialect({
41
- pool: new pg.Pool(TEST_DATABASE_CONFIG),
42
- }),
43
- plugins: [new CamelCasePlugin()],
44
- }),
45
- provider: new FileMigrationProvider({
46
- fs,
47
- path,
48
- migrationFolder,
49
- }),
50
- });
51
-
52
- return migrationProcessor.start();
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
+ };
53
54
  }