@forklaunch/testing 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.mts CHANGED
@@ -32,6 +32,26 @@ interface SQLiteConfig {
32
32
  interface RedisConfig {
33
33
  command?: string[];
34
34
  }
35
+ interface KafkaConfig {
36
+ /** Kafka cluster ID */
37
+ clusterId?: string;
38
+ /** Number of partitions */
39
+ numPartitions?: number;
40
+ /** Replication factor */
41
+ replicationFactor?: number;
42
+ /** Additional environment variables */
43
+ env?: Record<string, string>;
44
+ }
45
+ interface S3Config {
46
+ /** MinIO root user (access key) */
47
+ rootUser?: string;
48
+ /** MinIO root password (secret key) */
49
+ rootPassword?: string;
50
+ /** Default bucket to create */
51
+ defaultBucket?: string;
52
+ /** Region */
53
+ region?: string;
54
+ }
35
55
  type DatabaseConfig = PostgresConfig | MySQLConfig | MongoDBConfig | MSSQLConfig | SQLiteConfig;
36
56
  /**
37
57
  * Manages test containers (PostgreSQL, MySQL, MongoDB, Redis, etc.) for E2E testing
@@ -66,6 +86,14 @@ declare class TestContainerManager {
66
86
  * Setup Redis test container
67
87
  */
68
88
  setupRedisContainer(config?: RedisConfig): Promise<StartedTestContainer>;
89
+ /**
90
+ * Setup Kafka test container
91
+ */
92
+ setupKafkaContainer(config?: KafkaConfig): Promise<StartedTestContainer>;
93
+ /**
94
+ * Setup MinIO (S3-compatible) test container
95
+ */
96
+ setupS3Container(config?: S3Config): Promise<StartedTestContainer>;
69
97
  /**
70
98
  * Cleanup all containers
71
99
  */
@@ -74,8 +102,10 @@ declare class TestContainerManager {
74
102
 
75
103
  interface TestEnvConfig {
76
104
  database: StartedTestContainer | null;
77
- databaseType: DatabaseType;
105
+ databaseType?: DatabaseType;
78
106
  redis?: StartedTestContainer;
107
+ kafka?: StartedTestContainer;
108
+ s3?: StartedTestContainer;
79
109
  hmacSecret?: string;
80
110
  customVars?: Record<string, string>;
81
111
  }
@@ -113,19 +143,20 @@ interface MikroOrmTestConfig {
113
143
  */
114
144
  declare function setupTestORM(config: MikroOrmTestConfig): Promise<MikroORM>;
115
145
  /**
116
- * Clear all data from the test database
146
+ * Clear all data from the test database and/or cache
117
147
  */
118
- declare function clearTestDatabase(orm: MikroORM, redis?: Redis): Promise<void>;
148
+ declare function clearTestDatabase(orm?: MikroORM, redis?: Redis): Promise<void>;
119
149
 
120
150
  interface BlueprintTestConfig {
121
151
  /**
122
152
  * Function that imports and returns the MikroORM config
123
153
  * This is called AFTER environment variables are set
154
+ * Optional - if not provided, no database will be set up
124
155
  */
125
- getConfig: () => Promise<Options>;
156
+ getConfig?: () => Promise<Options>;
126
157
  /**
127
158
  * Database type (postgres, mysql, mongodb, etc.)
128
- * @default 'postgres'
159
+ * Optional - if not provided, no database will be set up
129
160
  */
130
161
  databaseType?: DatabaseType;
131
162
  /**
@@ -140,6 +171,18 @@ interface BlueprintTestConfig {
140
171
  * Whether the blueprint needs Redis
141
172
  */
142
173
  needsRedis?: boolean;
174
+ /**
175
+ * Whether the blueprint needs Kafka
176
+ */
177
+ needsKafka?: boolean;
178
+ /**
179
+ * Whether the blueprint needs S3 (MinIO)
180
+ */
181
+ needsS3?: boolean;
182
+ /**
183
+ * S3 bucket name to create (default: 'test-bucket')
184
+ */
185
+ s3Bucket?: string;
143
186
  /**
144
187
  * Custom environment variables to set
145
188
  */
@@ -152,7 +195,9 @@ interface BlueprintTestConfig {
152
195
  interface TestSetupResult {
153
196
  container: StartedTestContainer | null;
154
197
  redisContainer?: StartedTestContainer;
155
- orm: MikroORM;
198
+ kafkaContainer?: StartedTestContainer;
199
+ s3Container?: StartedTestContainer;
200
+ orm?: MikroORM;
156
201
  redis?: Redis;
157
202
  }
158
203
  /**
@@ -160,10 +205,14 @@ interface TestSetupResult {
160
205
  *
161
206
  * Handles container setup, environment configuration, and database initialization
162
207
  *
163
- * @example
208
+ * @example Database with ORM
164
209
  * ```typescript
165
210
  * const harness = new BlueprintTestHarness({
166
- * mikroOrmConfigPath: '../mikro-orm.config',
211
+ * getConfig: async () => {
212
+ * const { default: config } = await import('../mikro-orm.config');
213
+ * return config;
214
+ * },
215
+ * databaseType: 'postgres',
167
216
  * useMigrations: false,
168
217
  * needsRedis: true,
169
218
  * customEnvVars: {
@@ -172,7 +221,44 @@ interface TestSetupResult {
172
221
  * });
173
222
  *
174
223
  * const setup = await harness.setup();
175
- * // ... run tests
224
+ * // ... run tests with setup.orm and setup.redis
225
+ * await harness.cleanup();
226
+ * ```
227
+ *
228
+ * @example Cache-only (no database)
229
+ * ```typescript
230
+ * const harness = new BlueprintTestHarness({
231
+ * needsRedis: true,
232
+ * customEnvVars: {
233
+ * API_KEY: 'test_key'
234
+ * }
235
+ * });
236
+ *
237
+ * const setup = await harness.setup();
238
+ * // ... run tests with setup.redis only (setup.orm is undefined)
239
+ * await harness.cleanup();
240
+ * ```
241
+ *
242
+ * @example With Kafka and S3
243
+ * ```typescript
244
+ * const harness = new BlueprintTestHarness({
245
+ * getConfig: async () => {
246
+ * const { default: config } = await import('../mikro-orm.config');
247
+ * return config;
248
+ * },
249
+ * databaseType: 'postgres',
250
+ * needsKafka: true,
251
+ * needsS3: true,
252
+ * s3Bucket: 'my-test-bucket',
253
+ * customEnvVars: {
254
+ * API_KEY: 'test_key'
255
+ * }
256
+ * });
257
+ *
258
+ * const setup = await harness.setup();
259
+ * // Access containers via setup.kafkaContainer and setup.s3Container
260
+ * // Kafka broker: process.env.KAFKA_BROKERS
261
+ * // S3 endpoint: process.env.S3_ENDPOINT
176
262
  * await harness.cleanup();
177
263
  * ```
178
264
  */
@@ -182,7 +268,7 @@ declare class BlueprintTestHarness {
182
268
  private result?;
183
269
  constructor(config: BlueprintTestConfig);
184
270
  /**
185
- * Setup all test infrastructure (containers, ORM, Redis)
271
+ * Setup all test infrastructure (containers, ORM, Redis, Kafka, S3)
186
272
  */
187
273
  setup(): Promise<TestSetupResult>;
188
274
  /**
@@ -190,7 +276,7 @@ declare class BlueprintTestHarness {
190
276
  */
191
277
  cleanup(): Promise<void>;
192
278
  /**
193
- * Clear all data from the database
279
+ * Clear all data from the database and/or cache
194
280
  */
195
281
  clearDatabase(): Promise<void>;
196
282
  }
@@ -213,4 +299,4 @@ declare const TEST_TOKENS: {
213
299
  readonly HMAC_INVALID: "HMAC keyId=invalid-key ts=1234567890 nonce=invalid-nonce signature=invalid-signature";
214
300
  };
215
301
 
216
- export { type BlueprintTestConfig, BlueprintTestHarness, type DatabaseConfig, type DatabaseType, type MSSQLConfig, type MikroOrmTestConfig, type MongoDBConfig, type MySQLConfig, type PostgresConfig, type RedisConfig, type SQLiteConfig, TEST_TOKENS, TestContainerManager, type TestEnvConfig, type TestSetupResult, clearTestDatabase, setupTestEnvironment, setupTestORM };
302
+ export { type BlueprintTestConfig, BlueprintTestHarness, type DatabaseConfig, type DatabaseType, type KafkaConfig, type MSSQLConfig, type MikroOrmTestConfig, type MongoDBConfig, type MySQLConfig, type PostgresConfig, type RedisConfig, type S3Config, type SQLiteConfig, TEST_TOKENS, TestContainerManager, type TestEnvConfig, type TestSetupResult, clearTestDatabase, setupTestEnvironment, setupTestORM };
package/lib/index.d.ts CHANGED
@@ -32,6 +32,26 @@ interface SQLiteConfig {
32
32
  interface RedisConfig {
33
33
  command?: string[];
34
34
  }
35
+ interface KafkaConfig {
36
+ /** Kafka cluster ID */
37
+ clusterId?: string;
38
+ /** Number of partitions */
39
+ numPartitions?: number;
40
+ /** Replication factor */
41
+ replicationFactor?: number;
42
+ /** Additional environment variables */
43
+ env?: Record<string, string>;
44
+ }
45
+ interface S3Config {
46
+ /** MinIO root user (access key) */
47
+ rootUser?: string;
48
+ /** MinIO root password (secret key) */
49
+ rootPassword?: string;
50
+ /** Default bucket to create */
51
+ defaultBucket?: string;
52
+ /** Region */
53
+ region?: string;
54
+ }
35
55
  type DatabaseConfig = PostgresConfig | MySQLConfig | MongoDBConfig | MSSQLConfig | SQLiteConfig;
36
56
  /**
37
57
  * Manages test containers (PostgreSQL, MySQL, MongoDB, Redis, etc.) for E2E testing
@@ -66,6 +86,14 @@ declare class TestContainerManager {
66
86
  * Setup Redis test container
67
87
  */
68
88
  setupRedisContainer(config?: RedisConfig): Promise<StartedTestContainer>;
89
+ /**
90
+ * Setup Kafka test container
91
+ */
92
+ setupKafkaContainer(config?: KafkaConfig): Promise<StartedTestContainer>;
93
+ /**
94
+ * Setup MinIO (S3-compatible) test container
95
+ */
96
+ setupS3Container(config?: S3Config): Promise<StartedTestContainer>;
69
97
  /**
70
98
  * Cleanup all containers
71
99
  */
@@ -74,8 +102,10 @@ declare class TestContainerManager {
74
102
 
75
103
  interface TestEnvConfig {
76
104
  database: StartedTestContainer | null;
77
- databaseType: DatabaseType;
105
+ databaseType?: DatabaseType;
78
106
  redis?: StartedTestContainer;
107
+ kafka?: StartedTestContainer;
108
+ s3?: StartedTestContainer;
79
109
  hmacSecret?: string;
80
110
  customVars?: Record<string, string>;
81
111
  }
@@ -113,19 +143,20 @@ interface MikroOrmTestConfig {
113
143
  */
114
144
  declare function setupTestORM(config: MikroOrmTestConfig): Promise<MikroORM>;
115
145
  /**
116
- * Clear all data from the test database
146
+ * Clear all data from the test database and/or cache
117
147
  */
118
- declare function clearTestDatabase(orm: MikroORM, redis?: Redis): Promise<void>;
148
+ declare function clearTestDatabase(orm?: MikroORM, redis?: Redis): Promise<void>;
119
149
 
120
150
  interface BlueprintTestConfig {
121
151
  /**
122
152
  * Function that imports and returns the MikroORM config
123
153
  * This is called AFTER environment variables are set
154
+ * Optional - if not provided, no database will be set up
124
155
  */
125
- getConfig: () => Promise<Options>;
156
+ getConfig?: () => Promise<Options>;
126
157
  /**
127
158
  * Database type (postgres, mysql, mongodb, etc.)
128
- * @default 'postgres'
159
+ * Optional - if not provided, no database will be set up
129
160
  */
130
161
  databaseType?: DatabaseType;
131
162
  /**
@@ -140,6 +171,18 @@ interface BlueprintTestConfig {
140
171
  * Whether the blueprint needs Redis
141
172
  */
142
173
  needsRedis?: boolean;
174
+ /**
175
+ * Whether the blueprint needs Kafka
176
+ */
177
+ needsKafka?: boolean;
178
+ /**
179
+ * Whether the blueprint needs S3 (MinIO)
180
+ */
181
+ needsS3?: boolean;
182
+ /**
183
+ * S3 bucket name to create (default: 'test-bucket')
184
+ */
185
+ s3Bucket?: string;
143
186
  /**
144
187
  * Custom environment variables to set
145
188
  */
@@ -152,7 +195,9 @@ interface BlueprintTestConfig {
152
195
  interface TestSetupResult {
153
196
  container: StartedTestContainer | null;
154
197
  redisContainer?: StartedTestContainer;
155
- orm: MikroORM;
198
+ kafkaContainer?: StartedTestContainer;
199
+ s3Container?: StartedTestContainer;
200
+ orm?: MikroORM;
156
201
  redis?: Redis;
157
202
  }
158
203
  /**
@@ -160,10 +205,14 @@ interface TestSetupResult {
160
205
  *
161
206
  * Handles container setup, environment configuration, and database initialization
162
207
  *
163
- * @example
208
+ * @example Database with ORM
164
209
  * ```typescript
165
210
  * const harness = new BlueprintTestHarness({
166
- * mikroOrmConfigPath: '../mikro-orm.config',
211
+ * getConfig: async () => {
212
+ * const { default: config } = await import('../mikro-orm.config');
213
+ * return config;
214
+ * },
215
+ * databaseType: 'postgres',
167
216
  * useMigrations: false,
168
217
  * needsRedis: true,
169
218
  * customEnvVars: {
@@ -172,7 +221,44 @@ interface TestSetupResult {
172
221
  * });
173
222
  *
174
223
  * const setup = await harness.setup();
175
- * // ... run tests
224
+ * // ... run tests with setup.orm and setup.redis
225
+ * await harness.cleanup();
226
+ * ```
227
+ *
228
+ * @example Cache-only (no database)
229
+ * ```typescript
230
+ * const harness = new BlueprintTestHarness({
231
+ * needsRedis: true,
232
+ * customEnvVars: {
233
+ * API_KEY: 'test_key'
234
+ * }
235
+ * });
236
+ *
237
+ * const setup = await harness.setup();
238
+ * // ... run tests with setup.redis only (setup.orm is undefined)
239
+ * await harness.cleanup();
240
+ * ```
241
+ *
242
+ * @example With Kafka and S3
243
+ * ```typescript
244
+ * const harness = new BlueprintTestHarness({
245
+ * getConfig: async () => {
246
+ * const { default: config } = await import('../mikro-orm.config');
247
+ * return config;
248
+ * },
249
+ * databaseType: 'postgres',
250
+ * needsKafka: true,
251
+ * needsS3: true,
252
+ * s3Bucket: 'my-test-bucket',
253
+ * customEnvVars: {
254
+ * API_KEY: 'test_key'
255
+ * }
256
+ * });
257
+ *
258
+ * const setup = await harness.setup();
259
+ * // Access containers via setup.kafkaContainer and setup.s3Container
260
+ * // Kafka broker: process.env.KAFKA_BROKERS
261
+ * // S3 endpoint: process.env.S3_ENDPOINT
176
262
  * await harness.cleanup();
177
263
  * ```
178
264
  */
@@ -182,7 +268,7 @@ declare class BlueprintTestHarness {
182
268
  private result?;
183
269
  constructor(config: BlueprintTestConfig);
184
270
  /**
185
- * Setup all test infrastructure (containers, ORM, Redis)
271
+ * Setup all test infrastructure (containers, ORM, Redis, Kafka, S3)
186
272
  */
187
273
  setup(): Promise<TestSetupResult>;
188
274
  /**
@@ -190,7 +276,7 @@ declare class BlueprintTestHarness {
190
276
  */
191
277
  cleanup(): Promise<void>;
192
278
  /**
193
- * Clear all data from the database
279
+ * Clear all data from the database and/or cache
194
280
  */
195
281
  clearDatabase(): Promise<void>;
196
282
  }
@@ -213,4 +299,4 @@ declare const TEST_TOKENS: {
213
299
  readonly HMAC_INVALID: "HMAC keyId=invalid-key ts=1234567890 nonce=invalid-nonce signature=invalid-signature";
214
300
  };
215
301
 
216
- export { type BlueprintTestConfig, BlueprintTestHarness, type DatabaseConfig, type DatabaseType, type MSSQLConfig, type MikroOrmTestConfig, type MongoDBConfig, type MySQLConfig, type PostgresConfig, type RedisConfig, type SQLiteConfig, TEST_TOKENS, TestContainerManager, type TestEnvConfig, type TestSetupResult, clearTestDatabase, setupTestEnvironment, setupTestORM };
302
+ export { type BlueprintTestConfig, BlueprintTestHarness, type DatabaseConfig, type DatabaseType, type KafkaConfig, type MSSQLConfig, type MikroOrmTestConfig, type MongoDBConfig, type MySQLConfig, type PostgresConfig, type RedisConfig, type S3Config, type SQLiteConfig, TEST_TOKENS, TestContainerManager, type TestEnvConfig, type TestSetupResult, clearTestDatabase, setupTestEnvironment, setupTestORM };
package/lib/index.js CHANGED
@@ -174,6 +174,74 @@ var TestContainerManager = class {
174
174
  this.containers.push(container);
175
175
  return container;
176
176
  }
177
+ /**
178
+ * Setup Kafka test container
179
+ */
180
+ async setupKafkaContainer(config = {}) {
181
+ const {
182
+ clusterId = "test-cluster",
183
+ numPartitions = 1,
184
+ replicationFactor = 1,
185
+ env = {}
186
+ } = config;
187
+ const container = await new import_testcontainers.GenericContainer("confluentinc/cp-kafka:latest").withExposedPorts(9092, 9093).withEnvironment({
188
+ KAFKA_BROKER_ID: "1",
189
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT",
190
+ KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092",
191
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: replicationFactor.toString(),
192
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1",
193
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1",
194
+ KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0",
195
+ KAFKA_NUM_PARTITIONS: numPartitions.toString(),
196
+ KAFKA_CLUSTER_ID: clusterId,
197
+ KAFKA_PROCESS_ROLES: "broker,controller",
198
+ KAFKA_NODE_ID: "1",
199
+ KAFKA_CONTROLLER_QUORUM_VOTERS: "1@localhost:9093",
200
+ KAFKA_LISTENERS: "PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093,PLAINTEXT_HOST://0.0.0.0:9092",
201
+ KAFKA_INTER_BROKER_LISTENER_NAME: "PLAINTEXT",
202
+ KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER",
203
+ KAFKA_LOG_DIRS: "/tmp/kraft-combined-logs",
204
+ ...env
205
+ }).start();
206
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
207
+ this.containers.push(container);
208
+ return container;
209
+ }
210
+ /**
211
+ * Setup MinIO (S3-compatible) test container
212
+ */
213
+ async setupS3Container(config = {}) {
214
+ const {
215
+ rootUser = "minioadmin",
216
+ rootPassword = "minioadmin",
217
+ defaultBucket = "test-bucket",
218
+ region = "us-east-1"
219
+ } = config;
220
+ const container = await new import_testcontainers.GenericContainer("minio/minio:latest").withExposedPorts(9e3, 9001).withEnvironment({
221
+ MINIO_ROOT_USER: rootUser,
222
+ MINIO_ROOT_PASSWORD: rootPassword,
223
+ MINIO_REGION: region
224
+ }).withCommand(["server", "/data", "--console-address", ":9001"]).start();
225
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
226
+ if (defaultBucket) {
227
+ try {
228
+ await container.exec([
229
+ "mc",
230
+ "alias",
231
+ "set",
232
+ "local",
233
+ "http://localhost:9000",
234
+ rootUser,
235
+ rootPassword
236
+ ]);
237
+ await container.exec(["mc", "mb", `local/${defaultBucket}`]);
238
+ } catch (error) {
239
+ console.warn("Could not create default bucket:", error);
240
+ }
241
+ }
242
+ this.containers.push(container);
243
+ return container;
244
+ }
177
245
  /**
178
246
  * Cleanup all containers
179
247
  */
@@ -216,21 +284,41 @@ function setupTestEnvironment(config) {
216
284
  database,
217
285
  databaseType,
218
286
  redis,
287
+ kafka,
288
+ s3,
219
289
  hmacSecret = "test-secret-key",
220
290
  customVars = {}
221
291
  } = config;
222
- const dbPort = getDatabasePort(databaseType);
223
- process.env.DB_NAME = "test_db";
224
- if (databaseType === "sqlite" || databaseType === "better-sqlite" || databaseType === "libsql") {
225
- process.env.DB_PATH = ":memory:";
226
- } else if (database) {
227
- process.env.DB_HOST = database.getHost();
228
- process.env.DB_USER = databaseType === "mssql" ? "SA" : "test_user";
229
- process.env.DB_PASSWORD = databaseType === "mssql" ? "Test_Password123!" : "test_password";
230
- process.env.DB_PORT = database.getMappedPort(dbPort).toString();
292
+ if (databaseType) {
293
+ const dbPort = getDatabasePort(databaseType);
294
+ process.env.DB_NAME = "test_db";
295
+ if (databaseType === "sqlite" || databaseType === "better-sqlite" || databaseType === "libsql") {
296
+ process.env.DB_PATH = ":memory:";
297
+ } else if (database) {
298
+ process.env.DB_HOST = database.getHost();
299
+ process.env.DB_USER = databaseType === "mssql" ? "SA" : "test_user";
300
+ process.env.DB_PASSWORD = databaseType === "mssql" ? "Test_Password123!" : "test_password";
301
+ process.env.DB_PORT = database.getMappedPort(dbPort).toString();
302
+ }
231
303
  }
232
304
  if (redis) {
233
305
  process.env.REDIS_URL = `redis://${redis.getHost()}:${redis.getMappedPort(6379)}`;
306
+ process.env.REDIS_HOST = redis.getHost();
307
+ process.env.REDIS_PORT = redis.getMappedPort(6379).toString();
308
+ }
309
+ if (kafka) {
310
+ const kafkaBroker = `${kafka.getHost()}:${kafka.getMappedPort(9092)}`;
311
+ process.env.KAFKA_BROKERS = kafkaBroker;
312
+ process.env.KAFKA_CLIENT_ID = "test-client";
313
+ process.env.KAFKA_GROUP_ID = "test-group";
314
+ }
315
+ if (s3) {
316
+ process.env.S3_ENDPOINT = `http://${s3.getHost()}:${s3.getMappedPort(9e3)}`;
317
+ process.env.S3_ACCESS_KEY_ID = "minioadmin";
318
+ process.env.S3_SECRET_ACCESS_KEY = "minioadmin";
319
+ process.env.S3_REGION = "us-east-1";
320
+ process.env.S3_BUCKET = "test-bucket";
321
+ process.env.S3_FORCE_PATH_STYLE = "true";
234
322
  }
235
323
  process.env.HMAC_SECRET_KEY = hmacSecret;
236
324
  process.env.JWKS_PUBLIC_KEY_URL = "http://localhost:3000/.well-known/jwks.json";
@@ -333,18 +421,20 @@ async function clearTestDatabase(orm, redis) {
333
421
  if (redis) {
334
422
  await redis.flushall();
335
423
  }
336
- const em = orm.em.fork();
337
- const entities = Object.values(orm.getMetadata().getAll());
338
- for (const entity of entities.reverse()) {
339
- try {
340
- await em.nativeDelete(entity.class, {});
341
- } catch (error) {
342
- if (!error.message?.includes("does not exist")) {
343
- throw error;
424
+ if (orm) {
425
+ const em = orm.em.fork();
426
+ const entities = Object.values(orm.getMetadata().getAll());
427
+ for (const entity of entities.reverse()) {
428
+ try {
429
+ await em.nativeDelete(entity.class, {});
430
+ } catch (error) {
431
+ if (!error.message?.includes("does not exist")) {
432
+ throw error;
433
+ }
344
434
  }
345
435
  }
436
+ await em.flush();
346
437
  }
347
- await em.flush();
348
438
  }
349
439
 
350
440
  // src/harness.ts
@@ -357,26 +447,54 @@ var BlueprintTestHarness = class {
357
447
  containers;
358
448
  result;
359
449
  /**
360
- * Setup all test infrastructure (containers, ORM, Redis)
450
+ * Setup all test infrastructure (containers, ORM, Redis, Kafka, S3)
361
451
  */
362
452
  async setup() {
363
- const databaseType = this.config.databaseType || "postgres";
364
- const container = await this.containers.setupDatabaseContainer(databaseType);
365
- const redisContainer = this.config.needsRedis ? await this.containers.setupRedisContainer() : void 0;
366
- setupTestEnvironment({
367
- database: container,
368
- databaseType,
369
- redis: redisContainer,
370
- customVars: this.config.customEnvVars
371
- });
372
- const mikroOrmConfig = await this.config.getConfig();
373
- const orm = await setupTestORM({
374
- mikroOrmConfig,
375
- databaseType,
376
- useMigrations: this.config.useMigrations,
377
- migrationsPath: this.config.migrationsPath,
378
- container
379
- });
453
+ let container = null;
454
+ let orm;
455
+ let redisContainer;
456
+ let kafkaContainer;
457
+ let s3Container;
458
+ if (this.config.needsRedis) {
459
+ redisContainer = await this.containers.setupRedisContainer();
460
+ }
461
+ if (this.config.needsKafka) {
462
+ kafkaContainer = await this.containers.setupKafkaContainer();
463
+ }
464
+ if (this.config.needsS3) {
465
+ s3Container = await this.containers.setupS3Container({
466
+ defaultBucket: this.config.s3Bucket
467
+ });
468
+ }
469
+ if (this.config.databaseType && this.config.getConfig) {
470
+ const databaseType = this.config.databaseType;
471
+ container = await this.containers.setupDatabaseContainer(databaseType);
472
+ setupTestEnvironment({
473
+ database: container,
474
+ databaseType,
475
+ redis: redisContainer,
476
+ kafka: kafkaContainer,
477
+ s3: s3Container,
478
+ customVars: this.config.customEnvVars
479
+ });
480
+ const mikroOrmConfig = await this.config.getConfig();
481
+ orm = await setupTestORM({
482
+ mikroOrmConfig,
483
+ databaseType,
484
+ useMigrations: this.config.useMigrations,
485
+ migrationsPath: this.config.migrationsPath,
486
+ container
487
+ });
488
+ } else {
489
+ setupTestEnvironment({
490
+ database: null,
491
+ databaseType: void 0,
492
+ redis: redisContainer,
493
+ kafka: kafkaContainer,
494
+ s3: s3Container,
495
+ customVars: this.config.customEnvVars
496
+ });
497
+ }
380
498
  let redis;
381
499
  if (redisContainer) {
382
500
  redis = new import_ioredis.default({
@@ -386,7 +504,14 @@ var BlueprintTestHarness = class {
386
504
  });
387
505
  await redis.ping();
388
506
  }
389
- this.result = { container, redisContainer, orm, redis };
507
+ this.result = {
508
+ container,
509
+ redisContainer,
510
+ kafkaContainer,
511
+ s3Container,
512
+ orm,
513
+ redis
514
+ };
390
515
  if (this.config.onSetup) {
391
516
  await this.config.onSetup(this.result);
392
517
  }
@@ -406,7 +531,7 @@ var BlueprintTestHarness = class {
406
531
  this.result = void 0;
407
532
  }
408
533
  /**
409
- * Clear all data from the database
534
+ * Clear all data from the database and/or cache
410
535
  */
411
536
  async clearDatabase() {
412
537
  if (this.result) {
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/containers.ts","../src/environment.ts","../src/database.ts","../src/harness.ts","../src/tokens.ts"],"sourcesContent":["/**\n * @forklaunch/testing\n *\n * Testing utilities for forklaunch-js blueprints\n *\n * @packageDocumentation\n */\n\n// Container management\nexport {\n DatabaseConfig,\n DatabaseType,\n MongoDBConfig,\n MSSQLConfig,\n MySQLConfig,\n PostgresConfig,\n RedisConfig,\n SQLiteConfig,\n TestContainerManager\n} from './containers';\n\nexport { setupTestEnvironment, TestEnvConfig } from './environment';\n\nexport {\n clearTestDatabase,\n MikroOrmTestConfig,\n setupTestORM\n} from './database';\n\nexport {\n BlueprintTestConfig,\n BlueprintTestHarness,\n TestSetupResult\n} from './harness';\n\nexport { TEST_TOKENS } from './tokens';\n","import { GenericContainer, StartedTestContainer } from 'testcontainers';\n\nexport type DatabaseType =\n | 'postgres'\n | 'postgresql'\n | 'mysql'\n | 'mariadb'\n | 'mongodb'\n | 'mongo'\n | 'mssql'\n | 'libsql'\n | 'sqlite'\n | 'better-sqlite';\n\nexport interface PostgresConfig {\n user?: string;\n password?: string;\n database?: string;\n command?: string[];\n}\n\nexport interface MySQLConfig {\n user?: string;\n password?: string;\n database?: string;\n rootPassword?: string;\n}\n\nexport interface MongoDBConfig {\n user?: string;\n password?: string;\n database?: string;\n}\n\nexport interface MSSQLConfig {\n user?: string;\n password?: string;\n database?: string;\n saPassword?: string;\n}\n\nexport interface SQLiteConfig {\n database?: string;\n}\n\nexport interface RedisConfig {\n command?: string[];\n}\n\nexport type DatabaseConfig =\n | PostgresConfig\n | MySQLConfig\n | MongoDBConfig\n | MSSQLConfig\n | SQLiteConfig;\n\n/**\n * Manages test containers (PostgreSQL, MySQL, MongoDB, Redis, etc.) for E2E testing\n */\nexport class TestContainerManager {\n private containers: StartedTestContainer[] = [];\n\n /**\n * Setup database container based on type\n */\n async setupDatabaseContainer(\n type: DatabaseType,\n config: DatabaseConfig = {}\n ): Promise<StartedTestContainer | null> {\n const normalizedType = this.normalizeDatabaseType(type);\n\n switch (normalizedType) {\n case 'postgres':\n return this.setupPostgresContainer(config as PostgresConfig);\n case 'mysql':\n return this.setupMySQLContainer(config as MySQLConfig);\n case 'mongodb':\n return this.setupMongoDBContainer(config as MongoDBConfig);\n case 'mssql':\n return this.setupMSSQLContainer(config as MSSQLConfig);\n case 'sqlite':\n // SQLite doesn't need a container (file-based)\n return null;\n default:\n throw new Error(`Unsupported database type: ${type}`);\n }\n }\n\n /**\n * Normalize database type aliases\n */\n private normalizeDatabaseType(\n type: DatabaseType\n ): 'postgres' | 'mysql' | 'mongodb' | 'mssql' | 'sqlite' {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 'postgres';\n case 'mysql':\n case 'mariadb':\n return 'mysql';\n case 'mongodb':\n case 'mongo':\n return 'mongodb';\n case 'mssql':\n return 'mssql';\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 'sqlite';\n default:\n throw new Error(`Unknown database type: ${type}`);\n }\n }\n\n /**\n * Setup PostgreSQL test container\n */\n async setupPostgresContainer(\n config: PostgresConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db',\n command = ['postgres', '-c', 'log_statement=all']\n } = config;\n\n const container = await new GenericContainer('postgres:latest')\n .withExposedPorts(5432)\n .withEnvironment({\n POSTGRES_USER: user,\n POSTGRES_PASSWORD: password,\n POSTGRES_DB: database\n })\n .withCommand(command)\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MySQL test container\n */\n async setupMySQLContainer(\n config: MySQLConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db',\n rootPassword = 'root_password'\n } = config;\n\n const container = await new GenericContainer('mysql:8')\n .withExposedPorts(3306)\n .withEnvironment({\n MYSQL_ROOT_PASSWORD: rootPassword,\n MYSQL_DATABASE: database,\n MYSQL_USER: user,\n MYSQL_PASSWORD: password\n })\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MongoDB test container\n */\n async setupMongoDBContainer(\n config: MongoDBConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db'\n } = config;\n\n const container = await new GenericContainer('mongo:latest')\n .withExposedPorts(27017)\n .withEnvironment({\n MONGO_INITDB_ROOT_USERNAME: user,\n MONGO_INITDB_ROOT_PASSWORD: password,\n MONGO_INITDB_DATABASE: database\n })\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Microsoft SQL Server test container\n */\n async setupMSSQLContainer(\n config: MSSQLConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n user = 'SA',\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n password = 'Test_Password123!',\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n database = 'test_db',\n saPassword = 'Test_Password123!'\n } = config;\n\n const container = await new GenericContainer(\n 'mcr.microsoft.com/mssql/server:2022-latest'\n )\n .withExposedPorts(1433)\n .withEnvironment({\n ACCEPT_EULA: 'Y',\n SA_PASSWORD: saPassword,\n MSSQL_PID: 'Developer'\n })\n .start();\n\n // Wait a bit for SQL Server to be ready\n await new Promise((resolve) => setTimeout(resolve, 3000));\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Redis test container\n */\n async setupRedisContainer(\n config: RedisConfig = {}\n ): Promise<StartedTestContainer> {\n const { command = ['redis-server', '--appendonly', 'yes'] } = config;\n\n const container = await new GenericContainer('redis:latest')\n .withExposedPorts(6379)\n .withCommand(command)\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Cleanup all containers\n */\n async cleanup(): Promise<void> {\n await Promise.all(\n this.containers.map((container) =>\n container.stop({ remove: true, removeVolumes: true }).catch(() => {\n // Ignore cleanup errors\n })\n )\n );\n this.containers = [];\n }\n}\n","import { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType } from './containers';\n\nexport interface TestEnvConfig {\n database: StartedTestContainer | null;\n databaseType: DatabaseType;\n redis?: StartedTestContainer;\n hmacSecret?: string;\n customVars?: Record<string, string>;\n}\n\n/**\n * Get the default port for a database type\n */\nfunction getDatabasePort(type: DatabaseType): number {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 5432;\n case 'mysql':\n case 'mariadb':\n return 3306;\n case 'mongodb':\n case 'mongo':\n return 27017;\n case 'mssql':\n return 1433;\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 0; // SQLite is file-based, no port\n default:\n return 5432;\n }\n}\n\n/**\n * Setup test environment variables for a blueprint test\n */\nexport function setupTestEnvironment(config: TestEnvConfig): void {\n const {\n database,\n databaseType,\n redis,\n hmacSecret = 'test-secret-key',\n customVars = {}\n } = config;\n\n const dbPort = getDatabasePort(databaseType);\n\n // Database environment variables\n process.env.DB_NAME = 'test_db';\n\n // SQLite databases are file-based, no container needed\n if (\n databaseType === 'sqlite' ||\n databaseType === 'better-sqlite' ||\n databaseType === 'libsql'\n ) {\n process.env.DB_PATH = ':memory:'; // In-memory SQLite for tests\n } else if (database) {\n process.env.DB_HOST = database.getHost();\n process.env.DB_USER = databaseType === 'mssql' ? 'SA' : 'test_user';\n process.env.DB_PASSWORD =\n databaseType === 'mssql' ? 'Test_Password123!' : 'test_password';\n process.env.DB_PORT = database.getMappedPort(dbPort).toString();\n }\n\n // Redis environment variables (if provided)\n if (redis) {\n process.env.REDIS_URL = `redis://${redis.getHost()}:${redis.getMappedPort(6379)}`;\n }\n\n // Standard test environment variables\n process.env.HMAC_SECRET_KEY = hmacSecret;\n process.env.JWKS_PUBLIC_KEY_URL =\n 'http://localhost:3000/.well-known/jwks.json';\n process.env.OTEL_SERVICE_NAME = 'test-service';\n process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://localhost:4318';\n process.env.HOST = 'localhost';\n process.env.PORT = '3000';\n process.env.NODE_ENV = 'test';\n process.env.VERSION = 'v1';\n process.env.DOCS_PATH = '/docs';\n process.env.OTEL_LEVEL = 'info';\n process.env.DOTENV_FILE_PATH = '.env.test';\n\n // Custom environment variables\n Object.entries(customVars).forEach(([key, value]) => {\n process.env[key] = value;\n });\n}\n","import { MikroORM, Options } from '@mikro-orm/core';\nimport Redis from 'ioredis';\nimport { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType } from './containers';\n\nexport interface MikroOrmTestConfig {\n /**\n * MikroORM config object (imported from mikro-orm.config)\n */\n mikroOrmConfig: Options;\n\n /**\n * Database type (postgres, mysql, mongodb, etc.)\n */\n databaseType: DatabaseType;\n\n /**\n * Whether to use migrations (true) or schema generation (false)\n * - true: IAM blueprints (uses getMigrator().up())\n * - false: Billing blueprints (uses getSchemaGenerator().createSchema())\n */\n useMigrations?: boolean;\n\n /**\n * Path to migrations directory (required if useMigrations is true)\n */\n migrationsPath?: string;\n\n /**\n * Database container instance (null for file-based databases like SQLite)\n */\n container: StartedTestContainer | null;\n}\n\n/**\n * Get the default port for a database type\n */\nfunction getDatabasePort(type: DatabaseType): number {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 5432;\n case 'mysql':\n case 'mariadb':\n return 3306;\n case 'mongodb':\n case 'mongo':\n return 27017;\n case 'mssql':\n return 1433;\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 0; // SQLite is file-based, no port\n default:\n return 5432;\n }\n}\n\n/**\n * Setup MikroORM for testing with proper schema/migrations\n */\nexport async function setupTestORM(\n config: MikroOrmTestConfig\n): Promise<MikroORM> {\n const {\n mikroOrmConfig,\n databaseType,\n useMigrations = false,\n container\n } = config;\n\n const dbPort = getDatabasePort(databaseType);\n\n // SQLite databases are file-based\n let ormConfig: Options = {};\n if (\n databaseType === 'sqlite' ||\n databaseType === 'better-sqlite' ||\n databaseType === 'libsql'\n ) {\n ormConfig = {\n ...mikroOrmConfig,\n dbName: ':memory:', // In-memory SQLite for tests\n debug: false,\n ...(useMigrations\n ? {\n migrations: {\n path: config.migrationsPath,\n glob: '!(*.d).{js,ts}',\n dropTables: true\n }\n }\n : {\n schemaGenerator: {\n createForeignKeyConstraints: false\n }\n })\n };\n } else if (container) {\n // Container-based databases\n ormConfig = {\n ...mikroOrmConfig,\n dbName: 'test_db',\n host: container.getHost(),\n user: databaseType === 'mssql' ? 'SA' : 'test_user',\n password:\n databaseType === 'mssql' ? 'Test_Password123!' : 'test_password',\n port: container.getMappedPort(dbPort),\n debug: false,\n ...(useMigrations\n ? {\n migrations: {\n path: config.migrationsPath,\n glob: '!(*.d).{js,ts}',\n dropTables: true\n }\n }\n : {\n schemaGenerator: {\n createForeignKeyConstraints: false\n }\n })\n };\n }\n\n const orm = await MikroORM.init(ormConfig);\n\n // Initialize database schema\n if (useMigrations) {\n await orm.getMigrator().up();\n } else {\n await orm.getSchemaGenerator().createSchema();\n }\n\n return orm;\n}\n\n/**\n * Clear all data from the test database\n */\nexport async function clearTestDatabase(\n orm: MikroORM,\n redis?: Redis\n): Promise<void> {\n // Clear Redis if provided\n if (redis) {\n await redis.flushall();\n }\n\n // Clear all database entities\n const em = orm.em.fork();\n const entities = Object.values(orm.getMetadata().getAll());\n\n // Delete in reverse order to avoid foreign key constraints\n for (const entity of entities.reverse()) {\n try {\n await em.nativeDelete(entity.class, {});\n } catch (error) {\n // Ignore \"table does not exist\" errors\n if (!(error as Error).message?.includes('does not exist')) {\n throw error;\n }\n }\n }\n\n await em.flush();\n}\n","import { MikroORM, Options } from '@mikro-orm/core';\nimport Redis from 'ioredis';\nimport { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType, TestContainerManager } from './containers';\nimport { clearTestDatabase, setupTestORM } from './database';\nimport { setupTestEnvironment } from './environment';\n\nexport interface BlueprintTestConfig {\n /**\n * Function that imports and returns the MikroORM config\n * This is called AFTER environment variables are set\n */\n getConfig: () => Promise<Options>;\n\n /**\n * Database type (postgres, mysql, mongodb, etc.)\n * @default 'postgres'\n */\n databaseType?: DatabaseType;\n\n /**\n * Whether to use migrations (true) or schema generation (false)\n */\n useMigrations?: boolean;\n\n /**\n * Path to migrations directory (required if useMigrations is true)\n */\n migrationsPath?: string;\n\n /**\n * Whether the blueprint needs Redis\n */\n needsRedis?: boolean;\n\n /**\n * Custom environment variables to set\n */\n customEnvVars?: Record<string, string>;\n\n /**\n * Custom setup hook called after containers and ORM are initialized\n */\n onSetup?: (setup: TestSetupResult) => Promise<void>;\n}\n\nexport interface TestSetupResult {\n container: StartedTestContainer | null;\n redisContainer?: StartedTestContainer;\n orm: MikroORM;\n redis?: Redis;\n}\n\n/**\n * Complete test harness for blueprint E2E testing\n *\n * Handles container setup, environment configuration, and database initialization\n *\n * @example\n * ```typescript\n * const harness = new BlueprintTestHarness({\n * mikroOrmConfigPath: '../mikro-orm.config',\n * useMigrations: false,\n * needsRedis: true,\n * customEnvVars: {\n * STRIPE_API_KEY: 'sk_test_...'\n * }\n * });\n *\n * const setup = await harness.setup();\n * // ... run tests\n * await harness.cleanup();\n * ```\n */\nexport class BlueprintTestHarness {\n private containers: TestContainerManager;\n private result?: TestSetupResult;\n\n constructor(private config: BlueprintTestConfig) {\n this.containers = new TestContainerManager();\n }\n\n /**\n * Setup all test infrastructure (containers, ORM, Redis)\n */\n async setup(): Promise<TestSetupResult> {\n const databaseType = this.config.databaseType || 'postgres';\n\n // Setup database container\n const container =\n await this.containers.setupDatabaseContainer(databaseType);\n\n // Setup Redis container if needed\n const redisContainer = this.config.needsRedis\n ? await this.containers.setupRedisContainer()\n : undefined;\n\n // Setup environment variables\n setupTestEnvironment({\n database: container,\n databaseType,\n redis: redisContainer,\n customVars: this.config.customEnvVars\n });\n\n // Get the config AFTER environment is set\n const mikroOrmConfig = await this.config.getConfig();\n\n // Setup ORM\n const orm = await setupTestORM({\n mikroOrmConfig,\n databaseType,\n useMigrations: this.config.useMigrations,\n migrationsPath: this.config.migrationsPath,\n container\n });\n\n // Setup Redis client if needed\n let redis: Redis | undefined;\n if (redisContainer) {\n redis = new Redis({\n host: redisContainer.getHost(),\n port: redisContainer.getMappedPort(6379),\n maxRetriesPerRequest: 3\n });\n await redis.ping();\n }\n\n this.result = { container, redisContainer, orm, redis };\n\n // Call custom setup hook\n if (this.config.onSetup) {\n await this.config.onSetup(this.result);\n }\n\n return this.result;\n }\n\n /**\n * Cleanup all test infrastructure\n */\n async cleanup(): Promise<void> {\n if (this.result?.redis) {\n await this.result.redis.quit();\n }\n if (this.result?.orm) {\n await this.result.orm.close();\n }\n await this.containers.cleanup();\n this.result = undefined;\n }\n\n /**\n * Clear all data from the database\n */\n async clearDatabase(): Promise<void> {\n if (this.result) {\n await clearTestDatabase(this.result.orm, this.result.redis);\n }\n }\n}\n","/**\n * Standard mock authentication tokens for testing\n */\nexport const TEST_TOKENS = {\n /**\n * Mock Bearer token for testing\n */\n AUTH: 'Bearer test-token',\n\n /**\n * Mock valid HMAC token for testing\n */\n HMAC: 'HMAC keyId=test-key ts=1234567890 nonce=test-nonce signature=test-signature',\n\n /**\n * Mock invalid HMAC token for testing authentication failures\n */\n HMAC_INVALID:\n 'HMAC keyId=invalid-key ts=1234567890 nonce=invalid-nonce signature=invalid-signature'\n} as const;\n"],"mappings":"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","names":["getDatabasePort","Redis"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/containers.ts","../src/environment.ts","../src/database.ts","../src/harness.ts","../src/tokens.ts"],"sourcesContent":["/**\n * @forklaunch/testing\n *\n * Testing utilities for forklaunch-js blueprints\n *\n * @packageDocumentation\n */\n\n// Container management\nexport {\n DatabaseConfig,\n DatabaseType,\n KafkaConfig,\n MongoDBConfig,\n MSSQLConfig,\n MySQLConfig,\n PostgresConfig,\n RedisConfig,\n S3Config,\n SQLiteConfig,\n TestContainerManager\n} from './containers';\n\nexport { setupTestEnvironment, TestEnvConfig } from './environment';\n\nexport {\n clearTestDatabase,\n MikroOrmTestConfig,\n setupTestORM\n} from './database';\n\nexport {\n BlueprintTestConfig,\n BlueprintTestHarness,\n TestSetupResult\n} from './harness';\n\nexport { TEST_TOKENS } from './tokens';\n","import { GenericContainer, StartedTestContainer } from 'testcontainers';\n\nexport type DatabaseType =\n | 'postgres'\n | 'postgresql'\n | 'mysql'\n | 'mariadb'\n | 'mongodb'\n | 'mongo'\n | 'mssql'\n | 'libsql'\n | 'sqlite'\n | 'better-sqlite';\n\nexport interface PostgresConfig {\n user?: string;\n password?: string;\n database?: string;\n command?: string[];\n}\n\nexport interface MySQLConfig {\n user?: string;\n password?: string;\n database?: string;\n rootPassword?: string;\n}\n\nexport interface MongoDBConfig {\n user?: string;\n password?: string;\n database?: string;\n}\n\nexport interface MSSQLConfig {\n user?: string;\n password?: string;\n database?: string;\n saPassword?: string;\n}\n\nexport interface SQLiteConfig {\n database?: string;\n}\n\nexport interface RedisConfig {\n command?: string[];\n}\n\nexport interface KafkaConfig {\n /** Kafka cluster ID */\n clusterId?: string;\n /** Number of partitions */\n numPartitions?: number;\n /** Replication factor */\n replicationFactor?: number;\n /** Additional environment variables */\n env?: Record<string, string>;\n}\n\nexport interface S3Config {\n /** MinIO root user (access key) */\n rootUser?: string;\n /** MinIO root password (secret key) */\n rootPassword?: string;\n /** Default bucket to create */\n defaultBucket?: string;\n /** Region */\n region?: string;\n}\n\nexport type DatabaseConfig =\n | PostgresConfig\n | MySQLConfig\n | MongoDBConfig\n | MSSQLConfig\n | SQLiteConfig;\n\n/**\n * Manages test containers (PostgreSQL, MySQL, MongoDB, Redis, etc.) for E2E testing\n */\nexport class TestContainerManager {\n private containers: StartedTestContainer[] = [];\n\n /**\n * Setup database container based on type\n */\n async setupDatabaseContainer(\n type: DatabaseType,\n config: DatabaseConfig = {}\n ): Promise<StartedTestContainer | null> {\n const normalizedType = this.normalizeDatabaseType(type);\n\n switch (normalizedType) {\n case 'postgres':\n return this.setupPostgresContainer(config as PostgresConfig);\n case 'mysql':\n return this.setupMySQLContainer(config as MySQLConfig);\n case 'mongodb':\n return this.setupMongoDBContainer(config as MongoDBConfig);\n case 'mssql':\n return this.setupMSSQLContainer(config as MSSQLConfig);\n case 'sqlite':\n // SQLite doesn't need a container (file-based)\n return null;\n default:\n throw new Error(`Unsupported database type: ${type}`);\n }\n }\n\n /**\n * Normalize database type aliases\n */\n private normalizeDatabaseType(\n type: DatabaseType\n ): 'postgres' | 'mysql' | 'mongodb' | 'mssql' | 'sqlite' {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 'postgres';\n case 'mysql':\n case 'mariadb':\n return 'mysql';\n case 'mongodb':\n case 'mongo':\n return 'mongodb';\n case 'mssql':\n return 'mssql';\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 'sqlite';\n default:\n throw new Error(`Unknown database type: ${type}`);\n }\n }\n\n /**\n * Setup PostgreSQL test container\n */\n async setupPostgresContainer(\n config: PostgresConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db',\n command = ['postgres', '-c', 'log_statement=all']\n } = config;\n\n const container = await new GenericContainer('postgres:latest')\n .withExposedPorts(5432)\n .withEnvironment({\n POSTGRES_USER: user,\n POSTGRES_PASSWORD: password,\n POSTGRES_DB: database\n })\n .withCommand(command)\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MySQL test container\n */\n async setupMySQLContainer(\n config: MySQLConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db',\n rootPassword = 'root_password'\n } = config;\n\n const container = await new GenericContainer('mysql:8')\n .withExposedPorts(3306)\n .withEnvironment({\n MYSQL_ROOT_PASSWORD: rootPassword,\n MYSQL_DATABASE: database,\n MYSQL_USER: user,\n MYSQL_PASSWORD: password\n })\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MongoDB test container\n */\n async setupMongoDBContainer(\n config: MongoDBConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db'\n } = config;\n\n const container = await new GenericContainer('mongo:latest')\n .withExposedPorts(27017)\n .withEnvironment({\n MONGO_INITDB_ROOT_USERNAME: user,\n MONGO_INITDB_ROOT_PASSWORD: password,\n MONGO_INITDB_DATABASE: database\n })\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Microsoft SQL Server test container\n */\n async setupMSSQLContainer(\n config: MSSQLConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n user = 'SA',\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n password = 'Test_Password123!',\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n database = 'test_db',\n saPassword = 'Test_Password123!'\n } = config;\n\n const container = await new GenericContainer(\n 'mcr.microsoft.com/mssql/server:2022-latest'\n )\n .withExposedPorts(1433)\n .withEnvironment({\n ACCEPT_EULA: 'Y',\n SA_PASSWORD: saPassword,\n MSSQL_PID: 'Developer'\n })\n .start();\n\n // Wait a bit for SQL Server to be ready\n await new Promise((resolve) => setTimeout(resolve, 3000));\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Redis test container\n */\n async setupRedisContainer(\n config: RedisConfig = {}\n ): Promise<StartedTestContainer> {\n const { command = ['redis-server', '--appendonly', 'yes'] } = config;\n\n const container = await new GenericContainer('redis:latest')\n .withExposedPorts(6379)\n .withCommand(command)\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Kafka test container\n */\n async setupKafkaContainer(\n config: KafkaConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n clusterId = 'test-cluster',\n numPartitions = 1,\n replicationFactor = 1,\n env = {}\n } = config;\n\n const container = await new GenericContainer('confluentinc/cp-kafka:latest')\n .withExposedPorts(9092, 9093)\n .withEnvironment({\n KAFKA_BROKER_ID: '1',\n KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:\n 'PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT',\n KAFKA_ADVERTISED_LISTENERS:\n 'PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092',\n KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: replicationFactor.toString(),\n KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: '1',\n KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: '1',\n KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: '0',\n KAFKA_NUM_PARTITIONS: numPartitions.toString(),\n KAFKA_CLUSTER_ID: clusterId,\n KAFKA_PROCESS_ROLES: 'broker,controller',\n KAFKA_NODE_ID: '1',\n KAFKA_CONTROLLER_QUORUM_VOTERS: '1@localhost:9093',\n KAFKA_LISTENERS:\n 'PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093,PLAINTEXT_HOST://0.0.0.0:9092',\n KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT',\n KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER',\n KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs',\n ...env\n })\n .start();\n\n // Wait for Kafka to be ready\n await new Promise((resolve) => setTimeout(resolve, 5000));\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MinIO (S3-compatible) test container\n */\n async setupS3Container(config: S3Config = {}): Promise<StartedTestContainer> {\n const {\n rootUser = 'minioadmin',\n rootPassword = 'minioadmin',\n defaultBucket = 'test-bucket',\n region = 'us-east-1'\n } = config;\n\n const container = await new GenericContainer('minio/minio:latest')\n .withExposedPorts(9000, 9001)\n .withEnvironment({\n MINIO_ROOT_USER: rootUser,\n MINIO_ROOT_PASSWORD: rootPassword,\n MINIO_REGION: region\n })\n .withCommand(['server', '/data', '--console-address', ':9001'])\n .start();\n\n // Wait for MinIO to be ready\n await new Promise((resolve) => setTimeout(resolve, 2000));\n\n // Create default bucket if specified\n if (defaultBucket) {\n try {\n // Using MinIO client command via exec\n await container.exec([\n 'mc',\n 'alias',\n 'set',\n 'local',\n 'http://localhost:9000',\n rootUser,\n rootPassword\n ]);\n await container.exec(['mc', 'mb', `local/${defaultBucket}`]);\n } catch (error) {\n // Bucket creation might fail, but container is still usable\n console.warn('Could not create default bucket:', error);\n }\n }\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Cleanup all containers\n */\n async cleanup(): Promise<void> {\n await Promise.all(\n this.containers.map((container) =>\n container.stop({ remove: true, removeVolumes: true }).catch(() => {\n // Ignore cleanup errors\n })\n )\n );\n this.containers = [];\n }\n}\n","import { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType } from './containers';\n\nexport interface TestEnvConfig {\n database: StartedTestContainer | null;\n databaseType?: DatabaseType;\n redis?: StartedTestContainer;\n kafka?: StartedTestContainer;\n s3?: StartedTestContainer;\n hmacSecret?: string;\n customVars?: Record<string, string>;\n}\n\n/**\n * Get the default port for a database type\n */\nfunction getDatabasePort(type: DatabaseType): number {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 5432;\n case 'mysql':\n case 'mariadb':\n return 3306;\n case 'mongodb':\n case 'mongo':\n return 27017;\n case 'mssql':\n return 1433;\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 0; // SQLite is file-based, no port\n default:\n return 5432;\n }\n}\n\n/**\n * Setup test environment variables for a blueprint test\n */\nexport function setupTestEnvironment(config: TestEnvConfig): void {\n const {\n database,\n databaseType,\n redis,\n kafka,\n s3,\n hmacSecret = 'test-secret-key',\n customVars = {}\n } = config;\n\n // Only set database environment variables if database is configured\n if (databaseType) {\n const dbPort = getDatabasePort(databaseType);\n\n // Database environment variables\n process.env.DB_NAME = 'test_db';\n\n // SQLite databases are file-based, no container needed\n if (\n databaseType === 'sqlite' ||\n databaseType === 'better-sqlite' ||\n databaseType === 'libsql'\n ) {\n process.env.DB_PATH = ':memory:'; // In-memory SQLite for tests\n } else if (database) {\n process.env.DB_HOST = database.getHost();\n process.env.DB_USER = databaseType === 'mssql' ? 'SA' : 'test_user';\n process.env.DB_PASSWORD =\n databaseType === 'mssql' ? 'Test_Password123!' : 'test_password';\n process.env.DB_PORT = database.getMappedPort(dbPort).toString();\n }\n }\n\n // Redis environment variables (if provided)\n if (redis) {\n process.env.REDIS_URL = `redis://${redis.getHost()}:${redis.getMappedPort(6379)}`;\n process.env.REDIS_HOST = redis.getHost();\n process.env.REDIS_PORT = redis.getMappedPort(6379).toString();\n }\n\n // Kafka environment variables (if provided)\n if (kafka) {\n const kafkaBroker = `${kafka.getHost()}:${kafka.getMappedPort(9092)}`;\n process.env.KAFKA_BROKERS = kafkaBroker;\n process.env.KAFKA_CLIENT_ID = 'test-client';\n process.env.KAFKA_GROUP_ID = 'test-group';\n }\n\n // S3/MinIO environment variables (if provided)\n if (s3) {\n process.env.S3_ENDPOINT = `http://${s3.getHost()}:${s3.getMappedPort(9000)}`;\n process.env.S3_ACCESS_KEY_ID = 'minioadmin';\n process.env.S3_SECRET_ACCESS_KEY = 'minioadmin';\n process.env.S3_REGION = 'us-east-1';\n process.env.S3_BUCKET = 'test-bucket';\n process.env.S3_FORCE_PATH_STYLE = 'true'; // Required for MinIO\n }\n\n // Standard test environment variables\n process.env.HMAC_SECRET_KEY = hmacSecret;\n process.env.JWKS_PUBLIC_KEY_URL =\n 'http://localhost:3000/.well-known/jwks.json';\n process.env.OTEL_SERVICE_NAME = 'test-service';\n process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://localhost:4318';\n process.env.HOST = 'localhost';\n process.env.PORT = '3000';\n process.env.NODE_ENV = 'test';\n process.env.VERSION = 'v1';\n process.env.DOCS_PATH = '/docs';\n process.env.OTEL_LEVEL = 'info';\n process.env.DOTENV_FILE_PATH = '.env.test';\n\n // Custom environment variables\n Object.entries(customVars).forEach(([key, value]) => {\n process.env[key] = value;\n });\n}\n","import { MikroORM, Options } from '@mikro-orm/core';\nimport Redis from 'ioredis';\nimport { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType } from './containers';\n\nexport interface MikroOrmTestConfig {\n /**\n * MikroORM config object (imported from mikro-orm.config)\n */\n mikroOrmConfig: Options;\n\n /**\n * Database type (postgres, mysql, mongodb, etc.)\n */\n databaseType: DatabaseType;\n\n /**\n * Whether to use migrations (true) or schema generation (false)\n * - true: IAM blueprints (uses getMigrator().up())\n * - false: Billing blueprints (uses getSchemaGenerator().createSchema())\n */\n useMigrations?: boolean;\n\n /**\n * Path to migrations directory (required if useMigrations is true)\n */\n migrationsPath?: string;\n\n /**\n * Database container instance (null for file-based databases like SQLite)\n */\n container: StartedTestContainer | null;\n}\n\n/**\n * Get the default port for a database type\n */\nfunction getDatabasePort(type: DatabaseType): number {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 5432;\n case 'mysql':\n case 'mariadb':\n return 3306;\n case 'mongodb':\n case 'mongo':\n return 27017;\n case 'mssql':\n return 1433;\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 0; // SQLite is file-based, no port\n default:\n return 5432;\n }\n}\n\n/**\n * Setup MikroORM for testing with proper schema/migrations\n */\nexport async function setupTestORM(\n config: MikroOrmTestConfig\n): Promise<MikroORM> {\n const {\n mikroOrmConfig,\n databaseType,\n useMigrations = false,\n container\n } = config;\n\n const dbPort = getDatabasePort(databaseType);\n\n // SQLite databases are file-based\n let ormConfig: Options = {};\n if (\n databaseType === 'sqlite' ||\n databaseType === 'better-sqlite' ||\n databaseType === 'libsql'\n ) {\n ormConfig = {\n ...mikroOrmConfig,\n dbName: ':memory:', // In-memory SQLite for tests\n debug: false,\n ...(useMigrations\n ? {\n migrations: {\n path: config.migrationsPath,\n glob: '!(*.d).{js,ts}',\n dropTables: true\n }\n }\n : {\n schemaGenerator: {\n createForeignKeyConstraints: false\n }\n })\n };\n } else if (container) {\n // Container-based databases\n ormConfig = {\n ...mikroOrmConfig,\n dbName: 'test_db',\n host: container.getHost(),\n user: databaseType === 'mssql' ? 'SA' : 'test_user',\n password:\n databaseType === 'mssql' ? 'Test_Password123!' : 'test_password',\n port: container.getMappedPort(dbPort),\n debug: false,\n ...(useMigrations\n ? {\n migrations: {\n path: config.migrationsPath,\n glob: '!(*.d).{js,ts}',\n dropTables: true\n }\n }\n : {\n schemaGenerator: {\n createForeignKeyConstraints: false\n }\n })\n };\n }\n\n const orm = await MikroORM.init(ormConfig);\n\n // Initialize database schema\n if (useMigrations) {\n await orm.getMigrator().up();\n } else {\n await orm.getSchemaGenerator().createSchema();\n }\n\n return orm;\n}\n\n/**\n * Clear all data from the test database and/or cache\n */\nexport async function clearTestDatabase(\n orm?: MikroORM,\n redis?: Redis\n): Promise<void> {\n // Clear Redis if provided\n if (redis) {\n await redis.flushall();\n }\n\n // Clear all database entities (if ORM is provided)\n if (orm) {\n const em = orm.em.fork();\n const entities = Object.values(orm.getMetadata().getAll());\n\n // Delete in reverse order to avoid foreign key constraints\n for (const entity of entities.reverse()) {\n try {\n await em.nativeDelete(entity.class, {});\n } catch (error) {\n // Ignore \"table does not exist\" errors\n if (!(error as Error).message?.includes('does not exist')) {\n throw error;\n }\n }\n }\n\n await em.flush();\n }\n}\n","import { MikroORM, Options } from '@mikro-orm/core';\nimport Redis from 'ioredis';\nimport { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType, TestContainerManager } from './containers';\nimport { clearTestDatabase, setupTestORM } from './database';\nimport { setupTestEnvironment } from './environment';\n\nexport interface BlueprintTestConfig {\n /**\n * Function that imports and returns the MikroORM config\n * This is called AFTER environment variables are set\n * Optional - if not provided, no database will be set up\n */\n getConfig?: () => Promise<Options>;\n\n /**\n * Database type (postgres, mysql, mongodb, etc.)\n * Optional - if not provided, no database will be set up\n */\n databaseType?: DatabaseType;\n\n /**\n * Whether to use migrations (true) or schema generation (false)\n */\n useMigrations?: boolean;\n\n /**\n * Path to migrations directory (required if useMigrations is true)\n */\n migrationsPath?: string;\n\n /**\n * Whether the blueprint needs Redis\n */\n needsRedis?: boolean;\n\n /**\n * Whether the blueprint needs Kafka\n */\n needsKafka?: boolean;\n\n /**\n * Whether the blueprint needs S3 (MinIO)\n */\n needsS3?: boolean;\n\n /**\n * S3 bucket name to create (default: 'test-bucket')\n */\n s3Bucket?: string;\n\n /**\n * Custom environment variables to set\n */\n customEnvVars?: Record<string, string>;\n\n /**\n * Custom setup hook called after containers and ORM are initialized\n */\n onSetup?: (setup: TestSetupResult) => Promise<void>;\n}\n\nexport interface TestSetupResult {\n container: StartedTestContainer | null;\n redisContainer?: StartedTestContainer;\n kafkaContainer?: StartedTestContainer;\n s3Container?: StartedTestContainer;\n orm?: MikroORM;\n redis?: Redis;\n}\n\n/**\n * Complete test harness for blueprint E2E testing\n *\n * Handles container setup, environment configuration, and database initialization\n *\n * @example Database with ORM\n * ```typescript\n * const harness = new BlueprintTestHarness({\n * getConfig: async () => {\n * const { default: config } = await import('../mikro-orm.config');\n * return config;\n * },\n * databaseType: 'postgres',\n * useMigrations: false,\n * needsRedis: true,\n * customEnvVars: {\n * STRIPE_API_KEY: 'sk_test_...'\n * }\n * });\n *\n * const setup = await harness.setup();\n * // ... run tests with setup.orm and setup.redis\n * await harness.cleanup();\n * ```\n *\n * @example Cache-only (no database)\n * ```typescript\n * const harness = new BlueprintTestHarness({\n * needsRedis: true,\n * customEnvVars: {\n * API_KEY: 'test_key'\n * }\n * });\n *\n * const setup = await harness.setup();\n * // ... run tests with setup.redis only (setup.orm is undefined)\n * await harness.cleanup();\n * ```\n *\n * @example With Kafka and S3\n * ```typescript\n * const harness = new BlueprintTestHarness({\n * getConfig: async () => {\n * const { default: config } = await import('../mikro-orm.config');\n * return config;\n * },\n * databaseType: 'postgres',\n * needsKafka: true,\n * needsS3: true,\n * s3Bucket: 'my-test-bucket',\n * customEnvVars: {\n * API_KEY: 'test_key'\n * }\n * });\n *\n * const setup = await harness.setup();\n * // Access containers via setup.kafkaContainer and setup.s3Container\n * // Kafka broker: process.env.KAFKA_BROKERS\n * // S3 endpoint: process.env.S3_ENDPOINT\n * await harness.cleanup();\n * ```\n */\nexport class BlueprintTestHarness {\n private containers: TestContainerManager;\n private result?: TestSetupResult;\n\n constructor(private config: BlueprintTestConfig) {\n this.containers = new TestContainerManager();\n }\n\n /**\n * Setup all test infrastructure (containers, ORM, Redis, Kafka, S3)\n */\n async setup(): Promise<TestSetupResult> {\n // Setup database container only if database is needed\n let container: StartedTestContainer | null = null;\n let orm: MikroORM | undefined;\n let redisContainer: StartedTestContainer | undefined;\n let kafkaContainer: StartedTestContainer | undefined;\n let s3Container: StartedTestContainer | undefined;\n\n // Setup Redis container if needed (for both database and cache-only modes)\n if (this.config.needsRedis) {\n redisContainer = await this.containers.setupRedisContainer();\n }\n\n // Setup Kafka container if needed\n if (this.config.needsKafka) {\n kafkaContainer = await this.containers.setupKafkaContainer();\n }\n\n // Setup S3 container if needed\n if (this.config.needsS3) {\n s3Container = await this.containers.setupS3Container({\n defaultBucket: this.config.s3Bucket\n });\n }\n\n if (this.config.databaseType && this.config.getConfig) {\n const databaseType = this.config.databaseType;\n\n // Setup database container\n container = await this.containers.setupDatabaseContainer(databaseType);\n\n // Setup environment variables\n setupTestEnvironment({\n database: container,\n databaseType,\n redis: redisContainer,\n kafka: kafkaContainer,\n s3: s3Container,\n customVars: this.config.customEnvVars\n });\n\n // Get the config AFTER environment is set\n const mikroOrmConfig = await this.config.getConfig();\n\n // Setup ORM\n orm = await setupTestORM({\n mikroOrmConfig,\n databaseType,\n useMigrations: this.config.useMigrations,\n migrationsPath: this.config.migrationsPath,\n container\n });\n } else {\n // Cache-only mode: no database\n setupTestEnvironment({\n database: null,\n databaseType: undefined,\n redis: redisContainer,\n kafka: kafkaContainer,\n s3: s3Container,\n customVars: this.config.customEnvVars\n });\n }\n\n // Setup Redis client if needed\n let redis: Redis | undefined;\n if (redisContainer) {\n redis = new Redis({\n host: redisContainer.getHost(),\n port: redisContainer.getMappedPort(6379),\n maxRetriesPerRequest: 3\n });\n await redis.ping();\n }\n\n this.result = {\n container,\n redisContainer,\n kafkaContainer,\n s3Container,\n orm,\n redis\n };\n\n // Call custom setup hook\n if (this.config.onSetup) {\n await this.config.onSetup(this.result);\n }\n\n return this.result;\n }\n\n /**\n * Cleanup all test infrastructure\n */\n async cleanup(): Promise<void> {\n if (this.result?.redis) {\n await this.result.redis.quit();\n }\n if (this.result?.orm) {\n await this.result.orm.close();\n }\n await this.containers.cleanup();\n this.result = undefined;\n }\n\n /**\n * Clear all data from the database and/or cache\n */\n async clearDatabase(): Promise<void> {\n if (this.result) {\n await clearTestDatabase(this.result.orm, this.result.redis);\n }\n }\n}\n","/**\n * Standard mock authentication tokens for testing\n */\nexport const TEST_TOKENS = {\n /**\n * Mock Bearer token for testing\n */\n AUTH: 'Bearer test-token',\n\n /**\n * Mock valid HMAC token for testing\n */\n HMAC: 'HMAC keyId=test-key ts=1234567890 nonce=test-nonce signature=test-signature',\n\n /**\n * Mock invalid HMAC token for testing authentication failures\n */\n HMAC_INVALID:\n 'HMAC keyId=invalid-key ts=1234567890 nonce=invalid-nonce signature=invalid-signature'\n} as const;\n"],"mappings":"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","names":["getDatabasePort","Redis"]}
package/lib/index.mjs CHANGED
@@ -133,6 +133,74 @@ var TestContainerManager = class {
133
133
  this.containers.push(container);
134
134
  return container;
135
135
  }
136
+ /**
137
+ * Setup Kafka test container
138
+ */
139
+ async setupKafkaContainer(config = {}) {
140
+ const {
141
+ clusterId = "test-cluster",
142
+ numPartitions = 1,
143
+ replicationFactor = 1,
144
+ env = {}
145
+ } = config;
146
+ const container = await new GenericContainer("confluentinc/cp-kafka:latest").withExposedPorts(9092, 9093).withEnvironment({
147
+ KAFKA_BROKER_ID: "1",
148
+ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT",
149
+ KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092",
150
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: replicationFactor.toString(),
151
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1",
152
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1",
153
+ KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0",
154
+ KAFKA_NUM_PARTITIONS: numPartitions.toString(),
155
+ KAFKA_CLUSTER_ID: clusterId,
156
+ KAFKA_PROCESS_ROLES: "broker,controller",
157
+ KAFKA_NODE_ID: "1",
158
+ KAFKA_CONTROLLER_QUORUM_VOTERS: "1@localhost:9093",
159
+ KAFKA_LISTENERS: "PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093,PLAINTEXT_HOST://0.0.0.0:9092",
160
+ KAFKA_INTER_BROKER_LISTENER_NAME: "PLAINTEXT",
161
+ KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER",
162
+ KAFKA_LOG_DIRS: "/tmp/kraft-combined-logs",
163
+ ...env
164
+ }).start();
165
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
166
+ this.containers.push(container);
167
+ return container;
168
+ }
169
+ /**
170
+ * Setup MinIO (S3-compatible) test container
171
+ */
172
+ async setupS3Container(config = {}) {
173
+ const {
174
+ rootUser = "minioadmin",
175
+ rootPassword = "minioadmin",
176
+ defaultBucket = "test-bucket",
177
+ region = "us-east-1"
178
+ } = config;
179
+ const container = await new GenericContainer("minio/minio:latest").withExposedPorts(9e3, 9001).withEnvironment({
180
+ MINIO_ROOT_USER: rootUser,
181
+ MINIO_ROOT_PASSWORD: rootPassword,
182
+ MINIO_REGION: region
183
+ }).withCommand(["server", "/data", "--console-address", ":9001"]).start();
184
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
185
+ if (defaultBucket) {
186
+ try {
187
+ await container.exec([
188
+ "mc",
189
+ "alias",
190
+ "set",
191
+ "local",
192
+ "http://localhost:9000",
193
+ rootUser,
194
+ rootPassword
195
+ ]);
196
+ await container.exec(["mc", "mb", `local/${defaultBucket}`]);
197
+ } catch (error) {
198
+ console.warn("Could not create default bucket:", error);
199
+ }
200
+ }
201
+ this.containers.push(container);
202
+ return container;
203
+ }
136
204
  /**
137
205
  * Cleanup all containers
138
206
  */
@@ -175,21 +243,41 @@ function setupTestEnvironment(config) {
175
243
  database,
176
244
  databaseType,
177
245
  redis,
246
+ kafka,
247
+ s3,
178
248
  hmacSecret = "test-secret-key",
179
249
  customVars = {}
180
250
  } = config;
181
- const dbPort = getDatabasePort(databaseType);
182
- process.env.DB_NAME = "test_db";
183
- if (databaseType === "sqlite" || databaseType === "better-sqlite" || databaseType === "libsql") {
184
- process.env.DB_PATH = ":memory:";
185
- } else if (database) {
186
- process.env.DB_HOST = database.getHost();
187
- process.env.DB_USER = databaseType === "mssql" ? "SA" : "test_user";
188
- process.env.DB_PASSWORD = databaseType === "mssql" ? "Test_Password123!" : "test_password";
189
- process.env.DB_PORT = database.getMappedPort(dbPort).toString();
251
+ if (databaseType) {
252
+ const dbPort = getDatabasePort(databaseType);
253
+ process.env.DB_NAME = "test_db";
254
+ if (databaseType === "sqlite" || databaseType === "better-sqlite" || databaseType === "libsql") {
255
+ process.env.DB_PATH = ":memory:";
256
+ } else if (database) {
257
+ process.env.DB_HOST = database.getHost();
258
+ process.env.DB_USER = databaseType === "mssql" ? "SA" : "test_user";
259
+ process.env.DB_PASSWORD = databaseType === "mssql" ? "Test_Password123!" : "test_password";
260
+ process.env.DB_PORT = database.getMappedPort(dbPort).toString();
261
+ }
190
262
  }
191
263
  if (redis) {
192
264
  process.env.REDIS_URL = `redis://${redis.getHost()}:${redis.getMappedPort(6379)}`;
265
+ process.env.REDIS_HOST = redis.getHost();
266
+ process.env.REDIS_PORT = redis.getMappedPort(6379).toString();
267
+ }
268
+ if (kafka) {
269
+ const kafkaBroker = `${kafka.getHost()}:${kafka.getMappedPort(9092)}`;
270
+ process.env.KAFKA_BROKERS = kafkaBroker;
271
+ process.env.KAFKA_CLIENT_ID = "test-client";
272
+ process.env.KAFKA_GROUP_ID = "test-group";
273
+ }
274
+ if (s3) {
275
+ process.env.S3_ENDPOINT = `http://${s3.getHost()}:${s3.getMappedPort(9e3)}`;
276
+ process.env.S3_ACCESS_KEY_ID = "minioadmin";
277
+ process.env.S3_SECRET_ACCESS_KEY = "minioadmin";
278
+ process.env.S3_REGION = "us-east-1";
279
+ process.env.S3_BUCKET = "test-bucket";
280
+ process.env.S3_FORCE_PATH_STYLE = "true";
193
281
  }
194
282
  process.env.HMAC_SECRET_KEY = hmacSecret;
195
283
  process.env.JWKS_PUBLIC_KEY_URL = "http://localhost:3000/.well-known/jwks.json";
@@ -292,18 +380,20 @@ async function clearTestDatabase(orm, redis) {
292
380
  if (redis) {
293
381
  await redis.flushall();
294
382
  }
295
- const em = orm.em.fork();
296
- const entities = Object.values(orm.getMetadata().getAll());
297
- for (const entity of entities.reverse()) {
298
- try {
299
- await em.nativeDelete(entity.class, {});
300
- } catch (error) {
301
- if (!error.message?.includes("does not exist")) {
302
- throw error;
383
+ if (orm) {
384
+ const em = orm.em.fork();
385
+ const entities = Object.values(orm.getMetadata().getAll());
386
+ for (const entity of entities.reverse()) {
387
+ try {
388
+ await em.nativeDelete(entity.class, {});
389
+ } catch (error) {
390
+ if (!error.message?.includes("does not exist")) {
391
+ throw error;
392
+ }
303
393
  }
304
394
  }
395
+ await em.flush();
305
396
  }
306
- await em.flush();
307
397
  }
308
398
 
309
399
  // src/harness.ts
@@ -316,26 +406,54 @@ var BlueprintTestHarness = class {
316
406
  containers;
317
407
  result;
318
408
  /**
319
- * Setup all test infrastructure (containers, ORM, Redis)
409
+ * Setup all test infrastructure (containers, ORM, Redis, Kafka, S3)
320
410
  */
321
411
  async setup() {
322
- const databaseType = this.config.databaseType || "postgres";
323
- const container = await this.containers.setupDatabaseContainer(databaseType);
324
- const redisContainer = this.config.needsRedis ? await this.containers.setupRedisContainer() : void 0;
325
- setupTestEnvironment({
326
- database: container,
327
- databaseType,
328
- redis: redisContainer,
329
- customVars: this.config.customEnvVars
330
- });
331
- const mikroOrmConfig = await this.config.getConfig();
332
- const orm = await setupTestORM({
333
- mikroOrmConfig,
334
- databaseType,
335
- useMigrations: this.config.useMigrations,
336
- migrationsPath: this.config.migrationsPath,
337
- container
338
- });
412
+ let container = null;
413
+ let orm;
414
+ let redisContainer;
415
+ let kafkaContainer;
416
+ let s3Container;
417
+ if (this.config.needsRedis) {
418
+ redisContainer = await this.containers.setupRedisContainer();
419
+ }
420
+ if (this.config.needsKafka) {
421
+ kafkaContainer = await this.containers.setupKafkaContainer();
422
+ }
423
+ if (this.config.needsS3) {
424
+ s3Container = await this.containers.setupS3Container({
425
+ defaultBucket: this.config.s3Bucket
426
+ });
427
+ }
428
+ if (this.config.databaseType && this.config.getConfig) {
429
+ const databaseType = this.config.databaseType;
430
+ container = await this.containers.setupDatabaseContainer(databaseType);
431
+ setupTestEnvironment({
432
+ database: container,
433
+ databaseType,
434
+ redis: redisContainer,
435
+ kafka: kafkaContainer,
436
+ s3: s3Container,
437
+ customVars: this.config.customEnvVars
438
+ });
439
+ const mikroOrmConfig = await this.config.getConfig();
440
+ orm = await setupTestORM({
441
+ mikroOrmConfig,
442
+ databaseType,
443
+ useMigrations: this.config.useMigrations,
444
+ migrationsPath: this.config.migrationsPath,
445
+ container
446
+ });
447
+ } else {
448
+ setupTestEnvironment({
449
+ database: null,
450
+ databaseType: void 0,
451
+ redis: redisContainer,
452
+ kafka: kafkaContainer,
453
+ s3: s3Container,
454
+ customVars: this.config.customEnvVars
455
+ });
456
+ }
339
457
  let redis;
340
458
  if (redisContainer) {
341
459
  redis = new Redis({
@@ -345,7 +463,14 @@ var BlueprintTestHarness = class {
345
463
  });
346
464
  await redis.ping();
347
465
  }
348
- this.result = { container, redisContainer, orm, redis };
466
+ this.result = {
467
+ container,
468
+ redisContainer,
469
+ kafkaContainer,
470
+ s3Container,
471
+ orm,
472
+ redis
473
+ };
349
474
  if (this.config.onSetup) {
350
475
  await this.config.onSetup(this.result);
351
476
  }
@@ -365,7 +490,7 @@ var BlueprintTestHarness = class {
365
490
  this.result = void 0;
366
491
  }
367
492
  /**
368
- * Clear all data from the database
493
+ * Clear all data from the database and/or cache
369
494
  */
370
495
  async clearDatabase() {
371
496
  if (this.result) {
package/lib/index.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/containers.ts","../src/environment.ts","../src/database.ts","../src/harness.ts","../src/tokens.ts"],"sourcesContent":["import { GenericContainer, StartedTestContainer } from 'testcontainers';\n\nexport type DatabaseType =\n | 'postgres'\n | 'postgresql'\n | 'mysql'\n | 'mariadb'\n | 'mongodb'\n | 'mongo'\n | 'mssql'\n | 'libsql'\n | 'sqlite'\n | 'better-sqlite';\n\nexport interface PostgresConfig {\n user?: string;\n password?: string;\n database?: string;\n command?: string[];\n}\n\nexport interface MySQLConfig {\n user?: string;\n password?: string;\n database?: string;\n rootPassword?: string;\n}\n\nexport interface MongoDBConfig {\n user?: string;\n password?: string;\n database?: string;\n}\n\nexport interface MSSQLConfig {\n user?: string;\n password?: string;\n database?: string;\n saPassword?: string;\n}\n\nexport interface SQLiteConfig {\n database?: string;\n}\n\nexport interface RedisConfig {\n command?: string[];\n}\n\nexport type DatabaseConfig =\n | PostgresConfig\n | MySQLConfig\n | MongoDBConfig\n | MSSQLConfig\n | SQLiteConfig;\n\n/**\n * Manages test containers (PostgreSQL, MySQL, MongoDB, Redis, etc.) for E2E testing\n */\nexport class TestContainerManager {\n private containers: StartedTestContainer[] = [];\n\n /**\n * Setup database container based on type\n */\n async setupDatabaseContainer(\n type: DatabaseType,\n config: DatabaseConfig = {}\n ): Promise<StartedTestContainer | null> {\n const normalizedType = this.normalizeDatabaseType(type);\n\n switch (normalizedType) {\n case 'postgres':\n return this.setupPostgresContainer(config as PostgresConfig);\n case 'mysql':\n return this.setupMySQLContainer(config as MySQLConfig);\n case 'mongodb':\n return this.setupMongoDBContainer(config as MongoDBConfig);\n case 'mssql':\n return this.setupMSSQLContainer(config as MSSQLConfig);\n case 'sqlite':\n // SQLite doesn't need a container (file-based)\n return null;\n default:\n throw new Error(`Unsupported database type: ${type}`);\n }\n }\n\n /**\n * Normalize database type aliases\n */\n private normalizeDatabaseType(\n type: DatabaseType\n ): 'postgres' | 'mysql' | 'mongodb' | 'mssql' | 'sqlite' {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 'postgres';\n case 'mysql':\n case 'mariadb':\n return 'mysql';\n case 'mongodb':\n case 'mongo':\n return 'mongodb';\n case 'mssql':\n return 'mssql';\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 'sqlite';\n default:\n throw new Error(`Unknown database type: ${type}`);\n }\n }\n\n /**\n * Setup PostgreSQL test container\n */\n async setupPostgresContainer(\n config: PostgresConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db',\n command = ['postgres', '-c', 'log_statement=all']\n } = config;\n\n const container = await new GenericContainer('postgres:latest')\n .withExposedPorts(5432)\n .withEnvironment({\n POSTGRES_USER: user,\n POSTGRES_PASSWORD: password,\n POSTGRES_DB: database\n })\n .withCommand(command)\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MySQL test container\n */\n async setupMySQLContainer(\n config: MySQLConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db',\n rootPassword = 'root_password'\n } = config;\n\n const container = await new GenericContainer('mysql:8')\n .withExposedPorts(3306)\n .withEnvironment({\n MYSQL_ROOT_PASSWORD: rootPassword,\n MYSQL_DATABASE: database,\n MYSQL_USER: user,\n MYSQL_PASSWORD: password\n })\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MongoDB test container\n */\n async setupMongoDBContainer(\n config: MongoDBConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db'\n } = config;\n\n const container = await new GenericContainer('mongo:latest')\n .withExposedPorts(27017)\n .withEnvironment({\n MONGO_INITDB_ROOT_USERNAME: user,\n MONGO_INITDB_ROOT_PASSWORD: password,\n MONGO_INITDB_DATABASE: database\n })\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Microsoft SQL Server test container\n */\n async setupMSSQLContainer(\n config: MSSQLConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n user = 'SA',\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n password = 'Test_Password123!',\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n database = 'test_db',\n saPassword = 'Test_Password123!'\n } = config;\n\n const container = await new GenericContainer(\n 'mcr.microsoft.com/mssql/server:2022-latest'\n )\n .withExposedPorts(1433)\n .withEnvironment({\n ACCEPT_EULA: 'Y',\n SA_PASSWORD: saPassword,\n MSSQL_PID: 'Developer'\n })\n .start();\n\n // Wait a bit for SQL Server to be ready\n await new Promise((resolve) => setTimeout(resolve, 3000));\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Redis test container\n */\n async setupRedisContainer(\n config: RedisConfig = {}\n ): Promise<StartedTestContainer> {\n const { command = ['redis-server', '--appendonly', 'yes'] } = config;\n\n const container = await new GenericContainer('redis:latest')\n .withExposedPorts(6379)\n .withCommand(command)\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Cleanup all containers\n */\n async cleanup(): Promise<void> {\n await Promise.all(\n this.containers.map((container) =>\n container.stop({ remove: true, removeVolumes: true }).catch(() => {\n // Ignore cleanup errors\n })\n )\n );\n this.containers = [];\n }\n}\n","import { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType } from './containers';\n\nexport interface TestEnvConfig {\n database: StartedTestContainer | null;\n databaseType: DatabaseType;\n redis?: StartedTestContainer;\n hmacSecret?: string;\n customVars?: Record<string, string>;\n}\n\n/**\n * Get the default port for a database type\n */\nfunction getDatabasePort(type: DatabaseType): number {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 5432;\n case 'mysql':\n case 'mariadb':\n return 3306;\n case 'mongodb':\n case 'mongo':\n return 27017;\n case 'mssql':\n return 1433;\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 0; // SQLite is file-based, no port\n default:\n return 5432;\n }\n}\n\n/**\n * Setup test environment variables for a blueprint test\n */\nexport function setupTestEnvironment(config: TestEnvConfig): void {\n const {\n database,\n databaseType,\n redis,\n hmacSecret = 'test-secret-key',\n customVars = {}\n } = config;\n\n const dbPort = getDatabasePort(databaseType);\n\n // Database environment variables\n process.env.DB_NAME = 'test_db';\n\n // SQLite databases are file-based, no container needed\n if (\n databaseType === 'sqlite' ||\n databaseType === 'better-sqlite' ||\n databaseType === 'libsql'\n ) {\n process.env.DB_PATH = ':memory:'; // In-memory SQLite for tests\n } else if (database) {\n process.env.DB_HOST = database.getHost();\n process.env.DB_USER = databaseType === 'mssql' ? 'SA' : 'test_user';\n process.env.DB_PASSWORD =\n databaseType === 'mssql' ? 'Test_Password123!' : 'test_password';\n process.env.DB_PORT = database.getMappedPort(dbPort).toString();\n }\n\n // Redis environment variables (if provided)\n if (redis) {\n process.env.REDIS_URL = `redis://${redis.getHost()}:${redis.getMappedPort(6379)}`;\n }\n\n // Standard test environment variables\n process.env.HMAC_SECRET_KEY = hmacSecret;\n process.env.JWKS_PUBLIC_KEY_URL =\n 'http://localhost:3000/.well-known/jwks.json';\n process.env.OTEL_SERVICE_NAME = 'test-service';\n process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://localhost:4318';\n process.env.HOST = 'localhost';\n process.env.PORT = '3000';\n process.env.NODE_ENV = 'test';\n process.env.VERSION = 'v1';\n process.env.DOCS_PATH = '/docs';\n process.env.OTEL_LEVEL = 'info';\n process.env.DOTENV_FILE_PATH = '.env.test';\n\n // Custom environment variables\n Object.entries(customVars).forEach(([key, value]) => {\n process.env[key] = value;\n });\n}\n","import { MikroORM, Options } from '@mikro-orm/core';\nimport Redis from 'ioredis';\nimport { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType } from './containers';\n\nexport interface MikroOrmTestConfig {\n /**\n * MikroORM config object (imported from mikro-orm.config)\n */\n mikroOrmConfig: Options;\n\n /**\n * Database type (postgres, mysql, mongodb, etc.)\n */\n databaseType: DatabaseType;\n\n /**\n * Whether to use migrations (true) or schema generation (false)\n * - true: IAM blueprints (uses getMigrator().up())\n * - false: Billing blueprints (uses getSchemaGenerator().createSchema())\n */\n useMigrations?: boolean;\n\n /**\n * Path to migrations directory (required if useMigrations is true)\n */\n migrationsPath?: string;\n\n /**\n * Database container instance (null for file-based databases like SQLite)\n */\n container: StartedTestContainer | null;\n}\n\n/**\n * Get the default port for a database type\n */\nfunction getDatabasePort(type: DatabaseType): number {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 5432;\n case 'mysql':\n case 'mariadb':\n return 3306;\n case 'mongodb':\n case 'mongo':\n return 27017;\n case 'mssql':\n return 1433;\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 0; // SQLite is file-based, no port\n default:\n return 5432;\n }\n}\n\n/**\n * Setup MikroORM for testing with proper schema/migrations\n */\nexport async function setupTestORM(\n config: MikroOrmTestConfig\n): Promise<MikroORM> {\n const {\n mikroOrmConfig,\n databaseType,\n useMigrations = false,\n container\n } = config;\n\n const dbPort = getDatabasePort(databaseType);\n\n // SQLite databases are file-based\n let ormConfig: Options = {};\n if (\n databaseType === 'sqlite' ||\n databaseType === 'better-sqlite' ||\n databaseType === 'libsql'\n ) {\n ormConfig = {\n ...mikroOrmConfig,\n dbName: ':memory:', // In-memory SQLite for tests\n debug: false,\n ...(useMigrations\n ? {\n migrations: {\n path: config.migrationsPath,\n glob: '!(*.d).{js,ts}',\n dropTables: true\n }\n }\n : {\n schemaGenerator: {\n createForeignKeyConstraints: false\n }\n })\n };\n } else if (container) {\n // Container-based databases\n ormConfig = {\n ...mikroOrmConfig,\n dbName: 'test_db',\n host: container.getHost(),\n user: databaseType === 'mssql' ? 'SA' : 'test_user',\n password:\n databaseType === 'mssql' ? 'Test_Password123!' : 'test_password',\n port: container.getMappedPort(dbPort),\n debug: false,\n ...(useMigrations\n ? {\n migrations: {\n path: config.migrationsPath,\n glob: '!(*.d).{js,ts}',\n dropTables: true\n }\n }\n : {\n schemaGenerator: {\n createForeignKeyConstraints: false\n }\n })\n };\n }\n\n const orm = await MikroORM.init(ormConfig);\n\n // Initialize database schema\n if (useMigrations) {\n await orm.getMigrator().up();\n } else {\n await orm.getSchemaGenerator().createSchema();\n }\n\n return orm;\n}\n\n/**\n * Clear all data from the test database\n */\nexport async function clearTestDatabase(\n orm: MikroORM,\n redis?: Redis\n): Promise<void> {\n // Clear Redis if provided\n if (redis) {\n await redis.flushall();\n }\n\n // Clear all database entities\n const em = orm.em.fork();\n const entities = Object.values(orm.getMetadata().getAll());\n\n // Delete in reverse order to avoid foreign key constraints\n for (const entity of entities.reverse()) {\n try {\n await em.nativeDelete(entity.class, {});\n } catch (error) {\n // Ignore \"table does not exist\" errors\n if (!(error as Error).message?.includes('does not exist')) {\n throw error;\n }\n }\n }\n\n await em.flush();\n}\n","import { MikroORM, Options } from '@mikro-orm/core';\nimport Redis from 'ioredis';\nimport { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType, TestContainerManager } from './containers';\nimport { clearTestDatabase, setupTestORM } from './database';\nimport { setupTestEnvironment } from './environment';\n\nexport interface BlueprintTestConfig {\n /**\n * Function that imports and returns the MikroORM config\n * This is called AFTER environment variables are set\n */\n getConfig: () => Promise<Options>;\n\n /**\n * Database type (postgres, mysql, mongodb, etc.)\n * @default 'postgres'\n */\n databaseType?: DatabaseType;\n\n /**\n * Whether to use migrations (true) or schema generation (false)\n */\n useMigrations?: boolean;\n\n /**\n * Path to migrations directory (required if useMigrations is true)\n */\n migrationsPath?: string;\n\n /**\n * Whether the blueprint needs Redis\n */\n needsRedis?: boolean;\n\n /**\n * Custom environment variables to set\n */\n customEnvVars?: Record<string, string>;\n\n /**\n * Custom setup hook called after containers and ORM are initialized\n */\n onSetup?: (setup: TestSetupResult) => Promise<void>;\n}\n\nexport interface TestSetupResult {\n container: StartedTestContainer | null;\n redisContainer?: StartedTestContainer;\n orm: MikroORM;\n redis?: Redis;\n}\n\n/**\n * Complete test harness for blueprint E2E testing\n *\n * Handles container setup, environment configuration, and database initialization\n *\n * @example\n * ```typescript\n * const harness = new BlueprintTestHarness({\n * mikroOrmConfigPath: '../mikro-orm.config',\n * useMigrations: false,\n * needsRedis: true,\n * customEnvVars: {\n * STRIPE_API_KEY: 'sk_test_...'\n * }\n * });\n *\n * const setup = await harness.setup();\n * // ... run tests\n * await harness.cleanup();\n * ```\n */\nexport class BlueprintTestHarness {\n private containers: TestContainerManager;\n private result?: TestSetupResult;\n\n constructor(private config: BlueprintTestConfig) {\n this.containers = new TestContainerManager();\n }\n\n /**\n * Setup all test infrastructure (containers, ORM, Redis)\n */\n async setup(): Promise<TestSetupResult> {\n const databaseType = this.config.databaseType || 'postgres';\n\n // Setup database container\n const container =\n await this.containers.setupDatabaseContainer(databaseType);\n\n // Setup Redis container if needed\n const redisContainer = this.config.needsRedis\n ? await this.containers.setupRedisContainer()\n : undefined;\n\n // Setup environment variables\n setupTestEnvironment({\n database: container,\n databaseType,\n redis: redisContainer,\n customVars: this.config.customEnvVars\n });\n\n // Get the config AFTER environment is set\n const mikroOrmConfig = await this.config.getConfig();\n\n // Setup ORM\n const orm = await setupTestORM({\n mikroOrmConfig,\n databaseType,\n useMigrations: this.config.useMigrations,\n migrationsPath: this.config.migrationsPath,\n container\n });\n\n // Setup Redis client if needed\n let redis: Redis | undefined;\n if (redisContainer) {\n redis = new Redis({\n host: redisContainer.getHost(),\n port: redisContainer.getMappedPort(6379),\n maxRetriesPerRequest: 3\n });\n await redis.ping();\n }\n\n this.result = { container, redisContainer, orm, redis };\n\n // Call custom setup hook\n if (this.config.onSetup) {\n await this.config.onSetup(this.result);\n }\n\n return this.result;\n }\n\n /**\n * Cleanup all test infrastructure\n */\n async cleanup(): Promise<void> {\n if (this.result?.redis) {\n await this.result.redis.quit();\n }\n if (this.result?.orm) {\n await this.result.orm.close();\n }\n await this.containers.cleanup();\n this.result = undefined;\n }\n\n /**\n * Clear all data from the database\n */\n async clearDatabase(): Promise<void> {\n if (this.result) {\n await clearTestDatabase(this.result.orm, this.result.redis);\n }\n }\n}\n","/**\n * Standard mock authentication tokens for testing\n */\nexport const TEST_TOKENS = {\n /**\n * Mock Bearer token for testing\n */\n AUTH: 'Bearer test-token',\n\n /**\n * Mock valid HMAC token for testing\n */\n HMAC: 'HMAC keyId=test-key ts=1234567890 nonce=test-nonce signature=test-signature',\n\n /**\n * Mock invalid HMAC token for testing authentication failures\n */\n HMAC_INVALID:\n 'HMAC keyId=invalid-key ts=1234567890 nonce=invalid-nonce signature=invalid-signature'\n} as const;\n"],"mappings":";AAAA,SAAS,wBAA8C;AA2DhD,IAAM,uBAAN,MAA2B;AAAA,EACxB,aAAqC,CAAC;AAAA;AAAA;AAAA;AAAA,EAK9C,MAAM,uBACJ,MACA,SAAyB,CAAC,GACY;AACtC,UAAM,iBAAiB,KAAK,sBAAsB,IAAI;AAEtD,YAAQ,gBAAgB;AAAA,MACtB,KAAK;AACH,eAAO,KAAK,uBAAuB,MAAwB;AAAA,MAC7D,KAAK;AACH,eAAO,KAAK,oBAAoB,MAAqB;AAAA,MACvD,KAAK;AACH,eAAO,KAAK,sBAAsB,MAAuB;AAAA,MAC3D,KAAK;AACH,eAAO,KAAK,oBAAoB,MAAqB;AAAA,MACvD,KAAK;AAEH,eAAO;AAAA,MACT;AACE,cAAM,IAAI,MAAM,8BAA8B,IAAI,EAAE;AAAA,IACxD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBACN,MACuD;AACvD,YAAQ,MAAM;AAAA,MACZ,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH,eAAO;AAAA,MACT;AACE,cAAM,IAAI,MAAM,0BAA0B,IAAI,EAAE;AAAA,IACpD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,uBACJ,SAAyB,CAAC,GACK;AAC/B,UAAM;AAAA,MACJ,OAAO;AAAA,MACP,WAAW;AAAA,MACX,WAAW;AAAA,MACX,UAAU,CAAC,YAAY,MAAM,mBAAmB;AAAA,IAClD,IAAI;AAEJ,UAAM,YAAY,MAAM,IAAI,iBAAiB,iBAAiB,EAC3D,iBAAiB,IAAI,EACrB,gBAAgB;AAAA,MACf,eAAe;AAAA,MACf,mBAAmB;AAAA,MACnB,aAAa;AAAA,IACf,CAAC,EACA,YAAY,OAAO,EACnB,MAAM;AAET,SAAK,WAAW,KAAK,SAAS;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBACJ,SAAsB,CAAC,GACQ;AAC/B,UAAM;AAAA,MACJ,OAAO;AAAA,MACP,WAAW;AAAA,MACX,WAAW;AAAA,MACX,eAAe;AAAA,IACjB,IAAI;AAEJ,UAAM,YAAY,MAAM,IAAI,iBAAiB,SAAS,EACnD,iBAAiB,IAAI,EACrB,gBAAgB;AAAA,MACf,qBAAqB;AAAA,MACrB,gBAAgB;AAAA,MAChB,YAAY;AAAA,MACZ,gBAAgB;AAAA,IAClB,CAAC,EACA,MAAM;AAET,SAAK,WAAW,KAAK,SAAS;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,sBACJ,SAAwB,CAAC,GACM;AAC/B,UAAM;AAAA,MACJ,OAAO;AAAA,MACP,WAAW;AAAA,MACX,WAAW;AAAA,IACb,IAAI;AAEJ,UAAM,YAAY,MAAM,IAAI,iBAAiB,cAAc,EACxD,iBAAiB,KAAK,EACtB,gBAAgB;AAAA,MACf,4BAA4B;AAAA,MAC5B,4BAA4B;AAAA,MAC5B,uBAAuB;AAAA,IACzB,CAAC,EACA,MAAM;AAET,SAAK,WAAW,KAAK,SAAS;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBACJ,SAAsB,CAAC,GACQ;AAC/B,UAAM;AAAA;AAAA,MAEJ,OAAO;AAAA;AAAA,MAEP,WAAW;AAAA;AAAA,MAEX,WAAW;AAAA,MACX,aAAa;AAAA,IACf,IAAI;AAEJ,UAAM,YAAY,MAAM,IAAI;AAAA,MAC1B;AAAA,IACF,EACG,iBAAiB,IAAI,EACrB,gBAAgB;AAAA,MACf,aAAa;AAAA,MACb,aAAa;AAAA,MACb,WAAW;AAAA,IACb,CAAC,EACA,MAAM;AAGT,UAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AAExD,SAAK,WAAW,KAAK,SAAS;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,oBACJ,SAAsB,CAAC,GACQ;AAC/B,UAAM,EAAE,UAAU,CAAC,gBAAgB,gBAAgB,KAAK,EAAE,IAAI;AAE9D,UAAM,YAAY,MAAM,IAAI,iBAAiB,cAAc,EACxD,iBAAiB,IAAI,EACrB,YAAY,OAAO,EACnB,MAAM;AAET,SAAK,WAAW,KAAK,SAAS;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,UAAM,QAAQ;AAAA,MACZ,KAAK,WAAW;AAAA,QAAI,CAAC,cACnB,UAAU,KAAK,EAAE,QAAQ,MAAM,eAAe,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,QAElE,CAAC;AAAA,MACH;AAAA,IACF;AACA,SAAK,aAAa,CAAC;AAAA,EACrB;AACF;;;ACpPA,SAAS,gBAAgB,MAA4B;AACnD,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAKO,SAAS,qBAAqB,QAA6B;AAChE,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,aAAa,CAAC;AAAA,EAChB,IAAI;AAEJ,QAAM,SAAS,gBAAgB,YAAY;AAG3C,UAAQ,IAAI,UAAU;AAGtB,MACE,iBAAiB,YACjB,iBAAiB,mBACjB,iBAAiB,UACjB;AACA,YAAQ,IAAI,UAAU;AAAA,EACxB,WAAW,UAAU;AACnB,YAAQ,IAAI,UAAU,SAAS,QAAQ;AACvC,YAAQ,IAAI,UAAU,iBAAiB,UAAU,OAAO;AACxD,YAAQ,IAAI,cACV,iBAAiB,UAAU,sBAAsB;AACnD,YAAQ,IAAI,UAAU,SAAS,cAAc,MAAM,EAAE,SAAS;AAAA,EAChE;AAGA,MAAI,OAAO;AACT,YAAQ,IAAI,YAAY,WAAW,MAAM,QAAQ,CAAC,IAAI,MAAM,cAAc,IAAI,CAAC;AAAA,EACjF;AAGA,UAAQ,IAAI,kBAAkB;AAC9B,UAAQ,IAAI,sBACV;AACF,UAAQ,IAAI,oBAAoB;AAChC,UAAQ,IAAI,8BAA8B;AAC1C,UAAQ,IAAI,OAAO;AACnB,UAAQ,IAAI,OAAO;AACnB,UAAQ,IAAI,WAAW;AACvB,UAAQ,IAAI,UAAU;AACtB,UAAQ,IAAI,YAAY;AACxB,UAAQ,IAAI,aAAa;AACzB,UAAQ,IAAI,mBAAmB;AAG/B,SAAO,QAAQ,UAAU,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACnD,YAAQ,IAAI,GAAG,IAAI;AAAA,EACrB,CAAC;AACH;;;AC3FA,SAAS,gBAAyB;AAqClC,SAASA,iBAAgB,MAA4B;AACnD,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAKA,eAAsB,aACpB,QACmB;AACnB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,EACF,IAAI;AAEJ,QAAM,SAASA,iBAAgB,YAAY;AAG3C,MAAI,YAAqB,CAAC;AAC1B,MACE,iBAAiB,YACjB,iBAAiB,mBACjB,iBAAiB,UACjB;AACA,gBAAY;AAAA,MACV,GAAG;AAAA,MACH,QAAQ;AAAA;AAAA,MACR,OAAO;AAAA,MACP,GAAI,gBACA;AAAA,QACE,YAAY;AAAA,UACV,MAAM,OAAO;AAAA,UACb,MAAM;AAAA,UACN,YAAY;AAAA,QACd;AAAA,MACF,IACA;AAAA,QACE,iBAAiB;AAAA,UACf,6BAA6B;AAAA,QAC/B;AAAA,MACF;AAAA,IACN;AAAA,EACF,WAAW,WAAW;AAEpB,gBAAY;AAAA,MACV,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,MAAM,UAAU,QAAQ;AAAA,MACxB,MAAM,iBAAiB,UAAU,OAAO;AAAA,MACxC,UACE,iBAAiB,UAAU,sBAAsB;AAAA,MACnD,MAAM,UAAU,cAAc,MAAM;AAAA,MACpC,OAAO;AAAA,MACP,GAAI,gBACA;AAAA,QACE,YAAY;AAAA,UACV,MAAM,OAAO;AAAA,UACb,MAAM;AAAA,UACN,YAAY;AAAA,QACd;AAAA,MACF,IACA;AAAA,QACE,iBAAiB;AAAA,UACf,6BAA6B;AAAA,QAC/B;AAAA,MACF;AAAA,IACN;AAAA,EACF;AAEA,QAAM,MAAM,MAAM,SAAS,KAAK,SAAS;AAGzC,MAAI,eAAe;AACjB,UAAM,IAAI,YAAY,EAAE,GAAG;AAAA,EAC7B,OAAO;AACL,UAAM,IAAI,mBAAmB,EAAE,aAAa;AAAA,EAC9C;AAEA,SAAO;AACT;AAKA,eAAsB,kBACpB,KACA,OACe;AAEf,MAAI,OAAO;AACT,UAAM,MAAM,SAAS;AAAA,EACvB;AAGA,QAAM,KAAK,IAAI,GAAG,KAAK;AACvB,QAAM,WAAW,OAAO,OAAO,IAAI,YAAY,EAAE,OAAO,CAAC;AAGzD,aAAW,UAAU,SAAS,QAAQ,GAAG;AACvC,QAAI;AACF,YAAM,GAAG,aAAa,OAAO,OAAO,CAAC,CAAC;AAAA,IACxC,SAAS,OAAO;AAEd,UAAI,CAAE,MAAgB,SAAS,SAAS,gBAAgB,GAAG;AACzD,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,GAAG,MAAM;AACjB;;;ACtKA,OAAO,WAAW;AAyEX,IAAM,uBAAN,MAA2B;AAAA,EAIhC,YAAoB,QAA6B;AAA7B;AAClB,SAAK,aAAa,IAAI,qBAAqB;AAAA,EAC7C;AAAA,EALQ;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EASR,MAAM,QAAkC;AACtC,UAAM,eAAe,KAAK,OAAO,gBAAgB;AAGjD,UAAM,YACJ,MAAM,KAAK,WAAW,uBAAuB,YAAY;AAG3D,UAAM,iBAAiB,KAAK,OAAO,aAC/B,MAAM,KAAK,WAAW,oBAAoB,IAC1C;AAGJ,yBAAqB;AAAA,MACnB,UAAU;AAAA,MACV;AAAA,MACA,OAAO;AAAA,MACP,YAAY,KAAK,OAAO;AAAA,IAC1B,CAAC;AAGD,UAAM,iBAAiB,MAAM,KAAK,OAAO,UAAU;AAGnD,UAAM,MAAM,MAAM,aAAa;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,eAAe,KAAK,OAAO;AAAA,MAC3B,gBAAgB,KAAK,OAAO;AAAA,MAC5B;AAAA,IACF,CAAC;AAGD,QAAI;AACJ,QAAI,gBAAgB;AAClB,cAAQ,IAAI,MAAM;AAAA,QAChB,MAAM,eAAe,QAAQ;AAAA,QAC7B,MAAM,eAAe,cAAc,IAAI;AAAA,QACvC,sBAAsB;AAAA,MACxB,CAAC;AACD,YAAM,MAAM,KAAK;AAAA,IACnB;AAEA,SAAK,SAAS,EAAE,WAAW,gBAAgB,KAAK,MAAM;AAGtD,QAAI,KAAK,OAAO,SAAS;AACvB,YAAM,KAAK,OAAO,QAAQ,KAAK,MAAM;AAAA,IACvC;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,QAAQ,OAAO;AACtB,YAAM,KAAK,OAAO,MAAM,KAAK;AAAA,IAC/B;AACA,QAAI,KAAK,QAAQ,KAAK;AACpB,YAAM,KAAK,OAAO,IAAI,MAAM;AAAA,IAC9B;AACA,UAAM,KAAK,WAAW,QAAQ;AAC9B,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAA+B;AACnC,QAAI,KAAK,QAAQ;AACf,YAAM,kBAAkB,KAAK,OAAO,KAAK,KAAK,OAAO,KAAK;AAAA,IAC5D;AAAA,EACF;AACF;;;AC7JO,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA,EAIzB,MAAM;AAAA;AAAA;AAAA;AAAA,EAKN,MAAM;AAAA;AAAA;AAAA;AAAA,EAKN,cACE;AACJ;","names":["getDatabasePort"]}
1
+ {"version":3,"sources":["../src/containers.ts","../src/environment.ts","../src/database.ts","../src/harness.ts","../src/tokens.ts"],"sourcesContent":["import { GenericContainer, StartedTestContainer } from 'testcontainers';\n\nexport type DatabaseType =\n | 'postgres'\n | 'postgresql'\n | 'mysql'\n | 'mariadb'\n | 'mongodb'\n | 'mongo'\n | 'mssql'\n | 'libsql'\n | 'sqlite'\n | 'better-sqlite';\n\nexport interface PostgresConfig {\n user?: string;\n password?: string;\n database?: string;\n command?: string[];\n}\n\nexport interface MySQLConfig {\n user?: string;\n password?: string;\n database?: string;\n rootPassword?: string;\n}\n\nexport interface MongoDBConfig {\n user?: string;\n password?: string;\n database?: string;\n}\n\nexport interface MSSQLConfig {\n user?: string;\n password?: string;\n database?: string;\n saPassword?: string;\n}\n\nexport interface SQLiteConfig {\n database?: string;\n}\n\nexport interface RedisConfig {\n command?: string[];\n}\n\nexport interface KafkaConfig {\n /** Kafka cluster ID */\n clusterId?: string;\n /** Number of partitions */\n numPartitions?: number;\n /** Replication factor */\n replicationFactor?: number;\n /** Additional environment variables */\n env?: Record<string, string>;\n}\n\nexport interface S3Config {\n /** MinIO root user (access key) */\n rootUser?: string;\n /** MinIO root password (secret key) */\n rootPassword?: string;\n /** Default bucket to create */\n defaultBucket?: string;\n /** Region */\n region?: string;\n}\n\nexport type DatabaseConfig =\n | PostgresConfig\n | MySQLConfig\n | MongoDBConfig\n | MSSQLConfig\n | SQLiteConfig;\n\n/**\n * Manages test containers (PostgreSQL, MySQL, MongoDB, Redis, etc.) for E2E testing\n */\nexport class TestContainerManager {\n private containers: StartedTestContainer[] = [];\n\n /**\n * Setup database container based on type\n */\n async setupDatabaseContainer(\n type: DatabaseType,\n config: DatabaseConfig = {}\n ): Promise<StartedTestContainer | null> {\n const normalizedType = this.normalizeDatabaseType(type);\n\n switch (normalizedType) {\n case 'postgres':\n return this.setupPostgresContainer(config as PostgresConfig);\n case 'mysql':\n return this.setupMySQLContainer(config as MySQLConfig);\n case 'mongodb':\n return this.setupMongoDBContainer(config as MongoDBConfig);\n case 'mssql':\n return this.setupMSSQLContainer(config as MSSQLConfig);\n case 'sqlite':\n // SQLite doesn't need a container (file-based)\n return null;\n default:\n throw new Error(`Unsupported database type: ${type}`);\n }\n }\n\n /**\n * Normalize database type aliases\n */\n private normalizeDatabaseType(\n type: DatabaseType\n ): 'postgres' | 'mysql' | 'mongodb' | 'mssql' | 'sqlite' {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 'postgres';\n case 'mysql':\n case 'mariadb':\n return 'mysql';\n case 'mongodb':\n case 'mongo':\n return 'mongodb';\n case 'mssql':\n return 'mssql';\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 'sqlite';\n default:\n throw new Error(`Unknown database type: ${type}`);\n }\n }\n\n /**\n * Setup PostgreSQL test container\n */\n async setupPostgresContainer(\n config: PostgresConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db',\n command = ['postgres', '-c', 'log_statement=all']\n } = config;\n\n const container = await new GenericContainer('postgres:latest')\n .withExposedPorts(5432)\n .withEnvironment({\n POSTGRES_USER: user,\n POSTGRES_PASSWORD: password,\n POSTGRES_DB: database\n })\n .withCommand(command)\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MySQL test container\n */\n async setupMySQLContainer(\n config: MySQLConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db',\n rootPassword = 'root_password'\n } = config;\n\n const container = await new GenericContainer('mysql:8')\n .withExposedPorts(3306)\n .withEnvironment({\n MYSQL_ROOT_PASSWORD: rootPassword,\n MYSQL_DATABASE: database,\n MYSQL_USER: user,\n MYSQL_PASSWORD: password\n })\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MongoDB test container\n */\n async setupMongoDBContainer(\n config: MongoDBConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n user = 'test_user',\n password = 'test_password',\n database = 'test_db'\n } = config;\n\n const container = await new GenericContainer('mongo:latest')\n .withExposedPorts(27017)\n .withEnvironment({\n MONGO_INITDB_ROOT_USERNAME: user,\n MONGO_INITDB_ROOT_PASSWORD: password,\n MONGO_INITDB_DATABASE: database\n })\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Microsoft SQL Server test container\n */\n async setupMSSQLContainer(\n config: MSSQLConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n user = 'SA',\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n password = 'Test_Password123!',\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n database = 'test_db',\n saPassword = 'Test_Password123!'\n } = config;\n\n const container = await new GenericContainer(\n 'mcr.microsoft.com/mssql/server:2022-latest'\n )\n .withExposedPorts(1433)\n .withEnvironment({\n ACCEPT_EULA: 'Y',\n SA_PASSWORD: saPassword,\n MSSQL_PID: 'Developer'\n })\n .start();\n\n // Wait a bit for SQL Server to be ready\n await new Promise((resolve) => setTimeout(resolve, 3000));\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Redis test container\n */\n async setupRedisContainer(\n config: RedisConfig = {}\n ): Promise<StartedTestContainer> {\n const { command = ['redis-server', '--appendonly', 'yes'] } = config;\n\n const container = await new GenericContainer('redis:latest')\n .withExposedPorts(6379)\n .withCommand(command)\n .start();\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup Kafka test container\n */\n async setupKafkaContainer(\n config: KafkaConfig = {}\n ): Promise<StartedTestContainer> {\n const {\n clusterId = 'test-cluster',\n numPartitions = 1,\n replicationFactor = 1,\n env = {}\n } = config;\n\n const container = await new GenericContainer('confluentinc/cp-kafka:latest')\n .withExposedPorts(9092, 9093)\n .withEnvironment({\n KAFKA_BROKER_ID: '1',\n KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:\n 'PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT',\n KAFKA_ADVERTISED_LISTENERS:\n 'PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092',\n KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: replicationFactor.toString(),\n KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: '1',\n KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: '1',\n KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: '0',\n KAFKA_NUM_PARTITIONS: numPartitions.toString(),\n KAFKA_CLUSTER_ID: clusterId,\n KAFKA_PROCESS_ROLES: 'broker,controller',\n KAFKA_NODE_ID: '1',\n KAFKA_CONTROLLER_QUORUM_VOTERS: '1@localhost:9093',\n KAFKA_LISTENERS:\n 'PLAINTEXT://0.0.0.0:29092,CONTROLLER://0.0.0.0:9093,PLAINTEXT_HOST://0.0.0.0:9092',\n KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT',\n KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER',\n KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs',\n ...env\n })\n .start();\n\n // Wait for Kafka to be ready\n await new Promise((resolve) => setTimeout(resolve, 5000));\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Setup MinIO (S3-compatible) test container\n */\n async setupS3Container(config: S3Config = {}): Promise<StartedTestContainer> {\n const {\n rootUser = 'minioadmin',\n rootPassword = 'minioadmin',\n defaultBucket = 'test-bucket',\n region = 'us-east-1'\n } = config;\n\n const container = await new GenericContainer('minio/minio:latest')\n .withExposedPorts(9000, 9001)\n .withEnvironment({\n MINIO_ROOT_USER: rootUser,\n MINIO_ROOT_PASSWORD: rootPassword,\n MINIO_REGION: region\n })\n .withCommand(['server', '/data', '--console-address', ':9001'])\n .start();\n\n // Wait for MinIO to be ready\n await new Promise((resolve) => setTimeout(resolve, 2000));\n\n // Create default bucket if specified\n if (defaultBucket) {\n try {\n // Using MinIO client command via exec\n await container.exec([\n 'mc',\n 'alias',\n 'set',\n 'local',\n 'http://localhost:9000',\n rootUser,\n rootPassword\n ]);\n await container.exec(['mc', 'mb', `local/${defaultBucket}`]);\n } catch (error) {\n // Bucket creation might fail, but container is still usable\n console.warn('Could not create default bucket:', error);\n }\n }\n\n this.containers.push(container);\n return container;\n }\n\n /**\n * Cleanup all containers\n */\n async cleanup(): Promise<void> {\n await Promise.all(\n this.containers.map((container) =>\n container.stop({ remove: true, removeVolumes: true }).catch(() => {\n // Ignore cleanup errors\n })\n )\n );\n this.containers = [];\n }\n}\n","import { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType } from './containers';\n\nexport interface TestEnvConfig {\n database: StartedTestContainer | null;\n databaseType?: DatabaseType;\n redis?: StartedTestContainer;\n kafka?: StartedTestContainer;\n s3?: StartedTestContainer;\n hmacSecret?: string;\n customVars?: Record<string, string>;\n}\n\n/**\n * Get the default port for a database type\n */\nfunction getDatabasePort(type: DatabaseType): number {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 5432;\n case 'mysql':\n case 'mariadb':\n return 3306;\n case 'mongodb':\n case 'mongo':\n return 27017;\n case 'mssql':\n return 1433;\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 0; // SQLite is file-based, no port\n default:\n return 5432;\n }\n}\n\n/**\n * Setup test environment variables for a blueprint test\n */\nexport function setupTestEnvironment(config: TestEnvConfig): void {\n const {\n database,\n databaseType,\n redis,\n kafka,\n s3,\n hmacSecret = 'test-secret-key',\n customVars = {}\n } = config;\n\n // Only set database environment variables if database is configured\n if (databaseType) {\n const dbPort = getDatabasePort(databaseType);\n\n // Database environment variables\n process.env.DB_NAME = 'test_db';\n\n // SQLite databases are file-based, no container needed\n if (\n databaseType === 'sqlite' ||\n databaseType === 'better-sqlite' ||\n databaseType === 'libsql'\n ) {\n process.env.DB_PATH = ':memory:'; // In-memory SQLite for tests\n } else if (database) {\n process.env.DB_HOST = database.getHost();\n process.env.DB_USER = databaseType === 'mssql' ? 'SA' : 'test_user';\n process.env.DB_PASSWORD =\n databaseType === 'mssql' ? 'Test_Password123!' : 'test_password';\n process.env.DB_PORT = database.getMappedPort(dbPort).toString();\n }\n }\n\n // Redis environment variables (if provided)\n if (redis) {\n process.env.REDIS_URL = `redis://${redis.getHost()}:${redis.getMappedPort(6379)}`;\n process.env.REDIS_HOST = redis.getHost();\n process.env.REDIS_PORT = redis.getMappedPort(6379).toString();\n }\n\n // Kafka environment variables (if provided)\n if (kafka) {\n const kafkaBroker = `${kafka.getHost()}:${kafka.getMappedPort(9092)}`;\n process.env.KAFKA_BROKERS = kafkaBroker;\n process.env.KAFKA_CLIENT_ID = 'test-client';\n process.env.KAFKA_GROUP_ID = 'test-group';\n }\n\n // S3/MinIO environment variables (if provided)\n if (s3) {\n process.env.S3_ENDPOINT = `http://${s3.getHost()}:${s3.getMappedPort(9000)}`;\n process.env.S3_ACCESS_KEY_ID = 'minioadmin';\n process.env.S3_SECRET_ACCESS_KEY = 'minioadmin';\n process.env.S3_REGION = 'us-east-1';\n process.env.S3_BUCKET = 'test-bucket';\n process.env.S3_FORCE_PATH_STYLE = 'true'; // Required for MinIO\n }\n\n // Standard test environment variables\n process.env.HMAC_SECRET_KEY = hmacSecret;\n process.env.JWKS_PUBLIC_KEY_URL =\n 'http://localhost:3000/.well-known/jwks.json';\n process.env.OTEL_SERVICE_NAME = 'test-service';\n process.env.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://localhost:4318';\n process.env.HOST = 'localhost';\n process.env.PORT = '3000';\n process.env.NODE_ENV = 'test';\n process.env.VERSION = 'v1';\n process.env.DOCS_PATH = '/docs';\n process.env.OTEL_LEVEL = 'info';\n process.env.DOTENV_FILE_PATH = '.env.test';\n\n // Custom environment variables\n Object.entries(customVars).forEach(([key, value]) => {\n process.env[key] = value;\n });\n}\n","import { MikroORM, Options } from '@mikro-orm/core';\nimport Redis from 'ioredis';\nimport { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType } from './containers';\n\nexport interface MikroOrmTestConfig {\n /**\n * MikroORM config object (imported from mikro-orm.config)\n */\n mikroOrmConfig: Options;\n\n /**\n * Database type (postgres, mysql, mongodb, etc.)\n */\n databaseType: DatabaseType;\n\n /**\n * Whether to use migrations (true) or schema generation (false)\n * - true: IAM blueprints (uses getMigrator().up())\n * - false: Billing blueprints (uses getSchemaGenerator().createSchema())\n */\n useMigrations?: boolean;\n\n /**\n * Path to migrations directory (required if useMigrations is true)\n */\n migrationsPath?: string;\n\n /**\n * Database container instance (null for file-based databases like SQLite)\n */\n container: StartedTestContainer | null;\n}\n\n/**\n * Get the default port for a database type\n */\nfunction getDatabasePort(type: DatabaseType): number {\n switch (type) {\n case 'postgres':\n case 'postgresql':\n return 5432;\n case 'mysql':\n case 'mariadb':\n return 3306;\n case 'mongodb':\n case 'mongo':\n return 27017;\n case 'mssql':\n return 1433;\n case 'sqlite':\n case 'better-sqlite':\n case 'libsql':\n return 0; // SQLite is file-based, no port\n default:\n return 5432;\n }\n}\n\n/**\n * Setup MikroORM for testing with proper schema/migrations\n */\nexport async function setupTestORM(\n config: MikroOrmTestConfig\n): Promise<MikroORM> {\n const {\n mikroOrmConfig,\n databaseType,\n useMigrations = false,\n container\n } = config;\n\n const dbPort = getDatabasePort(databaseType);\n\n // SQLite databases are file-based\n let ormConfig: Options = {};\n if (\n databaseType === 'sqlite' ||\n databaseType === 'better-sqlite' ||\n databaseType === 'libsql'\n ) {\n ormConfig = {\n ...mikroOrmConfig,\n dbName: ':memory:', // In-memory SQLite for tests\n debug: false,\n ...(useMigrations\n ? {\n migrations: {\n path: config.migrationsPath,\n glob: '!(*.d).{js,ts}',\n dropTables: true\n }\n }\n : {\n schemaGenerator: {\n createForeignKeyConstraints: false\n }\n })\n };\n } else if (container) {\n // Container-based databases\n ormConfig = {\n ...mikroOrmConfig,\n dbName: 'test_db',\n host: container.getHost(),\n user: databaseType === 'mssql' ? 'SA' : 'test_user',\n password:\n databaseType === 'mssql' ? 'Test_Password123!' : 'test_password',\n port: container.getMappedPort(dbPort),\n debug: false,\n ...(useMigrations\n ? {\n migrations: {\n path: config.migrationsPath,\n glob: '!(*.d).{js,ts}',\n dropTables: true\n }\n }\n : {\n schemaGenerator: {\n createForeignKeyConstraints: false\n }\n })\n };\n }\n\n const orm = await MikroORM.init(ormConfig);\n\n // Initialize database schema\n if (useMigrations) {\n await orm.getMigrator().up();\n } else {\n await orm.getSchemaGenerator().createSchema();\n }\n\n return orm;\n}\n\n/**\n * Clear all data from the test database and/or cache\n */\nexport async function clearTestDatabase(\n orm?: MikroORM,\n redis?: Redis\n): Promise<void> {\n // Clear Redis if provided\n if (redis) {\n await redis.flushall();\n }\n\n // Clear all database entities (if ORM is provided)\n if (orm) {\n const em = orm.em.fork();\n const entities = Object.values(orm.getMetadata().getAll());\n\n // Delete in reverse order to avoid foreign key constraints\n for (const entity of entities.reverse()) {\n try {\n await em.nativeDelete(entity.class, {});\n } catch (error) {\n // Ignore \"table does not exist\" errors\n if (!(error as Error).message?.includes('does not exist')) {\n throw error;\n }\n }\n }\n\n await em.flush();\n }\n}\n","import { MikroORM, Options } from '@mikro-orm/core';\nimport Redis from 'ioredis';\nimport { StartedTestContainer } from 'testcontainers';\nimport { DatabaseType, TestContainerManager } from './containers';\nimport { clearTestDatabase, setupTestORM } from './database';\nimport { setupTestEnvironment } from './environment';\n\nexport interface BlueprintTestConfig {\n /**\n * Function that imports and returns the MikroORM config\n * This is called AFTER environment variables are set\n * Optional - if not provided, no database will be set up\n */\n getConfig?: () => Promise<Options>;\n\n /**\n * Database type (postgres, mysql, mongodb, etc.)\n * Optional - if not provided, no database will be set up\n */\n databaseType?: DatabaseType;\n\n /**\n * Whether to use migrations (true) or schema generation (false)\n */\n useMigrations?: boolean;\n\n /**\n * Path to migrations directory (required if useMigrations is true)\n */\n migrationsPath?: string;\n\n /**\n * Whether the blueprint needs Redis\n */\n needsRedis?: boolean;\n\n /**\n * Whether the blueprint needs Kafka\n */\n needsKafka?: boolean;\n\n /**\n * Whether the blueprint needs S3 (MinIO)\n */\n needsS3?: boolean;\n\n /**\n * S3 bucket name to create (default: 'test-bucket')\n */\n s3Bucket?: string;\n\n /**\n * Custom environment variables to set\n */\n customEnvVars?: Record<string, string>;\n\n /**\n * Custom setup hook called after containers and ORM are initialized\n */\n onSetup?: (setup: TestSetupResult) => Promise<void>;\n}\n\nexport interface TestSetupResult {\n container: StartedTestContainer | null;\n redisContainer?: StartedTestContainer;\n kafkaContainer?: StartedTestContainer;\n s3Container?: StartedTestContainer;\n orm?: MikroORM;\n redis?: Redis;\n}\n\n/**\n * Complete test harness for blueprint E2E testing\n *\n * Handles container setup, environment configuration, and database initialization\n *\n * @example Database with ORM\n * ```typescript\n * const harness = new BlueprintTestHarness({\n * getConfig: async () => {\n * const { default: config } = await import('../mikro-orm.config');\n * return config;\n * },\n * databaseType: 'postgres',\n * useMigrations: false,\n * needsRedis: true,\n * customEnvVars: {\n * STRIPE_API_KEY: 'sk_test_...'\n * }\n * });\n *\n * const setup = await harness.setup();\n * // ... run tests with setup.orm and setup.redis\n * await harness.cleanup();\n * ```\n *\n * @example Cache-only (no database)\n * ```typescript\n * const harness = new BlueprintTestHarness({\n * needsRedis: true,\n * customEnvVars: {\n * API_KEY: 'test_key'\n * }\n * });\n *\n * const setup = await harness.setup();\n * // ... run tests with setup.redis only (setup.orm is undefined)\n * await harness.cleanup();\n * ```\n *\n * @example With Kafka and S3\n * ```typescript\n * const harness = new BlueprintTestHarness({\n * getConfig: async () => {\n * const { default: config } = await import('../mikro-orm.config');\n * return config;\n * },\n * databaseType: 'postgres',\n * needsKafka: true,\n * needsS3: true,\n * s3Bucket: 'my-test-bucket',\n * customEnvVars: {\n * API_KEY: 'test_key'\n * }\n * });\n *\n * const setup = await harness.setup();\n * // Access containers via setup.kafkaContainer and setup.s3Container\n * // Kafka broker: process.env.KAFKA_BROKERS\n * // S3 endpoint: process.env.S3_ENDPOINT\n * await harness.cleanup();\n * ```\n */\nexport class BlueprintTestHarness {\n private containers: TestContainerManager;\n private result?: TestSetupResult;\n\n constructor(private config: BlueprintTestConfig) {\n this.containers = new TestContainerManager();\n }\n\n /**\n * Setup all test infrastructure (containers, ORM, Redis, Kafka, S3)\n */\n async setup(): Promise<TestSetupResult> {\n // Setup database container only if database is needed\n let container: StartedTestContainer | null = null;\n let orm: MikroORM | undefined;\n let redisContainer: StartedTestContainer | undefined;\n let kafkaContainer: StartedTestContainer | undefined;\n let s3Container: StartedTestContainer | undefined;\n\n // Setup Redis container if needed (for both database and cache-only modes)\n if (this.config.needsRedis) {\n redisContainer = await this.containers.setupRedisContainer();\n }\n\n // Setup Kafka container if needed\n if (this.config.needsKafka) {\n kafkaContainer = await this.containers.setupKafkaContainer();\n }\n\n // Setup S3 container if needed\n if (this.config.needsS3) {\n s3Container = await this.containers.setupS3Container({\n defaultBucket: this.config.s3Bucket\n });\n }\n\n if (this.config.databaseType && this.config.getConfig) {\n const databaseType = this.config.databaseType;\n\n // Setup database container\n container = await this.containers.setupDatabaseContainer(databaseType);\n\n // Setup environment variables\n setupTestEnvironment({\n database: container,\n databaseType,\n redis: redisContainer,\n kafka: kafkaContainer,\n s3: s3Container,\n customVars: this.config.customEnvVars\n });\n\n // Get the config AFTER environment is set\n const mikroOrmConfig = await this.config.getConfig();\n\n // Setup ORM\n orm = await setupTestORM({\n mikroOrmConfig,\n databaseType,\n useMigrations: this.config.useMigrations,\n migrationsPath: this.config.migrationsPath,\n container\n });\n } else {\n // Cache-only mode: no database\n setupTestEnvironment({\n database: null,\n databaseType: undefined,\n redis: redisContainer,\n kafka: kafkaContainer,\n s3: s3Container,\n customVars: this.config.customEnvVars\n });\n }\n\n // Setup Redis client if needed\n let redis: Redis | undefined;\n if (redisContainer) {\n redis = new Redis({\n host: redisContainer.getHost(),\n port: redisContainer.getMappedPort(6379),\n maxRetriesPerRequest: 3\n });\n await redis.ping();\n }\n\n this.result = {\n container,\n redisContainer,\n kafkaContainer,\n s3Container,\n orm,\n redis\n };\n\n // Call custom setup hook\n if (this.config.onSetup) {\n await this.config.onSetup(this.result);\n }\n\n return this.result;\n }\n\n /**\n * Cleanup all test infrastructure\n */\n async cleanup(): Promise<void> {\n if (this.result?.redis) {\n await this.result.redis.quit();\n }\n if (this.result?.orm) {\n await this.result.orm.close();\n }\n await this.containers.cleanup();\n this.result = undefined;\n }\n\n /**\n * Clear all data from the database and/or cache\n */\n async clearDatabase(): Promise<void> {\n if (this.result) {\n await clearTestDatabase(this.result.orm, this.result.redis);\n }\n }\n}\n","/**\n * Standard mock authentication tokens for testing\n */\nexport const TEST_TOKENS = {\n /**\n * Mock Bearer token for testing\n */\n AUTH: 'Bearer test-token',\n\n /**\n * Mock valid HMAC token for testing\n */\n HMAC: 'HMAC keyId=test-key ts=1234567890 nonce=test-nonce signature=test-signature',\n\n /**\n * Mock invalid HMAC token for testing authentication failures\n */\n HMAC_INVALID:\n 'HMAC keyId=invalid-key ts=1234567890 nonce=invalid-nonce signature=invalid-signature'\n} as const;\n"],"mappings":"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","names":["getDatabasePort"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forklaunch/testing",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Testing utilities for forklaunch-js blueprints including testcontainer management",
5
5
  "homepage": "https://github.com/forklaunch/forklaunch-js#readme",
6
6
  "bugs": {