Skip to content

test: make projection, metrics, and recovery waits deterministic - #603

Open
nmummau wants to merge 10 commits into
Eventuous:devfrom
nmummau:fix/postgres-projector-polling
Open

nmummau wants to merge 10 commits into
Eventuous:devfrom
nmummau:fix/postgres-projector-polling

Conversation

@nmummau

@nmummau nmummau commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Replace timing assumptions in asynchronous tests with bounded waits for observable progress, coordinate recovery scenarios explicitly, and keep parameterized test names stable between runs.

  • PostgreSQL and SQL Server: Replace fixed one-second projection delays with polling for the expected booking count. Assert that each row exists before reading its values.
  • MongoDB: Poll the configured subscription’s persisted checkpoint directly, avoiding the write-subject replacement caused by repeated GetLastCheckpoint calls. Report the expected and last observed positions on timeout, and always stop initialized hosts in finally blocks.
  • OpenTelemetry: Bound the previously unlimited wait for consumed events and propagate test cancellation.
  • RabbitMQ: Wait for message recovery, a drop callback, and a second subscribed callback. Redelivery can happen before resubscription completes, which caused an early assertion failure in CI.
  • Core subscriptions: Await explicit cancellation-recovery signals with phase-specific timeout messages. Coordinate the nack-burst test so all eight handlers are in flight before any can fail and cancel the pump.
  • Test reporting: Replace changing GUIDs and timestamps in parameterized test data with fixed values so unchanged tests are no longer reported as removed and added.

Projection and metrics polling uses a 30-second bound and preserves external cancellation. PostgreSQL explicitly resolves the shared extension-method ambiguity so the new awaits can use NoContext().

The cancelled-message recovery failure was not reproduced locally; its changes improve synchronization, cleanup, and diagnostics without claiming a confirmed root-cause fix.

Local validation passed:

  • PostgreSQL and SQL Server projection tests.
  • Metrics tests for PostgreSQL, SQL Server, SQLite, and KurrentDB.
  • All 9 MongoDB projection tests and all 5 RabbitMQ tests.
  • All 143 core subscription tests on .NET 8 and .NET 9.
  • Ten consecutive nack-burst test runs with one reported processor.
  • All 26 Service Bus serialization cases and 5 sequence tests.

Fixes #599
Fixes #604

@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Poll for PostgreSQL bookings before asserting projections

🧪 Tests 🐞 Bug fix 🕐 Less than 10 minutes

Grey Divider

AI Description

• Replace the fixed delay with polling so the test accommodates variable projection times.
• Stop polling after 30 seconds or on cancellation, with a timeout showing the observed count.
• Assert each booking row exists before checking its values.
Diagram

graph TD
  Events["Imported events"] --> Projector["Test projector"] --> Table["Bookings table"] --> Poll["Count poll"] --> Ready{"Expected count?"}
  Ready -->|"No; retry until timeout"| Poll
  Ready -->|Yes| Assertions["Row assertions"]
Loading
High-Level Assessment

Keep the bounded, cancellation-aware polling approach. A fixed delay cannot reliably accommodate variable projection times; counting rows first also makes subsequent per-row failures clearer.

Files changed (1) +23 / -2

Tests (1) +23 / -2
ProjectorTests.csWait for projected bookings before asserting their values +23/-2

Wait for projected bookings before asserting their values

• Replaces the one-second delay with a 100 ms count poll bounded by a 30-second timeout and the test's cancellation token. Reports the observed count on timeout and asserts that each queried booking row exists before reading its fields.

src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Three projection cleanups skip the helper ✓ Resolved
Description
The new finally blocks in the MongoDB projection tests await DisposeAsync() with
ConfigureAwait(false) instead of .NoContext(). Each test takes this shutdown path after
projection work, while the updated checkpoint polling code uses the repository helper.
Code

src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBuilder.cs[59]

+            await projectionFixture.DisposeAsync().ConfigureAwait(false);
Evidence
Checklist item 5 calls for applicable awaits to use .NoContext(). The three added shutdown awaits
instead use ConfigureAwait(false), while the updated checkpoint wait demonstrates that
.NoContext() is used in this test project.

CLAUDE.md: Use Async I/O and NoContext: CLAUDE.md: Use Async I/O and NoContext: CLAUDE.md: Use Async I/O and NoContext: CLAUDE.md: Use Async I/O and NoContext: CLAUDE.md: Use Async I/O and NoContext: CLAUDE.md: Use Async I/O and NoContext: CLAUDE.md: Use Async I/O and NoContext: CLAUDE.md: Use Async I/O and NoContext
src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBuilder.cs[58-60]
src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBulkBuilder.cs[47-49]
src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectingWithTypedHandlers.cs[38-40]
src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs[66-74]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The three new MongoDB projection cleanup awaits use `ConfigureAwait(false)` rather than the repository's `.NoContext()` convention.
## Fix Focus Areas
- src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBuilder.cs[58-60]
- src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectWithBulkBuilder.cs[47-49]
- src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectingWithTypedHandlers.cs[38-40]
## Recommended Fix
Import `Eventuous.Tools` where needed and replace each cleanup await's `ConfigureAwait(false)` with `.NoContext()`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. SQL Server poll skips await convention ✓ Resolved
Description
WaitForBookings awaits ExecuteScalarAsync directly instead of using NoContext(). Every
database read in the new polling loop uses that await, unlike the SQL Server integration code that
applies the repository helper to asynchronous I/O.
Code

src/SqlServer/test/Eventuous.Tests.SqlServer/Projections/ProjectorTests.cs[65]

+                projectedCount = (int)(await cmd.ExecuteScalarAsync(cts.Token))!;
Evidence
Rule 9 calls for relevant asynchronous I/O awaits to use NoContext(). The newly added SQL Server
polling read awaits ExecuteScalarAsync without it; the SQL Server integration code uses the helper
for its asynchronous I/O.

CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context
src/SqlServer/test/Eventuous.Tests.SqlServer/Projections/ProjectorTests.cs[61-68]
src/SqlServer/src/Eventuous.SqlServer/Projections/SqlServerProjector.cs[1-100]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new SQL Server projection poll awaits database I/O without the repository's NoContext helper.
## Fix Focus Areas
- src/SqlServer/test/Eventuous.Tests.SqlServer/Projections/ProjectorTests.cs[61-68]
## Recommended Fix
Make the helper available in the test project and apply NoContext() to the new asynchronous database and polling awaits.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Mongo checkpoint read skips await helper ✓ Resolved
Description
WaitForPosition uses ConfigureAwait(false) on its new checkpoint query instead of NoContext().
Each polling iteration takes that path, while the MongoDB checkpoint store uses the repository
helper for its equivalent query.
Code

src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs[R67-68]

+                    .SingleOrDefaultAsync(cts.Token)
+                    .ConfigureAwait(false);
Evidence
Rule 9 specifies NoContext() for relevant awaits. The added checkpoint query uses
ConfigureAwait(false), whereas the existing MongoDB checkpoint store applies NoContext() to
SingleOrDefaultAsync.

CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context: CLAUDE.md: Use Asynchronous I/O Without Capturing Context
src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs[65-73]
src/Mongo/src/Eventuous.Projections.MongoDB/MongoCheckpointStore.cs[37-41]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new MongoDB checkpoint poll uses ConfigureAwait(false) rather than the repository's NoContext helper.
## Fix Focus Areas
- src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs[65-73]
## Recommended Fix
Make NoContext() available in the test project and use it for the checkpoint query and polling delay.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@nmummau
nmummau force-pushed the fix/postgres-projector-polling branch from 36dea06 to ad8407a Compare October 6, 2026 02:20
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit ad8407a

@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Test Results

   48 files  ±0     48 suites  ±0   15m 7s ⏱️ -8s
  609 tests  - 4    609 ✅  - 4  0 💤 ±0  0 ❌ ±0 
1 226 runs  ±0  1 226 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 5a7399b. ± Comparison against base commit ed6cfd1.

This pull request removes 8 and adds 4 tests. Note that renamed tests count towards both.
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(09/21/2026 13:40:13 +00:00)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(09/21/2026 13:40:13)
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-09-21T13:35:10.6224849+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:10.6224849+00:00 }, CommitPosition { Position: 0, Sequence: 4, Timestamp: 2026-09-21T13:35:10.6224849+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-09-21T13:35:10.6224849+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:10.6224849+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-09-21T13:35:10.6224849+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:10.6224849+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-09-21T13:35:10.6224849+00:00 }, CommitPosition { Position: 0, Sequence: 8, Timestamp: 2026-09-21T13:35:10.6224849+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:10.6224849+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-09-21T13:35:13.7108596+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:13.7108596+00:00 }, CommitPosition { Position: 0, Sequence: 4, Timestamp: 2026-09-21T13:35:13.7108596+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-09-21T13:35:13.7108596+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:13.7108596+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-09-21T13:35:13.7108596+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:13.7108596+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-09-21T13:35:13.7108596+00:00 }, CommitPosition { Position: 0, Sequence: 8, Timestamp: 2026-09-21T13:35:13.7108596+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:13.7108596+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-09-21T13:35:17.3347072+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:17.3347072+00:00 }, CommitPosition { Position: 0, Sequence: 4, Timestamp: 2026-09-21T13:35:17.3347072+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-09-21T13:35:17.3347072+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:17.3347072+00:00 })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-09-21T13:35:17.3347072+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:17.3347072+00:00 }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-09-21T13:35:17.3347072+00:00 }, CommitPosition { Position: 0, Sequence: 8, Timestamp: 2026-09-21T13:35:17.3347072+00:00 }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-09-21T13:35:17.3347072+00:00 })
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(01/01/2026 12:00:00 +00:00)
Eventuous.Tests.Azure.ServiceBus.IsSerialisableByServiceBus ‑ Passes(01/01/2026 12:00:00)
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-01-01T12:00:00.0000000Z }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-01-01T12:00:00.0000000Z }, CommitPosition { Position: 0, Sequence: 4, Timestamp: 2026-01-01T12:00:00.0000000Z }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-01-01T12:00:00.0000000Z }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-01-01T12:00:00.0000000Z })
Eventuous.Tests.Subscriptions.SequenceTests ‑ ShouldReturnFirstBefore(CommitPosition { Position: 0, Sequence: 1, Timestamp: 2026-01-01T12:00:00.0000000Z }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-01-01T12:00:00.0000000Z }, CommitPosition { Position: 0, Sequence: 6, Timestamp: 2026-01-01T12:00:00.0000000Z }, CommitPosition { Position: 0, Sequence: 8, Timestamp: 2026-01-01T12:00:00.0000000Z }, CommitPosition { Position: 0, Sequence: 2, Timestamp: 2026-01-01T12:00:00.0000000Z })

♻️ This comment has been updated with latest results.

@nmummau nmummau changed the title test(postgres): replace fixed projection delay with polling test: make projection, metrics, and recovery waits deterministic Oct 6, 2026
@nmummau
nmummau force-pushed the fix/postgres-projector-polling branch from 9c1e706 to a301fd8 Compare October 6, 2026 02:42
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit a301fd8

nmummau added a commit to nmummau/eventuous that referenced this pull request Oct 6, 2026
Address Qodo's review finding that checkpoint timeouts could leave
per-test hosts and subscriptions running.

Wrap test operations in try/finally after successful host initialization
so timeouts, cancellation, and assertion failures still call DisposeAsync().
Preserve assertions that must run after host shutdown.

Verified all 9 MongoDB projection tests pass on .NET 10.

Review: Eventuous#603 (comment)
Comment thread src/Postgres/test/Eventuous.Tests.Postgres/Projections/ProjectorTests.cs Outdated
Comment thread src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 87568cb

Poll for projected bookings with a 30-second timeout and respect
cancellation. Assert rows exist before checking their values.

Fixes Eventuous#599
nmummau added a commit to nmummau/eventuous that referenced this pull request Oct 6, 2026
Address Qodo's review finding that checkpoint timeouts could leave
per-test hosts and subscriptions running.

Wrap test operations in try/finally after successful host initialization
so timeouts, cancellation, and assertion failures still call DisposeAsync().
Preserve assertions that must run after host shutdown.

Verified all 9 MongoDB projection tests pass on .NET 10.

Review: Eventuous#603 (comment)
@nmummau
nmummau force-pushed the fix/postgres-projector-polling branch from b88d5db to 7e86a3a Compare October 6, 2026 03:20
nmummau added a commit to nmummau/eventuous that referenced this pull request Oct 6, 2026
Address Qodo's review finding that checkpoint timeouts could leave
per-test hosts and subscriptions running.

Wrap test operations in try/finally after successful host initialization
so timeouts, cancellation, and assertion failures still call DisposeAsync().
Preserve assertions that must run after host shutdown.

Verified all 9 MongoDB projection tests pass on .NET 10.

Review: Eventuous#603 (comment)
@nmummau
nmummau force-pushed the fix/postgres-projector-polling branch from 7e86a3a to ba04816 Compare October 6, 2026 03:21
Comment thread src/SqlServer/test/Eventuous.Tests.SqlServer/Projections/ProjectorTests.cs Outdated
Comment thread src/Mongo/test/Eventuous.Tests.Projections.MongoDB/ProjectionTestBase.cs Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 969dda5

Poll for projected bookings with a 30-second timeout and respect
cancellation. Assert rows exist before checking their values.
Add a 30-second timeout and propagate test cancellation.
Report expected and observed event counts when polling times out.
WaitForPosition previously queried the checkpoint for ProjectWithBuilder
regardless of the fixture's configured subscription ID. Bulk-builder and
typed-handler tests therefore waited on another subscription, potentially
returning too early or exhausting the polling loop unnecessarily.

The helper also silently returned after 100 attempts, allowing document
assertions to run without confirming that the projection had caught up.

Use the fixture's configured subscription ID and replace the attempt limit
with a 30-second timeout. Include the subscription ID, expected position,
and last observed position in the timeout error.

Propagate test cancellation through checkpoint reads, polling delays, and
subsequent document reads. Preserve cancellation exceptions when the test
itself is cancelled.

Validation: all 9 MongoDB tests passed using MongoDB and KurrentDB
Testcontainers.
CI failed Resubscribes_and_redelivers_the_event_the_handler_failed_on
because the subscribed callback count was still 1 when the test expected
at least 2. The logs showed the supervisor waiting for its one-second
retry delay when test teardown stopped the subscription.

The recovery wait checked only whether all events had been handled.
RabbitMQ requeues the failed delivery before notifying the supervisor,
so message recovery can complete before resubscription.

Wait for all expected events, a drop callback, and a second subscribed
callback within the existing 30-second timeout. Preserve external test
cancellation and correct the comment claiming redelivery requires
resubscription.

Validation: all 5 RabbitMQ tests passed locally in Debug CI on net10.0.
CI repeatedly timed out waiting for redelivery after the subscription
reported that it had resubscribed.

Replace polling of handler completion signals with cancellation-aware
task waits bounded to 30 seconds. Add a separate dispatch signal and
phase-specific timeout messages to help locate recovery stalls.

Always dispose subscriptions after assertions fail, and make the
redelivery handler's success gate cancellable during cleanup.

All 143 subscription tests pass on .NET 8 locally. The CI failure has
not been reproduced locally, so its underlying cause remains unconfirmed.
Address Qodo's review finding that checkpoint timeouts could leave
per-test hosts and subscriptions running.

Wrap test operations in try/finally after successful host initialization
so timeouts, cancellation, and assertion failures still call DisposeAsync().
Preserve assertions that must run after host shutdown.

Verified all 9 MongoDB projection tests pass on .NET 10.

Review: Eventuous#603 (comment)
Replace generated GUIDs and current timestamps in Service Bus
serialization and sequence test data with fixed values.

These arguments appear in reported test names, causing CI to count
unchanged cases as removed and added on every run. Use UTC timestamps
to keep the values consistent across time zones.

Verified 26 serialization tests and 5 sequence tests pass.

Fixes Eventuous#604
@nmummau
nmummau force-pushed the fix/postgres-projector-polling branch from 969dda5 to 03d926d Compare October 6, 2026 03:56
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 03d926d

Import Eventuous.Tools and replace ConfigureAwait(false) with NoContext()
in all three projection cleanup blocks to follow repository conventions.

Verified the MongoDB test project builds on .NET 10.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 7c427ff

@nmummau
nmummau force-pushed the fix/postgres-projector-polling branch from 7c427ff to 5a7399b Compare October 6, 2026 04:43
@nmummau

nmummau commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

@alexeyzimarev This is ready for a review and merge when you are ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Test names use guids and dates Flaky Postgres test: ProjectorTests.ProjectImportedBookingsToTable relies on a fixed 1s delay

1 participant