nestjs-ddd-cli 2.2.1 → 3.2.1

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 (254) hide show
  1. package/README.md +247 -408
  2. package/ddd.schema.json +111 -0
  3. package/dist/commands/aggregate-validator.d.ts +9 -0
  4. package/dist/commands/aggregate-validator.js +953 -0
  5. package/dist/commands/aggregate-validator.js.map +1 -0
  6. package/dist/commands/ai-assist.d.ts +8 -0
  7. package/dist/commands/ai-assist.js +337 -0
  8. package/dist/commands/ai-assist.js.map +1 -0
  9. package/dist/commands/api-contracts.d.ts +9 -0
  10. package/dist/commands/api-contracts.js +1368 -0
  11. package/dist/commands/api-contracts.js.map +1 -0
  12. package/dist/commands/api-docs.d.ts +8 -0
  13. package/dist/commands/api-docs.js +408 -0
  14. package/dist/commands/api-docs.js.map +1 -0
  15. package/dist/commands/api-versioning.d.ts +11 -0
  16. package/dist/commands/api-versioning.js +643 -0
  17. package/dist/commands/api-versioning.js.map +1 -0
  18. package/dist/commands/audit-logging.d.ts +9 -0
  19. package/dist/commands/audit-logging.js +1129 -0
  20. package/dist/commands/audit-logging.js.map +1 -0
  21. package/dist/commands/batch-generate.d.ts +10 -0
  22. package/dist/commands/batch-generate.js +405 -0
  23. package/dist/commands/batch-generate.js.map +1 -0
  24. package/dist/commands/caching-strategies.d.ts +9 -0
  25. package/dist/commands/caching-strategies.js +874 -0
  26. package/dist/commands/caching-strategies.js.map +1 -0
  27. package/dist/commands/code-analyzer.d.ts +42 -0
  28. package/dist/commands/code-analyzer.js +474 -0
  29. package/dist/commands/code-analyzer.js.map +1 -0
  30. package/dist/commands/database-seeding.d.ts +6 -0
  31. package/dist/commands/database-seeding.js +621 -0
  32. package/dist/commands/database-seeding.js.map +1 -0
  33. package/dist/commands/db-optimization.d.ts +7 -0
  34. package/dist/commands/db-optimization.js +687 -0
  35. package/dist/commands/db-optimization.js.map +1 -0
  36. package/dist/commands/dependency-graph.d.ts +6 -0
  37. package/dist/commands/dependency-graph.js +329 -0
  38. package/dist/commands/dependency-graph.js.map +1 -0
  39. package/dist/commands/doctor-enhanced.d.ts +22 -0
  40. package/dist/commands/doctor-enhanced.js +543 -0
  41. package/dist/commands/doctor-enhanced.js.map +1 -0
  42. package/dist/commands/doctor.d.ts +4 -0
  43. package/dist/commands/doctor.js +151 -0
  44. package/dist/commands/doctor.js.map +1 -0
  45. package/dist/commands/env-manager.d.ts +6 -0
  46. package/dist/commands/env-manager.js +419 -0
  47. package/dist/commands/env-manager.js.map +1 -0
  48. package/dist/commands/event-sourcing-full.d.ts +10 -0
  49. package/dist/commands/event-sourcing-full.js +1107 -0
  50. package/dist/commands/event-sourcing-full.js.map +1 -0
  51. package/dist/commands/feature-flags.d.ts +9 -0
  52. package/dist/commands/feature-flags.js +824 -0
  53. package/dist/commands/feature-flags.js.map +1 -0
  54. package/dist/commands/filter-dsl.d.ts +10 -0
  55. package/dist/commands/filter-dsl.js +1407 -0
  56. package/dist/commands/filter-dsl.js.map +1 -0
  57. package/dist/commands/generate-all.js +485 -32
  58. package/dist/commands/generate-all.js.map +1 -1
  59. package/dist/commands/generate-deployment.d.ts +8 -0
  60. package/dist/commands/generate-deployment.js +746 -0
  61. package/dist/commands/generate-deployment.js.map +1 -0
  62. package/dist/commands/generate-domain-service.d.ts +14 -0
  63. package/dist/commands/generate-domain-service.js +796 -0
  64. package/dist/commands/generate-domain-service.js.map +1 -0
  65. package/dist/commands/generate-entity.js +82 -24
  66. package/dist/commands/generate-entity.js.map +1 -1
  67. package/dist/commands/generate-from-schema.d.ts +56 -0
  68. package/dist/commands/generate-from-schema.js +222 -0
  69. package/dist/commands/generate-from-schema.js.map +1 -0
  70. package/dist/commands/generate-orchestrator.d.ts +14 -0
  71. package/dist/commands/generate-orchestrator.js +887 -0
  72. package/dist/commands/generate-orchestrator.js.map +1 -0
  73. package/dist/commands/generate-repository.d.ts +14 -0
  74. package/dist/commands/generate-repository.js +1019 -0
  75. package/dist/commands/generate-repository.js.map +1 -0
  76. package/dist/commands/generate-shared.d.ts +4 -0
  77. package/dist/commands/generate-shared.js +388 -0
  78. package/dist/commands/generate-shared.js.map +1 -0
  79. package/dist/commands/generate-value-object.d.ts +32 -0
  80. package/dist/commands/generate-value-object.js +700 -0
  81. package/dist/commands/generate-value-object.js.map +1 -0
  82. package/dist/commands/graphql-subscriptions.d.ts +6 -0
  83. package/dist/commands/graphql-subscriptions.js +607 -0
  84. package/dist/commands/graphql-subscriptions.js.map +1 -0
  85. package/dist/commands/graphql-types.d.ts +5 -0
  86. package/dist/commands/graphql-types.js +423 -0
  87. package/dist/commands/graphql-types.js.map +1 -0
  88. package/dist/commands/health-probes-advanced.d.ts +6 -0
  89. package/dist/commands/health-probes-advanced.js +655 -0
  90. package/dist/commands/health-probes-advanced.js.map +1 -0
  91. package/dist/commands/i18n-setup.d.ts +10 -0
  92. package/dist/commands/i18n-setup.js +677 -0
  93. package/dist/commands/i18n-setup.js.map +1 -0
  94. package/dist/commands/init-config.d.ts +6 -0
  95. package/dist/commands/init-config.js +370 -0
  96. package/dist/commands/init-config.js.map +1 -0
  97. package/dist/commands/init-project.js +56 -6
  98. package/dist/commands/init-project.js.map +1 -1
  99. package/dist/commands/interactive-scaffold.d.ts +5 -0
  100. package/dist/commands/interactive-scaffold.js +271 -0
  101. package/dist/commands/interactive-scaffold.js.map +1 -0
  102. package/dist/commands/metrics-prometheus.d.ts +6 -0
  103. package/dist/commands/metrics-prometheus.js +681 -0
  104. package/dist/commands/metrics-prometheus.js.map +1 -0
  105. package/dist/commands/migration-engine.d.ts +6 -0
  106. package/dist/commands/migration-engine.js +446 -0
  107. package/dist/commands/migration-engine.js.map +1 -0
  108. package/dist/commands/migration.d.ts +12 -0
  109. package/dist/commands/migration.js +484 -0
  110. package/dist/commands/migration.js.map +1 -0
  111. package/dist/commands/monorepo.d.ts +8 -0
  112. package/dist/commands/monorepo.js +483 -0
  113. package/dist/commands/monorepo.js.map +1 -0
  114. package/dist/commands/multi-database.d.ts +5 -0
  115. package/dist/commands/multi-database.js +439 -0
  116. package/dist/commands/multi-database.js.map +1 -0
  117. package/dist/commands/observability-tracing.d.ts +10 -0
  118. package/dist/commands/observability-tracing.js +740 -0
  119. package/dist/commands/observability-tracing.js.map +1 -0
  120. package/dist/commands/openapi-export.d.ts +8 -0
  121. package/dist/commands/openapi-export.js +359 -0
  122. package/dist/commands/openapi-export.js.map +1 -0
  123. package/dist/commands/perf-analyzer.d.ts +8 -0
  124. package/dist/commands/perf-analyzer.js +423 -0
  125. package/dist/commands/perf-analyzer.js.map +1 -0
  126. package/dist/commands/rate-limiting.d.ts +10 -0
  127. package/dist/commands/rate-limiting.js +953 -0
  128. package/dist/commands/rate-limiting.js.map +1 -0
  129. package/dist/commands/recipe-plugin.d.ts +56 -0
  130. package/dist/commands/recipe-plugin.js +315 -0
  131. package/dist/commands/recipe-plugin.js.map +1 -0
  132. package/dist/commands/recipe.d.ts +6 -0
  133. package/dist/commands/recipe.js +3941 -0
  134. package/dist/commands/recipe.js.map +1 -0
  135. package/dist/commands/recipes/elasticsearch.recipe.d.ts +1 -0
  136. package/dist/commands/recipes/elasticsearch.recipe.js +761 -0
  137. package/dist/commands/recipes/elasticsearch.recipe.js.map +1 -0
  138. package/dist/commands/recipes/event-sourcing.recipe.d.ts +1 -0
  139. package/dist/commands/recipes/event-sourcing.recipe.js +889 -0
  140. package/dist/commands/recipes/event-sourcing.recipe.js.map +1 -0
  141. package/dist/commands/recipes/index.d.ts +7 -0
  142. package/dist/commands/recipes/index.js +24 -0
  143. package/dist/commands/recipes/index.js.map +1 -0
  144. package/dist/commands/recipes/message-queue.recipe.d.ts +1 -0
  145. package/dist/commands/recipes/message-queue.recipe.js +706 -0
  146. package/dist/commands/recipes/message-queue.recipe.js.map +1 -0
  147. package/dist/commands/recipes/middleware.recipe.d.ts +1 -0
  148. package/dist/commands/recipes/middleware.recipe.js +383 -0
  149. package/dist/commands/recipes/middleware.recipe.js.map +1 -0
  150. package/dist/commands/recipes/multi-tenancy.recipe.d.ts +1 -0
  151. package/dist/commands/recipes/multi-tenancy.recipe.js +520 -0
  152. package/dist/commands/recipes/multi-tenancy.recipe.js.map +1 -0
  153. package/dist/commands/recipes/oauth2.recipe.d.ts +1 -0
  154. package/dist/commands/recipes/oauth2.recipe.js +472 -0
  155. package/dist/commands/recipes/oauth2.recipe.js.map +1 -0
  156. package/dist/commands/recipes/websocket.recipe.d.ts +1 -0
  157. package/dist/commands/recipes/websocket.recipe.js +453 -0
  158. package/dist/commands/recipes/websocket.recipe.js.map +1 -0
  159. package/dist/commands/resilience-patterns.d.ts +13 -0
  160. package/dist/commands/resilience-patterns.js +1029 -0
  161. package/dist/commands/resilience-patterns.js.map +1 -0
  162. package/dist/commands/security-patterns.d.ts +11 -0
  163. package/dist/commands/security-patterns.js +2233 -0
  164. package/dist/commands/security-patterns.js.map +1 -0
  165. package/dist/commands/template-debug.d.ts +27 -0
  166. package/dist/commands/template-debug.js +388 -0
  167. package/dist/commands/template-debug.js.map +1 -0
  168. package/dist/commands/test-factory-full.d.ts +9 -0
  169. package/dist/commands/test-factory-full.js +1570 -0
  170. package/dist/commands/test-factory-full.js.map +1 -0
  171. package/dist/commands/test-scaffold.d.ts +7 -0
  172. package/dist/commands/test-scaffold.js +621 -0
  173. package/dist/commands/test-scaffold.js.map +1 -0
  174. package/dist/index.js +1088 -0
  175. package/dist/index.js.map +1 -1
  176. package/dist/templates/ai-context/CLAUDE.md.hbs +158 -0
  177. package/dist/templates/ai-context/conventions.md.hbs +154 -0
  178. package/dist/templates/command/create-command.hbs +6 -14
  179. package/dist/templates/command/delete-command.hbs +19 -0
  180. package/dist/templates/command/update-command.hbs +24 -0
  181. package/dist/templates/controller/controller.hbs +64 -17
  182. package/dist/templates/dto/create-dto.hbs +29 -5
  183. package/dist/templates/dto/filter-dto.hbs +52 -0
  184. package/dist/templates/dto/filter-query.dto.hbs +148 -0
  185. package/dist/templates/dto/paginated-response.dto.hbs +29 -0
  186. package/dist/templates/dto/pagination-query.dto.hbs +30 -0
  187. package/dist/templates/dto/response-dto.hbs +38 -0
  188. package/dist/templates/dto/update-dto.hbs +11 -0
  189. package/dist/templates/entity/entity.hbs +32 -1
  190. package/dist/templates/event/domain-event.hbs +33 -7
  191. package/dist/templates/event/event-handler.hbs +40 -0
  192. package/dist/templates/exception/base-exceptions.hbs +69 -0
  193. package/dist/templates/exception/entity-not-found.exception.hbs +7 -0
  194. package/dist/templates/mapper/mapper.hbs +49 -24
  195. package/dist/templates/module/module.hbs +34 -10
  196. package/dist/templates/orm-entity/orm-entity.hbs +63 -12
  197. package/dist/templates/prisma/prisma-mapper.hbs +71 -0
  198. package/dist/templates/prisma/prisma-repository.hbs +114 -0
  199. package/dist/templates/prisma/prisma-schema.hbs +20 -0
  200. package/dist/templates/prisma/prisma-service.hbs +51 -0
  201. package/dist/templates/query/get-all.query.hbs +50 -0
  202. package/dist/templates/query/get-by-id.query.hbs +31 -0
  203. package/dist/templates/repository/repository.hbs +55 -13
  204. package/dist/templates/resolver/graphql-input.hbs +54 -0
  205. package/dist/templates/resolver/graphql-type.hbs +58 -0
  206. package/dist/templates/resolver/pagination-args.hbs +33 -0
  207. package/dist/templates/resolver/resolver.hbs +62 -0
  208. package/dist/templates/shared/prisma-query-builder.util.hbs +189 -0
  209. package/dist/templates/shared/query-builder.util.hbs +218 -0
  210. package/dist/templates/test/controller.spec.hbs +124 -0
  211. package/dist/templates/test/repository.spec.hbs +158 -0
  212. package/dist/templates/test/usecase.spec.hbs +116 -0
  213. package/dist/templates/usecase/create-usecase.hbs +19 -7
  214. package/dist/templates/usecase/delete-usecase.hbs +17 -0
  215. package/dist/templates/usecase/update-usecase.hbs +31 -0
  216. package/dist/utils/config.utils.d.ts +45 -0
  217. package/dist/utils/config.utils.js +211 -0
  218. package/dist/utils/config.utils.js.map +1 -0
  219. package/dist/utils/error.utils.d.ts +145 -0
  220. package/dist/utils/error.utils.js +422 -0
  221. package/dist/utils/error.utils.js.map +1 -0
  222. package/dist/utils/field.utils.d.ts +54 -0
  223. package/dist/utils/field.utils.js +389 -0
  224. package/dist/utils/field.utils.js.map +1 -0
  225. package/dist/utils/file.utils.d.ts +19 -8
  226. package/dist/utils/file.utils.js +135 -4
  227. package/dist/utils/file.utils.js.map +1 -1
  228. package/dist/utils/idempotency.utils.d.ts +123 -0
  229. package/dist/utils/idempotency.utils.js +444 -0
  230. package/dist/utils/idempotency.utils.js.map +1 -0
  231. package/dist/utils/naming.utils.js +24 -5
  232. package/dist/utils/naming.utils.js.map +1 -1
  233. package/dist/utils/performance.utils.d.ts +37 -0
  234. package/dist/utils/performance.utils.js +158 -0
  235. package/dist/utils/performance.utils.js.map +1 -0
  236. package/dist/utils/relation.utils.d.ts +92 -0
  237. package/dist/utils/relation.utils.js +388 -0
  238. package/dist/utils/relation.utils.js.map +1 -0
  239. package/dist/utils/rollback.utils.d.ts +49 -0
  240. package/dist/utils/rollback.utils.js +306 -0
  241. package/dist/utils/rollback.utils.js.map +1 -0
  242. package/dist/utils/schema.utils.d.ts +123 -0
  243. package/dist/utils/schema.utils.js +419 -0
  244. package/dist/utils/schema.utils.js.map +1 -0
  245. package/dist/utils/security.utils.d.ts +57 -0
  246. package/dist/utils/security.utils.js +315 -0
  247. package/dist/utils/security.utils.js.map +1 -0
  248. package/dist/utils/template-engine.utils.d.ts +80 -0
  249. package/dist/utils/template-engine.utils.js +463 -0
  250. package/dist/utils/template-engine.utils.js.map +1 -0
  251. package/dist/utils/validation-registry.utils.d.ts +160 -0
  252. package/dist/utils/validation-registry.utils.js +526 -0
  253. package/dist/utils/validation-registry.utils.js.map +1 -0
  254. package/package.json +3 -1
@@ -0,0 +1,1570 @@
1
+ "use strict";
2
+ /**
3
+ * Enhanced Test Fixtures & Factories
4
+ * Complete test infrastructure generation
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.setupTestInfrastructure = setupTestInfrastructure;
44
+ const path = __importStar(require("path"));
45
+ const chalk_1 = __importDefault(require("chalk"));
46
+ const file_utils_1 = require("../utils/file.utils");
47
+ async function setupTestInfrastructure(basePath, options = {}) {
48
+ console.log(chalk_1.default.bold.blue('\n🧪 Setting up Test Infrastructure\n'));
49
+ const testPath = path.join(basePath, 'src/shared/testing');
50
+ await (0, file_utils_1.ensureDir)(testPath);
51
+ await (0, file_utils_1.ensureDir)(path.join(testPath, 'factories'));
52
+ await (0, file_utils_1.ensureDir)(path.join(testPath, 'fixtures'));
53
+ await (0, file_utils_1.ensureDir)(path.join(testPath, 'mocks'));
54
+ await (0, file_utils_1.ensureDir)(path.join(testPath, 'utils'));
55
+ // Generate base factory
56
+ await generateBaseFactory(testPath);
57
+ // Generate factory builder
58
+ await generateFactoryBuilder(testPath);
59
+ // Generate fixture loader
60
+ await generateFixtureLoader(testPath);
61
+ // Generate mock repository
62
+ await generateMockRepository(testPath);
63
+ // Generate database seeder
64
+ await generateDatabaseSeeder(testPath, options);
65
+ // Generate test module builder
66
+ await generateTestModuleBuilder(testPath);
67
+ // Generate test utils
68
+ await generateTestUtils(testPath);
69
+ // Generate sample factories
70
+ await generateSampleFactories(testPath);
71
+ // Generate index
72
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'index.ts'), `export * from './factories/base.factory';
73
+ export * from './factories/factory.builder';
74
+ export * from './fixtures/fixture.loader';
75
+ export * from './mocks/mock.repository';
76
+ export * from './utils/database.seeder';
77
+ export * from './utils/test-module.builder';
78
+ export * from './utils/test.utils';
79
+ `);
80
+ console.log(chalk_1.default.green('\n✅ Test Infrastructure set up!'));
81
+ }
82
+ async function generateBaseFactory(testPath) {
83
+ const content = `import { faker } from '@faker-js/faker';
84
+
85
+ /**
86
+ * Base factory class for generating test data
87
+ */
88
+ export abstract class BaseFactory<T, CreateInput = Partial<T>> {
89
+ protected abstract getDefaults(): T;
90
+
91
+ /**
92
+ * Create a single instance
93
+ */
94
+ create(overrides?: CreateInput): T {
95
+ return {
96
+ ...this.getDefaults(),
97
+ ...overrides,
98
+ } as T;
99
+ }
100
+
101
+ /**
102
+ * Create multiple instances
103
+ */
104
+ createMany(count: number, overrides?: CreateInput): T[] {
105
+ return Array.from({ length: count }, () => this.create(overrides));
106
+ }
107
+
108
+ /**
109
+ * Create with specific traits
110
+ */
111
+ with(traits: Partial<T>): this {
112
+ const original = this.getDefaults.bind(this);
113
+ this.getDefaults = () => ({ ...original(), ...traits });
114
+ return this;
115
+ }
116
+
117
+ /**
118
+ * Create a builder for fluent interface
119
+ */
120
+ builder(): FactoryBuilder<T> {
121
+ return new FactoryBuilder<T>(this);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Factory builder for fluent creation
127
+ */
128
+ export class FactoryBuilder<T> {
129
+ private traits: Partial<T> = {};
130
+ private afterCreate: Array<(entity: T) => T | Promise<T>> = [];
131
+
132
+ constructor(private factory: BaseFactory<T>) {}
133
+
134
+ with(traits: Partial<T>): this {
135
+ this.traits = { ...this.traits, ...traits };
136
+ return this;
137
+ }
138
+
139
+ afterCreating(callback: (entity: T) => T | Promise<T>): this {
140
+ this.afterCreate.push(callback);
141
+ return this;
142
+ }
143
+
144
+ async create(): Promise<T> {
145
+ let entity = this.factory.create(this.traits as any);
146
+ for (const callback of this.afterCreate) {
147
+ entity = await callback(entity);
148
+ }
149
+ return entity;
150
+ }
151
+
152
+ async createMany(count: number): Promise<T[]> {
153
+ const entities: T[] = [];
154
+ for (let i = 0; i < count; i++) {
155
+ entities.push(await this.create());
156
+ }
157
+ return entities;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Faker helpers for common fields
163
+ */
164
+ export const FakerHelpers = {
165
+ id: () => faker.string.uuid(),
166
+ email: () => faker.internet.email(),
167
+ password: () => faker.internet.password({ length: 12 }),
168
+ firstName: () => faker.person.firstName(),
169
+ lastName: () => faker.person.lastName(),
170
+ fullName: () => faker.person.fullName(),
171
+ username: () => faker.internet.username(),
172
+ phone: () => faker.phone.number(),
173
+ address: () => faker.location.streetAddress(),
174
+ city: () => faker.location.city(),
175
+ country: () => faker.location.country(),
176
+ zipCode: () => faker.location.zipCode(),
177
+ url: () => faker.internet.url(),
178
+ imageUrl: () => faker.image.url(),
179
+ avatarUrl: () => faker.image.avatar(),
180
+ paragraph: () => faker.lorem.paragraph(),
181
+ sentence: () => faker.lorem.sentence(),
182
+ word: () => faker.lorem.word(),
183
+ date: () => faker.date.past(),
184
+ futureDate: () => faker.date.future(),
185
+ recentDate: () => faker.date.recent(),
186
+ price: () => faker.number.float({ min: 1, max: 1000, fractionDigits: 2 }),
187
+ quantity: () => faker.number.int({ min: 1, max: 100 }),
188
+ boolean: () => faker.datatype.boolean(),
189
+ pick: <T>(arr: T[]) => faker.helpers.arrayElement(arr),
190
+ json: () => ({ key: faker.lorem.word(), value: faker.lorem.word() }),
191
+ };
192
+
193
+ /**
194
+ * Type-safe factory creator
195
+ */
196
+ export function defineFactory<T>(defaults: () => T): BaseFactory<T> {
197
+ return new (class extends BaseFactory<T> {
198
+ protected getDefaults(): T {
199
+ return defaults();
200
+ }
201
+ })();
202
+ }
203
+ `;
204
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'factories/base.factory.ts'), content);
205
+ console.log(chalk_1.default.green(' ✓ Base factory'));
206
+ }
207
+ async function generateFactoryBuilder(testPath) {
208
+ const content = `import { faker } from '@faker-js/faker';
209
+
210
+ /**
211
+ * Advanced factory builder with sequences and states
212
+ */
213
+ export class AdvancedFactoryBuilder<T> {
214
+ private defaults: () => T;
215
+ private traits: Map<string, Partial<T>> = new Map();
216
+ private sequences: Map<keyof T, Generator<any>> = new Map();
217
+ private afterCreateHooks: Array<(entity: T) => T | Promise<T>> = [];
218
+ private afterBuildHooks: Array<(entity: T) => T> = [];
219
+ private currentTraits: string[] = [];
220
+ private overrides: Partial<T> = {};
221
+
222
+ constructor(defaults: () => T) {
223
+ this.defaults = defaults;
224
+ }
225
+
226
+ /**
227
+ * Define a named trait (state)
228
+ */
229
+ trait(name: string, attributes: Partial<T>): this {
230
+ this.traits.set(name, attributes);
231
+ return this;
232
+ }
233
+
234
+ /**
235
+ * Define a sequence for a field
236
+ */
237
+ sequence<K extends keyof T>(field: K, generator: () => Generator<T[K]>): this {
238
+ this.sequences.set(field, generator());
239
+ return this;
240
+ }
241
+
242
+ /**
243
+ * Add after-build hook (synchronous)
244
+ */
245
+ afterBuild(callback: (entity: T) => T): this {
246
+ this.afterBuildHooks.push(callback);
247
+ return this;
248
+ }
249
+
250
+ /**
251
+ * Add after-create hook (can be async)
252
+ */
253
+ afterCreate(callback: (entity: T) => T | Promise<T>): this {
254
+ this.afterCreateHooks.push(callback);
255
+ return this;
256
+ }
257
+
258
+ /**
259
+ * Use a trait
260
+ */
261
+ use(...traitNames: string[]): AdvancedFactoryBuilder<T> {
262
+ const builder = this.clone();
263
+ builder.currentTraits = [...this.currentTraits, ...traitNames];
264
+ return builder;
265
+ }
266
+
267
+ /**
268
+ * Override specific attributes
269
+ */
270
+ with(attrs: Partial<T>): AdvancedFactoryBuilder<T> {
271
+ const builder = this.clone();
272
+ builder.overrides = { ...this.overrides, ...attrs };
273
+ return builder;
274
+ }
275
+
276
+ /**
277
+ * Build an entity (synchronous, no hooks)
278
+ */
279
+ build(): T {
280
+ let entity = this.defaults();
281
+
282
+ // Apply sequences
283
+ for (const [field, generator] of this.sequences) {
284
+ (entity as any)[field] = generator.next().value;
285
+ }
286
+
287
+ // Apply traits
288
+ for (const traitName of this.currentTraits) {
289
+ const trait = this.traits.get(traitName);
290
+ if (trait) {
291
+ entity = { ...entity, ...trait };
292
+ }
293
+ }
294
+
295
+ // Apply overrides
296
+ entity = { ...entity, ...this.overrides };
297
+
298
+ // Run after-build hooks
299
+ for (const hook of this.afterBuildHooks) {
300
+ entity = hook(entity);
301
+ }
302
+
303
+ return entity;
304
+ }
305
+
306
+ /**
307
+ * Build multiple entities
308
+ */
309
+ buildMany(count: number): T[] {
310
+ return Array.from({ length: count }, () => this.build());
311
+ }
312
+
313
+ /**
314
+ * Create an entity (runs after-create hooks)
315
+ */
316
+ async create(): Promise<T> {
317
+ let entity = this.build();
318
+
319
+ for (const hook of this.afterCreateHooks) {
320
+ entity = await hook(entity);
321
+ }
322
+
323
+ return entity;
324
+ }
325
+
326
+ /**
327
+ * Create multiple entities
328
+ */
329
+ async createMany(count: number): Promise<T[]> {
330
+ const entities: T[] = [];
331
+ for (let i = 0; i < count; i++) {
332
+ entities.push(await this.create());
333
+ }
334
+ return entities;
335
+ }
336
+
337
+ /**
338
+ * Clone the builder
339
+ */
340
+ private clone(): AdvancedFactoryBuilder<T> {
341
+ const builder = new AdvancedFactoryBuilder(this.defaults);
342
+ builder.traits = new Map(this.traits);
343
+ builder.sequences = new Map(this.sequences);
344
+ builder.afterCreateHooks = [...this.afterCreateHooks];
345
+ builder.afterBuildHooks = [...this.afterBuildHooks];
346
+ builder.currentTraits = [...this.currentTraits];
347
+ builder.overrides = { ...this.overrides };
348
+ return builder;
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Create an advanced factory
354
+ */
355
+ export function factory<T>(defaults: () => T): AdvancedFactoryBuilder<T> {
356
+ return new AdvancedFactoryBuilder(defaults);
357
+ }
358
+
359
+ /**
360
+ * Sequence generator helpers
361
+ */
362
+ export const sequences = {
363
+ /**
364
+ * Auto-incrementing number
365
+ */
366
+ *autoIncrement(start: number = 1): Generator<number> {
367
+ let current = start;
368
+ while (true) {
369
+ yield current++;
370
+ }
371
+ },
372
+
373
+ /**
374
+ * Unique email with counter
375
+ */
376
+ *uniqueEmail(domain: string = 'test.com'): Generator<string> {
377
+ let counter = 1;
378
+ while (true) {
379
+ yield \`user\${counter++}@\${domain}\`;
380
+ }
381
+ },
382
+
383
+ /**
384
+ * Unique username
385
+ */
386
+ *uniqueUsername(prefix: string = 'user'): Generator<string> {
387
+ let counter = 1;
388
+ while (true) {
389
+ yield \`\${prefix}\${counter++}\`;
390
+ }
391
+ },
392
+
393
+ /**
394
+ * Cycle through values
395
+ */
396
+ *cycle<T>(values: T[]): Generator<T> {
397
+ let index = 0;
398
+ while (true) {
399
+ yield values[index % values.length];
400
+ index++;
401
+ }
402
+ },
403
+
404
+ /**
405
+ * Random from array
406
+ */
407
+ *randomFrom<T>(values: T[]): Generator<T> {
408
+ while (true) {
409
+ yield faker.helpers.arrayElement(values);
410
+ }
411
+ },
412
+ };
413
+ `;
414
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'factories/factory.builder.ts'), content);
415
+ console.log(chalk_1.default.green(' ✓ Factory builder'));
416
+ }
417
+ async function generateFixtureLoader(testPath) {
418
+ const content = `import * as fs from 'fs';
419
+ import * as path from 'path';
420
+ import * as yaml from 'yaml';
421
+
422
+ export interface Fixture<T = any> {
423
+ name: string;
424
+ entity: string;
425
+ data: T[];
426
+ }
427
+
428
+ /**
429
+ * Load and manage test fixtures
430
+ */
431
+ export class FixtureLoader {
432
+ private fixtures: Map<string, Fixture> = new Map();
433
+ private basePath: string;
434
+
435
+ constructor(basePath?: string) {
436
+ this.basePath = basePath || path.join(process.cwd(), 'test/fixtures');
437
+ }
438
+
439
+ /**
440
+ * Load a fixture file (JSON or YAML)
441
+ */
442
+ load<T = any>(name: string): Fixture<T> {
443
+ if (this.fixtures.has(name)) {
444
+ return this.fixtures.get(name)! as Fixture<T>;
445
+ }
446
+
447
+ const jsonPath = path.join(this.basePath, \`\${name}.json\`);
448
+ const yamlPath = path.join(this.basePath, \`\${name}.yaml\`);
449
+ const ymlPath = path.join(this.basePath, \`\${name}.yml\`);
450
+
451
+ let data: any;
452
+
453
+ if (fs.existsSync(jsonPath)) {
454
+ data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
455
+ } else if (fs.existsSync(yamlPath)) {
456
+ data = yaml.parse(fs.readFileSync(yamlPath, 'utf-8'));
457
+ } else if (fs.existsSync(ymlPath)) {
458
+ data = yaml.parse(fs.readFileSync(ymlPath, 'utf-8'));
459
+ } else {
460
+ throw new Error(\`Fixture not found: \${name}\`);
461
+ }
462
+
463
+ const fixture: Fixture<T> = {
464
+ name,
465
+ entity: data.entity || name,
466
+ data: Array.isArray(data) ? data : data.data || [],
467
+ };
468
+
469
+ this.fixtures.set(name, fixture);
470
+ return fixture;
471
+ }
472
+
473
+ /**
474
+ * Load all fixtures from directory
475
+ */
476
+ loadAll(): Map<string, Fixture> {
477
+ if (!fs.existsSync(this.basePath)) {
478
+ return this.fixtures;
479
+ }
480
+
481
+ const files = fs.readdirSync(this.basePath);
482
+
483
+ for (const file of files) {
484
+ const ext = path.extname(file);
485
+ if (['.json', '.yaml', '.yml'].includes(ext)) {
486
+ const name = path.basename(file, ext);
487
+ this.load(name);
488
+ }
489
+ }
490
+
491
+ return this.fixtures;
492
+ }
493
+
494
+ /**
495
+ * Get fixture data by name
496
+ */
497
+ get<T = any>(name: string): T[] {
498
+ return this.load<T>(name).data;
499
+ }
500
+
501
+ /**
502
+ * Get first item from fixture
503
+ */
504
+ first<T = any>(name: string): T | undefined {
505
+ return this.get<T>(name)[0];
506
+ }
507
+
508
+ /**
509
+ * Get random item from fixture
510
+ */
511
+ random<T = any>(name: string): T | undefined {
512
+ const data = this.get<T>(name);
513
+ return data[Math.floor(Math.random() * data.length)];
514
+ }
515
+
516
+ /**
517
+ * Get item by index
518
+ */
519
+ at<T = any>(name: string, index: number): T | undefined {
520
+ return this.get<T>(name)[index];
521
+ }
522
+
523
+ /**
524
+ * Filter fixture data
525
+ */
526
+ filter<T = any>(name: string, predicate: (item: T) => boolean): T[] {
527
+ return this.get<T>(name).filter(predicate);
528
+ }
529
+
530
+ /**
531
+ * Find item in fixture
532
+ */
533
+ find<T = any>(name: string, predicate: (item: T) => boolean): T | undefined {
534
+ return this.get<T>(name).find(predicate);
535
+ }
536
+
537
+ /**
538
+ * Clear cached fixtures
539
+ */
540
+ clear(): void {
541
+ this.fixtures.clear();
542
+ }
543
+
544
+ /**
545
+ * Create fixtures from factory
546
+ */
547
+ static createFixture<T>(
548
+ name: string,
549
+ entity: string,
550
+ factory: () => T,
551
+ count: number = 10
552
+ ): Fixture<T> {
553
+ return {
554
+ name,
555
+ entity,
556
+ data: Array.from({ length: count }, factory),
557
+ };
558
+ }
559
+
560
+ /**
561
+ * Save fixture to file
562
+ */
563
+ async save<T>(name: string, fixture: Fixture<T>, format: 'json' | 'yaml' = 'json'): Promise<void> {
564
+ const filePath = path.join(this.basePath, \`\${name}.\${format}\`);
565
+
566
+ if (!fs.existsSync(this.basePath)) {
567
+ fs.mkdirSync(this.basePath, { recursive: true });
568
+ }
569
+
570
+ const content = format === 'json'
571
+ ? JSON.stringify(fixture, null, 2)
572
+ : yaml.stringify(fixture);
573
+
574
+ fs.writeFileSync(filePath, content);
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Create a fixture loader
580
+ */
581
+ export function createFixtureLoader(basePath?: string): FixtureLoader {
582
+ return new FixtureLoader(basePath);
583
+ }
584
+
585
+ /**
586
+ * Fixture reference for lazy loading
587
+ */
588
+ export class FixtureRef<T = any> {
589
+ constructor(
590
+ private loader: FixtureLoader,
591
+ private name: string,
592
+ private selector?: (data: T[]) => T
593
+ ) {}
594
+
595
+ get(): T | undefined {
596
+ const data = this.loader.get<T>(this.name);
597
+ return this.selector ? this.selector(data) : data[0];
598
+ }
599
+
600
+ all(): T[] {
601
+ return this.loader.get<T>(this.name);
602
+ }
603
+ }
604
+ `;
605
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'fixtures/fixture.loader.ts'), content);
606
+ console.log(chalk_1.default.green(' ✓ Fixture loader'));
607
+ }
608
+ async function generateMockRepository(testPath) {
609
+ const content = `/**
610
+ * Mock Repository Implementation
611
+ * For testing without database
612
+ */
613
+
614
+ export interface IMockRepository<T extends { id: string | number }> {
615
+ findAll(): Promise<T[]>;
616
+ findById(id: string | number): Promise<T | null>;
617
+ findOne(where: Partial<T>): Promise<T | null>;
618
+ findMany(where: Partial<T>): Promise<T[]>;
619
+ create(data: Partial<T>): Promise<T>;
620
+ update(id: string | number, data: Partial<T>): Promise<T | null>;
621
+ delete(id: string | number): Promise<void>;
622
+ clear(): void;
623
+ seed(data: T[]): void;
624
+ }
625
+
626
+ /**
627
+ * In-memory mock repository
628
+ */
629
+ export class MockRepository<T extends { id: string | number }> implements IMockRepository<T> {
630
+ private data: Map<string | number, T> = new Map();
631
+ private idGenerator: () => string | number;
632
+
633
+ constructor(
634
+ private options: {
635
+ idGenerator?: () => string | number;
636
+ initialData?: T[];
637
+ } = {}
638
+ ) {
639
+ this.idGenerator = options.idGenerator || (() => \`mock_\${Date.now()}_\${Math.random().toString(36).slice(2, 9)}\`);
640
+
641
+ if (options.initialData) {
642
+ this.seed(options.initialData);
643
+ }
644
+ }
645
+
646
+ async findAll(): Promise<T[]> {
647
+ return Array.from(this.data.values());
648
+ }
649
+
650
+ async findById(id: string | number): Promise<T | null> {
651
+ return this.data.get(id) || null;
652
+ }
653
+
654
+ async findOne(where: Partial<T>): Promise<T | null> {
655
+ const all = Array.from(this.data.values());
656
+ return all.find(item => this.matches(item, where)) || null;
657
+ }
658
+
659
+ async findMany(where: Partial<T>): Promise<T[]> {
660
+ const all = Array.from(this.data.values());
661
+ return all.filter(item => this.matches(item, where));
662
+ }
663
+
664
+ async create(data: Partial<T>): Promise<T> {
665
+ const id = data.id || this.idGenerator();
666
+ const entity = { ...data, id } as T;
667
+ this.data.set(id, entity);
668
+ return entity;
669
+ }
670
+
671
+ async update(id: string | number, data: Partial<T>): Promise<T | null> {
672
+ const existing = this.data.get(id);
673
+ if (!existing) return null;
674
+
675
+ const updated = { ...existing, ...data, id };
676
+ this.data.set(id, updated);
677
+ return updated;
678
+ }
679
+
680
+ async delete(id: string | number): Promise<void> {
681
+ this.data.delete(id);
682
+ }
683
+
684
+ clear(): void {
685
+ this.data.clear();
686
+ }
687
+
688
+ seed(data: T[]): void {
689
+ for (const item of data) {
690
+ this.data.set(item.id, item);
691
+ }
692
+ }
693
+
694
+ // For testing
695
+ getAll(): T[] {
696
+ return Array.from(this.data.values());
697
+ }
698
+
699
+ count(): number {
700
+ return this.data.size;
701
+ }
702
+
703
+ private matches(item: T, where: Partial<T>): boolean {
704
+ return Object.entries(where).every(([key, value]) => {
705
+ return (item as any)[key] === value;
706
+ });
707
+ }
708
+ }
709
+
710
+ /**
711
+ * Create a type-safe mock repository
712
+ */
713
+ export function createMockRepository<T extends { id: string | number }>(
714
+ options?: {
715
+ idGenerator?: () => string | number;
716
+ initialData?: T[];
717
+ }
718
+ ): MockRepository<T> {
719
+ return new MockRepository<T>(options);
720
+ }
721
+
722
+ /**
723
+ * Jest mock helpers for repositories
724
+ */
725
+ export function createJestMockRepository<T extends { id: string | number }>(): {
726
+ findAll: jest.Mock<Promise<T[]>>;
727
+ findById: jest.Mock<Promise<T | null>>;
728
+ findOne: jest.Mock<Promise<T | null>>;
729
+ findMany: jest.Mock<Promise<T[]>>;
730
+ create: jest.Mock<Promise<T>>;
731
+ update: jest.Mock<Promise<T | null>>;
732
+ delete: jest.Mock<Promise<void>>;
733
+ save: jest.Mock<Promise<T>>;
734
+ remove: jest.Mock<Promise<void>>;
735
+ } {
736
+ return {
737
+ findAll: jest.fn().mockResolvedValue([]),
738
+ findById: jest.fn().mockResolvedValue(null),
739
+ findOne: jest.fn().mockResolvedValue(null),
740
+ findMany: jest.fn().mockResolvedValue([]),
741
+ create: jest.fn().mockImplementation((data) => Promise.resolve(data)),
742
+ update: jest.fn().mockImplementation((id, data) => Promise.resolve({ id, ...data })),
743
+ delete: jest.fn().mockResolvedValue(undefined),
744
+ save: jest.fn().mockImplementation((data) => Promise.resolve(data)),
745
+ remove: jest.fn().mockResolvedValue(undefined),
746
+ };
747
+ }
748
+
749
+ /**
750
+ * Mock query builder for TypeORM
751
+ */
752
+ export class MockQueryBuilder<T> {
753
+ private data: T[] = [];
754
+ private conditions: any[] = [];
755
+ private orderByFields: any[] = [];
756
+ private skipCount = 0;
757
+ private takeCount = 0;
758
+ private selectedFields: string[] = [];
759
+ private relations: string[] = [];
760
+
761
+ constructor(initialData: T[] = []) {
762
+ this.data = [...initialData];
763
+ }
764
+
765
+ select(selection: string | string[]): this {
766
+ this.selectedFields = Array.isArray(selection) ? selection : [selection];
767
+ return this;
768
+ }
769
+
770
+ where(condition: string, params?: Record<string, any>): this {
771
+ this.conditions.push({ condition, params });
772
+ return this;
773
+ }
774
+
775
+ andWhere(condition: string, params?: Record<string, any>): this {
776
+ return this.where(condition, params);
777
+ }
778
+
779
+ orWhere(condition: string, params?: Record<string, any>): this {
780
+ return this.where(condition, params);
781
+ }
782
+
783
+ orderBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
784
+ this.orderByFields.push({ field, direction });
785
+ return this;
786
+ }
787
+
788
+ addOrderBy(field: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
789
+ return this.orderBy(field, direction);
790
+ }
791
+
792
+ skip(count: number): this {
793
+ this.skipCount = count;
794
+ return this;
795
+ }
796
+
797
+ take(count: number): this {
798
+ this.takeCount = count;
799
+ return this;
800
+ }
801
+
802
+ leftJoinAndSelect(relation: string, alias: string): this {
803
+ this.relations.push(relation);
804
+ return this;
805
+ }
806
+
807
+ async getMany(): Promise<T[]> {
808
+ let result = [...this.data];
809
+
810
+ if (this.skipCount > 0) {
811
+ result = result.slice(this.skipCount);
812
+ }
813
+
814
+ if (this.takeCount > 0) {
815
+ result = result.slice(0, this.takeCount);
816
+ }
817
+
818
+ return result;
819
+ }
820
+
821
+ async getOne(): Promise<T | null> {
822
+ return this.data[0] || null;
823
+ }
824
+
825
+ async getManyAndCount(): Promise<[T[], number]> {
826
+ const data = await this.getMany();
827
+ return [data, this.data.length];
828
+ }
829
+
830
+ async getCount(): Promise<number> {
831
+ return this.data.length;
832
+ }
833
+
834
+ setData(data: T[]): this {
835
+ this.data = data;
836
+ return this;
837
+ }
838
+ }
839
+
840
+ export function createMockQueryBuilder<T>(data: T[] = []): MockQueryBuilder<T> {
841
+ return new MockQueryBuilder(data);
842
+ }
843
+ `;
844
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'mocks/mock.repository.ts'), content);
845
+ console.log(chalk_1.default.green(' ✓ Mock repository'));
846
+ }
847
+ async function generateDatabaseSeeder(testPath, options) {
848
+ const content = `import { DataSource, EntityTarget, Repository } from 'typeorm';
849
+
850
+ export interface SeederOptions {
851
+ truncate?: boolean;
852
+ order?: string[];
853
+ }
854
+
855
+ export type SeederFactory<T> = () => T | T[] | Promise<T | T[]>;
856
+
857
+ export interface SeederDefinition<T = any> {
858
+ entity: EntityTarget<T>;
859
+ factory: SeederFactory<T>;
860
+ count?: number;
861
+ dependencies?: EntityTarget<any>[];
862
+ }
863
+
864
+ /**
865
+ * Database seeder for test setup
866
+ */
867
+ export class DatabaseSeeder {
868
+ private seeders: Map<EntityTarget<any>, SeederDefinition> = new Map();
869
+ private seededData: Map<EntityTarget<any>, any[]> = new Map();
870
+
871
+ constructor(private dataSource: DataSource) {}
872
+
873
+ /**
874
+ * Register a seeder
875
+ */
876
+ register<T>(definition: SeederDefinition<T>): this {
877
+ this.seeders.set(definition.entity, definition);
878
+ return this;
879
+ }
880
+
881
+ /**
882
+ * Run all seeders
883
+ */
884
+ async seed(options: SeederOptions = {}): Promise<void> {
885
+ const order = options.order || this.getSeederOrder();
886
+
887
+ if (options.truncate) {
888
+ await this.truncateAll(order);
889
+ }
890
+
891
+ for (const entity of order) {
892
+ const seeder = this.seeders.get(entity);
893
+ if (seeder) {
894
+ await this.runSeeder(seeder);
895
+ }
896
+ }
897
+ }
898
+
899
+ /**
900
+ * Run a single seeder
901
+ */
902
+ private async runSeeder<T>(definition: SeederDefinition<T>): Promise<void> {
903
+ const repository = this.dataSource.getRepository(definition.entity);
904
+ const count = definition.count || 1;
905
+ const data: T[] = [];
906
+
907
+ for (let i = 0; i < count; i++) {
908
+ const result = await definition.factory();
909
+ const items = Array.isArray(result) ? result : [result];
910
+ data.push(...items);
911
+ }
912
+
913
+ const saved = await repository.save(data as any);
914
+ this.seededData.set(definition.entity, Array.isArray(saved) ? saved : [saved]);
915
+ }
916
+
917
+ /**
918
+ * Get seeded data for an entity
919
+ */
920
+ getSeeded<T>(entity: EntityTarget<T>): T[] {
921
+ return this.seededData.get(entity) || [];
922
+ }
923
+
924
+ /**
925
+ * Get first seeded item
926
+ */
927
+ getFirst<T>(entity: EntityTarget<T>): T | undefined {
928
+ return this.getSeeded(entity)[0];
929
+ }
930
+
931
+ /**
932
+ * Truncate all tables
933
+ */
934
+ async truncateAll(order: EntityTarget<any>[]): Promise<void> {
935
+ // Reverse order for truncation to handle foreign keys
936
+ const reversed = [...order].reverse();
937
+
938
+ for (const entity of reversed) {
939
+ const repository = this.dataSource.getRepository(entity);
940
+ const tableName = repository.metadata.tableName;
941
+
942
+ try {
943
+ await this.dataSource.query(\`TRUNCATE TABLE "\${tableName}" CASCADE\`);
944
+ } catch {
945
+ // SQLite doesn't support TRUNCATE
946
+ await repository.clear();
947
+ }
948
+ }
949
+ }
950
+
951
+ /**
952
+ * Clean up seeded data
953
+ */
954
+ async cleanup(): Promise<void> {
955
+ const order = this.getSeederOrder().reverse();
956
+
957
+ for (const entity of order) {
958
+ const data = this.seededData.get(entity);
959
+ if (data && data.length > 0) {
960
+ const repository = this.dataSource.getRepository(entity);
961
+ await repository.remove(data);
962
+ }
963
+ }
964
+
965
+ this.seededData.clear();
966
+ }
967
+
968
+ /**
969
+ * Get seeder order based on dependencies
970
+ */
971
+ private getSeederOrder(): EntityTarget<any>[] {
972
+ const ordered: EntityTarget<any>[] = [];
973
+ const visited = new Set<EntityTarget<any>>();
974
+
975
+ const visit = (entity: EntityTarget<any>) => {
976
+ if (visited.has(entity)) return;
977
+ visited.add(entity);
978
+
979
+ const seeder = this.seeders.get(entity);
980
+ if (seeder?.dependencies) {
981
+ for (const dep of seeder.dependencies) {
982
+ visit(dep);
983
+ }
984
+ }
985
+
986
+ ordered.push(entity);
987
+ };
988
+
989
+ for (const entity of this.seeders.keys()) {
990
+ visit(entity);
991
+ }
992
+
993
+ return ordered;
994
+ }
995
+ }
996
+
997
+ /**
998
+ * Create a database seeder
999
+ */
1000
+ export function createSeeder(dataSource: DataSource): DatabaseSeeder {
1001
+ return new DatabaseSeeder(dataSource);
1002
+ }
1003
+
1004
+ /**
1005
+ * Test database helper
1006
+ */
1007
+ export class TestDatabase {
1008
+ private dataSource: DataSource | null = null;
1009
+
1010
+ async connect(options?: any): Promise<DataSource> {
1011
+ this.dataSource = new DataSource({
1012
+ type: 'sqlite',
1013
+ database: ':memory:',
1014
+ synchronize: true,
1015
+ dropSchema: true,
1016
+ logging: false,
1017
+ entities: options?.entities || [],
1018
+ ...options,
1019
+ });
1020
+
1021
+ await this.dataSource.initialize();
1022
+ return this.dataSource;
1023
+ }
1024
+
1025
+ async disconnect(): Promise<void> {
1026
+ if (this.dataSource?.isInitialized) {
1027
+ await this.dataSource.destroy();
1028
+ }
1029
+ }
1030
+
1031
+ async reset(): Promise<void> {
1032
+ if (this.dataSource?.isInitialized) {
1033
+ await this.dataSource.synchronize(true);
1034
+ }
1035
+ }
1036
+
1037
+ getDataSource(): DataSource {
1038
+ if (!this.dataSource) {
1039
+ throw new Error('Database not connected');
1040
+ }
1041
+ return this.dataSource;
1042
+ }
1043
+
1044
+ getRepository<T>(entity: EntityTarget<T>): Repository<T> {
1045
+ return this.getDataSource().getRepository(entity);
1046
+ }
1047
+ }
1048
+
1049
+ export const testDb = new TestDatabase();
1050
+ `;
1051
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'utils/database.seeder.ts'), content);
1052
+ console.log(chalk_1.default.green(' ✓ Database seeder'));
1053
+ }
1054
+ async function generateTestModuleBuilder(testPath) {
1055
+ const content = `import { Test, TestingModule, TestingModuleBuilder } from '@nestjs/testing';
1056
+ import { Type, Provider, ModuleMetadata, DynamicModule } from '@nestjs/common';
1057
+
1058
+ /**
1059
+ * Fluent builder for test modules
1060
+ */
1061
+ export class TestModuleBuilder {
1062
+ private imports: any[] = [];
1063
+ private providers: Provider[] = [];
1064
+ private controllers: Type<any>[] = [];
1065
+ private exports: any[] = [];
1066
+ private overrides: Map<Type<any> | string | symbol, any> = new Map();
1067
+
1068
+ /**
1069
+ * Add imports
1070
+ */
1071
+ withImports(...modules: any[]): this {
1072
+ this.imports.push(...modules);
1073
+ return this;
1074
+ }
1075
+
1076
+ /**
1077
+ * Add providers
1078
+ */
1079
+ withProviders(...providers: Provider[]): this {
1080
+ this.providers.push(...providers);
1081
+ return this;
1082
+ }
1083
+
1084
+ /**
1085
+ * Add controllers
1086
+ */
1087
+ withControllers(...controllers: Type<any>[]): this {
1088
+ this.controllers.push(...controllers);
1089
+ return this;
1090
+ }
1091
+
1092
+ /**
1093
+ * Add exports
1094
+ */
1095
+ withExports(...exports: any[]): this {
1096
+ this.exports.push(...exports);
1097
+ return this;
1098
+ }
1099
+
1100
+ /**
1101
+ * Override a provider
1102
+ */
1103
+ override<T>(token: Type<T> | string | symbol, mock: any): this {
1104
+ this.overrides.set(token as any, mock);
1105
+ return this;
1106
+ }
1107
+
1108
+ /**
1109
+ * Override with a value
1110
+ */
1111
+ overrideValue<T>(token: Type<T> | string | symbol, value: T): this {
1112
+ return this.override(token, { useValue: value });
1113
+ }
1114
+
1115
+ /**
1116
+ * Override with a factory
1117
+ */
1118
+ overrideFactory<T>(
1119
+ token: Type<T> | string | symbol,
1120
+ factory: (...args: any[]) => T,
1121
+ inject?: any[]
1122
+ ): this {
1123
+ return this.override(token, { useFactory: factory, inject });
1124
+ }
1125
+
1126
+ /**
1127
+ * Override with a class
1128
+ */
1129
+ overrideClass<T>(token: Type<T> | string | symbol, mockClass: Type<T>): this {
1130
+ return this.override(token, { useClass: mockClass });
1131
+ }
1132
+
1133
+ /**
1134
+ * Mock a provider with jest mocks
1135
+ */
1136
+ mock<T>(token: Type<T>): this {
1137
+ const mockProvider = this.createAutoMock(token);
1138
+ return this.override(token, { useValue: mockProvider });
1139
+ }
1140
+
1141
+ /**
1142
+ * Build the test module
1143
+ */
1144
+ async compile(): Promise<TestingModule> {
1145
+ let builder = Test.createTestingModule({
1146
+ imports: this.imports,
1147
+ providers: this.providers,
1148
+ controllers: this.controllers,
1149
+ exports: this.exports,
1150
+ });
1151
+
1152
+ // Apply overrides
1153
+ for (const [token, mock] of this.overrides) {
1154
+ if (mock.useValue !== undefined) {
1155
+ builder = builder.overrideProvider(token as any).useValue(mock.useValue);
1156
+ } else if (mock.useFactory !== undefined) {
1157
+ builder = builder.overrideProvider(token as any).useFactory({
1158
+ factory: mock.useFactory,
1159
+ inject: mock.inject,
1160
+ });
1161
+ } else if (mock.useClass !== undefined) {
1162
+ builder = builder.overrideProvider(token as any).useClass(mock.useClass);
1163
+ }
1164
+ }
1165
+
1166
+ return builder.compile();
1167
+ }
1168
+
1169
+ /**
1170
+ * Create auto-mock for a class
1171
+ */
1172
+ private createAutoMock<T>(classType: Type<T>): Record<string, jest.Mock> {
1173
+ const mock: Record<string, jest.Mock> = {};
1174
+ const prototype = classType.prototype;
1175
+
1176
+ const methodNames = Object.getOwnPropertyNames(prototype).filter(
1177
+ (name) => name !== 'constructor' && typeof prototype[name] === 'function'
1178
+ );
1179
+
1180
+ for (const methodName of methodNames) {
1181
+ mock[methodName] = jest.fn();
1182
+ }
1183
+
1184
+ return mock;
1185
+ }
1186
+
1187
+ /**
1188
+ * Create a new builder
1189
+ */
1190
+ static create(): TestModuleBuilder {
1191
+ return new TestModuleBuilder();
1192
+ }
1193
+ }
1194
+
1195
+ /**
1196
+ * Helper to create test module
1197
+ */
1198
+ export function testModule(): TestModuleBuilder {
1199
+ return TestModuleBuilder.create();
1200
+ }
1201
+
1202
+ /**
1203
+ * Quick compile a module for testing
1204
+ */
1205
+ export async function compileTestModule(metadata: ModuleMetadata): Promise<TestingModule> {
1206
+ return Test.createTestingModule(metadata).compile();
1207
+ }
1208
+
1209
+ /**
1210
+ * Test context with common utilities
1211
+ */
1212
+ export interface TestContext<T = any> {
1213
+ module: TestingModule;
1214
+ service: T;
1215
+ cleanup: () => Promise<void>;
1216
+ }
1217
+
1218
+ /**
1219
+ * Create a test context for a service
1220
+ */
1221
+ export async function createTestContext<T>(
1222
+ serviceClass: Type<T>,
1223
+ options: {
1224
+ imports?: any[];
1225
+ providers?: Provider[];
1226
+ mocks?: Map<Type<any>, any>;
1227
+ } = {}
1228
+ ): Promise<TestContext<T>> {
1229
+ const builder = testModule()
1230
+ .withImports(...(options.imports || []))
1231
+ .withProviders(serviceClass, ...(options.providers || []));
1232
+
1233
+ if (options.mocks) {
1234
+ for (const [token, mock] of options.mocks) {
1235
+ builder.override(token, { useValue: mock });
1236
+ }
1237
+ }
1238
+
1239
+ const module = await builder.compile();
1240
+ const service = module.get<T>(serviceClass);
1241
+
1242
+ return {
1243
+ module,
1244
+ service,
1245
+ cleanup: async () => {
1246
+ await module.close();
1247
+ },
1248
+ };
1249
+ }
1250
+ `;
1251
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'utils/test-module.builder.ts'), content);
1252
+ console.log(chalk_1.default.green(' ✓ Test module builder'));
1253
+ }
1254
+ async function generateTestUtils(testPath) {
1255
+ const content = `/**
1256
+ * Common test utilities
1257
+ */
1258
+
1259
+ /**
1260
+ * Wait for a condition to be true
1261
+ */
1262
+ export async function waitFor(
1263
+ condition: () => boolean | Promise<boolean>,
1264
+ options: { timeout?: number; interval?: number } = {}
1265
+ ): Promise<void> {
1266
+ const { timeout = 5000, interval = 100 } = options;
1267
+ const start = Date.now();
1268
+
1269
+ while (Date.now() - start < timeout) {
1270
+ if (await condition()) {
1271
+ return;
1272
+ }
1273
+ await delay(interval);
1274
+ }
1275
+
1276
+ throw new Error('waitFor timeout exceeded');
1277
+ }
1278
+
1279
+ /**
1280
+ * Delay execution
1281
+ */
1282
+ export function delay(ms: number): Promise<void> {
1283
+ return new Promise((resolve) => setTimeout(resolve, ms));
1284
+ }
1285
+
1286
+ /**
1287
+ * Retry a function until it succeeds
1288
+ */
1289
+ export async function retry<T>(
1290
+ fn: () => Promise<T>,
1291
+ options: { maxAttempts?: number; delay?: number } = {}
1292
+ ): Promise<T> {
1293
+ const { maxAttempts = 3, delay: delayMs = 100 } = options;
1294
+ let lastError: Error | undefined;
1295
+
1296
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1297
+ try {
1298
+ return await fn();
1299
+ } catch (error) {
1300
+ lastError = error as Error;
1301
+ if (attempt < maxAttempts) {
1302
+ await delay(delayMs * attempt);
1303
+ }
1304
+ }
1305
+ }
1306
+
1307
+ throw lastError;
1308
+ }
1309
+
1310
+ /**
1311
+ * Assert that a function throws
1312
+ */
1313
+ export async function expectThrows(
1314
+ fn: () => Promise<any>,
1315
+ errorType?: new (...args: any[]) => Error
1316
+ ): Promise<Error> {
1317
+ try {
1318
+ await fn();
1319
+ throw new Error('Expected function to throw');
1320
+ } catch (error) {
1321
+ if (errorType && !(error instanceof errorType)) {
1322
+ throw new Error(\`Expected \${errorType.name} but got \${(error as Error).constructor.name}\`);
1323
+ }
1324
+ return error as Error;
1325
+ }
1326
+ }
1327
+
1328
+ /**
1329
+ * Assert async function rejects
1330
+ */
1331
+ export async function expectRejects<T = Error>(
1332
+ promise: Promise<any>,
1333
+ errorType?: new (...args: any[]) => T
1334
+ ): Promise<T> {
1335
+ try {
1336
+ await promise;
1337
+ throw new Error('Expected promise to reject');
1338
+ } catch (error) {
1339
+ if (errorType && !(error instanceof errorType)) {
1340
+ throw new Error(\`Expected \${errorType.name} but got \${(error as Error).constructor.name}\`);
1341
+ }
1342
+ return error as T;
1343
+ }
1344
+ }
1345
+
1346
+ /**
1347
+ * Create a spy on a method
1348
+ */
1349
+ export function spyOn<T extends object, K extends keyof T>(
1350
+ obj: T,
1351
+ method: K
1352
+ ): jest.SpyInstance {
1353
+ return jest.spyOn(obj, method as any);
1354
+ }
1355
+
1356
+ /**
1357
+ * Create a mock function with typed return
1358
+ */
1359
+ export function mockFn<T>(): jest.Mock<T> {
1360
+ return jest.fn();
1361
+ }
1362
+
1363
+ /**
1364
+ * Create a mock that resolves to a value
1365
+ */
1366
+ export function mockResolve<T>(value: T): jest.Mock<Promise<T>> {
1367
+ return jest.fn().mockResolvedValue(value);
1368
+ }
1369
+
1370
+ /**
1371
+ * Create a mock that rejects with an error
1372
+ */
1373
+ export function mockReject(error: Error): jest.Mock<Promise<never>> {
1374
+ return jest.fn().mockRejectedValue(error);
1375
+ }
1376
+
1377
+ /**
1378
+ * Reset all mocks
1379
+ */
1380
+ export function resetMocks(...mocks: jest.Mock[]): void {
1381
+ mocks.forEach((mock) => mock.mockReset());
1382
+ }
1383
+
1384
+ /**
1385
+ * Clear all mocks
1386
+ */
1387
+ export function clearMocks(...mocks: jest.Mock[]): void {
1388
+ mocks.forEach((mock) => mock.mockClear());
1389
+ }
1390
+
1391
+ /**
1392
+ * Assert object shape
1393
+ */
1394
+ export function expectShape<T extends object>(
1395
+ actual: T,
1396
+ expected: Partial<T>
1397
+ ): void {
1398
+ for (const [key, value] of Object.entries(expected)) {
1399
+ expect((actual as any)[key]).toEqual(value);
1400
+ }
1401
+ }
1402
+
1403
+ /**
1404
+ * Create a partial match for expect
1405
+ */
1406
+ export function partialMatch<T>(expected: Partial<T>): T {
1407
+ return expect.objectContaining(expected);
1408
+ }
1409
+
1410
+ /**
1411
+ * Random data generators for tests
1412
+ */
1413
+ export const random = {
1414
+ string: (length = 10) =>
1415
+ Math.random().toString(36).substring(2, 2 + length),
1416
+ number: (min = 0, max = 100) =>
1417
+ Math.floor(Math.random() * (max - min + 1)) + min,
1418
+ boolean: () => Math.random() > 0.5,
1419
+ email: () => \`test_\${Date.now()}_\${Math.random().toString(36).slice(2, 7)}@test.com\`,
1420
+ uuid: () =>
1421
+ 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
1422
+ const r = (Math.random() * 16) | 0;
1423
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
1424
+ return v.toString(16);
1425
+ }),
1426
+ date: () => new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000),
1427
+ pick: <T>(arr: T[]) => arr[Math.floor(Math.random() * arr.length)],
1428
+ };
1429
+
1430
+ /**
1431
+ * Test timing utilities
1432
+ */
1433
+ export class TestTimer {
1434
+ private startTime: number = 0;
1435
+ private endTime: number = 0;
1436
+
1437
+ start(): this {
1438
+ this.startTime = Date.now();
1439
+ return this;
1440
+ }
1441
+
1442
+ stop(): this {
1443
+ this.endTime = Date.now();
1444
+ return this;
1445
+ }
1446
+
1447
+ get elapsed(): number {
1448
+ return (this.endTime || Date.now()) - this.startTime;
1449
+ }
1450
+
1451
+ assertWithin(maxMs: number): void {
1452
+ if (this.elapsed > maxMs) {
1453
+ throw new Error(\`Expected to complete within \${maxMs}ms but took \${this.elapsed}ms\`);
1454
+ }
1455
+ }
1456
+ }
1457
+
1458
+ export function timer(): TestTimer {
1459
+ return new TestTimer().start();
1460
+ }
1461
+ `;
1462
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'utils/test.utils.ts'), content);
1463
+ console.log(chalk_1.default.green(' ✓ Test utilities'));
1464
+ }
1465
+ async function generateSampleFactories(testPath) {
1466
+ const content = `/**
1467
+ * Sample factories demonstrating usage
1468
+ */
1469
+
1470
+ import { faker } from '@faker-js/faker';
1471
+ import { BaseFactory, FakerHelpers, defineFactory } from './base.factory';
1472
+ import { factory, sequences } from './factory.builder';
1473
+
1474
+ /**
1475
+ * Example: User entity interface
1476
+ */
1477
+ interface User {
1478
+ id: string;
1479
+ email: string;
1480
+ firstName: string;
1481
+ lastName: string;
1482
+ isActive: boolean;
1483
+ role: 'admin' | 'user' | 'guest';
1484
+ createdAt: Date;
1485
+ }
1486
+
1487
+ /**
1488
+ * Example: Class-based factory
1489
+ */
1490
+ export class UserFactory extends BaseFactory<User> {
1491
+ protected getDefaults(): User {
1492
+ return {
1493
+ id: FakerHelpers.id(),
1494
+ email: FakerHelpers.email(),
1495
+ firstName: FakerHelpers.firstName(),
1496
+ lastName: FakerHelpers.lastName(),
1497
+ isActive: true,
1498
+ role: 'user',
1499
+ createdAt: new Date(),
1500
+ };
1501
+ }
1502
+ }
1503
+
1504
+ /**
1505
+ * Example: Function-based factory using defineFactory
1506
+ */
1507
+ export const userFactory = defineFactory<User>(() => ({
1508
+ id: FakerHelpers.id(),
1509
+ email: FakerHelpers.email(),
1510
+ firstName: FakerHelpers.firstName(),
1511
+ lastName: FakerHelpers.lastName(),
1512
+ isActive: true,
1513
+ role: 'user',
1514
+ createdAt: new Date(),
1515
+ }));
1516
+
1517
+ /**
1518
+ * Example: Advanced factory with traits and sequences
1519
+ */
1520
+ export const advancedUserFactory = factory<User>(() => ({
1521
+ id: FakerHelpers.id(),
1522
+ email: FakerHelpers.email(),
1523
+ firstName: FakerHelpers.firstName(),
1524
+ lastName: FakerHelpers.lastName(),
1525
+ isActive: true,
1526
+ role: 'user',
1527
+ createdAt: new Date(),
1528
+ }))
1529
+ // Define traits
1530
+ .trait('admin', { role: 'admin', isActive: true })
1531
+ .trait('inactive', { isActive: false })
1532
+ .trait('guest', { role: 'guest' })
1533
+
1534
+ // Define sequences
1535
+ .sequence('email', sequences.uniqueEmail)
1536
+
1537
+ // After build hook
1538
+ .afterBuild((user) => ({
1539
+ ...user,
1540
+ email: user.email.toLowerCase(),
1541
+ }));
1542
+
1543
+ /**
1544
+ * Usage examples:
1545
+ *
1546
+ * // Basic usage
1547
+ * const user = new UserFactory().create();
1548
+ * const users = new UserFactory().createMany(5);
1549
+ *
1550
+ * // With overrides
1551
+ * const admin = new UserFactory().create({ role: 'admin' });
1552
+ *
1553
+ * // Using defineFactory
1554
+ * const user = userFactory.create();
1555
+ *
1556
+ * // Using advanced factory with traits
1557
+ * const adminUser = advancedUserFactory.use('admin').build();
1558
+ * const inactiveGuest = advancedUserFactory.use('inactive', 'guest').build();
1559
+ *
1560
+ * // With custom overrides
1561
+ * const customUser = advancedUserFactory
1562
+ * .use('admin')
1563
+ * .with({ firstName: 'John' })
1564
+ * .build();
1565
+ */
1566
+ `;
1567
+ await (0, file_utils_1.writeFile)(path.join(testPath, 'factories/examples.ts'), content);
1568
+ console.log(chalk_1.default.green(' ✓ Sample factories'));
1569
+ }
1570
+ //# sourceMappingURL=test-factory-full.js.map