Skip to content

chore: SNS support for SQS 26 - #445

Merged
CarlosGamero merged 21 commits into
mainfrom
chore/sns_support_sqs_26
May 26, 2026
Merged

CarlosGamero merged 21 commits into
mainfrom
chore/sns_support_sqs_26

Conversation

@CarlosGamero

@CarlosGamero CarlosGamero commented May 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • Breaking Changes

    • SNS service now uses grouped resource getters instead of individual fields; attempting to access before initialization will throw an error.
    • Added new areResourcesReady getter for checking resource initialization status.
    • Requires SQS peer dependency >=26.0.0.
  • Documentation

    • Added upgrade guide documenting breaking changes in SNS/SQS service resource handling and new initialization patterns.

Review Change Stack

@CarlosGamero
CarlosGamero requested a review from kibertoad May 25, 2026 16:50
@CarlosGamero CarlosGamero self-assigned this May 25, 2026
@coderabbitai

coderabbitai Bot commented May 25, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR refactors SNS/SQS resource management and non-blocking initialization. Internal ARN/URL/name fields are encapsulated as private state objects (_topic, _subscription, _queue) with protected getters that throw if accessed pre-init. Non-blocking initSnsSqs now returns undefined immediately and populates resources asynchronously via onResourcesReady callback with queueArn tracking. Consumer start() is gated on resource readiness.

Changes

Resource encapsulation and readiness gating

Layer / File(s) Summary
TopicResource type and AbstractSnsService encapsulation
packages/sns/lib/sns/AbstractSnsService.ts
Introduces TopicResource type; replaces protected topicArn with private _topic and exposes via protected get topic() that throws pre-init; adds setTopicResource() method; updates init/close lifecycle.
SubscriptionResource, consumer readiness, and init coordination
packages/sns/lib/sns/AbstractSnsSqsConsumer.ts
Introduces SubscriptionResource type; replaces protected topicArn/subscriptionArn fields with private _subscription; adds areResourcesReady getter; gates start() on readiness with startRequested flag; updates init/close for non-blocking coordination via setSubscriptionResource and setQueueResource.
Non-blocking initSnsSqs refactoring with queueArn tracking
packages/sns/lib/utils/snsInitter.ts
Changes initSnsSqs return type to Promise<InitSnsSqsResult | undefined>; returns undefined when resources not immediately ready instead of placeholder object; adds queueArn to result and callback; introduces markQueueAvailable() coordination; updates onResourcesReady callback signature with queueArn; uses QueueAttributesResult typing.

Implementation and test updates to use new resource contracts

Layer / File(s) Summary
AbstractSnsPublisher and test publishers use topic resource getter
packages/sns/lib/sns/AbstractSnsPublisher.ts, packages/sns/lib/sns/fakes/TestSnsPublisher.ts, packages/sns/test/publishers/SnsPermissionPublisher.ts, packages/sns/test/publishers/SnsPermissionPublisherFifo.ts
Publishers updated to read topic ARN from this.topic.arn instead of this.topicArn; TestSnsPublisher.publish() accesses topic/subscription from private _topic?.arn and _subscription?.topicArn.
Test consumer subscriptionProps gated on areResourcesReady
packages/sns/test/consumers/SnsSqsPermissionConsumer.ts, packages/sns/test/consumers/SnsSqsPermissionConsumerFifo.ts, packages/sns/test/consumers/SnsSqsEntityConsumer.ts, packages/sns/test/consumers/CreateLocateConfigMixConsumer.ts
Test consumers return undefined ARN/URL/name values until areResourcesReady is true; when ready, source values from subscription, queue, and deadLetterQueue resource objects.
Test suite updates for non-blocking readiness and resource access
packages/sns/test/consumers/SnsSqsPermissionConsumer.codec.spec.ts, packages/sns/test/consumers/SnsSqsPermissionConsumer.deadLetterQueue.spec.ts, packages/sns/test/consumers/SnsSqsPermissionConsumer.spec.ts, packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts
Tests updated to expect initSnsSqs returning undefined in non-blocking mode; subscriptionProps fields remain undefined until onResourcesReady callback; tests capture resource values before close and assert readiness conditions.
UPGRADING guide and peer dependency update
UPGRADING.md, packages/sns/package.json
Documents breaking changes: grouped resource getters replacing fields, fail-fast throws on pre-init access, new areResourcesReady helper; updates @message-queue-toolkit/sqs peer dependency to >=26.0.0.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

major

Suggested reviewers

  • kibertoad
  • kjamrog
  • Drodevbar

Poem

🐰 Resources now hide in their private embrace,
With getters and setters keeping the pace,
Non-blocking flows bloom with queueArn in hand,
Readiness gates make the startup so grand!
The toolkit hops forward, refactored with care. 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'chore: SNS support for SQS 26' accurately summarizes the main change—updating SNS to support SQS version 26 through API refactoring and peer dependency updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/sns_support_sqs_26

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@CarlosGamero CarlosGamero changed the title Chore/sns support sqs 26 chore: SNS support for SQS 26 May 25, 2026

@kibertoad kibertoad left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Drive-by review of the SQS-26 mirroring work. Two P0 correctness issues called out inline (close() leak, queueArn! assertion), plus a few P1/P2 follow-ups. A combined UPGRADING.md migration note for the SQS-26 + SNS protected-field rename would also be worth adding before merge.

* callback). Subclasses read via the {@link subscription} getter so the
* ARNs are impossible to access in an "uninitialised" state.
*/
private _subscription?: SubscriptionResource

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P0 — close() leaks this field, defeating the stated invariant.

The class inherits AbstractSqsConsumer.close() → AbstractSqsService.close(), which only resets _queue and isInitted. _subscription (added here) is never cleared, nor are resourcesReady or startRequested. So immediately after close():

  • consumer.subscription.topicArn still returns the stale ARN — the docstring above says the ARN should be "impossible to access in an 'uninitialised' state", but it is.
  • resourcesReady === true can mislead a re-init code path.
  • startRequested may incorrectly carry across init cycles.

Suggested fix — add an override:

public override async close(abort?: boolean): Promise<void> {
  this._subscription = undefined
  this.resourcesReady = false
  this.startRequested = false
  await super.close(abort)
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks for catching this — totally right. Added a close() override in 4d9d523 that resets _subscription, resourcesReady and startRequested before delegating to super.close(abort).

Comment thread packages/sns/lib/utils/snsInitter.ts Outdated
topicArn: subscriptionTopicArn,
queueUrl,
// biome-ignore lint/style/noNonNullAssertion: queueAvailable is checked above
queueArn: queueArn!,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P0 — queueArn! is unsafe.

markQueueAvailable (just below) sets queueAvailable = true whenever getQueueAttributes doesn't throw, but attrs.result?.attributes?.QueueArn can be undefined (the Attributes map only contains requested names, and SDK responses with an empty map have been seen in the wild). When that happens, onResourcesReady fires with queueArn: undefined, setQueueResource happily stores it, and any later this.queue.arn access returns undefined — silently.

Mirror the explicit check used on the synchronous path at the bottom of initSnsSqs:

if (!queueArn) {
  extraParams?.onResourcesError?.(
    new Error(`Could not resolve QueueArn for queue at ${queueUrl}`),
    { isFinal: true },
  )
  return
}
queueAvailable = true

…then the non-null assertion here goes away.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, good spot. Fixed in 4d9d523: markQueueAvailable now reports via onResourcesError (final) when the QueueArn fetch returns an empty Attributes map, instead of silently flipping the queue to "ready". Took the chance to drop the separate queueAvailable flag and use queueArn truthiness as the ready signal, so the ! assertion in notifyIfBothReady goes away too.

Comment thread packages/sns/lib/utils/snsInitter.ts Outdated

const markQueueAvailable = async () => {
try {
const attrs = await getQueueAttributes(sqsClient, queueUrl, ['QueueArn'])

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P2 — extra GetQueueAttributes round-trip per init.

pollForQueue's checkFn already calls getQueueAttributes(queueUrl) with default ['All'] (which includes QueueArn) but throws the result away because pollForQueue is typed Promise<unknown | undefined>. Same applies to the synchronous getQueueAttributes(..., ['QueueArn']) at L482.

Threading the result through (change pollForQueue return type to Promise<QueueAttributesResult | undefined>) lets you reuse it and eliminates both extra round-trips. Modest gain per startup but it compounds with the new background polling path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. Done in c4e60ab:

  • QueueAttributesResult is now exported from @message-queue-toolkit/sqs.
  • pollForQueue is typed Promise<QueueAttributesResult | undefined> and threads the attrs through onResourceAvailable(attrs).
  • markQueueAvailable is now a sync helper that reads QueueArn from the attrs it receives — no separate fetch.
  • The non-polling branch (Promise.all) captures queueCheckResult.result into a shared queueAttributes variable.
  • The L482 final-return fetch is gone — it reads QueueArn from the captured queueAttributes instead.

Net: 2 fewer round-trips per init in the locator-only polling path, 1 fewer in the non-polling path.

@@ -152,8 +165,8 @@ export abstract class AbstractSnsSqsConsumer<
if (this.startRequested) {
this.logger.info({
message: 'Resources now ready, starting consumers',

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P1 — log loses context for locator-only setups.

When the consumer was wired with locatorConfig.topicName (not topicArn), the resolved ARN isn't reachable here yet (_subscription is set in the same callback, but the log is built before this.subscription is safe to read — and even after, you can't read it for the startRequested branch at L229).

Same issue at L229: topicArn: this.locatorConfig?.topicArn will be undefined for any locator using topicName, or for a locator with only subscriptionArn.

Prefer reading from result.topicArn here (already in scope), and at L229 either resolve and cache the topic ARN once via buildTopicArn(...) for diagnostics, or omit the field when unavailable rather than emitting undefined.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. Fixed in 2dd1123:

  • L167 callback log now reads queueName from result too (same source of truth as result.topicArn already was).
  • L229 start() log uses a new buildConfiguredResourceLogContext() helper that emits only the identifiers we can derive from config — queueName, plus topicArn or topicName as fallback — and omits the field entirely when unknown (so locator-only-with-subscriptionArn no longer logs topicArn: undefined).

Skipped the buildTopicArn cache path for now — felt heavier than the diagnostic warrants.

return this._subscription
}

protected setSubscriptionResource(resource: SubscriptionResource): void {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P3 — asymmetry: setQueueResource and setTopicResource flip isInitted, this one doesn't.

It's correct today because setSubscriptionResource is always paired with setQueueResource in the flow, but the next maintainer who reaches for this from a custom subclass will silently leave isInitted = false. Either:

  • add a one-line comment explaining the asymmetry, or
  • make it set isInitted = true for symmetry (cheap idempotent set).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. Done in 9f2328a — setSubscriptionResource now flips isInitted = true for symmetry with setQueueResource / setTopicResource. Idempotent at the existing call sites (always paired with setQueueResource).

deadLetterQueueUrl: this.deadLetterQueueUrl,
// topicArn / subscriptionArn / _queue are populated together by init;
// if the queue resource isn't resolved yet, none of the others are either.
try {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

P1 — this try/catch pattern is duplicated across 5 consumer files and conflates two failure modes.

Same block appears in SnsSqsPermissionConsumer.ts, SnsSqsPermissionConsumerFifo.ts, SnsSqsEntityConsumer.ts, CreateLocateConfigMixConsumer.ts, and the polling spec's TestStartupResourcePollingConsumer. Two suggestions:

  1. Lift it into AbstractSnsSqsConsumer (e.g., protected getResourceHandles(): { … } | undefined or an isResourcesReady getter) so test consumers don't each catch errors.
  2. Inside the helper, prefer probing private state (if (!this._subscription || !this._queue) return undefined) over try/catch — that way unexpected getter exceptions still bubble up instead of being silently swallowed into an all-undefined shape that looks like "not initialised yet".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks. Done in 3efb46e:

  • Added protected get areResourcesReady on AbstractSnsSqsConsumer (exposes the existing private resourcesReady flag).
  • All five test consumers' subscriptionProps getters now short-circuit on !this.areResourcesReady and read the throwing queue/subscription getters directly when ready. No more try/catch, so unexpected exceptions propagate as you suggested instead of being flattened into an all-undefined shape that looks like "not initialised yet".

CarlosGamero and others added 9 commits May 25, 2026 20:43
- AbstractSnsSqsConsumer.close() now resets _subscription, resourcesReady
  and startRequested so the subscription getter does not return a stale
  ARN after close and a re-init does not observe leftover state.
- initSnsSqs non-blocking path: markQueueAvailable now reports via
  onResourcesError when QueueArn cannot be resolved instead of advertising
  the queue as ready with an undefined ARN. notifyIfBothReady gates on
  queueArn truthiness so the non-null assertion is no longer needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a consumer is wired with locator-only and only `topicName` (or just
`subscriptionArn`) is provided, `this.locatorConfig?.topicArn` was
`undefined` in the "Start requested but resources not ready yet" log,
producing a noisy `topicArn: undefined` line with no usable identifier.

Replace the inline log payload with `buildConfiguredResourceLogContext()`
which only emits the identifiers we actually have from config (queueName,
topicArn or topicName as fallback). The earlier `Resources now ready`
log inside `onResourcesReady` now sources `queueName` from `result` for
the same reason — `result` already carries the resolved identifiers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five test consumers reproduced the same try/catch around the throwing
`queue` / `subscription` getters to coerce a "not ready yet" failure
into an all-undefined shape, which also swallowed any unexpected
exceptions.

Expose `protected get areResourcesReady` on `AbstractSnsSqsConsumer` so
callers that legitimately run before resources are populated can
short-circuit explicitly. Unexpected getter exceptions now propagate
instead of being silently flattened.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…trips

`pollForQueue.checkFn` already calls `getQueueAttributes(queueUrl)` (which
includes `QueueArn` in the default `['All']` response) but the helper was
typed `Promise<unknown | undefined>` so callers couldn't reuse the
result. `markQueueAvailable` then re-fetched the ARN, and the synchronous
path at the bottom of `initSnsSqs` re-fetched it a third time.

- Type `pollForQueue` as `Promise<QueueAttributesResult | undefined>`
  and pass the attrs through the `onResourceAvailable` callback.
- `markQueueAvailable` is now a pure synchronous helper that reads the
  ARN from the attrs it was given.
- The non-polling branch captures `queueCheckResult.result` into a
  shared `queueAttributes` variable.
- The final return reads `QueueArn` from `queueAttributes` — no extra
  GetQueueAttributes call.

Net: 2 fewer GetQueueAttributes round-trips per init in the locator-only
polling path, 1 fewer in the non-polling path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches setQueueResource / setTopicResource so a maintainer reaching for
setSubscriptionResource from a custom subclass doesn't silently leave
isInitted = false. Idempotent — when paired with setQueueResource (the
existing call sites) it just flips the flag once.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CarlosGamero
CarlosGamero requested a review from kibertoad May 25, 2026 19:34
Base automatically changed from chore/protected_dead_letter_queue_props_getter to main May 26, 2026 09:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sns/lib/sns/AbstractSnsSqsConsumer.ts (1)

156-179: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

DLQ init failure is swallowed in non-blocking mode, but blocks in synchronous mode.

The promise chain structure causes consumers to start even when initDeadLetterQueue() fails:

this.initDeadLetterQueue()
  .catch((err) => { /* logs but doesn't rethrow */ })
  .then(() => { /* runs anyway → starts consumers */ })

In contrast, the synchronous path at line 203 (await this.initDeadLetterQueue()) propagates failures. This inconsistency means non-blocking consumers may run without DLQ protection, risking message loss on processing failures.

Suggested fix: propagate DLQ failure or gate consumer start
           this.initDeadLetterQueue()
-            .catch((err) => {
-              this.logger.error({
-                message: 'Failed to initialize dead letter queue after resources became ready',
-                error: err,
-              })
-            })
             .then(() => {
               // If start() was called while resources weren't ready, start consumers now
               if (this.startRequested) {
                 this.logger.info({
                   message: 'Resources now ready, starting consumers',
                   queueName: result.queueName,
                   topicArn: result.topicArn,
                 })
                 return this.startConsumers()
               }
             })
             .catch((err) => {
               this.logger.error({
-                message: 'Failed to start consumers after resources became ready',
+                message: 'Failed to initialize DLQ or start consumers after resources became ready',
                 error: err,
               })
             })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sns/lib/sns/AbstractSnsSqsConsumer.ts` around lines 156 - 179, The
current non-blocking path swallows initDeadLetterQueue() failures because the
catch handler on initDeadLetterQueue() does not rethrow, allowing then() to run
and call startConsumers() even if DLQ initialization failed; change the promise
chain so that the catch either rethrows the error or prevents the then()-branch
from executing (e.g., rethrow inside the catch or move the startConsumers() call
into the try/then after a successful init), ensuring initDeadLetterQueue()
failures propagate and gate startConsumers() when startRequested is true; update
the handlers around initDeadLetterQueue(), startConsumers(), startRequested and
logger.error calls accordingly so behavior matches the synchronous await path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts`:
- Around line 361-369: Tests that call consumer.init() in non-blocking mode
return early and leave the consumer's background polling running; update the
affected specs that perform early-return assertions (the ones asserting
subscriptionProps fields after init) to ensure the consumer is closed afterwards
by calling await consumer.close(), ideally placed in a finally block so
background polling is always stopped even if assertions fail; locate the
instances using the consumer.init() and consumer.subscriptionProps checks and
add the finally + await consumer.close() cleanup around each test.

---

Outside diff comments:
In `@packages/sns/lib/sns/AbstractSnsSqsConsumer.ts`:
- Around line 156-179: The current non-blocking path swallows
initDeadLetterQueue() failures because the catch handler on
initDeadLetterQueue() does not rethrow, allowing then() to run and call
startConsumers() even if DLQ initialization failed; change the promise chain so
that the catch either rethrows the error or prevents the then()-branch from
executing (e.g., rethrow inside the catch or move the startConsumers() call into
the try/then after a successful init), ensuring initDeadLetterQueue() failures
propagate and gate startConsumers() when startRequested is true; update the
handlers around initDeadLetterQueue(), startConsumers(), startRequested and
logger.error calls accordingly so behavior matches the synchronous await path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81a175d2-8570-4f35-a7b2-fbd7c729e00f

📥 Commits

Reviewing files that changed from the base of the PR and between 0c9c264 and 4fc35a1.

📒 Files selected for processing (17)
  • UPGRADING.md
  • packages/sns/lib/sns/AbstractSnsPublisher.ts
  • packages/sns/lib/sns/AbstractSnsService.ts
  • packages/sns/lib/sns/AbstractSnsSqsConsumer.ts
  • packages/sns/lib/sns/fakes/TestSnsPublisher.ts
  • packages/sns/lib/utils/snsInitter.ts
  • packages/sns/package.json
  • packages/sns/test/consumers/CreateLocateConfigMixConsumer.ts
  • packages/sns/test/consumers/SnsSqsEntityConsumer.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.codec.spec.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.deadLetterQueue.spec.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.spec.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumer.ts
  • packages/sns/test/consumers/SnsSqsPermissionConsumerFifo.ts
  • packages/sns/test/publishers/SnsPermissionPublisher.ts
  • packages/sns/test/publishers/SnsPermissionPublisherFifo.ts

Comment on lines +361 to 369
// Init should return immediately even though topic doesn't exist;
// resources are populated together via the background callback, so
// none of subscriptionProps' fields are set yet.
await consumer.init()

// queueName should still be set
expect(consumer.subscriptionProps.queueName).toBe(queueName)
expect(consumer.subscriptionProps.queueName).toBeUndefined()
expect(consumer.subscriptionProps.queueUrl).toBeUndefined()
expect(consumer.subscriptionProps.topicArn).toBeUndefined()
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Close non-blocking consumers after early-return assertions.

On Line 364 and Line 393, consumer.init() returns before readiness in non-blocking mode, but these tests do not close the consumer. That can leave background polling active and create test flakiness/interference across cases. Add await consumer.close() (preferably in finally) in these early-return tests.

Proposed test cleanup pattern
-      await consumer.init()
-
-      expect(consumer.subscriptionProps.queueName).toBeUndefined()
-      expect(consumer.subscriptionProps.queueUrl).toBeUndefined()
-      expect(consumer.subscriptionProps.topicArn).toBeUndefined()
+      try {
+        await consumer.init()
+        expect(consumer.subscriptionProps.queueName).toBeUndefined()
+        expect(consumer.subscriptionProps.queueUrl).toBeUndefined()
+        expect(consumer.subscriptionProps.topicArn).toBeUndefined()
+      } finally {
+        await consumer.close()
+      }

Also applies to: 390-397

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/sns/test/consumers/SnsSqsPermissionConsumer.startupResourcePolling.spec.ts`
around lines 361 - 369, Tests that call consumer.init() in non-blocking mode
return early and leave the consumer's background polling running; update the
affected specs that perform early-return assertions (the ones asserting
subscriptionProps fields after init) to ensure the consumer is closed afterwards
by calling await consumer.close(), ideally placed in a finally block so
background polling is always stopped even if assertions fail; locate the
instances using the consumer.init() and consumer.subscriptionProps checks and
add the finally + await consumer.close() cleanup around each test.

@CarlosGamero
CarlosGamero merged commit 8dd1737 into main May 26, 2026
10 of 11 checks passed
@CarlosGamero
CarlosGamero deleted the chore/sns_support_sqs_26 branch May 26, 2026 09:41
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.

2 participants