Skip to content

tests: Add ConcurrentCommandGuardExtensions and improve exclusivity#418

Merged
samtrion merged 4 commits into
mainfrom
test/ConcurrentCommandGuardExtensions
Apr 17, 2026
Merged

tests: Add ConcurrentCommandGuardExtensions and improve exclusivity#418
samtrion merged 4 commits into
mainfrom
test/ConcurrentCommandGuardExtensions

Conversation

@samtrion

@samtrion samtrion commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Introduce extension methods for registering ConcurrentCommandGuardInterceptor with IMediatorBuilder, supporting open-generic, closed-generic, and void command overloads. Refactor the interceptor to implement IDisposable for proper SemaphoreSlim disposal and restrict it to IExclusiveCommand only. Update and expand tests to cover registration and deduplication scenarios. Apply minor doc and type improvements, and ensure consistent usage of Extensibility.Void.

Resolves #256

Summary by CodeRabbit

  • New Features

    • Added concurrent command guard extension methods for registering exclusive command execution support in the mediator builder.
  • Refactor

    • Improved concurrent command guard interceptor with refined resource management and instance-level lifecycle handling.
    • Simplified dependency injection registration configuration.
  • Documentation

    • Updated XML documentation references for improved code clarity and consistency.
  • Tests

    • Added comprehensive unit test coverage for concurrent command guard registration.
    • Enhanced integration and unit tests with proper resource disposal patterns.

@samtrion samtrion self-assigned this Apr 17, 2026
@samtrion
samtrion requested a review from a team as a code owner April 17, 2026 11:02
@samtrion
samtrion requested a review from Spacemonkay April 17, 2026 11:02
@samtrion samtrion added state:ready for merge Indicates that a pull request has been reviewed and approved, and is ready to be merged into the mai type:chore Indicates some housework that needs to be done. labels Apr 17, 2026
@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@samtrion has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 44 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 10 minutes and 44 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 412c86d4-b041-4d7f-930b-e174ec73b1ab

📥 Commits

Reviewing files that changed from the base of the PR and between 399dcd9 and f068256.

📒 Files selected for processing (11)
  • src/NetEvolve.Pulse.AspNetCore/EndpointRouteBuilderExtensions.cs
  • src/NetEvolve.Pulse.EntityFramework/Configurations/MySqlOutboxMessageConfiguration.cs
  • src/NetEvolve.Pulse.EntityFramework/EntityFrameworkIdempotencyExtensions.cs
  • src/NetEvolve.Pulse.EntityFramework/ModelBuilderExtensions.cs
  • src/NetEvolve.Pulse.Extensibility/IExclusiveCommand1.cs`
  • src/NetEvolve.Pulse.MySql/MySqlIdempotencyMediatorBuilderExtensions.cs
  • src/NetEvolve.Pulse/ConcurrentCommandGuardExtensions.cs
  • src/NetEvolve.Pulse/Interceptors/ConcurrentCommandGuardInterceptor.cs
  • tests/NetEvolve.Pulse.Tests.Integration/Idempotency/IdempotencyTestsBase.cs
  • tests/NetEvolve.Pulse.Tests.Unit/ConcurrentCommandGuardExtensionsTests.cs
  • tests/NetEvolve.Pulse.Tests.Unit/Interceptors/ConcurrentCommandGuardInterceptorTests.cs

Walkthrough

This PR updates the codebase with XML documentation refinements, introduces a new ConcurrentCommandGuardExtensions public API for registering exclusive command guard interceptors via DI, refactors the ConcurrentCommandGuardInterceptor to constrain exclusively to IExclusiveCommand types with instance-level semaphore management and IDisposable support, and adds comprehensive unit tests for the new extension methods and updated interceptor behavior.

Changes

Cohort / File(s) Summary
Documentation Cleanup
src/NetEvolve.Pulse.AspNetCore/EndpointRouteBuilderExtensions.cs, src/NetEvolve.Pulse.EntityFramework/Configurations/MySqlOutboxMessageConfiguration.cs, src/NetEvolve.Pulse.EntityFramework/ModelBuilderExtensions.cs, src/NetEvolve.Pulse.Extensibility/IExclusiveCommand\1.cs, src/NetEvolve.Pulse.MySql/MySqlIdempotencyMediatorBuilderExtensions.cs`
XML documentation type reference updates (removing System. qualifications, shortening fully qualified names).
DI Configuration Adjustments
src/NetEvolve.Pulse.EntityFramework/EntityFrameworkIdempotencyExtensions.cs, src/NetEvolve.Pulse.AspNetCore/EndpointRouteBuilderExtensions.cs
Minor code adjustments: removed explicit generic type argument from Configure call and from TypedResults.ServerSentEvents invocation.
Concurrent Command Guard — Public API
src/NetEvolve.Pulse/ConcurrentCommandGuardExtensions.cs
New extension class with three IMediatorBuilder methods for registering ConcurrentCommandGuardInterceptor<,> as IRequestInterceptor<,> using open-generic and closed-generic DI patterns with singleton lifetime and duplicate-prevention logic.
Concurrent Command Guard — Interceptor Refactor
src/NetEvolve.Pulse/Interceptors/ConcurrentCommandGuardInterceptor.cs
Significant refactor: interface changed to ICommandInterceptor<TRequest, TResponse> with constraint TRequest : IExclusiveCommand<TResponse>, semaphore storage moved from static dictionary to instance-level, and IDisposable implementation added for proper resource cleanup.
Test Coverage — New Suites
tests/NetEvolve.Pulse.Tests.Unit/ConcurrentCommandGuardExtensionsTests.cs, tests/NetEvolve.Pulse.Tests.Unit/Interceptors/ConcurrentCommandGuardInterceptorTests.cs
New comprehensive unit test files validating extension method registrations, null-guard checks, singleton lifecycle, idempotency of repeated registrations, and interceptor disposal semantics. Removed test case for non-exclusive command pass-through (no longer applicable due to interface constraint).
Test Updates
tests/NetEvolve.Pulse.Tests.Integration/Idempotency/IdempotencyTestsBase.cs
Namespace simplification: changed Extensibility.Void references to unqualified Void in test handler registration and return type.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Exclusive commands now stand guard,
With semaphores once static, now leaned hard—
Instance-bound and cleanly disposed,
New public APIs proudly composed.
Tests aplenty, registrations bright,
The concurrent gate locks down tight! 🔒

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding extension methods for ConcurrentCommandGuardExtensions and improving the interceptor's exclusivity constraints.
Linked Issues check ✅ Passed The PR addresses all requirements from issue #256: adds interceptor unit tests for exclusive/non-exclusive command handling, parallel execution, exception handling, and CancellationToken behavior; implements extension methods for registration; validates singleton and deduplication scenarios.
Out of Scope Changes check ✅ Passed Minor documentation and type reference updates (XML docs, System namespace qualifications, Extensibility.Void standardization) are supporting refactoring changes; the interceptor's IDisposable implementation and constraint changes directly enable the test requirements; all changes are aligned with issue #256 objectives.

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


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.

Introduce extension methods for registering ConcurrentCommandGuardInterceptor with IMediatorBuilder, supporting open-generic, closed-generic, and void command overloads. Refactor the interceptor to implement IDisposable for proper SemaphoreSlim disposal and restrict it to IExclusiveCommand<TResponse> only. Update and expand tests to cover registration and deduplication scenarios. Apply minor doc and type improvements, and ensure consistent usage of Extensibility.Void.

Resolves #256
@samtrion
samtrion force-pushed the test/ConcurrentCommandGuardExtensions branch from 399dcd9 to 7c523e4 Compare April 17, 2026 11:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
tests/NetEvolve.Pulse.Tests.Integration/Idempotency/IdempotencyTestsBase.cs (1)

288-293: Minor inconsistency: line 293 still uses Extensibility.Void.Completed.

Now that Void is unqualified in the signature and registration, consider using Void.Completed here too for consistency.

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

In `@tests/NetEvolve.Pulse.Tests.Integration/Idempotency/IdempotencyTestsBase.cs`
around lines 288 - 293, The HandleAsync implementation in
TestIdempotentVoidCommandHandler currently returns Extensibility.Void.Completed
while the signature and registration use the unqualified Void type; update the
return expression in HandleAsync to use Void.Completed instead to match the rest
of the codebase and maintain consistency for the
ICommandHandler<TestIdempotentVoidCommand, Void> implementation.
tests/NetEvolve.Pulse.Tests.Unit/Interceptors/ConcurrentCommandGuardInterceptorTests.cs (1)

121-127: Max-concurrency tracking is racy and could mask a broken guard.

maxConcurrent is read non-atomically before the CompareExchange, and max = maxConcurrent is re-read from the field rather than the CAS result. If the interceptor ever regressed and allowed concurrent execution, two threads could both observe a stale max and lose an update, weakening this test. Consider a simple Interlocked.Exchange / InterlockedMax-style loop driven off the CAS return value.

♻️ Suggested tightening
-                    var current = Interlocked.Increment(ref currentConcurrent);
-                    var max = maxConcurrent;
-                    while (current > max)
-                    {
-                        _ = Interlocked.CompareExchange(ref maxConcurrent, current, max);
-                        max = maxConcurrent;
-                    }
+                    var current = Interlocked.Increment(ref currentConcurrent);
+                    int max;
+                    do
+                    {
+                        max = Volatile.Read(ref maxConcurrent);
+                        if (current <= max)
+                        {
+                            break;
+                        }
+                    } while (Interlocked.CompareExchange(ref maxConcurrent, current, max) != max);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/NetEvolve.Pulse.Tests.Unit/Interceptors/ConcurrentCommandGuardInterceptorTests.cs`
around lines 121 - 127, The max-concurrency tracking loop is racy because it
reads maxConcurrent non-atomically and then re-reads the field instead of using
the CAS return value; replace the current CompareExchange loop so it uses the
CAS return value (or an InterlockedMax-style loop) to update maxConcurrent
atomically based on the returned previous value from
Interlocked.CompareExchange, referencing the currentConcurrent and maxConcurrent
variables and the Interlocked.CompareExchange call in the
ConcurrentCommandGuardInterceptorTests to ensure no lost updates when multiple
threads try to raise the max.
src/NetEvolve.Pulse/Interceptors/ConcurrentCommandGuardInterceptor.cs (1)

70-93: Optional: simplify the disposal pattern for a sealed type.

Because the class is sealed, the full Dispose(bool disposing) virtual-style pattern with _disposedValue is unnecessary and adds boilerplate/finalizer complexity you don't need. A simpler pattern suffices:

♻️ Proposed simplification
-    private bool _disposedValue;
+    private bool _disposed;
...
-    private void Dispose(bool disposing)
-    {
-        if (!_disposedValue)
-        {
-            if (disposing)
-            {
-                // Dispose managed state (managed objects)
-                foreach (var semaphore in _semaphores.Values)
-                {
-                    semaphore.Dispose();
-                }
-                _semaphores.Clear();
-            }
-
-            _disposedValue = true;
-        }
-    }
-
-    /// <inheritdoc />
-    public void Dispose()
-    {
-        Dispose(disposing: true);
-        GC.SuppressFinalize(this);
-    }
+    /// <inheritdoc />
+    public void Dispose()
+    {
+        if (_disposed)
+        {
+            return;
+        }
+
+        foreach (var semaphore in _semaphores.Values)
+        {
+            semaphore.Dispose();
+        }
+        _semaphores.Clear();
+        _disposed = true;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/NetEvolve.Pulse/Interceptors/ConcurrentCommandGuardInterceptor.cs` around
lines 70 - 93, The disposal pattern is overengineered for a sealed class: remove
the private Dispose(bool disposing) method and the _disposedValue flag, and
replace with a single public Dispose() that iterates _semaphores.Values
disposing each SemaphoreSlim (or semaphore type), clears _semaphores, and calls
GC.SuppressFinalize(this); update any references to Dispose(bool) to call
Dispose() and keep the existing semaphore loop and Clear() logic from the
current Dispose(bool) implementation; leave no finalizer or virtual disposal
hooks since the class is sealed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/NetEvolve.Pulse/ConcurrentCommandGuardExtensions.cs`:
- Around line 26-41: Update the XML summary for the
ConcurrentCommandGuardExtensions method to correct the lifetime description:
replace the word "scoped" with "singleton" and reword the first sentence to
state that it registers an open-generic
ConcurrentCommandGuardInterceptor{TRequest,TResponse} as a singleton
IRequestInterceptor{TRequest,TResponse} for all IExclusiveCommand{TResponse}
implementations; ensure the summary aligns with the remarks and the actual
registration using ServiceLifetime.Singleton and TryAddEnumerable on the
IMediatorBuilder so consumers are not misled.
- Line 104: The registration currently calls
services.TryAdd(ServiceDescriptor.Singleton(typeof(ConcurrentCommandGuardInterceptor<,>)))
which incorrectly registers System.Type; change the registration to register the
open-generic interceptor type as a service implementation (for example use
services.TryAddSingleton(typeof(IRequestInterceptor<,>),
typeof(ConcurrentCommandGuardInterceptor<,>)) or
services.TryAdd(ServiceDescriptor.Singleton(typeof(IRequestInterceptor<,>),
typeof(ConcurrentCommandGuardInterceptor<,>))) so the DI container can resolve
ConcurrentCommandGuardInterceptor<TRequest,TResponse>; update the factory usage
around the existing factory code that resolves the interceptor (the code
referencing ConcurrentCommandGuardInterceptor<,> in the registration block) to
rely on the IRequestInterceptor<TRequest,TResponse> service, and add a unit test
that builds a provider and calls
provider.GetRequiredService<IRequestInterceptor<ExclusiveCommand, string>>() to
prevent regression.
- Around line 83-88: The XML doc is wrong: registering both
AddConcurrentCommandGuard() (open-generic) and
AddConcurrentCommandGuard<TRequest,TResponse>() currently produces two separate
ConcurrentCommandGuardInterceptor instances with independent _semaphores; fix by
changing the typed overload AddConcurrentCommandGuard<TRequest,TResponse>() to
first detect whether an open-generic descriptor for IRequestInterceptor<,> →
ConcurrentCommandGuardInterceptor<,> is already registered in the
IServiceCollection and skip adding the self-mapped descriptor if so (or
alternatively use TryAddEnumerable for the closed
IRequestInterceptor<TRequest,TResponse> to avoid creating a duplicate
singleton); update AddConcurrentCommandGuard<TRequest,TResponse>(),
ConcurrentCommandGuardInterceptor, and related registration logic accordingly or
update the XML docs to reflect the actual two-instance behavior if you prefer
the documented outcome.

In `@src/NetEvolve.Pulse/Interceptors/ConcurrentCommandGuardInterceptor.cs`:
- Around line 40-46: The open-generic
ConcurrentCommandGuardInterceptor<TRequest,TResponse> narrows the generic
constraint to TRequest : IExclusiveCommand<TResponse> whereas
ICommandInterceptor<TRequest,TResponse> only requires TRequest :
ICommand<TResponse>, which can cause DI to skip descriptors for non-exclusive
commands; add an integration test that registers the open-generic
ConcurrentCommandGuardInterceptor<,> (the class name) as IRequestInterceptor<,>
and then resolves IEnumerable<IRequestInterceptor<NonExclusiveCommand,
SomeResponse>> (use a concrete NonExclusiveCommand type that implements
ICommand<SomeResponse> but not IExclusiveCommand) to assert the DI container
returns a pipeline without throwing or missing required interceptors, ensuring
the registration and resolution behavior is validated.

In `@tests/NetEvolve.Pulse.Tests.Unit/ConcurrentCommandGuardExtensionsTests.cs`:
- Around line 170-200: The test
AddConcurrentCommandGuard_Typed_CombinedWithOpenGeneric_DoesNotDuplicateInterfaceRegistrations
should also assert actual resolution uniqueness: after registering both
AddConcurrentCommandGuard() and
AddConcurrentCommandGuard<ExclusiveCommand,string>(), build a service provider
(services.BuildServiceProvider()), resolve
IEnumerable<IRequestInterceptor<ExclusiveCommand,string>> via
provider.GetServices<IRequestInterceptor<ExclusiveCommand,string>>() and assert
that the resulting collection contains a single instance (instance identity), so
the test verifies the open‑generic mapping to
ConcurrentCommandGuardInterceptor<,> and the closed generic factory produce the
same underlying instance rather than two separate interceptor objects.
- Around line 18-24: The test methods
AddConcurrentCommandGuard_WithNullConfigurator_ThrowsArgumentNullException,
AddConcurrentCommandGuard_TypedWithNullConfigurator_..., and
AddConcurrentCommandGuard_VoidWithNullConfigurator_... are declared async but
contain no awaits, causing CS1998; fix each by either removing the async
modifier and returning Task.CompletedTask, or keeping async and adding a minimal
await such as await Task.Yield(); so the signature remains async. Update the
test method declarations (the ones above) and their return statements
accordingly to eliminate the compiler warning while preserving the existing
Assert logic.
- Around line 78-97: Add runtime resolution checks to the typed tests: after
calling ConcurrentCommandGuardExtensions.AddConcurrentCommandGuard<TCommand,
TResponse>() and verifying the service descriptor, call
services.BuildServiceProvider(), resolve the service via
provider.GetRequiredService<IRequestInterceptor<ExclusiveCommand, string>>() and
assert the resolved instance is a
ConcurrentCommandGuardInterceptor<ExclusiveCommand, string> (verifying the typed
overload actually registers a resolvable implementation and matches the
open-generic registration).

---

Nitpick comments:
In `@src/NetEvolve.Pulse/Interceptors/ConcurrentCommandGuardInterceptor.cs`:
- Around line 70-93: The disposal pattern is overengineered for a sealed class:
remove the private Dispose(bool disposing) method and the _disposedValue flag,
and replace with a single public Dispose() that iterates _semaphores.Values
disposing each SemaphoreSlim (or semaphore type), clears _semaphores, and calls
GC.SuppressFinalize(this); update any references to Dispose(bool) to call
Dispose() and keep the existing semaphore loop and Clear() logic from the
current Dispose(bool) implementation; leave no finalizer or virtual disposal
hooks since the class is sealed.

In `@tests/NetEvolve.Pulse.Tests.Integration/Idempotency/IdempotencyTestsBase.cs`:
- Around line 288-293: The HandleAsync implementation in
TestIdempotentVoidCommandHandler currently returns Extensibility.Void.Completed
while the signature and registration use the unqualified Void type; update the
return expression in HandleAsync to use Void.Completed instead to match the rest
of the codebase and maintain consistency for the
ICommandHandler<TestIdempotentVoidCommand, Void> implementation.

In
`@tests/NetEvolve.Pulse.Tests.Unit/Interceptors/ConcurrentCommandGuardInterceptorTests.cs`:
- Around line 121-127: The max-concurrency tracking loop is racy because it
reads maxConcurrent non-atomically and then re-reads the field instead of using
the CAS return value; replace the current CompareExchange loop so it uses the
CAS return value (or an InterlockedMax-style loop) to update maxConcurrent
atomically based on the returned previous value from
Interlocked.CompareExchange, referencing the currentConcurrent and maxConcurrent
variables and the Interlocked.CompareExchange call in the
ConcurrentCommandGuardInterceptorTests to ensure no lost updates when multiple
threads try to raise the max.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7101bf89-886b-4efb-84f1-27f46078a627

📥 Commits

Reviewing files that changed from the base of the PR and between 7811477 and 399dcd9.

📒 Files selected for processing (11)
  • src/NetEvolve.Pulse.AspNetCore/EndpointRouteBuilderExtensions.cs
  • src/NetEvolve.Pulse.EntityFramework/Configurations/MySqlOutboxMessageConfiguration.cs
  • src/NetEvolve.Pulse.EntityFramework/EntityFrameworkIdempotencyExtensions.cs
  • src/NetEvolve.Pulse.EntityFramework/ModelBuilderExtensions.cs
  • src/NetEvolve.Pulse.Extensibility/IExclusiveCommand1.cs`
  • src/NetEvolve.Pulse.MySql/MySqlIdempotencyMediatorBuilderExtensions.cs
  • src/NetEvolve.Pulse/ConcurrentCommandGuardExtensions.cs
  • src/NetEvolve.Pulse/Interceptors/ConcurrentCommandGuardInterceptor.cs
  • tests/NetEvolve.Pulse.Tests.Integration/Idempotency/IdempotencyTestsBase.cs
  • tests/NetEvolve.Pulse.Tests.Unit/ConcurrentCommandGuardExtensionsTests.cs
  • tests/NetEvolve.Pulse.Tests.Unit/Interceptors/ConcurrentCommandGuardInterceptorTests.cs

Comment thread src/NetEvolve.Pulse/ConcurrentCommandGuardExtensions.cs
Comment thread src/NetEvolve.Pulse/ConcurrentCommandGuardExtensions.cs
Comment thread src/NetEvolve.Pulse/ConcurrentCommandGuardExtensions.cs
Comment thread src/NetEvolve.Pulse/Interceptors/ConcurrentCommandGuardInterceptor.cs Outdated
@codecov

codecov Bot commented Apr 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.20%. Comparing base (7811477) to head (f068256).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../Interceptors/ConcurrentCommandGuardInterceptor.cs 75.00% 1 Missing and 1 partial ⚠️
...etEvolve.Pulse/ConcurrentCommandGuardExtensions.cs 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #418      +/-   ##
==========================================
+ Coverage   92.09%   92.20%   +0.11%     
==========================================
  Files         145      146       +1     
  Lines        5475     5494      +19     
  Branches      515      517       +2     
==========================================
+ Hits         5042     5066      +24     
+ Misses        287      281       -6     
- Partials      146      147       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

samtrion and others added 3 commits April 17, 2026 13:49
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Martin Stühmer <me@samtrion.net>
Refactored ConcurrentCommandGuardInterceptor to simplify the Dispose pattern by removing Dispose(bool), renaming _disposedValue to _disposed, and consolidating disposal logic into the public Dispose() method. Now ensures proper resource cleanup and prevents multiple disposals.
Updated three test methods in ConcurrentCommandGuardExtensionsTests.cs to be synchronous, removing unnecessary async/await usage for ArgumentNullException checks. Also added missing using directives for required namespaces.
@samtrion
samtrion merged commit 4aa0ffd into main Apr 17, 2026
12 checks passed
@samtrion
samtrion deleted the test/ConcurrentCommandGuardExtensions branch April 17, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:ready for merge Indicates that a pull request has been reviewed and approved, and is ready to be merged into the mai type:chore Indicates some housework that needs to be done.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test: Unit tests for ConcurrentCommandGuardInterceptor

1 participant