@geekmidas/testkit 0.0.3 → 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 +7 -2
  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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/testkit",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,6 +13,11 @@
13
13
  "types": "./src/kysely.ts",
14
14
  "import": "./dist/kysely.mjs",
15
15
  "require": "./dist/kysely.cjs"
16
+ },
17
+ "./faker": {
18
+ "types": "./src/faker.ts",
19
+ "import": "./dist/faker.mjs",
20
+ "require": "./dist/faker.cjs"
16
21
  }
17
22
  },
18
23
  "dependencies": {
@@ -31,6 +36,6 @@
31
36
  "knex": "~3.1.0",
32
37
  "objection": "~3.1.5",
33
38
  "db-errors": "~0.2.3",
34
- "@geekmidas/envkit": "0.0.2"
39
+ "@geekmidas/envkit": "0.0.3"
35
40
  }
36
41
  }
package/src/Factory.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { FakerFactory } from './faker';
2
+
1
3
  export abstract class Factory<
2
4
  Builders extends Record<string, any>,
3
5
  Seeds extends Record<string, any>,
@@ -28,7 +30,7 @@ export abstract class Factory<
28
30
  builderName: K,
29
31
  attrs?:
30
32
  | Parameters<Builders[K]>[0]
31
- | ((idx: number) => Parameters<Builders[K]>[0]),
33
+ | ((idx: number, faker: FakerFactory) => Parameters<Builders[K]>[0]),
32
34
  ): Promise<Awaited<ReturnType<Builders[K]>>[]>;
33
35
 
34
36
  /**
@@ -5,6 +5,7 @@ import type {
5
5
  Selectable,
6
6
  } from 'kysely';
7
7
  import { Factory, type FactorySeed } from './Factory.ts';
8
+ import { type FakerFactory, faker } from './faker.ts';
8
9
 
9
10
  export class KyselyFactory<
10
11
  DB,
@@ -31,67 +32,55 @@ export class KyselyFactory<
31
32
  >,
32
33
  Factory = any,
33
34
  Result = Selectable<DB[TableName]>,
34
- >(config: {
35
- table: TableName;
36
- defaults?: (
35
+ >(
36
+ table: TableName,
37
+ item?: (
37
38
  attrs: Attrs,
38
39
  factory: Factory,
39
40
  db: Kysely<DB>,
41
+ faker: FakerFactory,
40
42
  ) =>
41
43
  | Partial<Insertable<DB[TableName]>>
42
- | Promise<Partial<Insertable<DB[TableName]>>>;
43
- transform?: (
44
- data: Partial<Insertable<DB[TableName]>>,
45
- factory: Factory,
46
- db: Kysely<DB>,
47
- ) =>
48
- | Partial<Insertable<DB[TableName]>>
49
- | Promise<Partial<Insertable<DB[TableName]>>>;
50
- relations?: (
51
- record: Result,
44
+ | Promise<Partial<Insertable<DB[TableName]>>>,
45
+ autoInsert?: boolean,
46
+ ): (
47
+ attrs: Attrs,
48
+ factory: Factory,
49
+ db: Kysely<DB>,
50
+ faker: FakerFactory,
51
+ ) => Promise<Result> {
52
+ return async (
52
53
  attrs: Attrs,
53
54
  factory: Factory,
54
55
  db: Kysely<DB>,
55
- ) => Promise<void>;
56
- autoInsert?: boolean;
57
- }): (attrs: Attrs, factory: Factory, db: Kysely<DB>) => Promise<Result> {
58
- return async (attrs: Attrs, factory: Factory, db: Kysely<DB>) => {
56
+ faker: FakerFactory,
57
+ ) => {
59
58
  // Start with attributes
60
59
  let data: Partial<Insertable<DB[TableName]>> = { ...attrs };
61
60
 
62
61
  // Apply defaults
63
- if (config.defaults) {
64
- const defaults = await config.defaults(attrs, factory, db);
62
+ if (item) {
63
+ const defaults = await item(attrs, factory, db, faker);
65
64
  data = { ...defaults, ...data };
66
65
  }
67
66
 
68
- // Apply transformations
69
- if (config.transform) {
70
- data = await config.transform(data, factory, db);
71
- }
72
-
73
67
  // Handle insertion based on autoInsert flag
74
- if (config.autoInsert !== false) {
68
+ if (autoInsert !== false) {
75
69
  // Auto insert is enabled by default
76
70
  const result = await db
77
- .insertInto(config.table)
71
+ .insertInto(table)
78
72
  .values(data as Insertable<DB[TableName]>)
79
73
  .returningAll()
80
74
  .executeTakeFirst();
81
75
 
82
76
  if (!result) {
83
- throw new Error(`Failed to insert into ${config.table}`);
84
- }
85
-
86
- // Handle relations if defined
87
- if (config.relations) {
88
- await config.relations(result as Result, attrs, factory, db);
77
+ throw new Error(`Failed to insert into ${table}`);
89
78
  }
90
79
 
91
80
  return result as Result;
92
81
  } else {
93
82
  // Return object for factory to handle insertion
94
- return { table: config.table, data } as any;
83
+ return { table, data } as any;
95
84
  }
96
85
  };
97
86
  }
@@ -108,7 +97,12 @@ export class KyselyFactory<
108
97
  );
109
98
  }
110
99
 
111
- const result = await this.builders[builderName](attrs || {}, this, this.db);
100
+ const result = await this.builders[builderName](
101
+ attrs || {},
102
+ this,
103
+ this.db,
104
+ faker,
105
+ );
112
106
 
113
107
  // For Kysely, we expect the builder to return an object with table and data properties
114
108
  // or to handle the insertion itself and return the inserted record
@@ -141,7 +135,7 @@ export class KyselyFactory<
141
135
  async insertMany<K extends keyof Builders>(
142
136
  count: number,
143
137
  builderName: K,
144
- attrs: (idx: number) => Parameters<Builders[K]>[0],
138
+ attrs: (idx: number, faker: FakerFactory) => Parameters<Builders[K]>[0],
145
139
  ): Promise<Awaited<ReturnType<Builders[K]>>[]>;
146
140
  async insertMany<K extends keyof Builders>(
147
141
  count: number,
@@ -159,7 +153,7 @@ export class KyselyFactory<
159
153
  const promises: Promise<any>[] = [];
160
154
 
161
155
  for (let i = 0; i < count; i++) {
162
- const newAttrs = typeof attrs === 'function' ? attrs(i) : attrs;
156
+ const newAttrs = typeof attrs === 'function' ? attrs(i, faker) : attrs;
163
157
  promises.push(this.insert(builderName, newAttrs));
164
158
  }
165
159
 
@@ -0,0 +1,23 @@
1
+ import type { Kysely, Transaction } from 'kysely';
2
+ import {
3
+ type IsolationLevel,
4
+ VitestPostgresTransactionIsolator,
5
+ } from './VitestTransactionIsolator';
6
+
7
+ export class VitestKyselyTransactionIsolator<
8
+ Database,
9
+ > extends VitestPostgresTransactionIsolator<
10
+ Kysely<Database>,
11
+ Transaction<Database>
12
+ > {
13
+ async transact(
14
+ conn: Kysely<Database>,
15
+ level: IsolationLevel,
16
+ fn: (trx: Transaction<Database>) => Promise<void>,
17
+ ): Promise<void> {
18
+ const isolationLevel =
19
+ level.toLocaleLowerCase() as Lowercase<IsolationLevel>;
20
+ await conn.transaction().setIsolationLevel(isolationLevel).execute(fn);
21
+ }
22
+ // Implement any Kysely-specific transaction logic here
23
+ }
@@ -0,0 +1,70 @@
1
+ import { test as base } from 'vitest';
2
+
3
+ export interface DatabaseFixtures<Transaction> {
4
+ trx: Transaction;
5
+ }
6
+
7
+ export enum IsolationLevel {
8
+ READ_UNCOMMITTED = 'READ UNCOMMITTED',
9
+ READ_COMMITTED = 'READ COMMITTED',
10
+ REPEATABLE_READ = 'REPEATABLE READ',
11
+ SERIALIZABLE = 'SERIALIZABLE',
12
+ }
13
+
14
+ export abstract class VitestPostgresTransactionIsolator<
15
+ Connection,
16
+ Transaction,
17
+ > {
18
+ abstract transact(
19
+ conn: Connection,
20
+ isolationLevel: IsolationLevel,
21
+ fn: (trx: Transaction) => Promise<void>,
22
+ ): Promise<void>;
23
+
24
+ wrapVitestWithTransaction(
25
+ conn: Connection,
26
+ setup?: (trx: Transaction) => Promise<void>,
27
+ level: IsolationLevel = IsolationLevel.REPEATABLE_READ,
28
+ ) {
29
+ return base.extend<DatabaseFixtures<Transaction>>({
30
+ // This fixture automatically provides a transaction to each test
31
+ trx: async ({}, use) => {
32
+ // Create a custom error class for rollback
33
+ class TestRollback extends Error {
34
+ constructor() {
35
+ super('Test rollback');
36
+ this.name = 'TestRollback';
37
+ }
38
+ }
39
+
40
+ let testError: Error | undefined;
41
+
42
+ try {
43
+ await this.transact(conn, level, async (transaction) => {
44
+ try {
45
+ // Provide the transaction to the test
46
+ await setup?.(transaction);
47
+ await use(transaction);
48
+ } catch (error) {
49
+ // Capture any test errors
50
+ testError = error as Error;
51
+ }
52
+
53
+ // Always throw to trigger rollback
54
+ throw new TestRollback();
55
+ });
56
+ } catch (error) {
57
+ // Only rethrow if it's not our rollback error
58
+ if (!(error instanceof TestRollback)) {
59
+ throw error;
60
+ }
61
+
62
+ // If the test had an error, throw it now
63
+ if (testError) {
64
+ throw testError;
65
+ }
66
+ }
67
+ },
68
+ });
69
+ }
70
+ }
@@ -0,0 +1,164 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import {
3
+ Factory,
4
+ type FactorySeed,
5
+ type MixedFactoryBuilder,
6
+ } from '../Factory';
7
+
8
+ // Create a concrete implementation for testing
9
+ class TestFactory extends Factory<
10
+ { testBuilder: (attrs: any) => any },
11
+ { testSeed: (attrs: any, factory: any, db: any) => any }
12
+ > {
13
+ async insert(builderName: any, attrs?: any) {
14
+ return Promise.resolve({ id: 1, ...attrs });
15
+ }
16
+
17
+ async insertMany(count: number, builderName: any, attrs?: any) {
18
+ const results: any[] = [];
19
+ for (let i = 0; i < count; i++) {
20
+ const newAttrs = typeof attrs === 'function' ? attrs(i) : attrs;
21
+ results.push(await this.insert(builderName, newAttrs));
22
+ }
23
+ return results;
24
+ }
25
+
26
+ seed(seedName: any, attrs?: any): any {
27
+ return Promise.resolve({ seedResult: true, ...attrs });
28
+ }
29
+ }
30
+
31
+ describe('Factory', () => {
32
+ describe('abstract class functionality', () => {
33
+ it('should be instantiable through concrete implementation', () => {
34
+ const factory = new TestFactory();
35
+ expect(factory).toBeInstanceOf(Factory);
36
+ expect(factory).toBeInstanceOf(TestFactory);
37
+ });
38
+
39
+ it('should have abstract methods defined', () => {
40
+ const factory = new TestFactory();
41
+ expect(typeof factory.insert).toBe('function');
42
+ expect(typeof factory.insertMany).toBe('function');
43
+ expect(typeof factory.seed).toBe('function');
44
+ });
45
+ });
46
+
47
+ describe('createSeed static method', () => {
48
+ it('should return the seed function unchanged', () => {
49
+ const seedFn = async (attrs: any, factory: any, db: any) => {
50
+ return { id: 1, name: 'test' };
51
+ };
52
+
53
+ const result = Factory.createSeed(seedFn);
54
+
55
+ expect(result).toBe(seedFn);
56
+ expect(typeof result).toBe('function');
57
+ });
58
+
59
+ it('should work with different seed function signatures', () => {
60
+ const simpleSeed = () => Promise.resolve({ simple: true });
61
+ const complexSeed = async (
62
+ attrs: { name: string },
63
+ factory: any,
64
+ db: any,
65
+ ) => {
66
+ return { name: attrs.name, created: true };
67
+ };
68
+
69
+ const result1 = Factory.createSeed(simpleSeed);
70
+ const result2 = Factory.createSeed(complexSeed);
71
+
72
+ expect(result1).toBe(simpleSeed);
73
+ expect(result2).toBe(complexSeed);
74
+ });
75
+ });
76
+
77
+ describe('concrete implementation behavior', () => {
78
+ let factory: TestFactory;
79
+
80
+ beforeEach(() => {
81
+ factory = new TestFactory();
82
+ });
83
+
84
+ it('should implement insert method', async () => {
85
+ const result = await factory.insert('testBuilder', { name: 'test' });
86
+ expect(result).toEqual({ id: 1, name: 'test' });
87
+ });
88
+
89
+ it('should implement insertMany method', async () => {
90
+ const results = await factory.insertMany(3, 'testBuilder', {
91
+ name: 'test',
92
+ });
93
+ expect(results).toHaveLength(3);
94
+ expect(results[0]).toEqual({ id: 1, name: 'test' });
95
+ });
96
+
97
+ it('should implement insertMany with function attributes', async () => {
98
+ const results = await factory.insertMany(2, 'testBuilder', (idx) => ({
99
+ name: `test${idx}`,
100
+ }));
101
+ expect(results).toHaveLength(2);
102
+ expect(results[0]).toEqual({ id: 1, name: 'test0' });
103
+ expect(results[1]).toEqual({ id: 1, name: 'test1' });
104
+ });
105
+
106
+ it('should implement seed method', async () => {
107
+ const result = await factory.seed('testSeed', { custom: 'value' });
108
+ expect(result).toEqual({ seedResult: true, custom: 'value' });
109
+ });
110
+ });
111
+
112
+ describe('type definitions', () => {
113
+ it('should properly type MixedFactoryBuilder', () => {
114
+ // Test that the type allows both sync and async returns
115
+ const syncBuilder: MixedFactoryBuilder = (attrs, factory, db) => ({
116
+ sync: true,
117
+ });
118
+ const asyncBuilder: MixedFactoryBuilder = async (attrs, factory, db) => ({
119
+ async: true,
120
+ });
121
+
122
+ expect(typeof syncBuilder).toBe('function');
123
+ expect(typeof asyncBuilder).toBe('function');
124
+ });
125
+
126
+ it('should properly type FactorySeed', () => {
127
+ // Test that FactorySeed requires async return
128
+ const seed: FactorySeed = async (attrs, factory, db) => ({
129
+ seeded: true,
130
+ });
131
+
132
+ expect(typeof seed).toBe('function');
133
+ });
134
+ });
135
+ });
136
+
137
+ // Test the type exports
138
+ describe('Factory types', () => {
139
+ it('should export MixedFactoryBuilder type', () => {
140
+ const builder: MixedFactoryBuilder<
141
+ { name: string },
142
+ TestFactory,
143
+ { id: number; name: string },
144
+ any
145
+ > = (attrs, factory, db) => {
146
+ return { id: 1, name: attrs.name };
147
+ };
148
+
149
+ expect(typeof builder).toBe('function');
150
+ });
151
+
152
+ it('should export FactorySeed type', () => {
153
+ const seed: FactorySeed<
154
+ { count: number },
155
+ TestFactory,
156
+ { created: number },
157
+ any
158
+ > = async (attrs, factory, db) => {
159
+ return { created: attrs.count };
160
+ };
161
+
162
+ expect(typeof seed).toBe('function');
163
+ });
164
+ });