V5.0.7/service update#54
Conversation
Greptile SummaryThis PR is a v5.0.7 service update that consolidates package dependencies, refactors NATS/Azure/Amazon components for testability, fixes the
Confidence Score: 5/5Safe to merge; the refactoring is mechanical and well-scoped, the EFCore key-boxing fix is correct, and all testability changes use protected/virtual correctly. The code changes are narrowly scoped — extracting inline SDK calls into overridable factory methods and wrappers without altering any public API surface or observable behavior. The EFCore fix closes a real key-boxing gap for value-type keys. The Microsoft.Extensions.Logging.Abstractions version consolidation mirrors the already-merged Azure.Identity consolidation and is low-risk given the package's additive-only versioning policy. No concurrency, security, or data-integrity regressions were found. Directory.Packages.props — verify the net9 CI build resolves Microsoft.Extensions.Logging.Abstractions 10.0.8 without TFM compatibility errors. Important Files Changed
Class Diagram%%{init: {'theme': 'neutral'}}%%
classDiagram
class AzureQueue~TRequest~ {
#AzureQueue(marshaller, options, serviceClient, client, sendFormatter, receiveFormatter)
#AzureQueue(marshaller, options, sendFormatter, receiveFormatter)
#SendMessageAsync()
#ReceiveMessagesAsync()
}
class AzureEventBus {
+AzureEventBus(marshaller, queueOptions, eventBusOptions)
#AzureEventBus(marshaller, queueOptions, eventBusOptions, queueServiceClient, queueClient, eventGridClient)
+PublishAsync()
+SubscribeAsync()
-FormatCloudEventQueueMessage()
}
class NatsCommandQueue {
+SendAsync()
+ReceiveAsync()
#CreateJetStreamContext()
#PublishMessageAsync()
#CreateConsumerAsync()
#FetchMessagesAsync()
#ReceivedNatsMessage
}
class NatsEventBus {
+PublishAsync()
+SubscribeAsync()
#PublishMessageAsync()
#SubscribeMessagesAsync()
#ReceivedNatsMessage
}
class AmazonMessage~TRequest~ {
#RetrieveMessagesAsync()
#CreateSimpleQueueServiceClient()
}
class AmazonCommandQueue {
+SendAsync()
+GetHealthCheckTarget()
}
class AmazonEventBus {
+PublishAsync()
+GetHealthCheckTarget()
#CreateSimpleNotificationServiceClient()
}
AzureQueue~TRequest~ <|-- AzureEventBus
AmazonMessage~TRequest~ <|-- AmazonCommandQueue
AmazonMessage~TRequest~ <|-- AmazonEventBus
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
Directory.Packages.props:25
**`Microsoft.Extensions.Logging.Abstractions` cross-major-version pin for net9**
The package was previously declared per-TFM (`9.0.15` for net9, `10.0.6` for net10). It is now unconditionally pinned to `10.0.8` for all frameworks. With `CentralPackageTransitivePinningEnabled=true`, net9 builds will resolve all transitive references — including those from `Microsoft.EntityFrameworkCore` 9.0.16, which depends on the 9.0.x series — to 10.0.8.
`Microsoft.Extensions.Logging.Abstractions` is additive-only across major versions, so this is unlikely to cause runtime breakage. However, if the package's net10-only TFMs are chosen over a `netstandard2.0`/`net9.0` target during restore, net9 projects could fail to build. Verify that the net9 CI leg resolves and builds cleanly after this change.
Reviews (9): Last reviewed commit: "♻️ extract jetstream context creation in..." | Re-trigger Greptile |
This patch release resolves the Azure.Identity compatibility issue from v5.0.4 through careful TFM-specific version targeting (1.17.2 for net9, 1.21.0 for net10). Also includes updates to Codebelt and Cuemon utility libraries, NATS client, Microsoft test/logging packages, and DocFX build environment (nginx 1.30.0 to 1.31.0).
Root cause: RabbitMQ 4.x deprecated the transient_nonexcl_queues feature — a queue that is simultaneously non-durable (transient) and non-exclusive. RabbitMqCommandQueueOptions defaulted both Durable and Exclusive to false, producing exactly this combination on every QueueDeclareAsync call. Fix: Changed the default value of Durable to true in RabbitMqCommandQueueOptions. Why Durable = true is the right long-term default: Durable queues survive broker restarts and are the standard/recommended pattern for command queues. If a consumer needs a truly transient queue they can opt-out explicitly, but the non-durable + non-exclusive combination is the pattern RabbitMQ is actively removing. RabbitMqEventBus.SubscribeAsync is unaffected — it calls QueueDeclareAsync() with no arguments, which creates a server-named exclusive queue (the standard fanout subscriber pattern). Exclusive queues are not transient_nonexcl and RabbitMQ has no plans to deprecate them.
Expand test coverage across multiple extension packages and core modules: • EventDriven: Add integration tests, messaging tests (CloudEvents, cryptography, MessageAsyncEnumerable, Acknowledgeable) • Extensions.Dapper: Expand DataSource and QueryOptions tests • Extensions.DependencyInjection: Add ServiceCollection registration tests for Dapper and EFCore.Domain • Extensions.Dispatchers: Add MediatorSync tests and SavvyioOptionsExtensions tests • Extensions.Newtonsoft.Json: Add converter tests (AggregateRootConverter, ValueObjectConverter) and marshaller tests • Extensions.QueueStorage: Add AzureEventBusOptions and expand existing AzureEventBusTest • Extensions.RabbitMQ: Add RabbitMqCommandQueueTest and RabbitMqEventBusTest; update RabbitMqCommandQueueOptionsTest for new Durable default • Extensions.Text.Json: Add DateTime and DateTimeOffset converter tests, MetadataDictionaryConverter tests • Domain.EventSourcing: Add EFCore extension tests and TracedDomainEvent extension tests
Add and enhance test coverage across cloud and ORM extension packages: • Extensions.EFCore: Add DomainEventDispatcher extension tests and expand existing EfCoreDataStore and EfCoreRepository tests • Extensions.NATS: Add NatsCommandQueue tests and NatsEventBus tests; update NatsEventBusOptions and other options tests • Extensions.SimpleQueueService (Amazon): Add AmazonMessage tests and expand existing queue and event bus test coverage
Update LocalStack Docker image version from 4.13.1 to 4.14.0 in both Dockerfile.localstack and docker-compose.yml for integration testing.
| var ta1 = new TracedAccount(id1, providerId, "Name1", "email1@test.com"); | ||
| var ta2 = new TracedAccount(id2, providerId, "Name2", "email2@test.com"); | ||
|
|
||
| sut4.AddRange([ta1, ta2]); |
| var ta2 = new TracedAccount(id2, providerId, "Name2", "email2@test.com"); | ||
|
|
||
| sut4.AddRange([ta1, ta2]); | ||
| await ds.SaveChangesAsync(); |
| sut.Add("TenantId", 42); | ||
| sut["TraceId"] = "abc123"; | ||
|
|
||
| Assert.True(sut.ContainsKey("tenantid")); |
Updated CHANGELOG.md with comprehensive v5.0.7 release notes including: - Full patch release description highlighting Azure.Identity compatibility, RabbitMQ durability correction, and test coverage expansion - Added: 7 bullets on comprehensive test coverage across EventDriven, Dapper, DependencyInjection, Dispatchers, Newtonsoft.Json, QueueStorage, RabbitMQ, Text.Json, and Domain.EventSourcing modules - Changed: 7 bullets on dependency updates (Azure.Identity TFM-specific versions, RabbitMQ Durable default, LocalStack 4.14.0, NATS.Client, Microsoft packages, DocFX nginx, Codebelt/Cuemon libraries) - Fixed: 2 bullets on RabbitMQ transient_nonexcl_queues deprecation and GetByIdAsync parameter handling - Removed: NuGet prompt file cleanup - Updated release date to 2026-05-24 and added [Unreleased] compare link
Add detailed release notes for v5.0.7 across all affected packages: - RabbitMQ: Document Durable default change (false→true) and RabbitMQ 4.x deprecation compliance - EFCore: Document GetByIdAsync parameter handling fix - QueueStorage: Detail Azure.Identity TFM-specific versioning (1.17.2 for .NET 9, 1.21.0 for .NET 10) - NATS packages: Highlight NATS.Client upgrade to 2.8.0 for stability - DapperExtensions: Call out Dapper and DapperExtensions version bumps All packages document ALM (Application Lifecycle Management) changes with dependency upgrade specifics. Release is patch-level maintenance with focus on dependency compatibility, test coverage expansion, and API behavior improvements.
… (see if official can replace ours)
…heory tests EF Core caches the compiled model statically per DbContext type. When another test (AddRange or GetByIdAsync) shares the same EfCoreDbContext<JsonMarshaller> type, EF Core reuses the cached service provider and skips OnModelCreating for subsequent instances. This caused the ModelConstructor lambda never to run, leaving 'schema' null and throwing NullReferenceException at runtime. Fix: add EnableServiceProviderCaching(false) to the ContextConfigurator for both theory test cases (Newtonsoft and JsonMarshaller). This forces EF Core to build a fresh service provider for each DbContext instance, ensuring OnModelCreating is always called and 'schema' is always assigned. Root cause was only observable on Linux (ARM64 and X64 Debug) and Windows Release due to test execution order differences across platforms/frameworks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| } | ||
|
|
||
| Assert.NotNull(createAccountMessage); | ||
| var receivedCommand = Assert.IsType<CreateAccount>(createAccountMessage.Data); |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #54 +/- ##
===========================================
+ Coverage 79.06% 98.40% +19.34%
===========================================
Files 177 177
Lines 3711 3773 +62
Branches 362 363 +1
===========================================
+ Hits 2934 3713 +779
+ Misses 776 58 -718
- Partials 1 2 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
NatsCommandQueue: extracted PublishMessageAsync, CreateConsumerAsync, CreateJetStreamContext, and FetchMessagesAsync as protected virtual methods. Introduced ReceivedNatsMessage inner class to wrap NatsJSMsg<string> and decouple the acknowledge callback from the NATS SDK type directly. NatsEventBus: extracted PublishMessageAsync and SubscribeMessagesAsync as protected virtual methods. Added ReceivedNatsMessage inner class to carry headers and data from the NATS subscription loop without exposing the SDK NatsMsg type directly. NatsCommandQueueTest: added SendAsync_ShouldPublishSerializedMessages, SendAsync_ShouldUseJetStreamContext_WhenPublishingMessage, ReceiveAsync_ShouldDeserializeFetchedMessagesAndAcknowledge, and ReceiveAsync_ShouldUseJetStreamContext_WhenCreatingConsumer using Testable and Context subclasses backed by Moq. NatsEventBusTest: added PublishAsync_ShouldPublishSerializedMessage and SubscribeAsync_ShouldDeserializeMessagesAndInvokeHandler using TestableNatsEventBus. Added Moq package reference to Savvyio.Extensions.NATS.Tests.csproj.
…ge for testability AzureQueue<TRequest>: added a protected constructor that accepts QueueServiceClient and QueueClient directly, allowing subclasses to inject test doubles without hitting real Azure endpoints. The new constructor calls options.SetConfiguredClient so the same client lifecycle applies as the public path. AzureEventBus: added a protected constructor accepting QueueServiceClient, QueueClient, and EventGridPublisherClient for the same reason. Extracted the inline CloudEvent receive formatter lambda into a private static FormatCloudEventQueueMessage method so it is reused between the public and protected constructor paths without duplication. AzureCommandQueueTest: added SendMessageAsync_ShouldSendFormattedMessages and ReceiveMessagesAsync_ShouldYieldMessagesAndDeleteOnAcknowledge using a TestAzureQueue subclass backed by a mocked QueueClient. AzureEventBusTest: added corresponding publish and subscribe tests using a TestAzureEventBus subclass with mocked Azure clients.
…estability AmazonMessage<TRequest>: extracted inline AmazonSQSClient construction into a protected virtual CreateSimpleQueueServiceClient method. All receive paths in RetrieveMessagesAsync now call this single factory instead of repeating the credential/endpoint branching logic. AmazonCommandQueue: replaced two separate inline AmazonSQSClient constructions in SendAsync and GetHealthCheckTarget with calls to the inherited CreateSimpleQueueServiceClient, removing the duplication entirely. AmazonEventBus: extracted AmazonSimpleNotificationServiceClient construction into a protected virtual CreateSimpleNotificationServiceClient method. Both PublishAsync and GetHealthCheckTarget now delegate to this factory, eliminating repeated credential/endpoint branching. AmazonCommandQueueTest: added SendAsync_ShouldSendMessagesInBatches (verifies 11 messages split into two SQS batches with correct entry count and message attributes) and ReceiveAsync_ShouldDeserializeMessagesAndDeleteAcknowledgedMessages using a TestableAmazonCommandQueue subclass backed by a mocked IAmazonSQS. AmazonEventBusTest: added corresponding publish and subscribe tests using a TestableAmazonEventBus subclass backed by a mocked IAmazonSimpleNotificationService.
CHANGELOG.md: Updated v5.0.7 release highlight and Added/Changed sections to document protected virtual methods and constructors now exposed in NATS, Azure QueueStorage, and Amazon SQS/SNS extensions for improved testability and extensibility. Updated release date to 2026-05-26. NuGet Release Notes: Added Improvements sections to Savvyio.Extensions.NATS, Savvyio.Extensions.QueueStorage, and Savvyio.Extensions.SimpleQueueService packages documenting the new protected virtual methods, constructors, factory methods, and inner classes exposed for testability.
Azure.Identity consolidated to 1.21.0 for all target frameworks (previously split as 1.17.2 for net9, 1.21.0 for net10) to resolve transitive dependency conflicts introduced in v5.0.4
Extract CreateJetStreamContext() outside the message loop to avoid redundant allocations per iteration. Pass the context as a parameter to PublishMessageAsync instead of creating a new context each time.
| var deserialized = Marshaller.Deserialize(await message.Data.FromBase64().ToStreamAsync().ConfigureAwait(false), messageType) as IMessage<ICommand>; | ||
| deserialized!.Properties.Add(nameof(CancellationToken), options.CancellationToken); | ||
| deserialized.Properties.Add(nameof(NatsJSMsg<string>), message); | ||
| deserialized.Properties.Add(nameof(ReceivedNatsMessage), message); |
|

This is a service update that focuses on package dependencies.
Automated changes:
Note: Third-party packages (Microsoft.Extensions.*, BenchmarkDotNet, etc.) are not auto-updated.
Use Dependabot or manual updates for those.
Generated by codebelt-aicia
Triggered by: yamldotnet @ 10.1.3