@amqp-contract/testing 0.5.0 → 0.7.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/dist/extension.mjs +14 -2
- package/dist/extension.mjs.map +1 -1
- package/package.json +14 -3
package/dist/extension.mjs
CHANGED
|
@@ -42,14 +42,16 @@ const it = it$1.extend({
|
|
|
42
42
|
await use(publishMessage);
|
|
43
43
|
},
|
|
44
44
|
initConsumer: async ({ amqpChannel }, use) => {
|
|
45
|
+
const consumerTags = [];
|
|
45
46
|
async function initConsumer(exchange, routingKey) {
|
|
46
47
|
const queue = randomUUID();
|
|
47
48
|
await amqpChannel.assertQueue(queue);
|
|
48
49
|
await amqpChannel.bindQueue(queue, exchange, routingKey);
|
|
49
50
|
const messages = [];
|
|
50
|
-
await amqpChannel.consume(queue, (msg) => {
|
|
51
|
+
const consumer = await amqpChannel.consume(queue, (msg) => {
|
|
51
52
|
if (msg) messages.push(msg);
|
|
52
53
|
}, { noAck: true });
|
|
54
|
+
consumerTags.push(consumer.consumerTag);
|
|
53
55
|
return async (options = {}) => {
|
|
54
56
|
const { nbEvents = 1, timeout = 5e3 } = options;
|
|
55
57
|
await vi.waitFor(() => {
|
|
@@ -58,7 +60,17 @@ const it = it$1.extend({
|
|
|
58
60
|
return messages.splice(0, nbEvents);
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
|
-
|
|
63
|
+
try {
|
|
64
|
+
await use(initConsumer);
|
|
65
|
+
} finally {
|
|
66
|
+
await Promise.all(consumerTags.map(async (consumerTag) => {
|
|
67
|
+
try {
|
|
68
|
+
await amqpChannel.cancel(consumerTag);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error("Failed to cancel AMQP consumer during fixture cleanup:", error);
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
62
74
|
}
|
|
63
75
|
});
|
|
64
76
|
async function createVhost() {
|
package/dist/extension.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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":"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"}
|
|
1
|
+
{"version":3,"file":"extension.mjs","names":["vitestIt","consumerTags: string[]","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 const consumerTags: string[] = [];\n\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 const consumer = await amqpChannel.consume(\n queue,\n (msg) => {\n if (msg) {\n messages.push(msg);\n }\n },\n { noAck: true },\n );\n\n consumerTags.push(consumer.consumerTag);\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\n try {\n await use(initConsumer);\n } finally {\n // Cancel all consumers before fixture cleanup (which deletes the vhost)\n await Promise.all(\n consumerTags.map(async (consumerTag) => {\n try {\n await amqpChannel.cancel(consumerTag);\n } catch (error) {\n // Swallow cancellation errors during cleanup\n // eslint-disable-next-line no-console\n console.error(\"Failed to cancel AMQP consumer during fixture cleanup:\", error);\n }\n }),\n );\n }\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":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amqp-contract/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Testing utilities for AMQP contracts with testcontainers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"amqp",
|
|
7
|
+
"contract",
|
|
8
|
+
"rabbitmq",
|
|
9
|
+
"typescript",
|
|
10
|
+
"testing",
|
|
11
|
+
"testcontainers",
|
|
12
|
+
"nodejs",
|
|
13
|
+
"type-safe",
|
|
14
|
+
"vitest"
|
|
15
|
+
],
|
|
5
16
|
"license": "MIT",
|
|
6
17
|
"type": "module",
|
|
7
18
|
"exports": {
|
|
@@ -19,11 +30,11 @@
|
|
|
19
30
|
],
|
|
20
31
|
"dependencies": {
|
|
21
32
|
"amqplib": "0.10.9",
|
|
22
|
-
"testcontainers": "11.
|
|
33
|
+
"testcontainers": "11.11.0"
|
|
23
34
|
},
|
|
24
35
|
"devDependencies": {
|
|
25
36
|
"@types/amqplib": "0.10.8",
|
|
26
|
-
"tsdown": "0.18.
|
|
37
|
+
"tsdown": "0.18.4",
|
|
27
38
|
"typedoc": "0.28.15",
|
|
28
39
|
"typedoc-plugin-markdown": "4.9.0",
|
|
29
40
|
"typescript": "5.9.3",
|