@amqp-contract/testing 0.4.0 โ†’ 0.6.0

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/README.md CHANGED
@@ -77,18 +77,29 @@ describe("Order Processing", () => {
77
77
 
78
78
  The extension provides:
79
79
 
80
+ - `vhost`: A unique virtual host created for test isolation (automatically cleaned up after the test)
81
+ - `amqpConnectionUrl`: A connection URL pre-configured with the test vhost
80
82
  - `amqpConnection`: An established connection to the RabbitMQ testcontainer
81
- - Automatic connection cleanup after each test
83
+ - `amqpChannel`: A channel for AMQP operations
84
+ - `publishMessage`: Helper function for publishing test messages
85
+ - `initConsumer`: Helper function for setting up test consumers
86
+ - Automatic connection and vhost cleanup after each test
82
87
 
83
88
  ## What It Does
84
89
 
85
- The global setup:
90
+ **Global Setup:**
86
91
 
87
92
  1. Starts a RabbitMQ container with management plugin
88
93
  2. Waits for RabbitMQ to be healthy
89
94
  3. Provides connection details to your tests
90
95
  4. Cleans up the container after tests complete
91
96
 
97
+ **Test Extension:**
98
+
99
+ 1. Creates a unique virtual host (vhost) for each test to ensure complete isolation
100
+ 2. Provides pre-configured connections and helpers for interacting with RabbitMQ
101
+ 3. Automatically cleans up the vhost and connections after each test completes
102
+
92
103
  ## Container Details
93
104
 
94
105
  - **Image**: `rabbitmq:3-management-alpine`
@@ -1,8 +1,18 @@
1
+ import amqpLib from "amqplib";
1
2
  import * as vitest0 from "vitest";
2
3
 
3
4
  //#region src/extension.d.ts
5
+
4
6
  declare const it: vitest0.TestAPI<{
7
+ vhost: string;
5
8
  amqpConnectionUrl: string;
9
+ amqpConnection: amqpLib.ChannelModel;
10
+ amqpChannel: amqpLib.Channel;
11
+ publishMessage: (exchange: string, routingKey: string, content: unknown) => void;
12
+ initConsumer: (exchange: string, routingKey: string) => Promise<(options?: {
13
+ nbEvents?: number;
14
+ timeout?: number;
15
+ }) => Promise<amqpLib.ConsumeMessage[]>>;
6
16
  }>;
7
17
  //#endregion
8
18
  export { it };
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"sourcesContent":[],"mappings":";;;cAEa,IAQX,OAAA,CARa"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"sourcesContent":[],"mappings":";;;;;AAee,cAAF,EAAE,EASD,OAAA,CATC,OAAA,CAAA;;;;;;0DASR;;;QACoD,QAAQ,OAAA,CAAQ"}
@@ -1,9 +1,94 @@
1
- import { inject, it as it$1 } from "vitest";
1
+ import amqpLib from "amqplib";
2
+ import { inject, it as it$1, vi } from "vitest";
3
+ import { randomUUID } from "node:crypto";
2
4
 
3
5
  //#region src/extension.ts
4
- const it = it$1.extend({ amqpConnectionUrl: async ({}, use) => {
5
- await use(`amqp://guest:guest@${inject("__TESTCONTAINERS_RABBITMQ_IP__")}:${inject("__TESTCONTAINERS_RABBITMQ_PORT_5672__")}`);
6
- } });
6
+ /**
7
+ * Vitest extension module for AMQP testing utilities
8
+ *
9
+ * This module provides a Vitest test extension that adds AMQP-specific fixtures
10
+ * to your tests. Each test gets an isolated virtual host (vhost) with pre-configured
11
+ * connections, channels, and helper functions for publishing and consuming messages.
12
+ *
13
+ * @module extension
14
+ * @packageDocumentation
15
+ */
16
+ const it = it$1.extend({
17
+ vhost: async ({}, use) => {
18
+ const vhost = await createVhost();
19
+ try {
20
+ await use(vhost);
21
+ } finally {
22
+ await deleteVhost(vhost);
23
+ }
24
+ },
25
+ amqpConnectionUrl: async ({ vhost }, use) => {
26
+ await use(`amqp://guest:guest@${inject("__TESTCONTAINERS_RABBITMQ_IP__")}:${inject("__TESTCONTAINERS_RABBITMQ_PORT_5672__")}/${vhost}`);
27
+ },
28
+ amqpConnection: async ({ amqpConnectionUrl }, use) => {
29
+ const connection = await amqpLib.connect(amqpConnectionUrl);
30
+ await use(connection);
31
+ await connection.close();
32
+ },
33
+ amqpChannel: async ({ amqpConnection }, use) => {
34
+ const channel = await amqpConnection.createChannel();
35
+ await use(channel);
36
+ await channel.close();
37
+ },
38
+ publishMessage: async ({ amqpChannel }, use) => {
39
+ function publishMessage(exchange, routingKey, content) {
40
+ if (!amqpChannel.publish(exchange, routingKey, Buffer.from(JSON.stringify(content)))) throw new Error(`Failed to publish message to exchange "${exchange}" with routing key "${routingKey}"`);
41
+ }
42
+ await use(publishMessage);
43
+ },
44
+ initConsumer: async ({ amqpChannel }, use) => {
45
+ async function initConsumer(exchange, routingKey) {
46
+ const queue = randomUUID();
47
+ await amqpChannel.assertQueue(queue);
48
+ await amqpChannel.bindQueue(queue, exchange, routingKey);
49
+ const messages = [];
50
+ await amqpChannel.consume(queue, (msg) => {
51
+ if (msg) messages.push(msg);
52
+ }, { noAck: true });
53
+ return async (options = {}) => {
54
+ const { nbEvents = 1, timeout = 5e3 } = options;
55
+ await vi.waitFor(() => {
56
+ if (messages.length < nbEvents) throw new Error(`Expected ${nbEvents} message(s) but only received ${messages.length}`);
57
+ }, { timeout });
58
+ return messages.splice(0, nbEvents);
59
+ };
60
+ }
61
+ await use(initConsumer);
62
+ }
63
+ });
64
+ async function createVhost() {
65
+ const namespace = randomUUID();
66
+ const username = inject("__TESTCONTAINERS_RABBITMQ_USERNAME__");
67
+ const password = inject("__TESTCONTAINERS_RABBITMQ_PASSWORD__");
68
+ const vhostResponse = await fetch(`http://${inject("__TESTCONTAINERS_RABBITMQ_IP__")}:${inject("__TESTCONTAINERS_RABBITMQ_PORT_15672__")}/api/vhosts/${encodeURIComponent(namespace)}`, {
69
+ method: "PUT",
70
+ headers: { Authorization: `Basic ${btoa(`${username}:${password}`)}` }
71
+ });
72
+ if (vhostResponse.status !== 201) {
73
+ const responseBody = await vhostResponse.text().catch(() => "");
74
+ const errorMessage = responseBody ? `Failed to create vhost '${namespace}': ${vhostResponse.status} - ${responseBody}` : `Failed to create vhost '${namespace}': ${vhostResponse.status}`;
75
+ throw new Error(errorMessage, { cause: vhostResponse });
76
+ }
77
+ return namespace;
78
+ }
79
+ async function deleteVhost(vhost) {
80
+ const username = inject("__TESTCONTAINERS_RABBITMQ_USERNAME__");
81
+ const password = inject("__TESTCONTAINERS_RABBITMQ_PASSWORD__");
82
+ const vhostResponse = await fetch(`http://${inject("__TESTCONTAINERS_RABBITMQ_IP__")}:${inject("__TESTCONTAINERS_RABBITMQ_PORT_15672__")}/api/vhosts/${encodeURIComponent(vhost)}`, {
83
+ method: "DELETE",
84
+ headers: { Authorization: `Basic ${btoa(`${username}:${password}`)}` }
85
+ });
86
+ if (vhostResponse.status !== 204 && vhostResponse.status !== 404) {
87
+ const responseBody = await vhostResponse.text().catch(() => "");
88
+ const errorMessage = responseBody ? `Failed to delete vhost '${vhost}': ${vhostResponse.status} - ${responseBody}` : `Failed to delete vhost '${vhost}': ${vhostResponse.status}`;
89
+ throw new Error(errorMessage, { cause: vhostResponse });
90
+ }
91
+ }
7
92
 
8
93
  //#endregion
9
94
  export { it };
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":["vitestIt"],"sources":["../src/extension.ts"],"sourcesContent":["import { inject, it as vitestIt } from \"vitest\";\n\nexport const it = vitestIt.extend<{\n amqpConnectionUrl: string;\n}>({\n // oxlint-disable-next-line no-empty-pattern\n amqpConnectionUrl: async ({}, use) => {\n const url = `amqp://guest:guest@${inject(\"__TESTCONTAINERS_RABBITMQ_IP__\")}:${inject(\"__TESTCONTAINERS_RABBITMQ_PORT_5672__\")}`;\n await use(url);\n },\n});\n"],"mappings":";;;AAEA,MAAa,KAAKA,KAAS,OAExB,EAED,mBAAmB,OAAO,IAAI,QAAQ;AAEpC,OAAM,IADM,sBAAsB,OAAO,iCAAiC,CAAC,GAAG,OAAO,wCAAwC,GAC/G;GAEjB,CAAC"}
1
+ {"version":3,"file":"extension.mjs","names":["vitestIt","messages: amqpLib.ConsumeMessage[]"],"sources":["../src/extension.ts"],"sourcesContent":["/**\n * Vitest extension module for AMQP testing utilities\n *\n * This module provides a Vitest test extension that adds AMQP-specific fixtures\n * to your tests. Each test gets an isolated virtual host (vhost) with pre-configured\n * connections, channels, and helper functions for publishing and consuming messages.\n *\n * @module extension\n * @packageDocumentation\n */\n\nimport amqpLib, { type Channel, type ChannelModel } from \"amqplib\";\nimport { inject, vi, it as vitestIt } from \"vitest\";\nimport { randomUUID } from \"node:crypto\";\n\nexport const it = vitestIt.extend<{\n vhost: string;\n amqpConnectionUrl: string;\n amqpConnection: ChannelModel;\n amqpChannel: Channel;\n publishMessage: (exchange: string, routingKey: string, content: unknown) => void;\n initConsumer: (\n exchange: string,\n routingKey: string,\n ) => Promise<\n (options?: { nbEvents?: number; timeout?: number }) => Promise<amqpLib.ConsumeMessage[]>\n >;\n}>({\n /**\n * Test fixture that provides an isolated RabbitMQ virtual host (vhost) for the test.\n *\n * Creates a new vhost with a random UUID name for test isolation. The vhost is automatically\n * created before the test runs using the RabbitMQ Management API.\n *\n * @example\n * ```typescript\n * it('should use isolated vhost', async ({ vhost }) => {\n * console.log(`Test running in vhost: ${vhost}`);\n * });\n * ```\n */\n // oxlint-disable-next-line no-empty-pattern\n vhost: async ({}, use) => {\n const vhost = await createVhost();\n try {\n await use(vhost);\n } finally {\n await deleteVhost(vhost);\n }\n },\n /**\n * Test fixture that provides the AMQP connection URL for the test container.\n *\n * Constructs a connection URL using the test container's IP and port, along with\n * the isolated vhost. The URL follows the format: `amqp://guest:guest@host:port/vhost`.\n *\n * @example\n * ```typescript\n * it('should connect with URL', async ({ amqpConnectionUrl }) => {\n * console.log(`Connecting to: ${amqpConnectionUrl}`);\n * });\n * ```\n */\n amqpConnectionUrl: async ({ vhost }, use) => {\n const url = `amqp://guest:guest@${inject(\"__TESTCONTAINERS_RABBITMQ_IP__\")}:${inject(\"__TESTCONTAINERS_RABBITMQ_PORT_5672__\")}/${vhost}`;\n await use(url);\n },\n /**\n * Test fixture that provides an active AMQP connection to RabbitMQ.\n *\n * Establishes a connection using the provided connection URL and automatically closes\n * it after the test completes. This fixture is useful for tests that need direct\n * access to the connection object (e.g., to create multiple channels).\n *\n * @example\n * ```typescript\n * it('should use connection', async ({ amqpConnection }) => {\n * const channel = await amqpConnection.createChannel();\n * // ... use channel\n * });\n * ```\n */\n amqpConnection: async ({ amqpConnectionUrl }, use) => {\n const connection = await amqpLib.connect(amqpConnectionUrl);\n await use(connection);\n await connection.close();\n },\n /**\n * Test fixture that provides an AMQP channel for interacting with RabbitMQ.\n *\n * Creates a channel from the active connection and automatically closes it after\n * the test completes. The channel is used for declaring exchanges, queues, bindings,\n * and publishing/consuming messages.\n *\n * @example\n * ```typescript\n * it('should use channel', async ({ amqpChannel }) => {\n * await amqpChannel.assertExchange('test-exchange', 'topic');\n * await amqpChannel.assertQueue('test-queue');\n * });\n * ```\n */\n amqpChannel: async ({ amqpConnection }, use) => {\n const channel = await amqpConnection.createChannel();\n await use(channel);\n await channel.close();\n },\n /**\n * Test fixture for publishing messages to an AMQP exchange.\n *\n * Provides a helper function to publish messages directly to an exchange during tests.\n * The message content is automatically serialized to JSON and converted to a Buffer.\n *\n * @param exchange - The name of the exchange to publish to\n * @param routingKey - The routing key for message routing\n * @param content - The message payload (will be JSON serialized)\n * @throws Error if the message cannot be published (e.g., write buffer is full)\n *\n * @example\n * ```typescript\n * it('should publish message', async ({ publishMessage }) => {\n * publishMessage('my-exchange', 'routing.key', { data: 'test' });\n * });\n * ```\n */\n publishMessage: async ({ amqpChannel }, use) => {\n function publishMessage(exchange: string, routingKey: string, content: unknown): void {\n const success = amqpChannel.publish(\n exchange,\n routingKey,\n Buffer.from(JSON.stringify(content)),\n );\n if (!success) {\n throw new Error(\n `Failed to publish message to exchange \"${exchange}\" with routing key \"${routingKey}\"`,\n );\n }\n }\n await use(publishMessage);\n },\n /**\n * Test fixture for initializing a message consumer on an AMQP queue.\n *\n * Creates a temporary queue, binds it to the specified exchange with the given routing key,\n * and returns a function to collect messages from that queue. The queue is automatically\n * created with a random UUID name to avoid conflicts between tests.\n *\n * The returned function uses `vi.waitFor()` with a configurable timeout to wait for messages.\n * If the expected number of messages is not received within the timeout period, the Promise\n * will reject with a timeout error, preventing tests from hanging indefinitely.\n *\n * @param exchange - The name of the exchange to bind the queue to\n * @param routingKey - The routing key pattern for message filtering\n * @returns A function that accepts optional configuration ({ nbEvents?, timeout? }) and returns a Promise that resolves to an array of ConsumeMessage objects\n *\n * @example\n * ```typescript\n * it('should consume messages', async ({ initConsumer, publishMessage }) => {\n * const waitForMessages = await initConsumer('my-exchange', 'routing.key');\n * publishMessage('my-exchange', 'routing.key', { data: 'test' });\n * // With defaults (1 message, 5000ms timeout)\n * const messages = await waitForMessages();\n * expect(messages).toHaveLength(1);\n *\n * // With custom options\n * publishMessage('my-exchange', 'routing.key', { data: 'test2' });\n * publishMessage('my-exchange', 'routing.key', { data: 'test3' });\n * const messages2 = await waitForMessages({ nbEvents: 2, timeout: 10000 });\n * expect(messages2).toHaveLength(2);\n * });\n * ```\n */\n initConsumer: async ({ amqpChannel }, use) => {\n async function initConsumer(\n exchange: string,\n routingKey: string,\n ): Promise<\n (options?: { nbEvents?: number; timeout?: number }) => Promise<amqpLib.ConsumeMessage[]>\n > {\n const queue = randomUUID();\n\n await amqpChannel.assertQueue(queue);\n await amqpChannel.bindQueue(queue, exchange, routingKey);\n\n const messages: amqpLib.ConsumeMessage[] = [];\n await amqpChannel.consume(\n queue,\n (msg) => {\n if (msg) {\n messages.push(msg);\n }\n },\n { noAck: true },\n );\n\n return async (options = {}) => {\n const { nbEvents = 1, timeout = 5000 } = options;\n await vi.waitFor(\n () => {\n if (messages.length < nbEvents) {\n throw new Error(\n `Expected ${nbEvents} message(s) but only received ${messages.length}`,\n );\n }\n },\n { timeout },\n );\n return messages.splice(0, nbEvents);\n };\n }\n await use(initConsumer);\n },\n});\n\nasync function createVhost() {\n const namespace = randomUUID();\n\n const username = inject(\"__TESTCONTAINERS_RABBITMQ_USERNAME__\");\n const password = inject(\"__TESTCONTAINERS_RABBITMQ_PASSWORD__\");\n\n const vhostResponse = await fetch(\n `http://${inject(\"__TESTCONTAINERS_RABBITMQ_IP__\")}:${inject(\"__TESTCONTAINERS_RABBITMQ_PORT_15672__\")}/api/vhosts/${encodeURIComponent(namespace)}`,\n {\n method: \"PUT\",\n headers: {\n Authorization: `Basic ${btoa(`${username}:${password}`)}`,\n },\n },\n );\n\n if (vhostResponse.status !== 201) {\n const responseBody = await vhostResponse.text().catch(() => \"\");\n const errorMessage = responseBody\n ? `Failed to create vhost '${namespace}': ${vhostResponse.status} - ${responseBody}`\n : `Failed to create vhost '${namespace}': ${vhostResponse.status}`;\n throw new Error(errorMessage, {\n cause: vhostResponse,\n });\n }\n\n return namespace;\n}\n\nasync function deleteVhost(vhost: string) {\n const username = inject(\"__TESTCONTAINERS_RABBITMQ_USERNAME__\");\n const password = inject(\"__TESTCONTAINERS_RABBITMQ_PASSWORD__\");\n\n const vhostResponse = await fetch(\n `http://${inject(\"__TESTCONTAINERS_RABBITMQ_IP__\")}:${inject(\"__TESTCONTAINERS_RABBITMQ_PORT_15672__\")}/api/vhosts/${encodeURIComponent(vhost)}`,\n {\n method: \"DELETE\",\n headers: {\n Authorization: `Basic ${btoa(`${username}:${password}`)}`,\n },\n },\n );\n\n // 204 = successfully deleted, 404 = already deleted or doesn't exist\n if (vhostResponse.status !== 204 && vhostResponse.status !== 404) {\n const responseBody = await vhostResponse.text().catch(() => \"\");\n const errorMessage = responseBody\n ? `Failed to delete vhost '${vhost}': ${vhostResponse.status} - ${responseBody}`\n : `Failed to delete vhost '${vhost}': ${vhostResponse.status}`;\n throw new Error(errorMessage, {\n cause: vhostResponse,\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAa,KAAKA,KAAS,OAYxB;CAeD,OAAO,OAAO,IAAI,QAAQ;EACxB,MAAM,QAAQ,MAAM,aAAa;AACjC,MAAI;AACF,SAAM,IAAI,MAAM;YACR;AACR,SAAM,YAAY,MAAM;;;CAgB5B,mBAAmB,OAAO,EAAE,SAAS,QAAQ;AAE3C,QAAM,IADM,sBAAsB,OAAO,iCAAiC,CAAC,GAAG,OAAO,wCAAwC,CAAC,GAAG,QACnH;;CAiBhB,gBAAgB,OAAO,EAAE,qBAAqB,QAAQ;EACpD,MAAM,aAAa,MAAM,QAAQ,QAAQ,kBAAkB;AAC3D,QAAM,IAAI,WAAW;AACrB,QAAM,WAAW,OAAO;;CAiB1B,aAAa,OAAO,EAAE,kBAAkB,QAAQ;EAC9C,MAAM,UAAU,MAAM,eAAe,eAAe;AACpD,QAAM,IAAI,QAAQ;AAClB,QAAM,QAAQ,OAAO;;CAoBvB,gBAAgB,OAAO,EAAE,eAAe,QAAQ;EAC9C,SAAS,eAAe,UAAkB,YAAoB,SAAwB;AAMpF,OAAI,CALY,YAAY,QAC1B,UACA,YACA,OAAO,KAAK,KAAK,UAAU,QAAQ,CAAC,CACrC,CAEC,OAAM,IAAI,MACR,0CAA0C,SAAS,sBAAsB,WAAW,GACrF;;AAGL,QAAM,IAAI,eAAe;;CAkC3B,cAAc,OAAO,EAAE,eAAe,QAAQ;EAC5C,eAAe,aACb,UACA,YAGA;GACA,MAAM,QAAQ,YAAY;AAE1B,SAAM,YAAY,YAAY,MAAM;AACpC,SAAM,YAAY,UAAU,OAAO,UAAU,WAAW;GAExD,MAAMC,WAAqC,EAAE;AAC7C,SAAM,YAAY,QAChB,QACC,QAAQ;AACP,QAAI,IACF,UAAS,KAAK,IAAI;MAGtB,EAAE,OAAO,MAAM,CAChB;AAED,UAAO,OAAO,UAAU,EAAE,KAAK;IAC7B,MAAM,EAAE,WAAW,GAAG,UAAU,QAAS;AACzC,UAAM,GAAG,cACD;AACJ,SAAI,SAAS,SAAS,SACpB,OAAM,IAAI,MACR,YAAY,SAAS,gCAAgC,SAAS,SAC/D;OAGL,EAAE,SAAS,CACZ;AACD,WAAO,SAAS,OAAO,GAAG,SAAS;;;AAGvC,QAAM,IAAI,aAAa;;CAE1B,CAAC;AAEF,eAAe,cAAc;CAC3B,MAAM,YAAY,YAAY;CAE9B,MAAM,WAAW,OAAO,uCAAuC;CAC/D,MAAM,WAAW,OAAO,uCAAuC;CAE/D,MAAM,gBAAgB,MAAM,MAC1B,UAAU,OAAO,iCAAiC,CAAC,GAAG,OAAO,yCAAyC,CAAC,cAAc,mBAAmB,UAAU,IAClJ;EACE,QAAQ;EACR,SAAS,EACP,eAAe,SAAS,KAAK,GAAG,SAAS,GAAG,WAAW,IACxD;EACF,CACF;AAED,KAAI,cAAc,WAAW,KAAK;EAChC,MAAM,eAAe,MAAM,cAAc,MAAM,CAAC,YAAY,GAAG;EAC/D,MAAM,eAAe,eACjB,2BAA2B,UAAU,KAAK,cAAc,OAAO,KAAK,iBACpE,2BAA2B,UAAU,KAAK,cAAc;AAC5D,QAAM,IAAI,MAAM,cAAc,EAC5B,OAAO,eACR,CAAC;;AAGJ,QAAO;;AAGT,eAAe,YAAY,OAAe;CACxC,MAAM,WAAW,OAAO,uCAAuC;CAC/D,MAAM,WAAW,OAAO,uCAAuC;CAE/D,MAAM,gBAAgB,MAAM,MAC1B,UAAU,OAAO,iCAAiC,CAAC,GAAG,OAAO,yCAAyC,CAAC,cAAc,mBAAmB,MAAM,IAC9I;EACE,QAAQ;EACR,SAAS,EACP,eAAe,SAAS,KAAK,GAAG,SAAS,GAAG,WAAW,IACxD;EACF,CACF;AAGD,KAAI,cAAc,WAAW,OAAO,cAAc,WAAW,KAAK;EAChE,MAAM,eAAe,MAAM,cAAc,MAAM,CAAC,YAAY,GAAG;EAC/D,MAAM,eAAe,eACjB,2BAA2B,MAAM,KAAK,cAAc,OAAO,KAAK,iBAChE,2BAA2B,MAAM,KAAK,cAAc;AACxD,QAAM,IAAI,MAAM,cAAc,EAC5B,OAAO,eACR,CAAC"}
@@ -1,16 +1,38 @@
1
1
  import { TestProject } from "vitest/node";
2
2
 
3
3
  //#region src/global-setup.d.ts
4
+
4
5
  declare module "vitest" {
5
6
  interface ProvidedContext {
6
7
  __TESTCONTAINERS_RABBITMQ_IP__: string;
7
8
  __TESTCONTAINERS_RABBITMQ_PORT_5672__: number;
8
9
  __TESTCONTAINERS_RABBITMQ_PORT_15672__: number;
10
+ __TESTCONTAINERS_RABBITMQ_USERNAME__: string;
11
+ __TESTCONTAINERS_RABBITMQ_PASSWORD__: string;
9
12
  }
10
13
  }
11
14
  /**
12
15
  * Setup function for Vitest globalSetup
13
- * Starts a RabbitMQ container before all tests
16
+ *
17
+ * Starts a RabbitMQ container before all tests and provides connection details
18
+ * to tests via Vitest's provide API. The container is automatically stopped
19
+ * and cleaned up after all tests complete.
20
+ *
21
+ * This function should be configured in your `vitest.config.ts`:
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { defineConfig } from "vitest/config";
26
+ *
27
+ * export default defineConfig({
28
+ * test: {
29
+ * globalSetup: ["@amqp-contract/testing/global-setup"],
30
+ * },
31
+ * });
32
+ * ```
33
+ *
34
+ * @param provide - Function to provide context values to tests
35
+ * @returns Cleanup function that stops the RabbitMQ container
14
36
  */
15
37
  declare function setup({
16
38
  provide
@@ -1 +1 @@
1
- {"version":3,"file":"global-setup.d.mts","names":[],"sources":["../src/global-setup.ts"],"sourcesContent":[],"mappings":";;;;;IAC+C,8BAAA,EAAA,MAAA;IAAA,qCAAA,EAAA,MAAA;IAAA,sCAAA,EAAA,MAAA;EAAA;;AAAA;;;;AAea,iBAA9B,KAAA,CAA8B;EAAA;AAAA,CAAA,EAAX,WAAW,CAAA,EAAA,OAAA,CAAA,GAAA,GAAA,OAAA,CAAA,IAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"global-setup.d.mts","names":[],"sources":["../src/global-setup.ts"],"sourcesContent":[],"mappings":";;;;eAgD4D,QAAA,CAAA;EAAA,UAAA,eAAA,CAAA;IAAA,8BAAA,EAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA9B,KAAA;;GAAmB,cAAW,cAAA"}
@@ -2,8 +2,37 @@ import { GenericContainer, Wait } from "testcontainers";
2
2
 
3
3
  //#region src/global-setup.ts
4
4
  /**
5
+ * Global setup module for starting RabbitMQ test containers
6
+ *
7
+ * This module provides a Vitest globalSetup function that automatically starts
8
+ * a RabbitMQ container with the management plugin before tests run, and stops
9
+ * it after all tests complete.
10
+ *
11
+ * @module global-setup
12
+ * @packageDocumentation
13
+ */
14
+ /**
5
15
  * Setup function for Vitest globalSetup
6
- * Starts a RabbitMQ container before all tests
16
+ *
17
+ * Starts a RabbitMQ container before all tests and provides connection details
18
+ * to tests via Vitest's provide API. The container is automatically stopped
19
+ * and cleaned up after all tests complete.
20
+ *
21
+ * This function should be configured in your `vitest.config.ts`:
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { defineConfig } from "vitest/config";
26
+ *
27
+ * export default defineConfig({
28
+ * test: {
29
+ * globalSetup: ["@amqp-contract/testing/global-setup"],
30
+ * },
31
+ * });
32
+ * ```
33
+ *
34
+ * @param provide - Function to provide context values to tests
35
+ * @returns Cleanup function that stops the RabbitMQ container
7
36
  */
8
37
  async function setup({ provide }) {
9
38
  console.log("๐Ÿณ Starting RabbitMQ test environment...");
@@ -26,9 +55,13 @@ async function setup({ provide }) {
26
55
  const __TESTCONTAINERS_RABBITMQ_IP__ = rabbitmqContainer.getHost();
27
56
  const __TESTCONTAINERS_RABBITMQ_PORT_5672__ = rabbitmqContainer.getMappedPort(5672);
28
57
  const __TESTCONTAINERS_RABBITMQ_PORT_15672__ = rabbitmqContainer.getMappedPort(15672);
58
+ const __TESTCONTAINERS_RABBITMQ_USERNAME__ = "guest";
59
+ const __TESTCONTAINERS_RABBITMQ_PASSWORD__ = "guest";
29
60
  provide("__TESTCONTAINERS_RABBITMQ_IP__", __TESTCONTAINERS_RABBITMQ_IP__);
30
61
  provide("__TESTCONTAINERS_RABBITMQ_PORT_5672__", __TESTCONTAINERS_RABBITMQ_PORT_5672__);
31
62
  provide("__TESTCONTAINERS_RABBITMQ_PORT_15672__", __TESTCONTAINERS_RABBITMQ_PORT_15672__);
63
+ provide("__TESTCONTAINERS_RABBITMQ_USERNAME__", __TESTCONTAINERS_RABBITMQ_USERNAME__);
64
+ provide("__TESTCONTAINERS_RABBITMQ_PASSWORD__", __TESTCONTAINERS_RABBITMQ_PASSWORD__);
32
65
  console.log(`๐Ÿš€ RabbitMQ test environment is ready at ${__TESTCONTAINERS_RABBITMQ_IP__}:${__TESTCONTAINERS_RABBITMQ_PORT_5672__}`);
33
66
  console.log(`๐Ÿ“Š RabbitMQ management console available at http://${__TESTCONTAINERS_RABBITMQ_IP__}:${__TESTCONTAINERS_RABBITMQ_PORT_15672__}`);
34
67
  return async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"global-setup.mjs","names":[],"sources":["../src/global-setup.ts"],"sourcesContent":["import { GenericContainer, Wait } from \"testcontainers\";\nimport type { TestProject } from \"vitest/node\";\n\ndeclare module \"vitest\" {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- Module augmentation requires interface for declaration merging\n export interface ProvidedContext {\n __TESTCONTAINERS_RABBITMQ_IP__: string;\n __TESTCONTAINERS_RABBITMQ_PORT_5672__: number;\n __TESTCONTAINERS_RABBITMQ_PORT_15672__: number;\n }\n}\n\n/**\n * Setup function for Vitest globalSetup\n * Starts a RabbitMQ container before all tests\n */\nexport default async function setup({ provide }: TestProject) {\n console.log(\"๐Ÿณ Starting RabbitMQ test environment...\");\n\n // Start RabbitMQ container with management plugin\n const rabbitmqContainer = await new GenericContainer(\"rabbitmq:4.2.1-management-alpine\")\n .withExposedPorts(5672, 15672)\n .withEnvironment({\n RABBITMQ_DEFAULT_USER: \"guest\",\n RABBITMQ_DEFAULT_PASS: \"guest\",\n })\n .withHealthCheck({\n test: [\"CMD\", \"rabbitmq-diagnostics\", \"-q\", \"check_running\"],\n interval: 1_000,\n retries: 30,\n startPeriod: 3_000,\n timeout: 5_000,\n })\n .withWaitStrategy(Wait.forHealthCheck())\n .start();\n\n console.log(\"โœ… RabbitMQ container started\");\n\n const __TESTCONTAINERS_RABBITMQ_IP__ = rabbitmqContainer.getHost();\n const __TESTCONTAINERS_RABBITMQ_PORT_5672__ = rabbitmqContainer.getMappedPort(5672);\n const __TESTCONTAINERS_RABBITMQ_PORT_15672__ = rabbitmqContainer.getMappedPort(15672);\n\n // Provide context values with type assertions to work around TypeScript limitations\n provide(\"__TESTCONTAINERS_RABBITMQ_IP__\", __TESTCONTAINERS_RABBITMQ_IP__);\n provide(\"__TESTCONTAINERS_RABBITMQ_PORT_5672__\", __TESTCONTAINERS_RABBITMQ_PORT_5672__);\n provide(\"__TESTCONTAINERS_RABBITMQ_PORT_15672__\", __TESTCONTAINERS_RABBITMQ_PORT_15672__);\n\n console.log(\n `๐Ÿš€ RabbitMQ test environment is ready at ${__TESTCONTAINERS_RABBITMQ_IP__}:${__TESTCONTAINERS_RABBITMQ_PORT_5672__}`,\n );\n console.log(\n `๐Ÿ“Š RabbitMQ management console available at http://${__TESTCONTAINERS_RABBITMQ_IP__}:${__TESTCONTAINERS_RABBITMQ_PORT_15672__}`,\n );\n\n // Return cleanup function\n return async () => {\n console.log(\"๐Ÿงน Stopping RabbitMQ test environment...\");\n await rabbitmqContainer.stop();\n console.log(\"โœ… RabbitMQ test environment stopped\");\n };\n}\n"],"mappings":";;;;;;;AAgBA,eAA8B,MAAM,EAAE,WAAwB;AAC5D,SAAQ,IAAI,2CAA2C;CAGvD,MAAM,oBAAoB,MAAM,IAAI,iBAAiB,mCAAmC,CACrF,iBAAiB,MAAM,MAAM,CAC7B,gBAAgB;EACf,uBAAuB;EACvB,uBAAuB;EACxB,CAAC,CACD,gBAAgB;EACf,MAAM;GAAC;GAAO;GAAwB;GAAM;GAAgB;EAC5D,UAAU;EACV,SAAS;EACT,aAAa;EACb,SAAS;EACV,CAAC,CACD,iBAAiB,KAAK,gBAAgB,CAAC,CACvC,OAAO;AAEV,SAAQ,IAAI,+BAA+B;CAE3C,MAAM,iCAAiC,kBAAkB,SAAS;CAClE,MAAM,wCAAwC,kBAAkB,cAAc,KAAK;CACnF,MAAM,yCAAyC,kBAAkB,cAAc,MAAM;AAGrF,SAAQ,kCAAkC,+BAA+B;AACzE,SAAQ,yCAAyC,sCAAsC;AACvF,SAAQ,0CAA0C,uCAAuC;AAEzF,SAAQ,IACN,4CAA4C,+BAA+B,GAAG,wCAC/E;AACD,SAAQ,IACN,sDAAsD,+BAA+B,GAAG,yCACzF;AAGD,QAAO,YAAY;AACjB,UAAQ,IAAI,2CAA2C;AACvD,QAAM,kBAAkB,MAAM;AAC9B,UAAQ,IAAI,sCAAsC"}
1
+ {"version":3,"file":"global-setup.mjs","names":[],"sources":["../src/global-setup.ts"],"sourcesContent":["/**\n * Global setup module for starting RabbitMQ test containers\n *\n * This module provides a Vitest globalSetup function that automatically starts\n * a RabbitMQ container with the management plugin before tests run, and stops\n * it after all tests complete.\n *\n * @module global-setup\n * @packageDocumentation\n */\n\nimport { GenericContainer, Wait } from \"testcontainers\";\nimport type { TestProject } from \"vitest/node\";\n\ndeclare module \"vitest\" {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- Module augmentation requires interface for declaration merging\n export interface ProvidedContext {\n __TESTCONTAINERS_RABBITMQ_IP__: string;\n __TESTCONTAINERS_RABBITMQ_PORT_5672__: number;\n __TESTCONTAINERS_RABBITMQ_PORT_15672__: number;\n __TESTCONTAINERS_RABBITMQ_USERNAME__: string;\n __TESTCONTAINERS_RABBITMQ_PASSWORD__: string;\n }\n}\n\n/**\n * Setup function for Vitest globalSetup\n *\n * Starts a RabbitMQ container before all tests and provides connection details\n * to tests via Vitest's provide API. The container is automatically stopped\n * and cleaned up after all tests complete.\n *\n * This function should be configured in your `vitest.config.ts`:\n *\n * @example\n * ```typescript\n * import { defineConfig } from \"vitest/config\";\n *\n * export default defineConfig({\n * test: {\n * globalSetup: [\"@amqp-contract/testing/global-setup\"],\n * },\n * });\n * ```\n *\n * @param provide - Function to provide context values to tests\n * @returns Cleanup function that stops the RabbitMQ container\n */\nexport default async function setup({ provide }: TestProject) {\n console.log(\"๐Ÿณ Starting RabbitMQ test environment...\");\n\n // Start RabbitMQ container with management plugin\n const rabbitmqContainer = await new GenericContainer(\"rabbitmq:4.2.1-management-alpine\")\n .withExposedPorts(5672, 15672)\n .withEnvironment({\n RABBITMQ_DEFAULT_USER: \"guest\",\n RABBITMQ_DEFAULT_PASS: \"guest\",\n })\n .withHealthCheck({\n test: [\"CMD\", \"rabbitmq-diagnostics\", \"-q\", \"check_running\"],\n interval: 1_000,\n retries: 30,\n startPeriod: 3_000,\n timeout: 5_000,\n })\n .withWaitStrategy(Wait.forHealthCheck())\n .start();\n\n console.log(\"โœ… RabbitMQ container started\");\n\n const __TESTCONTAINERS_RABBITMQ_IP__ = rabbitmqContainer.getHost();\n const __TESTCONTAINERS_RABBITMQ_PORT_5672__ = rabbitmqContainer.getMappedPort(5672);\n const __TESTCONTAINERS_RABBITMQ_PORT_15672__ = rabbitmqContainer.getMappedPort(15672);\n const __TESTCONTAINERS_RABBITMQ_USERNAME__ = \"guest\";\n const __TESTCONTAINERS_RABBITMQ_PASSWORD__ = \"guest\";\n\n // Provide context values with type assertions to work around TypeScript limitations\n provide(\"__TESTCONTAINERS_RABBITMQ_IP__\", __TESTCONTAINERS_RABBITMQ_IP__);\n provide(\"__TESTCONTAINERS_RABBITMQ_PORT_5672__\", __TESTCONTAINERS_RABBITMQ_PORT_5672__);\n provide(\"__TESTCONTAINERS_RABBITMQ_PORT_15672__\", __TESTCONTAINERS_RABBITMQ_PORT_15672__);\n provide(\"__TESTCONTAINERS_RABBITMQ_USERNAME__\", __TESTCONTAINERS_RABBITMQ_USERNAME__);\n provide(\"__TESTCONTAINERS_RABBITMQ_PASSWORD__\", __TESTCONTAINERS_RABBITMQ_PASSWORD__);\n\n console.log(\n `๐Ÿš€ RabbitMQ test environment is ready at ${__TESTCONTAINERS_RABBITMQ_IP__}:${__TESTCONTAINERS_RABBITMQ_PORT_5672__}`,\n );\n console.log(\n `๐Ÿ“Š RabbitMQ management console available at http://${__TESTCONTAINERS_RABBITMQ_IP__}:${__TESTCONTAINERS_RABBITMQ_PORT_15672__}`,\n );\n\n // Return cleanup function\n return async () => {\n console.log(\"๐Ÿงน Stopping RabbitMQ test environment...\");\n await rabbitmqContainer.stop();\n console.log(\"โœ… RabbitMQ test environment stopped\");\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,eAA8B,MAAM,EAAE,WAAwB;AAC5D,SAAQ,IAAI,2CAA2C;CAGvD,MAAM,oBAAoB,MAAM,IAAI,iBAAiB,mCAAmC,CACrF,iBAAiB,MAAM,MAAM,CAC7B,gBAAgB;EACf,uBAAuB;EACvB,uBAAuB;EACxB,CAAC,CACD,gBAAgB;EACf,MAAM;GAAC;GAAO;GAAwB;GAAM;GAAgB;EAC5D,UAAU;EACV,SAAS;EACT,aAAa;EACb,SAAS;EACV,CAAC,CACD,iBAAiB,KAAK,gBAAgB,CAAC,CACvC,OAAO;AAEV,SAAQ,IAAI,+BAA+B;CAE3C,MAAM,iCAAiC,kBAAkB,SAAS;CAClE,MAAM,wCAAwC,kBAAkB,cAAc,KAAK;CACnF,MAAM,yCAAyC,kBAAkB,cAAc,MAAM;CACrF,MAAM,uCAAuC;CAC7C,MAAM,uCAAuC;AAG7C,SAAQ,kCAAkC,+BAA+B;AACzE,SAAQ,yCAAyC,sCAAsC;AACvF,SAAQ,0CAA0C,uCAAuC;AACzF,SAAQ,wCAAwC,qCAAqC;AACrF,SAAQ,wCAAwC,qCAAqC;AAErF,SAAQ,IACN,4CAA4C,+BAA+B,GAAG,wCAC/E;AACD,SAAQ,IACN,sDAAsD,+BAA+B,GAAG,yCACzF;AAGD,QAAO,YAAY;AACjB,UAAQ,IAAI,2CAA2C;AACvD,QAAM,kBAAkB,MAAM;AAC9B,UAAQ,IAAI,sCAAsC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amqp-contract/testing",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Testing utilities for AMQP contracts with testcontainers",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -18,19 +18,25 @@
18
18
  "dist"
19
19
  ],
20
20
  "dependencies": {
21
+ "amqplib": "0.10.9",
21
22
  "testcontainers": "11.10.0"
22
23
  },
23
24
  "devDependencies": {
24
- "tsdown": "0.18.2",
25
+ "@types/amqplib": "0.10.8",
26
+ "tsdown": "0.18.3",
27
+ "typedoc": "0.28.15",
28
+ "typedoc-plugin-markdown": "4.9.0",
25
29
  "typescript": "5.9.3",
26
30
  "vitest": "4.0.16",
27
- "@amqp-contract/tsconfig": "0.0.0"
31
+ "@amqp-contract/tsconfig": "0.0.0",
32
+ "@amqp-contract/typedoc": "0.0.1"
28
33
  },
29
34
  "peerDependencies": {
30
35
  "vitest": "^4"
31
36
  },
32
37
  "scripts": {
33
38
  "build": "tsdown src/global-setup.ts src/extension.ts --format esm --dts --clean",
39
+ "build:docs": "typedoc",
34
40
  "typecheck": "tsc --noEmit"
35
41
  }
36
42
  }