Skip to content

Add test SNS publisher#404

Merged
kibertoad merged 2 commits intomainfrom
feat/test-sns-publisher
Feb 22, 2026
Merged

Add test SNS publisher#404
kibertoad merged 2 commits intomainfrom
feat/test-sns-publisher

Conversation

@kibertoad
Copy link
Copy Markdown
Owner

@kibertoad kibertoad commented Feb 22, 2026

Summary by CodeRabbit

  • New Features

    • Added a test SNS publisher utility with multiple publishing modes (direct ARN, name resolution, consumer/publisher-derived) and FIFO support.
    • Made the test publisher and its options type publicly exportable for downstream use.
  • Tests

    • Added a comprehensive test suite covering topic publishing paths, FIFO behavior, consumer integration, and error scenarios.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Adds TestSnsPublisher and TestSnsPublishOptions, a test utility enabling flexible SNS publishing (topicArn, topicName, consumer, or publisher) with FIFO support, accompanying comprehensive tests, and public re-exports in package exports.

Changes

Cohort / File(s) Summary
Core Test Utility
packages/sns/lib/sns/fakes/TestSnsPublisher.ts
New TestSnsPublisher class and TestSnsPublishOptions type; publish method resolves target ARN from topicArn, topicName (via STS), consumer, or publisher; supports FIFO fields and error cases.
Test Suite
packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts
New comprehensive end-to-end tests exercising publishing paths: direct ARN, name resolution, publisher-derived, consumer-derived, FIFO topics, consumer integration, and error scenarios; uses AWS clients, created topics/queues, and SQS consumers for verification.
Public Exports
packages/sns/lib/index.ts
Re-exports TestSnsPublisher and type TestSnsPublishOptions to public package API.

Sequence Diagram

sequenceDiagram
    participant Test as Test Code
    participant TSNSP as TestSnsPublisher
    participant STS as STS Client
    participant SNS as SNS Client
    participant Topic as SNS Topic
    participant Queue as SQS Queue
    participant Consumer as SQS Consumer

    Test->>TSNSP: publish(payload, options)
    alt options.topicName
        TSNSP->>STS: Resolve topic name -> ARN
        STS-->>TSNSP: Return ARN
    else options.consumer / options.publisher
        TSNSP->>TSNSP: Extract ARN from consumer/publisher
    else options.topicArn
        TSNSP->>TSNSP: Use provided ARN
    end
    TSNSP->>SNS: PublishCommand(payload, ARN, FIFO attrs)
    SNS->>Topic: Deliver message
    Topic->>Queue: Forward to subscribed queue
    Consumer->>Queue: Poll for messages
    Queue-->>Consumer: Return message
    Consumer-->>Test: Verify payload
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • CarlosGamero
  • dariacm

Poem

🐰 I hop a message to the sky,
With ARN or name, I give it a try.
FIFO or plain, it finds its way,
Through queues and tests it hops all day—
A tiny rabbit cheers: publish hooray! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add test SNS publisher' accurately summarizes the main change: introducing TestSnsPublisher, a new test utility class for SNS publishing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/test-sns-publisher

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/sns/lib/sns/fakes/TestSnsPublisher.ts (1)

13-53: Consider extracting common FIFO fields to reduce repetition.

MessageGroupId and MessageDeduplicationId are duplicated across all four union variants. You could extract them into a base type and intersect:

♻️ Suggested refactor
+type FifoOptions = {
+  MessageGroupId?: string
+  MessageDeduplicationId?: string
+}
+
 export type TestSnsPublishOptions =
-  | {
-      topicArn: string
-      topicName?: never
-      consumer?: never
-      publisher?: never
-      MessageGroupId?: string
-      MessageDeduplicationId?: string
-    }
-  | {
-      topicName: string
-      topicArn?: never
-      consumer?: never
-      publisher?: never
-      MessageGroupId?: string
-      MessageDeduplicationId?: string
-    }
+  | ({ topicArn: string; topicName?: never; consumer?: never; publisher?: never } & FifoOptions)
+  | ({ topicName: string; topicArn?: never; consumer?: never; publisher?: never } & FifoOptions)
   // ... same for consumer and publisher variants
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sns/lib/sns/fakes/TestSnsPublisher.ts` around lines 13 - 53, Extract
the duplicated FIFO fields into a shared base type and intersect it with the
existing union to remove repetition: create a base type (e.g., SnsFifoFields)
that declares MessageGroupId?: string and MessageDeduplicationId?: string, then
update TestSnsPublishOptions to be the union of the four existing variant types
intersected with that base (keep existing variant names/structure referencing
AbstractSnsSqsConsumer and AbstractSnsPublisher unchanged so callers still match
the same discriminants). This keeps the same public shape while centralizing
FIFO fields for easier maintenance.
packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts (1)

69-92: Consider extracting the repeated SQS polling helper.

The pattern of creating a Consumer, starting it, waitAndRetry-ing, stopping it, and parsing the SNS envelope is repeated six times. A small helper would reduce boilerplate:

async function collectMessagesFromQueue(
  sqsClient: SQSClient,
  queueUrl: string,
): Promise<unknown[]> {
  const receivedMessages: SQSMessage[] = []
  const consumer = Consumer.create({
    queueUrl,
    sqs: sqsClient,
    handleMessage: async (message: SQSMessage) => {
      receivedMessages.push(message)
      return message
    },
  })
  consumer.start()
  await waitAndRetry(() => receivedMessages.length > 0)
  consumer.stop()
  return receivedMessages.map((m) => {
    const envelope = JSON.parse(m.Body!)
    return JSON.parse(envelope.Message)
  })
}

Also applies to: 108-127, 145-164, 188-207, 251-270, 317-336

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts` around lines 69 - 92,
Extract the repeated SQS polling + parse logic into a helper function (e.g.,
collectMessagesFromQueue) and replace the six duplicated blocks with calls to
it; the helper should create a receivedMessages array, call Consumer.create({
queueUrl, sqs: sqsClient, handleMessage: async (message: SQSMessage) => {
receivedMessages.push(message); return message } }), start the consumer, await
waitAndRetry(() => receivedMessages.length > 0), stop the consumer, and return
the parsed SNS messages by JSON.parse-ing message.Body and then JSON.parse-ing
the envelope.Message. Update each test case that currently uses Consumer.create
/ waitAndRetry / consumer.start / consumer.stop / JSON.parse(...) to call
collectMessagesFromQueue(sqsClient, queueUrl) and assert against the returned
parsed messages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts`:
- Around line 1-18: The imports are out of Biome's organizeImports order: move
the local utility imports (deserializeSNSMessage, subscribeToTopic, assertTopic,
deleteTopic) so they come before the deeper relative test imports
(SnsSqsPermissionConsumer, PERMISSIONS_ADD_MESSAGE_SCHEMA,
SnsPermissionPublisher, Dependencies, registerDependencies, TestSnsPublisher),
then run Biome's auto-fix to apply the correct ordering (npx `@biomejs/biome`
check --write packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts).

---

Nitpick comments:
In `@packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts`:
- Around line 69-92: Extract the repeated SQS polling + parse logic into a
helper function (e.g., collectMessagesFromQueue) and replace the six duplicated
blocks with calls to it; the helper should create a receivedMessages array, call
Consumer.create({ queueUrl, sqs: sqsClient, handleMessage: async (message:
SQSMessage) => { receivedMessages.push(message); return message } }), start the
consumer, await waitAndRetry(() => receivedMessages.length > 0), stop the
consumer, and return the parsed SNS messages by JSON.parse-ing message.Body and
then JSON.parse-ing the envelope.Message. Update each test case that currently
uses Consumer.create / waitAndRetry / consumer.start / consumer.stop /
JSON.parse(...) to call collectMessagesFromQueue(sqsClient, queueUrl) and assert
against the returned parsed messages.

In `@packages/sns/lib/sns/fakes/TestSnsPublisher.ts`:
- Around line 13-53: Extract the duplicated FIFO fields into a shared base type
and intersect it with the existing union to remove repetition: create a base
type (e.g., SnsFifoFields) that declares MessageGroupId?: string and
MessageDeduplicationId?: string, then update TestSnsPublishOptions to be the
union of the four existing variant types intersected with that base (keep
existing variant names/structure referencing AbstractSnsSqsConsumer and
AbstractSnsPublisher unchanged so callers still match the same discriminants).
This keeps the same public shape while centralizing FIFO fields for easier
maintenance.

Comment thread packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts (2)

77-80: Stale SQS consumer on test failure — wrap start/stop in try/finally.

This Consumer.create/start/stop pattern is repeated six times (lines 77–80, 116–119, 153–156, 196–199, 261–262, 325–328). If waitAndRetry times out and throws, consumer.stop() is never called. The stale poller continues draining from the queue and can consume messages intended by later tests, causing intermittent failures.

The same problem applies to the un-awaited afterEach-invisible resources regularPublisher.close() (line 206) and consumer.close() (lines 269, 365) — both only execute on the happy path.

♻️ Example fix for one occurrence (apply the same pattern to all six)
-      consumer.start()
-
-      await waitAndRetry(() => receivedMessages.length > 0)
-      consumer.stop()
+      consumer.start()
+      try {
+        await waitAndRetry(() => receivedMessages.length > 0)
+      } finally {
+        consumer.stop()
+      }

For the framework-level resources in the "publish with publisher" test:

-      await publisher.publish({ test: 'publisher' }, { publisher: regularPublisher })
-      /* ... Consumer create/start/stop ... */
-      expect(receivedMessages).toHaveLength(1)
-      /* ... */
-      await regularPublisher.close()
+      try {
+        await publisher.publish({ test: 'publisher' }, { publisher: regularPublisher })
+        /* ... Consumer create/start/stop ... */
+        expect(receivedMessages).toHaveLength(1)
+        /* ... */
+      } finally {
+        await regularPublisher.close()
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts` around lines 77 - 80,
Tests create an SQS poller via Consumer.create and call consumer.start(), but if
waitAndRetry(...) throws the test will exit before consumer.stop() (and other
cleanup like regularPublisher.close() and consumer.close()) runs, leaving a
stale consumer; wrap each consumer.start()/consumer.stop() pair in a try/finally
so consumer.stop() is always invoked (and await it if it returns a promise), and
ensure we also await/guard calls to regularPublisher.close() and
consumer.close() (e.g., in the test-level try/finally or a shared afterEach that
awaits the closers) for all occurrences around
Consumer.create/consumer.start/consumer.stop, waitAndRetry,
regularPublisher.close and consumer.close.

67-76: Extract repeated Consumer.create boilerplate into a helper.

The same 8-line block (biome-ignore comment, Consumer.create, handler) appears six times. A small helper would reduce noise and make the fix for the try/finally issue above easier to apply consistently.

♻️ Proposed helper
+function createTestConsumer(sqsClient: SQSClient, queueUrl: string, bucket: SQSMessage[]) {
+  return Consumer.create({
+    queueUrl,
+    sqs: sqsClient,
+    // biome-ignore lint/suspicious/useAwait: Consumer.create requires async handler
+    handleMessage: async (message: SQSMessage) => {
+      bucket.push(message)
+      return message
+    },
+  })
+}

Usage at each call site:

-      const receivedMessages: SQSMessage[] = []
-      const consumer = Consumer.create({
-        queueUrl,
-        sqs: sqsClient,
-        // biome-ignore lint/suspicious/useAwait: Consumer.create requires async handler
-        handleMessage: async (message: SQSMessage) => {
-          receivedMessages.push(message)
-          return message
-        },
-      })
+      const receivedMessages: SQSMessage[] = []
+      const consumer = createTestConsumer(sqsClient, queueUrl, receivedMessages)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts` around lines 67 - 76,
Extract the repeated Consumer.create boilerplate into a single helper function
(e.g., createTestConsumer) that accepts the queueUrl, sqsClient and a
receivedMessages array (or returns a consumer with an injected handler),
encapsulates the biome-ignore lint comment, calls Consumer.create with queueUrl
and sqs, and sets handleMessage to the async handler that pushes the message
into receivedMessages and returns it; replace each inline Consumer.create block
with calls to this helper (refer to Consumer.create, handleMessage and
receivedMessages in the diff).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts`:
- Around line 1-16: Imports in TestSnsPublisher.spec.ts are not ordered
consistently; reorder the import statements so external packages (e.g.,
'@aws-sdk/*', '@lokalise/*', 'sqs-consumer', 'vitest') come first, then
third-party toolkit modules (e.g., '@message-queue-toolkit/sqs'), then
types/local test utilities and finally local project modules (e.g.,
SnsSqsPermissionConsumer, SnsPermissionPublisher, registerDependencies,
subscribeToTopic, assertTopic/deleteTopic, TestSnsPublisher); within each group
sort alphabetically (e.g., SNSClient, SQSClient, STSClient together;
waitAndRetry; SQSMessage, assertQueue/deleteQueue; AwilixContainer; Consumer;
vitest imports), ensuring consistent grouping and alphabetical order across the
file to satisfy the import-order rule.

---

Nitpick comments:
In `@packages/sns/lib/sns/fakes/TestSnsPublisher.spec.ts`:
- Around line 77-80: Tests create an SQS poller via Consumer.create and call
consumer.start(), but if waitAndRetry(...) throws the test will exit before
consumer.stop() (and other cleanup like regularPublisher.close() and
consumer.close()) runs, leaving a stale consumer; wrap each
consumer.start()/consumer.stop() pair in a try/finally so consumer.stop() is
always invoked (and await it if it returns a promise), and ensure we also
await/guard calls to regularPublisher.close() and consumer.close() (e.g., in the
test-level try/finally or a shared afterEach that awaits the closers) for all
occurrences around Consumer.create/consumer.start/consumer.stop, waitAndRetry,
regularPublisher.close and consumer.close.
- Around line 67-76: Extract the repeated Consumer.create boilerplate into a
single helper function (e.g., createTestConsumer) that accepts the queueUrl,
sqsClient and a receivedMessages array (or returns a consumer with an injected
handler), encapsulates the biome-ignore lint comment, calls Consumer.create with
queueUrl and sqs, and sets handleMessage to the async handler that pushes the
message into receivedMessages and returns it; replace each inline
Consumer.create block with calls to this helper (refer to Consumer.create,
handleMessage and receivedMessages in the diff).

@kibertoad kibertoad merged commit 543c4cb into main Feb 22, 2026
6 checks passed
@kibertoad kibertoad deleted the feat/test-sns-publisher branch February 22, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant