Skip to content

Conversation

@Jaben
Copy link
Member

@Jaben Jaben commented Oct 24, 2025

Summary

This PR adds comprehensive unit tests to validate the MongoDB error handling fix introduced in PR #99, which changed from string-based error matching to using MongoDB error codes (CodeName).

Motivation

PR #99 fixed a critical bug where error handling failed in MongoDB 7.x due to changes in error message format. These tests ensure:

  • The fix works correctly across MongoDB versions
  • Future changes don't break error handling
  • Error codes remain stable (NamespaceExists=48, IndexOptionsConflict=85)

Changes

Test Coverage (12 new tests)

✅ VerifyCollectionExists Error Handling

  • Collection creation when it doesn't exist
  • Graceful handling of existing collections
  • Race condition handling (NamespaceExists error code 48)
  • Collection creation with custom options
  • Idempotency validation

✅ VerifyExpireTTLSetup Error Handling

  • TTL index creation
  • Handling existing indexes with same options
  • Index recreation when options differ (IndexOptionsConflict error code 85)
  • Index removal when expireTTL is null
  • Graceful handling of non-existent index removal
  • Multi-call idempotency

✅ MongoDB Driver Error Code Validation

  • Confirms MongoDB returns CodeName = "NamespaceExists" for error code 48
  • Confirms MongoDB returns CodeName = "IndexOptionsConflict" for error code 85

Dependencies

  • Added NSubstitute 5.3.0 for mocking support
  • Updated GlobalUsings.cs to include NSubstitute

Test Results

✅ All 27 tests pass (15 existing + 12 new)
⏱️ Total execution time: 1.8 seconds

Key Features

Related

Reviewer Notes

All tests use the existing test infrastructure (MongoTestFixture with EphemeralMongo). The tests exercise real MongoDB operations to ensure the error code handling is accurate and reliable.

Summary by CodeRabbit

  • Tests

    • Added comprehensive error-handling tests for MongoDB collection creation, TTL index lifecycle, and MongoDB error-code scenarios to improve reliability.
  • Chores

    • Added NSubstitute to the test project to enhance mocking in tests.
    • Adjusted test workflow to disable package generation during test runs.

Add unit tests for PR #99 to verify MongoDB error code handling works
correctly across different MongoDB versions. Tests validate:

- VerifyCollectionExists handles NamespaceExists (error code 48)
- VerifyExpireTTLSetup handles IndexOptionsConflict (error code 85)
- Race condition handling when collections are created concurrently
- TTL index creation, updates, and removal scenarios
- Error code validation to ensure MongoDB driver compatibility

The tests use the actual MongoDB driver behavior to confirm that
CodeName-based exception filtering is more reliable than string
matching and works across MongoDB versions 4.x through 7.x.

Changes:
- Add NSubstitute 5.3.0 for mocking support
- Add MongoDbHelperErrorHandlingTests.cs with 12 comprehensive tests
- Update GlobalUsings.cs to include NSubstitute

All 27 tests pass (15 existing + 12 new).
@coderabbitai
Copy link

coderabbitai bot commented Oct 24, 2025

Warning

Rate limit exceeded

@Jaben has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 50 seconds before requesting another review.

⌛ 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.

📥 Commits

Reviewing files that changed from the base of the PR and between aeb86f9 and cc044ad.

📒 Files selected for processing (3)
  • .github/workflows/deploy.yml (1 hunks)
  • test/Serilog.Sinks.MongoDB.Tests/MongoDbHelperErrorHandlingTests.cs (1 hunks)
  • test/Serilog.Sinks.MongoDB.Tests/Serilog.Sinks.MongoDB.Tests.csproj (1 hunks)

Walkthrough

The pull request adds comprehensive test coverage for MongoDB error handling in MongoDbHelper. A new test suite with 12 test methods validates collection creation, TTL index management, and exception handling. The NSubstitute mocking library was added as a test dependency to support these tests.

Changes

Cohort / File(s) Summary
Test Infrastructure
test/Serilog.Sinks.MongoDB.Tests/GlobalUsings.cs
Added global using directive for NSubstitute to enable mocking utilities across test suite
Error Handling Test Suite
test/Serilog.Sinks.MongoDB.Tests/MongoDbHelperErrorHandlingTests.cs
New test class with 12 test methods covering collection existence verification, TTL index creation/recreation/removal, handling of race conditions (NamespaceExists errors), and validation of MongoDB error codes and CodeNames (IndexOptionsConflict)
Test Project Dependencies
test/Serilog.Sinks.MongoDB.Tests/Serilog.Sinks.MongoDB.Tests.csproj
Added NSubstitute (v5.3.0) NuGet package reference for mocking support

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The review requires understanding 12 distinct test scenarios with varying MongoDB error handling paths, mock setup across multiple tests, and validation of specific MongoDB error codes (NamespaceExists, IndexOptionsConflict). While the tests follow consistent patterns, each scenario requires individual reasoning about expected behavior and error conditions.

Possibly related PRs

Poem

🐰 Hops of mocks, a TTL delight,
Substitutes shimmer, tests shining bright,
NamespaceExists? IndexConflict too?
Error codes caught—we verify all true! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "test: add comprehensive unit tests for MongoDB error handling (PR #99)" is directly and fully aligned with the main changes in the changeset. The primary modification is the addition of 12 comprehensive unit tests in MongoDbHelperErrorHandlingTests that validate MongoDB error handling across collection existence and TTL index management scenarios. The title accurately captures this main objective, is concise and specific enough for a teammate to understand the primary purpose from a commit history view, and avoids vague terminology or unnecessary noise.

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
test/Serilog.Sinks.MongoDB.Tests/Serilog.Sinks.MongoDB.Tests.csproj (1)

16-16: Optional: mark test-only dependency as PrivateAssets.

Prevents accidental transitive exposure if the test project is referenced elsewhere (rare, but safe).

-      <PackageReference Include="NSubstitute" Version="5.3.0" />
+      <PackageReference Include="NSubstitute" Version="5.3.0" PrivateAssets="all" />
test/Serilog.Sinks.MongoDB.Tests/MongoDbHelperErrorHandlingTests.cs (5)

8-16: Avoid cross-test interference: use NonParallelizable or per-test DB names.

Current constant DB name plus per-test drops risks flaky behavior if tests run in parallel. Two options:

  • Easiest: mark the fixture as non-parallelizable.
  • Better isolation: generate a unique DB name per test (e.g., in SetUp) and use it in GetDatabase and TearDown.
 [TestFixture]
+ [NonParallelizable]
 public class MongoDbHelperErrorHandlingTests
 {
-    private const string MongoDatabaseName = "mongodb-sink-error-handling-tests";
+    // Option A (keep NonParallelizable): keep constant name.
+    private const string MongoDatabaseName = "mongodb-sink-error-handling-tests";
+
+    // Option B (per-test isolation):
+    // private string _dbName = null!;
+    //
+    // [SetUp]
+    // public void SetUp() => _dbName = $"mongodb_sink_tests_{Guid.NewGuid():N}";
+    //
+    // private (MongoClient, IMongoDatabase) GetDatabase()
+    // {
+    //     var mongoClient = new MongoClient(MongoConnectionString);
+    //     return (mongoClient, mongoClient.GetDatabase(_dbName));
+    // }
+    //
+    // [TearDown]
+    // public void Cleanup()
+    // {
+    //     var mongoClient = new MongoClient(MongoConnectionString);
+    //     mongoClient.DropDatabase(_dbName);
+    // }

If your NUnit settings already disable parallelization globally, Option A alone is sufficient. Otherwise prefer Option B.


46-47: Remove redundant DropDatabase calls inside tests.

You already drop the DB in [TearDown]. The extra drops increase runtime and can amplify race risks.

-        mongoClient.DropDatabase(MongoDatabaseName);
+        // Dropped in [TearDown]

Also applies to: 63-64, 84-85, 107-108, 137-138, 164-165, 201-202, 234-235, 250-251, 277-278, 326-327, 374-375


66-85: “Race condition” test doesn’t exercise the catch path; make it truly concurrent.

As written, the early existence check returns before CreateCollection, so no NamespaceExists path is exercised. Drive real concurrency to validate the handler.

-    [Test]
-    public void VerifyCollectionExists_WithRaceCondition_ShouldHandleNamespaceExistsError()
-    {
-        // Arrange
-        var (mongoClient, mongoDatabase) = GetDatabase();
-        var collectionName = $"{MongoCollectionName}_race";
-
-        // Pre-create the collection but the method doesn't know about it
-        // This simulates a race condition where CollectionExists returns false
-        // but the collection gets created before CreateCollection is called
-        mongoDatabase.CreateCollection(collectionName);
-
-        // Act & Assert - Even though it exists, should handle gracefully
-        // The internal check will see it exists and return early,
-        // but if it didn't, the catch block should handle NamespaceExists
-        var act = () => mongoDatabase.VerifyCollectionExists(collectionName);
-        act.Should().NotThrow("Should handle NamespaceExists error code gracefully");
-    }
+    [Test]
+    public void VerifyCollectionExists_WithRaceCondition_ShouldHandleNamespaceExistsError()
+    {
+        // Arrange
+        var (_, mongoDatabase) = GetDatabase();
+        var collectionName = $"{MongoCollectionName}_race";
+
+        // Act: run two concurrent creations to force one CreateCollection to hit NamespaceExists
+        Action a = () => mongoDatabase.VerifyCollectionExists(collectionName);
+        Action b = () => mongoDatabase.VerifyCollectionExists(collectionName);
+
+        // Assert
+        Action act = () => Parallel.Invoke(a, b);
+        act.Should().NotThrow("Concurrent VerifyCollectionExists calls must tolerate NamespaceExists");
+        mongoDatabase.CollectionExists(collectionName).Should().BeTrue();
+    }

132-136: Make TTL comparisons robust to BSON numeric type (int/long/double).

Some servers/drivers serialize expireAfterSeconds as Int64 or Double. Normalize before asserting.

-        ttlIndex!["expireAfterSeconds"].Should().Be((int)expireTtl.TotalSeconds);
+        Convert.ToInt64(ttlIndex!["expireAfterSeconds"].ToDouble())
+            .Should().Be((long)expireTtl.TotalSeconds);
-        ttlIndex!["expireAfterSeconds"].Should().Be((int)newExpireTtl.TotalSeconds,
-            "Index should be recreated with new expiration time");
+        Convert.ToInt64(ttlIndex!["expireAfterSeconds"].ToDouble())
+            .Should().Be((long)newExpireTtl.TotalSeconds,
+                "Index should be recreated with new expiration time");
-        ttlIndexes[0]["expireAfterSeconds"].Should().Be((int)expireTtl.TotalSeconds);
+        Convert.ToInt64(ttlIndexes[0]["expireAfterSeconds"].ToDouble())
+            .Should().Be((long)expireTtl.TotalSeconds);

Also applies to: 197-200, 274-276


88-106: Optional: assert collection options were honored.

You verify existence but not that Capped/MaxSize/MaxDocuments persisted. Consider validating via listCollections filter.

var spec = mongoDatabase.ListCollections(
    new ListCollectionsOptions { Filter = new BsonDocument("name", collectionName) })
    .FirstOrDefault();

spec.Should().NotBeNull();
spec!["options"]["capped"].AsBoolean.Should().BeTrue();
spec["options"]["size"].ToInt64().Should().Be(1024 * 1024);
spec["options"]["max"].ToInt64().Should().Be(1000);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2fc22d and aeb86f9.

📒 Files selected for processing (3)
  • test/Serilog.Sinks.MongoDB.Tests/GlobalUsings.cs (1 hunks)
  • test/Serilog.Sinks.MongoDB.Tests/MongoDbHelperErrorHandlingTests.cs (1 hunks)
  • test/Serilog.Sinks.MongoDB.Tests/Serilog.Sinks.MongoDB.Tests.csproj (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/Serilog.Sinks.MongoDB.Tests/MongoDbHelperErrorHandlingTests.cs (2)
src/Serilog.Sinks.MongoDB/Sinks/MongoDB/MongoDBSinkBase.cs (1)
  • IMongoDatabase (67-87)
src/Serilog.Sinks.MongoDB/Helpers/MongoDbHelpers.cs (3)
  • VerifyCollectionExists (47-65)
  • CollectionExists (33-39)
  • VerifyExpireTTLSetup (67-119)
🔇 Additional comments (1)
test/Serilog.Sinks.MongoDB.Tests/GlobalUsings.cs (1)

16-17: LGTM: global using aligns with new test dependency.

No issues; consistent with csproj changes.

Jaben added 2 commits October 23, 2025 20:51
Update the GitHub Actions workflow to add `-p:GeneratePackageOnBuild=false`
to the dotnet test command. This prevents unnecessary package creation during
CI test runs, improving build performance.

The project has `<GeneratePackageOnBuild>true</GeneratePackageOnBuild>` set,
which causes packages to be created on every build, including test runs.
Packages are still properly created during the "Pack" step for releases.

Benefits:
- Faster CI test execution
- Cleaner build output
- Packages only created when actually needed (Release builds)
Apply all CodeRabbit nitpick suggestions to improve test quality:

1. **Test Isolation**: Add [NonParallelizable] attribute to prevent
   concurrent test execution issues when using shared database name

2. **Dependency Management**: Add PrivateAssets="all" to NSubstitute
   package reference to prevent transitive dependency exposure

3. **Cleanup Optimization**: Remove redundant DropDatabase calls within
   test methods since cleanup is handled by [TearDown]

4. **Cross-Version Compatibility**: Fix TTL assertion type handling to
   support MongoDB's Int64/Double serialization variations across server
   versions by using Convert.ToInt64()

5. **Race Condition Testing**: Improve authenticity of race condition
   test by using Parallel.Invoke() to force genuine concurrent collection
   creation attempts, exercising the actual NamespaceExists error path

All 12 tests continue to pass. These changes improve test robustness,
reduce runtime overhead, and ensure compatibility across MongoDB versions.
@Jaben Jaben merged commit f71a593 into dev Oct 24, 2025
2 checks passed
Jaben added a commit that referenced this pull request Oct 26, 2025
Update version and release notes for v7.2.0 release:

Changes:
- MongoDB v7.x compatibility fix using error codes (#98, #99)
  Thanks to @ntark for the PR!
- Comprehensive unit tests for error handling (#100)
- CI workflow optimization

Updated files:
- CHANGES.md: Added v7.2.0 release notes with contributor attribution
- README.md: Updated "What's New" section and added link to CHANGES.md
- Serilog.Sinks.MongoDB.csproj: Bumped version to 7.2.0, updated
  copyright year to 2025, and updated PackageReleaseNotes
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.

2 participants