Skip to content

Cover the provider outbox paths with integration tests and fix the store robustness gaps they exposed#323

Merged
Vulthil merged 1 commit into
mainfrom
fix/outbox-provider-coverage-and-robustness
Jul 18, 2026
Merged

Cover the provider outbox paths with integration tests and fix the store robustness gaps they exposed#323
Vulthil merged 1 commit into
mainfrom
fix/outbox-provider-coverage-and-robustness

Conversation

@Vulthil

@Vulthil Vulthil commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Tests-first: this PR first adds provider-level integration coverage for every outbox path that had none (only Npgsql was exercised end-to-end), then fixes what the new tests proved broken.

New coverage (24 tests in Vulthil.IntegrationTests, running against real containers on the shared assembly host):

  • MySQL end-to-end on mysql:8.4: domain-event capture on SaveChangesAsyncFOR UPDATE SKIP LOCKED relay fetch → set-based marks → dead-letter at MaxRetries → retention sweep, plus dispatch order and the 256-char Type cap (rejects, never truncates). First real-MySQL execution of the dialect SQL and the net10 ServerVersion.AutoDetect path.
  • Cosmos outbox on the emulator (the suite previously covered the Cosmos inbox only): capture → relay on a container created by EnsureCreated with default indexing, dead-letter through the base update path, retention. This settles a suspected defect: the inherited two-property ORDER BY (OccurredOnUtc, Id) was believed to fail every poll with 400 on default indexing — it does not; the query runs fine on the emulator. No Cosmos code change; the tests pin the behavior, and the package docs now note the composite index to add if the Azure service (documented as requiring composite indexes for multi-property ORDER BY) ever rejects the relay query.
  • Relational store overrides on PostgreSQL (reference dialect): two concurrent stores claim disjoint batches via SKIP LOCKED (no double-dispatch), set-based success/failure recording, dead-letter, deterministic dispatch order.
  • Renamed-model relays on both dialects, and registration-shape tests for store selection on all three providers.

Fixes (each observed failing before, passing after):

  • Provider fetch SQL hardcoded FROM "OutboxMessages" and default column names; a model with a naming convention or custom mapping broke the relay at runtime (42P01 / table-not-found reproduced). The fetch SQL is now composed from the EF model's mapped identifiers via ISqlGenerationHelper, and the composed query carries an explicit outer ORDER BY (OccurredOnUtc, Id), making dispatch order guaranteed rather than incidental.
  • RelationalOutboxStore.DeleteProcessedAsync ignored batchSize (one unbounded ExecuteDelete; a first sweep over a big backlog was one giant locking delete). It now deletes the oldest ≤batchSize rows by key per call, matching the IOutboxRetentionStore contract and the relational inbox store's existing pattern (which already complied).
  • The base UpdateMessagesAsync issued a query+save per failure plus a dead-letter re-query pass; it now fetches all affected rows once and saves once (only non-relational stores hit this path — covered by the Cosmos tests).
  • Provider Use* extensions eagerly overwrote a store the user selected with UseOutboxStore when called after it. Store selection now defers to OnConfigured and applies only as a default, so chaining order is genuinely irrelevant — as the docs already promised. Adds one public member: DatabaseInfrastructureConfigurator<TDbContext>.OutboxStoreCustomized (Unshipped).
  • CosmosOutboxStore returned an internal NoOpDbTransaction; it now returns null, which the base batch unit already handles. The class is deleted.

Docs: provider articles updated (identifier support, ordering guarantee, store-selection precedence), the Cosmos article's incorrect "transactional batches" description replaced with the actual relay behavior plus the indexing note, and the retention article now states the per-batch delete bound.

Verification

  • Full Release build, net9 + net10, zero warnings; PublicAPI analyzers green with the single Unshipped entry.
  • New integration suite verified red-then-green: retention-batch and renamed-model tests failed on the unfixed stores exactly as predicted; registration-order tests failed for all three providers; all pass after the fixes. The Cosmos relay tests passed before and after (no fix needed).
  • Full Vulthil.IntegrationTests run (33 tests: 24 new + 9 pre-existing): all pass in ≈15.5 s wall on net10 with warm images — the three containers (PostgreSQL, MySQL, Cosmos emulator) start lazily and are shared across classes. Registration tests also verified on net9.
  • Existing outbox suites all green post-change: Vulthil.SharedKernel.Outbox.Tests, Vulthil.SharedKernel.Outbox.EntityFrameworkCore.Tests, Vulthil.Messaging.Outbox.Tests, Vulthil.SharedKernel.Infrastructure.Relational.Tests.

Backport to v1.0: no — fix-forward to 1.1.0; no 1.0.x consumers use the affected providers, and the label can be applied retroactively if one appears.

…preserve custom outbox stores; cover the provider outbox paths with integration tests
@Vulthil
Vulthil merged commit 131e966 into main Jul 18, 2026
6 checks passed
@Vulthil
Vulthil deleted the fix/outbox-provider-coverage-and-robustness branch July 18, 2026 21:34
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.

1 participant