Fix audit findings for coverage, architecture checks, and XML docs
All checks were successful
NuGet Publish / build-and-pack (push) Successful in 45s
NuGet Publish / publish-to-gitea (push) Successful in 52s

This commit is contained in:
Joseph Doherty
2026-02-20 15:43:25 -05:00
parent 5528806518
commit 3ffd468c79
99 changed files with 23746 additions and 9548 deletions

View File

@@ -16,6 +16,9 @@ namespace ZB.MOM.WW.CBDD.Tests
private readonly string _dbPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes test database state used by advanced query tests.
/// </summary>
public AdvancedQueryTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_advanced_{Guid.NewGuid()}.db");
@@ -30,12 +33,18 @@ namespace ZB.MOM.WW.CBDD.Tests
_db.SaveChanges();
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_db.Dispose();
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Verifies grouping by a simple key returns expected groups and counts.
/// </summary>
[Fact]
public void GroupBy_Simple_Key_Works()
{
@@ -57,6 +66,9 @@ namespace ZB.MOM.WW.CBDD.Tests
groupC.Count().ShouldBe(1);
}
/// <summary>
/// Verifies grouped projection with aggregation returns expected totals.
/// </summary>
[Fact]
public void GroupBy_With_Aggregation_Select()
{
@@ -77,6 +89,9 @@ namespace ZB.MOM.WW.CBDD.Tests
results[2].Total.ShouldBe(50); // 50
}
/// <summary>
/// Verifies direct aggregate operators return expected values.
/// </summary>
[Fact]
public void Aggregations_Direct_Works()
{
@@ -89,6 +104,9 @@ namespace ZB.MOM.WW.CBDD.Tests
query.Max(x => x.Amount).ShouldBe(50);
}
/// <summary>
/// Verifies aggregate operators with predicates return expected values.
/// </summary>
[Fact]
public void Aggregations_With_Predicate_Works()
{
@@ -98,6 +116,9 @@ namespace ZB.MOM.WW.CBDD.Tests
query.Sum(x => x.Amount).ShouldBe(30);
}
/// <summary>
/// Verifies in-memory join query execution returns expected rows.
/// </summary>
[Fact]
public void Join_Works_InMemory()
{
@@ -126,6 +147,9 @@ namespace ZB.MOM.WW.CBDD.Tests
}
/// <summary>
/// Verifies projection of nested object properties works.
/// </summary>
[Fact]
public void Select_Project_Nested_Object()
{
@@ -152,6 +176,9 @@ namespace ZB.MOM.WW.CBDD.Tests
query[0].Street.ShouldBe("5th Ave");
}
/// <summary>
/// Verifies projection of nested scalar fields works.
/// </summary>
[Fact]
public void Select_Project_Nested_Field()
{
@@ -172,6 +199,9 @@ namespace ZB.MOM.WW.CBDD.Tests
cities[0].ShouldBe("New York");
}
/// <summary>
/// Verifies anonymous projection including nested values works.
/// </summary>
[Fact]
public void Select_Anonymous_Complex()
{
@@ -196,6 +226,9 @@ namespace ZB.MOM.WW.CBDD.Tests
result[0].City.Name.ShouldBe("New York");
}
/// <summary>
/// Verifies projection and retrieval of nested arrays of objects works.
/// </summary>
[Fact]
public void Select_Project_Nested_Array_Of_Objects()
{

View File

@@ -15,6 +15,9 @@ public class ArchitectureFitnessTests
private const string SourceGeneratorsProject = "src/CBDD.SourceGenerators/ZB.MOM.WW.CBDD.SourceGenerators.csproj";
private const string FacadeProject = "src/CBDD/ZB.MOM.WW.CBDD.csproj";
/// <summary>
/// Executes Solution_DependencyGraph_ShouldRemainAcyclic_AndFollowLayerDirection.
/// </summary>
[Fact]
public void Solution_DependencyGraph_ShouldRemainAcyclic_AndFollowLayerDirection()
{
@@ -40,6 +43,9 @@ public class ArchitectureFitnessTests
.ShouldBeFalse("Project references must remain acyclic.");
}
/// <summary>
/// Executes HighLevelCollectionApi_ShouldNotExpandRawBsonReaderWriterSurface.
/// </summary>
[Fact]
public void HighLevelCollectionApi_ShouldNotExpandRawBsonReaderWriterSurface()
{
@@ -65,6 +71,9 @@ public class ArchitectureFitnessTests
dbContextOffenders.ShouldBeEmpty();
}
/// <summary>
/// Executes CollectionAndIndexOrchestration_ShouldUseStoragePortInternally.
/// </summary>
[Fact]
public void CollectionAndIndexOrchestration_ShouldUseStoragePortInternally()
{

View File

@@ -11,17 +11,26 @@ public class AsyncTests : IDisposable
{
private readonly string _dbPath;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncTests"/> class.
/// </summary>
public AsyncTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_async_{Guid.NewGuid()}.db");
}
/// <summary>
/// Executes Dispose.
/// </summary>
public void Dispose()
{
if (File.Exists(_dbPath)) File.Delete(_dbPath);
if (File.Exists(Path.ChangeExtension(_dbPath, ".wal"))) File.Delete(Path.ChangeExtension(_dbPath, ".wal"));
}
/// <summary>
/// Executes Async_Transaction_Commit_Should_Persist_Data.
/// </summary>
[Fact]
public async Task Async_Transaction_Commit_Should_Persist_Data()
{
@@ -48,6 +57,9 @@ public class AsyncTests : IDisposable
doc2.Name.ShouldBe("Async2");
}
/// <summary>
/// Executes Async_Transaction_Rollback_Should_Discard_Data.
/// </summary>
[Fact]
public async Task Async_Transaction_Rollback_Should_Discard_Data()
{
@@ -63,6 +75,9 @@ public class AsyncTests : IDisposable
doc.ShouldBeNull();
}
/// <summary>
/// Executes Bulk_Async_Insert_Should_Persist_Data.
/// </summary>
[Fact]
public async Task Bulk_Async_Insert_Should_Persist_Data()
{
@@ -78,6 +93,9 @@ public class AsyncTests : IDisposable
doc50.Name.ShouldBe("Bulk50");
}
/// <summary>
/// Executes Bulk_Async_Update_Should_Persist_Changes.
/// </summary>
[Fact]
public async Task Bulk_Async_Update_Should_Persist_Changes()
{
@@ -102,6 +120,9 @@ public class AsyncTests : IDisposable
doc50.Name.ShouldBe("Updated50");
}
/// <summary>
/// Executes High_Concurrency_Async_Commits.
/// </summary>
[Fact]
public async Task High_Concurrency_Async_Commits()
{

View File

@@ -13,6 +13,9 @@ namespace ZB.MOM.WW.CBDD.Tests
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, ushort> _keyMap = new(StringComparer.OrdinalIgnoreCase);
private readonly System.Collections.Concurrent.ConcurrentDictionary<ushort, string> _keys = new();
/// <summary>
/// Initializes lookup maps used by attribute mapper tests.
/// </summary>
public AttributeTests()
{
ushort id = 1;
@@ -25,6 +28,9 @@ namespace ZB.MOM.WW.CBDD.Tests
}
}
/// <summary>
/// Verifies table attribute mapping resolves the expected collection name.
/// </summary>
[Fact]
public void Test_Table_Attribute_Mapping()
{
@@ -33,6 +39,9 @@ namespace ZB.MOM.WW.CBDD.Tests
mapper.CollectionName.ShouldBe("test.custom_users");
}
/// <summary>
/// Verifies required attribute validation is enforced.
/// </summary>
[Fact]
public void Test_Required_Validation()
{
@@ -52,6 +61,9 @@ namespace ZB.MOM.WW.CBDD.Tests
thrown.ShouldBeTrue("Should throw ValidationException for empty Name.");
}
/// <summary>
/// Verifies string length attribute validation is enforced.
/// </summary>
[Fact]
public void Test_StringLength_Validation()
{
@@ -69,6 +81,9 @@ namespace ZB.MOM.WW.CBDD.Tests
thrown.ShouldBeTrue("Should throw ValidationException for Name too long.");
}
/// <summary>
/// Verifies range attribute validation is enforced.
/// </summary>
[Fact]
public void Test_Range_Validation()
{
@@ -81,6 +96,9 @@ namespace ZB.MOM.WW.CBDD.Tests
thrown.ShouldBeTrue("Should throw ValidationException for Age out of range.");
}
/// <summary>
/// Verifies column attribute maps to the expected BSON field name.
/// </summary>
[Fact]
public void Test_Column_Name_Mapping()
{
@@ -108,6 +126,9 @@ namespace ZB.MOM.WW.CBDD.Tests
foundDisplayName.ShouldBeTrue("BSON field name should be 'display_name' from [Column] attribute.");
}
/// <summary>
/// Verifies not-mapped attribute excludes properties from BSON serialization.
/// </summary>
[Fact]
public void Test_NotMapped_Attribute()
{

View File

@@ -6,16 +6,25 @@ namespace ZB.MOM.WW.CBDD.Tests
{
private const string DbPath = "autoinit.db";
/// <summary>
/// Initializes a new instance of the <see cref="AutoInitTests"/> class.
/// </summary>
public AutoInitTests()
{
if (File.Exists(DbPath)) File.Delete(DbPath);
}
/// <summary>
/// Releases test resources.
/// </summary>
public void Dispose()
{
if (File.Exists(DbPath)) File.Delete(DbPath);
}
/// <summary>
/// Verifies generated collection initializers set up collections automatically.
/// </summary>
[Fact]
public void Collections_Are_Initialized_By_Generator()
{

View File

@@ -5,6 +5,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class BTreeDeleteUnderflowTests
{
/// <summary>
/// Executes Delete_HeavyWorkload_Should_Remain_Queryable_After_Merges.
/// </summary>
[Fact]
public void Delete_HeavyWorkload_Should_Remain_Queryable_After_Merges()
{

View File

@@ -8,6 +8,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class BsonDocumentAndBufferWriterTests
{
/// <summary>
/// Verifies BSON document creation and typed retrieval roundtrip.
/// </summary>
[Fact]
public void BsonDocument_Create_And_TryGet_RoundTrip()
{
@@ -42,6 +45,9 @@ public class BsonDocumentAndBufferWriterTests
reader.ReadDocumentSize().ShouldBeGreaterThan(0);
}
/// <summary>
/// Verifies typed getters return false for missing fields and type mismatches.
/// </summary>
[Fact]
public void BsonDocument_TryGet_Should_Return_False_For_Missing_Or_Wrong_Type()
{
@@ -64,6 +70,9 @@ public class BsonDocumentAndBufferWriterTests
wrapped.TryGetObjectId("age", out _).ShouldBeFalse();
}
/// <summary>
/// Verifies the BSON document builder grows its internal buffer for large documents.
/// </summary>
[Fact]
public void BsonDocumentBuilder_Should_Grow_Buffer_When_Document_Is_Large()
{
@@ -90,6 +99,9 @@ public class BsonDocumentAndBufferWriterTests
value.ShouldBe(180);
}
/// <summary>
/// Verifies BSON buffer writer emits expected nested document and array layout.
/// </summary>
[Fact]
public void BsonBufferWriter_Should_Write_Nested_Document_And_Array()
{
@@ -151,6 +163,9 @@ public class BsonDocumentAndBufferWriterTests
reader.ReadBsonType().ShouldBe(BsonType.EndOfDocument);
}
/// <summary>
/// Verifies single-byte and C-string span reads operate correctly.
/// </summary>
[Fact]
public void BsonSpanReader_ReadByte_And_ReadCStringSpan_Should_Work()
{

View File

@@ -11,12 +11,30 @@ public class BsonSchemaTests
{
public class SimpleEntity
{
/// <summary>
/// Gets or sets the identifier.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the age.
/// </summary>
public int Age { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is active.
/// </summary>
public bool IsActive { get; set; }
}
/// <summary>
/// Verifies schema generation for a simple entity.
/// </summary>
[Fact]
public void GenerateSchema_SimpleEntity()
{
@@ -37,10 +55,20 @@ public class BsonSchemaTests
public class CollectionEntity
{
/// <summary>
/// Gets or sets tags.
/// </summary>
public List<string> Tags { get; set; } = new();
/// <summary>
/// Gets or sets scores.
/// </summary>
public int[] Scores { get; set; } = Array.Empty<int>();
}
/// <summary>
/// Verifies schema generation for collection fields.
/// </summary>
[Fact]
public void GenerateSchema_Collections()
{
@@ -57,9 +85,15 @@ public class BsonSchemaTests
public class NestedEntity
{
/// <summary>
/// Gets or sets the parent entity.
/// </summary>
public SimpleEntity Parent { get; set; } = new();
}
/// <summary>
/// Verifies schema generation for nested document fields.
/// </summary>
[Fact]
public void GenerateSchema_Nested()
{
@@ -73,9 +107,15 @@ public class BsonSchemaTests
public class ComplexCollectionEntity
{
/// <summary>
/// Gets or sets items.
/// </summary>
public List<SimpleEntity> Items { get; set; } = new();
}
/// <summary>
/// Verifies schema generation for collections of complex types.
/// </summary>
[Fact]
public void GenerateSchema_ComplexCollection()
{

View File

@@ -9,6 +9,9 @@ public class BsonSpanReaderWriterTests
private readonly ConcurrentDictionary<string, ushort> _keyMap = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<ushort, string> _keys = new();
/// <summary>
/// Initializes a new instance of the <see cref="BsonSpanReaderWriterTests"/> class.
/// </summary>
public BsonSpanReaderWriterTests()
{
ushort id = 1;
@@ -21,6 +24,9 @@ public class BsonSpanReaderWriterTests
}
}
/// <summary>
/// Tests write and read simple document.
/// </summary>
[Fact]
public void WriteAndRead_SimpleDocument()
{
@@ -65,6 +71,9 @@ public class BsonSpanReaderWriterTests
value3.ShouldBeTrue();
}
/// <summary>
/// Tests write and read object id.
/// </summary>
[Fact]
public void WriteAndRead_ObjectId()
{
@@ -90,6 +99,9 @@ public class BsonSpanReaderWriterTests
readOid.ShouldBe(oid);
}
/// <summary>
/// Tests read write double.
/// </summary>
[Fact]
public void ReadWrite_Double()
{
@@ -108,6 +120,9 @@ public class BsonSpanReaderWriterTests
val.ShouldBe(123.456);
}
/// <summary>
/// Tests read write decimal128 round trip.
/// </summary>
[Fact]
public void ReadWrite_Decimal128_RoundTrip()
{
@@ -127,6 +142,9 @@ public class BsonSpanReaderWriterTests
val.ShouldBe(original);
}
/// <summary>
/// Tests write and read date time.
/// </summary>
[Fact]
public void WriteAndRead_DateTime()
{
@@ -155,6 +173,9 @@ public class BsonSpanReaderWriterTests
readTime.ShouldBe(expectedTime);
}
/// <summary>
/// Tests write and read numeric types.
/// </summary>
[Fact]
public void WriteAndRead_NumericTypes()
{
@@ -185,6 +206,9 @@ public class BsonSpanReaderWriterTests
Math.Round(reader.ReadDouble(), 5).ShouldBe(Math.Round(3.14159, 5));
}
/// <summary>
/// Tests write and read binary.
/// </summary>
[Fact]
public void WriteAndRead_Binary()
{
@@ -211,6 +235,9 @@ public class BsonSpanReaderWriterTests
testData.AsSpan().SequenceEqual(readData).ShouldBeTrue();
}
/// <summary>
/// Tests write and read nested document.
/// </summary>
[Fact]
public void WriteAndRead_NestedDocument()
{

View File

@@ -15,6 +15,9 @@ public class BulkOperationsTests : IDisposable
private readonly string _walPath;
private readonly Shared.TestDbContext _dbContext;
/// <summary>
/// Initializes a new instance of the <see cref="BulkOperationsTests"/> class.
/// </summary>
public BulkOperationsTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"test_bulk_{Guid.NewGuid()}.db");
@@ -23,11 +26,17 @@ public class BulkOperationsTests : IDisposable
_dbContext = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Executes Dispose.
/// </summary>
public void Dispose()
{
_dbContext.Dispose();
}
/// <summary>
/// Executes UpdateBulk_UpdatesMultipleDocuments.
/// </summary>
[Fact]
public void UpdateBulk_UpdatesMultipleDocuments()
{
@@ -64,6 +73,9 @@ public class BulkOperationsTests : IDisposable
}
}
/// <summary>
/// Executes DeleteBulk_RemovesMultipleDocuments.
/// </summary>
[Fact]
public void DeleteBulk_RemovesMultipleDocuments()
{
@@ -103,6 +115,9 @@ public class BulkOperationsTests : IDisposable
_dbContext.Users.FindAll().Count().ShouldBe(50);
}
/// <summary>
/// Executes DeleteBulk_WithTransaction_Rollworks.
/// </summary>
[Fact]
public void DeleteBulk_WithTransaction_Rollworks()
{

View File

@@ -13,12 +13,18 @@ public class CdcScalabilityTests : IDisposable
private readonly Shared.TestDbContext _db;
private readonly string _dbPath;
/// <summary>
/// Initializes a new instance of the <see cref="CdcScalabilityTests"/> class.
/// </summary>
public CdcScalabilityTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cdc_scaling_{Guid.NewGuid()}.db");
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Verifies CDC dispatch reaches all registered subscribers.
/// </summary>
[Fact]
public async Task Test_Cdc_1000_Subscribers_Receive_Events()
{
@@ -55,6 +61,9 @@ public class CdcScalabilityTests : IDisposable
foreach (var sub in subscriptions) sub.Dispose();
}
/// <summary>
/// Verifies a slow subscriber does not block other subscribers.
/// </summary>
[Fact(Skip = "Performance test - run manually when needed")]
public async Task Test_Cdc_Slow_Subscriber_Does_Not_Block_Others()
{
@@ -99,6 +108,9 @@ public class CdcScalabilityTests : IDisposable
slowEventCount.ShouldBe(2);
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_db.Dispose();

View File

@@ -21,11 +21,17 @@ public class CdcTests : IDisposable
private readonly string _dbPath = $"cdc_test_{Guid.NewGuid()}.db";
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="CdcTests"/> class.
/// </summary>
public CdcTests()
{
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Verifies that an insert operation publishes a CDC event.
/// </summary>
[Fact]
public async Task Test_Cdc_Basic_Insert_Fires_Event()
{
@@ -47,6 +53,9 @@ public class CdcTests : IDisposable
snapshot[0].Entity!.Name.ShouldBe("John");
}
/// <summary>
/// Verifies payload is omitted when CDC capture payload is disabled.
/// </summary>
[Fact]
public async Task Test_Cdc_No_Payload_When_Not_Requested()
{
@@ -65,6 +74,9 @@ public class CdcTests : IDisposable
snapshot[0].Entity.ShouldBeNull();
}
/// <summary>
/// Verifies CDC events are published only for committed changes.
/// </summary>
[Fact]
public async Task Test_Cdc_Commit_Only()
{
@@ -95,6 +107,9 @@ public class CdcTests : IDisposable
snapshot[0].DocumentId.ShouldBe(2);
}
/// <summary>
/// Verifies update and delete operations publish CDC events.
/// </summary>
[Fact]
public async Task Test_Cdc_Update_And_Delete()
{
@@ -125,6 +140,9 @@ public class CdcTests : IDisposable
snapshot[2].DocumentId.ShouldBe(1);
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_db.Dispose();
@@ -155,6 +173,13 @@ public class CdcTests : IDisposable
// Simple helper to avoid System.Reactive dependency in tests
public static class ObservableExtensions
{
/// <summary>
/// Subscribes to an observable sequence using an action callback.
/// </summary>
/// <typeparam name="T">The event type.</typeparam>
/// <param name="observable">The observable sequence.</param>
/// <param name="onNext">The callback for next events.</param>
/// <returns>An <see cref="IDisposable"/> subscription.</returns>
public static IDisposable Subscribe<T>(this IObservable<T> observable, Action<T> onNext)
{
return observable.Subscribe(new AnonymousObserver<T>(onNext));
@@ -163,9 +188,28 @@ public static class ObservableExtensions
private class AnonymousObserver<T> : IObserver<T>
{
private readonly Action<T> _onNext;
/// <summary>
/// Initializes a new instance of the <see cref="AnonymousObserver{T}"/> class.
/// </summary>
/// <param name="onNext">The callback for next events.</param>
public AnonymousObserver(Action<T> onNext) => _onNext = onNext;
/// <summary>
/// Handles completion.
/// </summary>
public void OnCompleted() { }
/// <summary>
/// Handles an observable error.
/// </summary>
/// <param name="error">The observed error.</param>
public void OnError(Exception error) { }
/// <summary>
/// Handles the next value.
/// </summary>
/// <param name="value">The observed value.</param>
public void OnNext(T value) => _onNext(value);
}
}

View File

@@ -20,12 +20,18 @@ public class CircularReferenceTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestDbContext _context;
/// <summary>
/// Initializes a new instance of the <see cref="CircularReferenceTests"/> class.
/// </summary>
public CircularReferenceTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_circular_test_{Guid.NewGuid()}");
_context = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Executes Dispose.
/// </summary>
public void Dispose()
{
_context?.Dispose();
@@ -39,6 +45,9 @@ public class CircularReferenceTests : IDisposable
// Self-Reference Tests (Employee hierarchy with ObjectId references)
// ========================================
/// <summary>
/// Executes SelfReference_InsertAndQuery_ShouldWork.
/// </summary>
[Fact]
public void SelfReference_InsertAndQuery_ShouldWork()
{
@@ -115,6 +124,9 @@ public class CircularReferenceTests : IDisposable
(queriedDeveloper.DirectReportIds ?? new List<ObjectId>()).ShouldBeEmpty();
}
/// <summary>
/// Executes SelfReference_UpdateDirectReports_ShouldPersist.
/// </summary>
[Fact]
public void SelfReference_UpdateDirectReports_ShouldPersist()
{
@@ -164,6 +176,9 @@ public class CircularReferenceTests : IDisposable
queried.DirectReportIds.ShouldContain(employee2Id);
}
/// <summary>
/// Executes SelfReference_QueryByManagerId_ShouldWork.
/// </summary>
[Fact]
public void SelfReference_QueryByManagerId_ShouldWork()
{
@@ -214,6 +229,9 @@ public class CircularReferenceTests : IDisposable
// BEST PRACTICE for document databases
// ========================================
/// <summary>
/// Executes NtoNReferencing_InsertAndQuery_ShouldWork.
/// </summary>
[Fact]
public void NtoNReferencing_InsertAndQuery_ShouldWork()
{
@@ -279,6 +297,9 @@ public class CircularReferenceTests : IDisposable
queriedProduct.CategoryIds.ShouldContain(categoryId2);
}
/// <summary>
/// Executes NtoNReferencing_UpdateRelationships_ShouldPersist.
/// </summary>
[Fact]
public void NtoNReferencing_UpdateRelationships_ShouldPersist()
{
@@ -336,6 +357,9 @@ public class CircularReferenceTests : IDisposable
queriedProduct2.CategoryIds.ShouldContain(categoryId);
}
/// <summary>
/// Executes NtoNReferencing_DocumentSize_RemainSmall.
/// </summary>
[Fact]
public void NtoNReferencing_DocumentSize_RemainSmall()
{
@@ -365,6 +389,9 @@ public class CircularReferenceTests : IDisposable
// This demonstrates why referencing is preferred for large N-N relationships
}
/// <summary>
/// Executes NtoNReferencing_QueryByProductId_ShouldWork.
/// </summary>
[Fact]
public void NtoNReferencing_QueryByProductId_ShouldWork()
{

View File

@@ -7,6 +7,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CollectionIndexManagerAndDefinitionTests
{
/// <summary>
/// Tests find best index should prefer unique index.
/// </summary>
[Fact]
public void FindBestIndex_Should_Prefer_Unique_Index()
{
@@ -32,6 +35,9 @@ public class CollectionIndexManagerAndDefinitionTests
}
}
/// <summary>
/// Tests find best compound index should choose longest prefix.
/// </summary>
[Fact]
public void FindBestCompoundIndex_Should_Choose_Longest_Prefix()
{
@@ -69,6 +75,9 @@ public class CollectionIndexManagerAndDefinitionTests
}
}
/// <summary>
/// Tests drop index should remove metadata and be idempotent.
/// </summary>
[Fact]
public void DropIndex_Should_Remove_Metadata_And_Be_Idempotent()
{
@@ -97,6 +106,9 @@ public class CollectionIndexManagerAndDefinitionTests
}
}
/// <summary>
/// Tests collection index definition should respect query support rules.
/// </summary>
[Fact]
public void CollectionIndexDefinition_Should_Respect_Query_Support_Rules()
{
@@ -116,6 +128,9 @@ public class CollectionIndexManagerAndDefinitionTests
definition.ToString().ShouldContain("Name");
}
/// <summary>
/// Tests collection index info to string should include diagnostics.
/// </summary>
[Fact]
public void CollectionIndexInfo_ToString_Should_Include_Diagnostics()
{

View File

@@ -6,6 +6,10 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CompactionCrashRecoveryTests
{
/// <summary>
/// Verifies compaction resumes from marker phases and preserves data.
/// </summary>
/// <param name="phase">The crash marker phase to resume from.</param>
[Theory]
[InlineData("Started")]
[InlineData("Copied")]
@@ -49,6 +53,9 @@ public class CompactionCrashRecoveryTests
}
}
/// <summary>
/// Verifies corrupted compaction markers are recovered deterministically.
/// </summary>
[Fact]
public void ResumeCompaction_WithCorruptedMarker_ShouldRecoverDeterministically()
{

View File

@@ -8,6 +8,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CompactionOfflineTests
{
/// <summary>
/// Tests offline compact should preserve logical data equivalence.
/// </summary>
[Fact]
public void OfflineCompact_ShouldPreserveLogicalDataEquivalence()
{
@@ -72,6 +75,9 @@ public class CompactionOfflineTests
}
}
/// <summary>
/// Tests offline compact should keep index results consistent.
/// </summary>
[Fact]
public void OfflineCompact_ShouldKeepIndexResultsConsistent()
{
@@ -127,6 +133,83 @@ public class CompactionOfflineTests
}
}
/// <summary>
/// Tests offline compact should rebuild hash index metadata and preserve results.
/// </summary>
[Fact]
public void OfflineCompact_ShouldRebuildHashIndexMetadataAndPreserveResults()
{
var dbPath = NewDbPath();
try
{
using var db = new TestDbContext(dbPath);
for (var i = 0; i < 300; i++)
{
db.People.Insert(new Person
{
Name = $"hash-person-{i:D4}",
Age = i % 12
});
}
db.SaveChanges();
db.ForceCheckpoint();
var expectedByAge = db.People.FindAll()
.GroupBy(p => p.Age)
.ToDictionary(g => g.Key, g => g.Select(x => x.Name).OrderBy(x => x).ToArray());
var metadata = db.Storage.GetCollectionMetadata("people_collection");
metadata.ShouldNotBeNull();
var targetIndex = metadata!.Indexes
.FirstOrDefault(index => index.PropertyPaths.Any(path => path.Equals("Age", StringComparison.OrdinalIgnoreCase)));
targetIndex.ShouldNotBeNull();
targetIndex!.Type = IndexType.Hash;
db.Storage.SaveCollectionMetadata(metadata);
db.SaveChanges();
var stats = db.Compact(new CompactionOptions
{
DefragmentSlottedPages = true,
NormalizeFreeList = true,
EnableTailTruncation = true
});
stats.PrePageCount.ShouldBeGreaterThanOrEqualTo(stats.PostPageCount);
var reloadedMetadata = db.Storage.GetCollectionMetadata("people_collection");
reloadedMetadata.ShouldNotBeNull();
var rebuiltIndex = reloadedMetadata!.Indexes.FirstOrDefault(index => index.Name == targetIndex.Name);
rebuiltIndex.ShouldNotBeNull();
rebuiltIndex!.Type.ShouldBe(IndexType.Hash);
rebuiltIndex.RootPageId.ShouldBeGreaterThan(0u);
var runtimeIndex = db.People.GetIndexes().FirstOrDefault(index => index.Name == targetIndex.Name);
runtimeIndex.ShouldNotBeNull();
runtimeIndex!.Type.ShouldBe(IndexType.Hash);
foreach (var age in expectedByAge.Keys.OrderBy(x => x))
{
var actual = db.People.FindAll(p => p.Age == age)
.Select(x => x.Name)
.OrderBy(x => x)
.ToArray();
actual.ShouldBe(expectedByAge[age]);
}
}
finally
{
CleanupFiles(dbPath);
}
}
/// <summary>
/// Tests offline compact when tail is reclaimable should reduce file size.
/// </summary>
[Fact]
public void OfflineCompact_WhenTailIsReclaimable_ShouldReduceFileSize()
{
@@ -178,6 +261,9 @@ public class CompactionOfflineTests
}
}
/// <summary>
/// Tests offline compact with invalid primary root metadata should fail validation.
/// </summary>
[Fact]
public void OfflineCompact_WithInvalidPrimaryRootMetadata_ShouldFailValidation()
{
@@ -208,6 +294,9 @@ public class CompactionOfflineTests
}
}
/// <summary>
/// Tests offline compact with invalid secondary root metadata should fail validation.
/// </summary>
[Fact]
public void OfflineCompact_WithInvalidSecondaryRootMetadata_ShouldFailValidation()
{
@@ -239,6 +328,9 @@ public class CompactionOfflineTests
}
}
/// <summary>
/// Tests offline compact should report live bytes relocation and throughput telemetry.
/// </summary>
[Fact]
public void OfflineCompact_ShouldReportLiveBytesRelocationAndThroughputTelemetry()
{
@@ -290,6 +382,9 @@ public class CompactionOfflineTests
}
}
/// <summary>
/// Tests offline compact when primary index points to deleted slot should fail validation.
/// </summary>
[Fact]
public void OfflineCompact_WhenPrimaryIndexPointsToDeletedSlot_ShouldFailValidation()
{

View File

@@ -6,6 +6,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CompactionOnlineConcurrencyTests
{
/// <summary>
/// Verifies online compaction completes without deadlock under concurrent workload.
/// </summary>
[Fact]
public async Task OnlineCompaction_WithConcurrentishWorkload_ShouldCompleteWithoutDeadlock()
{

View File

@@ -6,6 +6,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CompactionWalCoordinationTests
{
/// <summary>
/// Verifies offline compaction checkpoints and leaves the WAL empty.
/// </summary>
[Fact]
public void OfflineCompact_ShouldCheckpointAndLeaveWalEmpty()
{
@@ -42,6 +45,9 @@ public class CompactionWalCoordinationTests
}
}
/// <summary>
/// Verifies compaction after WAL recovery preserves durable data.
/// </summary>
[Fact]
public void Compact_AfterWalRecovery_ShouldKeepDataDurable()
{

View File

@@ -9,6 +9,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CompressionCompatibilityTests
{
/// <summary>
/// Verifies opening legacy uncompressed files with compression enabled does not mutate database bytes.
/// </summary>
[Fact]
public void OpeningLegacyUncompressedFile_WithCompressionEnabled_ShouldNotMutateDbFile()
{
@@ -56,6 +59,9 @@ public class CompressionCompatibilityTests
}
}
/// <summary>
/// Verifies mixed compressed and uncompressed documents remain readable after partial migration.
/// </summary>
[Fact]
public void MixedFormatDocuments_ShouldRemainReadableAfterPartialMigration()
{

View File

@@ -9,6 +9,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CompressionCorruptionTests
{
/// <summary>
/// Verifies corrupted compressed payload checksum triggers invalid data errors.
/// </summary>
[Fact]
public void Read_WithBadChecksum_ShouldThrowInvalidData()
{
@@ -34,6 +37,9 @@ public class CompressionCorruptionTests
}
}
/// <summary>
/// Verifies invalid original length metadata triggers invalid data errors.
/// </summary>
[Fact]
public void Read_WithBadOriginalLength_ShouldThrowInvalidData()
{
@@ -57,6 +63,9 @@ public class CompressionCorruptionTests
}
}
/// <summary>
/// Verifies oversized declared decompressed length enforces safety guardrails.
/// </summary>
[Fact]
public void Read_WithOversizedDeclaredLength_ShouldEnforceGuardrail()
{
@@ -81,6 +90,9 @@ public class CompressionCorruptionTests
}
}
/// <summary>
/// Verifies invalid codec identifiers in compressed headers trigger invalid data errors.
/// </summary>
[Fact]
public void Read_WithInvalidCodecId_ShouldThrowInvalidData()
{

View File

@@ -8,6 +8,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CompressionInsertReadTests
{
/// <summary>
/// Tests insert with threshold should store mixed compressed and uncompressed slots.
/// </summary>
[Fact]
public void Insert_WithThreshold_ShouldStoreMixedCompressedAndUncompressedSlots()
{
@@ -46,6 +49,9 @@ public class CompressionInsertReadTests
}
}
/// <summary>
/// Tests find by id should read mixed compressed and uncompressed documents.
/// </summary>
[Fact]
public void FindById_ShouldReadMixedCompressedAndUncompressedDocuments()
{
@@ -91,6 +97,9 @@ public class CompressionInsertReadTests
}
}
/// <summary>
/// Tests insert when codec throws should fallback to uncompressed storage.
/// </summary>
[Fact]
public void Insert_WhenCodecThrows_ShouldFallbackToUncompressedStorage()
{
@@ -186,11 +195,25 @@ public class CompressionInsertReadTests
private sealed class FailingBrotliCodec : ICompressionCodec
{
/// <summary>
/// Gets or sets the codec.
/// </summary>
public CompressionCodec Codec => CompressionCodec.Brotli;
/// <summary>
/// Tests compress.
/// </summary>
/// <param name="input">Payload bytes to compress.</param>
/// <param name="level">Compression level.</param>
public byte[] Compress(ReadOnlySpan<byte> input, CompressionLevel level)
=> throw new InvalidOperationException("Forced codec failure for test coverage.");
/// <summary>
/// Tests decompress.
/// </summary>
/// <param name="input">Compressed payload bytes.</param>
/// <param name="expectedLength">Expected decompressed payload length.</param>
/// <param name="maxDecompressedSizeBytes">Maximum allowed decompressed size.</param>
public byte[] Decompress(ReadOnlySpan<byte> input, int expectedLength, int maxDecompressedSizeBytes)
=> throw new InvalidOperationException("This codec should not be used for reads in this scenario.");
}

View File

@@ -8,6 +8,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class CompressionOverflowTests
{
/// <summary>
/// Tests insert compressed document spanning overflow pages should round trip.
/// </summary>
[Fact]
public void Insert_CompressedDocumentSpanningOverflowPages_ShouldRoundTrip()
{
@@ -43,6 +46,9 @@ public class CompressionOverflowTests
}
}
/// <summary>
/// Tests update should transition across compression thresholds.
/// </summary>
[Fact]
public void Update_ShouldTransitionAcrossCompressionThresholds()
{

View File

@@ -11,6 +11,9 @@ public class CursorTests : IDisposable
private readonly StorageEngine _storage;
private readonly BTreeIndex _index;
/// <summary>
/// Initializes a new instance of the <see cref="CursorTests"/> class.
/// </summary>
public CursorTests()
{
_testFile = Path.Combine(Path.GetTempPath(), $"docdb_cursor_test_{Guid.NewGuid()}.db");
@@ -34,6 +37,9 @@ public class CursorTests : IDisposable
_storage.CommitTransaction(txnId);
}
/// <summary>
/// Tests move to first should position at first.
/// </summary>
[Fact]
public void MoveToFirst_ShouldPositionAtFirst()
{
@@ -42,6 +48,9 @@ public class CursorTests : IDisposable
cursor.Current.Key.ShouldBe(IndexKey.Create(10));
}
/// <summary>
/// Tests move to last should position at last.
/// </summary>
[Fact]
public void MoveToLast_ShouldPositionAtLast()
{
@@ -50,6 +59,9 @@ public class CursorTests : IDisposable
cursor.Current.Key.ShouldBe(IndexKey.Create(30));
}
/// <summary>
/// Tests move next should traverse forward.
/// </summary>
[Fact]
public void MoveNext_ShouldTraverseForward()
{
@@ -65,6 +77,9 @@ public class CursorTests : IDisposable
cursor.MoveNext().ShouldBeFalse(); // End
}
/// <summary>
/// Tests move prev should traverse backward.
/// </summary>
[Fact]
public void MovePrev_ShouldTraverseBackward()
{
@@ -80,6 +95,9 @@ public class CursorTests : IDisposable
cursor.MovePrev().ShouldBeFalse(); // Start
}
/// <summary>
/// Tests seek should position exact or next.
/// </summary>
[Fact]
public void Seek_ShouldPositionExact_OrNext()
{
@@ -99,6 +117,9 @@ public class CursorTests : IDisposable
Should.Throw<InvalidOperationException>(() => cursor.Current);
}
/// <summary>
/// Disposes the resources used by this instance.
/// </summary>
public void Dispose()
{
_storage.Dispose();

View File

@@ -11,18 +11,27 @@ public class DbContextInheritanceTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestExtendedDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="DbContextInheritanceTests"/> class.
/// </summary>
public DbContextInheritanceTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_inheritance_{Guid.NewGuid()}.db");
_db = new Shared.TestExtendedDbContext(_dbPath);
}
/// <summary>
/// Releases test resources.
/// </summary>
public void Dispose()
{
_db.Dispose();
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Verifies parent collections are initialized in the extended context.
/// </summary>
[Fact]
public void ExtendedContext_Should_Initialize_Parent_Collections()
{
@@ -35,6 +44,9 @@ public class DbContextInheritanceTests : IDisposable
_db.TestDocuments.ShouldNotBeNull();
}
/// <summary>
/// Verifies extended context collections are initialized.
/// </summary>
[Fact]
public void ExtendedContext_Should_Initialize_Own_Collections()
{
@@ -42,6 +54,9 @@ public class DbContextInheritanceTests : IDisposable
_db.ExtendedEntities.ShouldNotBeNull();
}
/// <summary>
/// Verifies parent collections are usable from the extended context.
/// </summary>
[Fact]
public void ExtendedContext_Can_Use_Parent_Collections()
{
@@ -57,6 +72,9 @@ public class DbContextInheritanceTests : IDisposable
retrieved.Age.ShouldBe(30);
}
/// <summary>
/// Verifies extended collections are usable from the extended context.
/// </summary>
[Fact]
public void ExtendedContext_Can_Use_Own_Collections()
{
@@ -76,6 +94,9 @@ public class DbContextInheritanceTests : IDisposable
retrieved.Description.ShouldBe("Test Extended Entity");
}
/// <summary>
/// Verifies parent and extended collections can be used together.
/// </summary>
[Fact]
public void ExtendedContext_Can_Use_Both_Parent_And_Own_Collections()
{

View File

@@ -12,11 +12,17 @@ public class DbContextTests : IDisposable
{
private string _dbPath;
/// <summary>
/// Initializes test file paths for database context tests.
/// </summary>
public DbContextTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"test_dbcontext_{Guid.NewGuid()}.db");
}
/// <summary>
/// Verifies the basic database context lifecycle works.
/// </summary>
[Fact]
public void DbContext_BasicLifecycle_Works()
{
@@ -31,6 +37,9 @@ public class DbContextTests : IDisposable
found.Age.ShouldBe(30);
}
/// <summary>
/// Verifies multiple CRUD operations execute correctly in one context.
/// </summary>
[Fact]
public void DbContext_MultipleOperations_Work()
{
@@ -59,6 +68,9 @@ public class DbContextTests : IDisposable
db.Users.Count().ShouldBe(1);
}
/// <summary>
/// Verifies disposing and reopening context preserves persisted data.
/// </summary>
[Fact]
public void DbContext_Dispose_ReleasesResources()
{
@@ -90,6 +102,9 @@ public class DbContextTests : IDisposable
return Convert.ToHexString(sha256.ComputeHash(stream));
}
/// <summary>
/// Verifies database file size and content change after insert and checkpoint.
/// </summary>
[Fact]
public void DatabaseFile_SizeAndContent_ChangeAfterInsert()
{
@@ -117,6 +132,9 @@ public class DbContextTests : IDisposable
afterInsertHash.ShouldNotBe(initialHash);
}
/// <summary>
/// Verifies the WAL file path is auto-derived from database path.
/// </summary>
[Fact]
public void DbContext_AutoDerivesWalPath()
{
@@ -127,6 +145,9 @@ public class DbContextTests : IDisposable
File.Exists(walPath).ShouldBeTrue();
}
/// <summary>
/// Verifies custom page file and compression options support roundtrip data access.
/// </summary>
[Fact]
public void DbContext_WithCustomPageFileAndCompressionOptions_ShouldSupportRoundTrip()
{
@@ -165,6 +186,9 @@ public class DbContextTests : IDisposable
}
}
/// <summary>
/// Verifies compact API returns stats and preserves data consistency.
/// </summary>
[Fact]
public void DbContext_CompactApi_ShouldReturnStatsAndPreserveData()
{
@@ -197,6 +221,9 @@ public class DbContextTests : IDisposable
}
}
/// <summary>
/// Disposes test resources and cleans up generated files.
/// </summary>
public void Dispose()
{
try

View File

@@ -9,6 +9,9 @@ public class DictionaryPageTests
{
private const int PageSize = 16384;
/// <summary>
/// Verifies dictionary page initialization sets expected defaults.
/// </summary>
[Fact]
public void Initialize_ShouldSetupEmptyPage()
{
@@ -26,6 +29,9 @@ public class DictionaryPageTests
freeSpaceEnd.ShouldBe((ushort)PageSize);
}
/// <summary>
/// Verifies insert adds entries and keeps them ordered.
/// </summary>
[Fact]
public void Insert_ShouldAddEntryAndSort()
{
@@ -58,6 +64,9 @@ public class DictionaryPageTests
entries[2].Value.ShouldBe((ushort)30);
}
/// <summary>
/// Verifies key lookup returns the expected value.
/// </summary>
[Fact]
public void TryFind_ShouldReturnCorrectValue()
{
@@ -76,6 +85,9 @@ public class DictionaryPageTests
found.ShouldBeFalse();
}
/// <summary>
/// Verifies inserts fail when the page is full.
/// </summary>
[Fact]
public void Overflow_ShouldReturnFalse_WhenFull()
{
@@ -105,6 +117,9 @@ public class DictionaryPageTests
inserted.ShouldBeFalse();
}
/// <summary>
/// Verifies global lookup finds keys across chained dictionary pages.
/// </summary>
[Fact]
public void Chaining_ShouldFindKeysInLinkedPages()
{
@@ -158,6 +173,9 @@ public class DictionaryPageTests
if (File.Exists(Path.ChangeExtension(dbPath, ".wal"))) File.Delete(Path.ChangeExtension(dbPath, ".wal"));
}
/// <summary>
/// Verifies global enumeration returns keys across chained dictionary pages.
/// </summary>
[Fact]
public void FindAllGlobal_ShouldRetrieveAllKeys()
{

View File

@@ -13,12 +13,18 @@ public class DictionaryPersistenceTests : IDisposable
private readonly string _dbPath;
private readonly StorageEngine _storage;
/// <summary>
/// Initializes a new instance of the <see cref="DictionaryPersistenceTests"/> class.
/// </summary>
public DictionaryPersistenceTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_dict_{Guid.NewGuid():N}.db");
_storage = new StorageEngine(_dbPath, PageFileConfig.Default);
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_storage.Dispose();
@@ -32,22 +38,41 @@ public class DictionaryPersistenceTests : IDisposable
private readonly string _collectionName;
private readonly List<string> _keys;
/// <summary>
/// Initializes a new instance of the <see cref="MockMapper"/> class.
/// </summary>
/// <param name="name">The collection name.</param>
/// <param name="keys">The mapper keys.</param>
public MockMapper(string name, params string[] keys)
{
_collectionName = name;
_keys = keys.ToList();
}
/// <inheritdoc />
public override string CollectionName => _collectionName;
/// <inheritdoc />
public override IEnumerable<string> UsedKeys => _keys;
/// <inheritdoc />
public override BsonSchema GetSchema() => new BsonSchema { Title = _collectionName };
/// <inheritdoc />
public override ObjectId GetId(Dictionary<string, object> entity) => throw new NotImplementedException();
/// <inheritdoc />
public override void SetId(Dictionary<string, object> entity, ObjectId id) => throw new NotImplementedException();
/// <inheritdoc />
public override int Serialize(Dictionary<string, object> entity, BsonSpanWriter writer) => throw new NotImplementedException();
/// <inheritdoc />
public override Dictionary<string, object> Deserialize(BsonSpanReader reader) => throw new NotImplementedException();
}
/// <summary>
/// Verifies mapper registration adds all unique dictionary keys.
/// </summary>
[Fact]
public void RegisterMappers_Registers_All_Unique_Keys()
{
@@ -73,6 +98,9 @@ public class DictionaryPersistenceTests : IDisposable
ids.Count.ShouldBe(4);
}
/// <summary>
/// Verifies dictionary keys persist across storage restarts.
/// </summary>
[Fact]
public void Dictionary_Keys_Persist_Across_Restarts()
{
@@ -93,7 +121,10 @@ public class DictionaryPersistenceTests : IDisposable
private class NestedMockMapper : DocumentMapperBase<ObjectId, object>
{
/// <inheritdoc />
public override string CollectionName => "Nested";
/// <inheritdoc />
public override BsonSchema GetSchema()
{
var schema = new BsonSchema { Title = "Nested" };
@@ -109,12 +140,22 @@ public class DictionaryPersistenceTests : IDisposable
return schema;
}
/// <inheritdoc />
public override ObjectId GetId(object entity) => throw new NotImplementedException();
/// <inheritdoc />
public override void SetId(object entity, ObjectId id) => throw new NotImplementedException();
/// <inheritdoc />
public override int Serialize(object entity, BsonSpanWriter writer) => throw new NotImplementedException();
/// <inheritdoc />
public override object Deserialize(BsonSpanReader reader) => throw new NotImplementedException();
}
/// <summary>
/// Verifies nested schema fields are registered as dictionary keys.
/// </summary>
[Fact]
public void RegisterMappers_Handles_Nested_Keys()
{

View File

@@ -14,6 +14,9 @@ public class DocumentCollectionDeleteTests : IDisposable
private readonly string _walPath;
private readonly Shared.TestDbContext _dbContext;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentCollectionDeleteTests"/> class.
/// </summary>
public DocumentCollectionDeleteTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"test_delete_{Guid.NewGuid()}.db");
@@ -22,11 +25,17 @@ public class DocumentCollectionDeleteTests : IDisposable
_dbContext = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Releases test resources.
/// </summary>
public void Dispose()
{
_dbContext.Dispose();
}
/// <summary>
/// Verifies delete removes both the document and its index entry.
/// </summary>
[Fact]
public void Delete_RemovesDocumentAndIndexEntry()
{
@@ -52,6 +61,9 @@ public class DocumentCollectionDeleteTests : IDisposable
all.ShouldBeEmpty();
}
/// <summary>
/// Verifies delete returns false for a non-existent document.
/// </summary>
[Fact]
public void Delete_NonExistent_ReturnsFalse()
{
@@ -61,6 +73,9 @@ public class DocumentCollectionDeleteTests : IDisposable
deleted.ShouldBeFalse();
}
/// <summary>
/// Verifies deletes inside a transaction commit successfully.
/// </summary>
[Fact]
public void Delete_WithTransaction_CommitsSuccessfully()
{

View File

@@ -8,12 +8,18 @@ public class DocumentCollectionIndexApiTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentCollectionIndexApiTests"/> class.
/// </summary>
public DocumentCollectionIndexApiTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"collection_index_api_{Guid.NewGuid():N}.db");
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Verifies vector index creation and deletion behavior.
/// </summary>
[Fact]
public void CreateVectorIndex_And_DropIndex_Should_Work()
{
@@ -32,6 +38,9 @@ public class DocumentCollectionIndexApiTests : IDisposable
_db.VectorItems.GetIndexes().Select(x => x.Name).ShouldNotContain("idx_vector_extra");
}
/// <summary>
/// Verifies ensure-index returns existing indexes when already present.
/// </summary>
[Fact]
public void EnsureIndex_Should_Return_Existing_Index_When_Already_Present()
{
@@ -41,12 +50,18 @@ public class DocumentCollectionIndexApiTests : IDisposable
ReferenceEquals(first, second).ShouldBeTrue();
}
/// <summary>
/// Verifies dropping the primary index name is rejected.
/// </summary>
[Fact]
public void DropIndex_Should_Reject_Primary_Index_Name()
{
Should.Throw<InvalidOperationException>(() => _db.People.DropIndex("_id"));
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_db.Dispose();

View File

@@ -13,6 +13,9 @@ public class DocumentCollectionTests : IDisposable
private readonly string _walPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentCollectionTests"/> class.
/// </summary>
public DocumentCollectionTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"test_collection_{Guid.NewGuid()}.db");
@@ -21,6 +24,9 @@ public class DocumentCollectionTests : IDisposable
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Verifies insert and find-by-id operations.
/// </summary>
[Fact]
public void Insert_And_FindById_Works()
{
@@ -39,6 +45,9 @@ public class DocumentCollectionTests : IDisposable
found.Age.ShouldBe(30);
}
/// <summary>
/// Verifies find-by-id returns null when no document is found.
/// </summary>
[Fact]
public void FindById_Returns_Null_When_Not_Found()
{
@@ -49,6 +58,9 @@ public class DocumentCollectionTests : IDisposable
found.ShouldBeNull();
}
/// <summary>
/// Verifies find-all returns all entities.
/// </summary>
[Fact]
public void FindAll_Returns_All_Entities()
{
@@ -68,6 +80,9 @@ public class DocumentCollectionTests : IDisposable
all.ShouldContain(u => u.Name == "Charlie");
}
/// <summary>
/// Verifies update modifies an existing entity.
/// </summary>
[Fact]
public void Update_Modifies_Entity()
{
@@ -89,6 +104,9 @@ public class DocumentCollectionTests : IDisposable
found.Age.ShouldBe(31);
}
/// <summary>
/// Verifies update returns false when the entity does not exist.
/// </summary>
[Fact]
public void Update_Returns_False_When_Not_Found()
{
@@ -103,6 +121,9 @@ public class DocumentCollectionTests : IDisposable
updated.ShouldBeFalse();
}
/// <summary>
/// Verifies delete removes an entity.
/// </summary>
[Fact]
public void Delete_Removes_Entity()
{
@@ -120,6 +141,9 @@ public class DocumentCollectionTests : IDisposable
_db.Users.FindById(id).ShouldBeNull();
}
/// <summary>
/// Verifies delete returns false when the entity does not exist.
/// </summary>
[Fact]
public void Delete_Returns_False_When_Not_Found()
{
@@ -131,6 +155,9 @@ public class DocumentCollectionTests : IDisposable
deleted.ShouldBeFalse();
}
/// <summary>
/// Verifies count returns the correct entity count.
/// </summary>
[Fact]
public void Count_Returns_Correct_Count()
{
@@ -146,6 +173,9 @@ public class DocumentCollectionTests : IDisposable
count.ShouldBe(2);
}
/// <summary>
/// Verifies predicate queries filter entities correctly.
/// </summary>
[Fact]
public void Find_With_Predicate_Filters_Correctly()
{
@@ -163,6 +193,9 @@ public class DocumentCollectionTests : IDisposable
over30[0].Name.ShouldBe("Charlie");
}
/// <summary>
/// Verifies bulk insert stores multiple entities.
/// </summary>
[Fact]
public void InsertBulk_Inserts_Multiple_Entities()
{
@@ -183,6 +216,9 @@ public class DocumentCollectionTests : IDisposable
_db.Users.Count().ShouldBe(3);
}
/// <summary>
/// Verifies inserts preserve an explicitly assigned identifier.
/// </summary>
[Fact]
public void Insert_With_SpecifiedId_RetainsId()
{
@@ -203,6 +239,9 @@ public class DocumentCollectionTests : IDisposable
found.Name.ShouldBe("SpecifiedID");
}
/// <summary>
/// Releases test resources.
/// </summary>
public void Dispose()
{
_db?.Dispose();

View File

@@ -16,6 +16,9 @@ public class DocumentOverflowTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="DocumentOverflowTests"/> class.
/// </summary>
public DocumentOverflowTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"test_overflow_{Guid.NewGuid()}.db");
@@ -23,12 +26,18 @@ public class DocumentOverflowTests : IDisposable
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Releases test resources.
/// </summary>
public void Dispose()
{
_db.Dispose();
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Verifies inserting a medium-sized document succeeds.
/// </summary>
[Fact]
public void Insert_MediumDoc_64KB_ShouldSucceed()
{
@@ -50,6 +59,9 @@ public class DocumentOverflowTests : IDisposable
retrieved.Name.ShouldBe(largeString);
}
/// <summary>
/// Verifies inserting a large document succeeds.
/// </summary>
[Fact]
public void Insert_LargeDoc_100KB_ShouldSucceed()
{
@@ -70,6 +82,9 @@ public class DocumentOverflowTests : IDisposable
retrieved.Name.ShouldBe(largeString);
}
/// <summary>
/// Verifies inserting a very large document succeeds.
/// </summary>
[Fact]
public void Insert_HugeDoc_3MB_ShouldSucceed()
{
@@ -93,6 +108,9 @@ public class DocumentOverflowTests : IDisposable
retrieved.Name.Substring(retrieved.Name.Length - 100).ShouldBe(largeString.Substring(largeString.Length - 100));
}
/// <summary>
/// Verifies updating from a small payload to a huge payload succeeds.
/// </summary>
[Fact]
public void Update_SmallToHuge_ShouldSucceed()
{
@@ -114,6 +132,9 @@ public class DocumentOverflowTests : IDisposable
retrieved.Name.Length.ShouldBe(hugeString.Length);
}
/// <summary>
/// Verifies bulk inserts with mixed payload sizes succeed.
/// </summary>
[Fact]
public void InsertBulk_MixedSizes_ShouldSucceed()
{
@@ -136,6 +157,9 @@ public class DocumentOverflowTests : IDisposable
}
}
/// <summary>
/// Verifies huge inserts succeed with compression enabled and small page configuration.
/// </summary>
[Fact]
public void Insert_HugeDoc_WithCompressionEnabledAndSmallPages_ShouldSucceed()
{
@@ -172,6 +196,9 @@ public class DocumentOverflowTests : IDisposable
}
}
/// <summary>
/// Verifies updates from huge to small payloads succeed with compression enabled.
/// </summary>
[Fact]
public void Update_HugeToSmall_WithCompressionEnabled_ShouldSucceed()
{

View File

@@ -7,12 +7,18 @@ public class GeospatialStressTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes database state for geospatial stress tests.
/// </summary>
public GeospatialStressTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"geo_stress_{Guid.NewGuid():N}.db");
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Verifies spatial index handles node splits and query operations under load.
/// </summary>
[Fact]
public void SpatialIndex_Should_Handle_Node_Splits_And_Queries()
{
@@ -40,6 +46,9 @@ public class GeospatialStressTests : IDisposable
near.Count.ShouldBeGreaterThan(0);
}
/// <summary>
/// Disposes test resources and removes generated files.
/// </summary>
public void Dispose()
{
_db.Dispose();

View File

@@ -12,12 +12,18 @@ public class GeospatialTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="GeospatialTests"/> class.
/// </summary>
public GeospatialTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_geo_{Guid.NewGuid()}.db");
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Verifies spatial within queries return expected results.
/// </summary>
[Fact]
public void Can_Insert_And_Search_Within()
{
@@ -38,6 +44,9 @@ public class GeospatialTests : IDisposable
results.ShouldContain(r => r.Name == "Point 2");
}
/// <summary>
/// Verifies near queries return expected proximity results.
/// </summary>
[Fact]
public void Can_Search_Near_Proximity()
{
@@ -59,6 +68,9 @@ public class GeospatialTests : IDisposable
results.ShouldNotContain(r => r.Name == "New York Office");
}
/// <summary>
/// Verifies LINQ near integration returns expected results.
/// </summary>
[Fact]
public void LINQ_Integration_Near_Works()
{
@@ -76,6 +88,9 @@ public class GeospatialTests : IDisposable
results[0].Name.ShouldBe("Milan Office");
}
/// <summary>
/// Verifies LINQ within integration returns expected results.
/// </summary>
[Fact]
public void LINQ_Integration_Within_Works()
{
@@ -94,6 +109,9 @@ public class GeospatialTests : IDisposable
results[0].Name.ShouldBe("Milan Office");
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_db.Dispose();

View File

@@ -5,6 +5,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class HashIndexTests
{
/// <summary>
/// Executes Insert_And_TryFind_Should_Return_Location.
/// </summary>
[Fact]
public void Insert_And_TryFind_Should_Return_Location()
{
@@ -19,6 +22,9 @@ public class HashIndexTests
found.SlotIndex.ShouldBe(location.SlotIndex);
}
/// <summary>
/// Executes Unique_HashIndex_Should_Throw_On_Duplicate_Key.
/// </summary>
[Fact]
public void Unique_HashIndex_Should_Throw_On_Duplicate_Key()
{
@@ -38,6 +44,9 @@ public class HashIndexTests
index.Insert(key, new DocumentLocation(2, 2)));
}
/// <summary>
/// Executes Remove_Should_Remove_Only_Matching_Entry.
/// </summary>
[Fact]
public void Remove_Should_Remove_Only_Matching_Entry()
{
@@ -61,6 +70,9 @@ public class HashIndexTests
index.FindAll(key).ShouldBeEmpty();
}
/// <summary>
/// Executes FindAll_Should_Return_All_Matching_Entries.
/// </summary>
[Fact]
public void FindAll_Should_Return_All_Matching_Entries()
{

View File

@@ -14,6 +14,9 @@ public class IndexDirectionTests : IDisposable
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes database state for index direction tests.
/// </summary>
public IndexDirectionTests()
{
if (File.Exists(_dbPath)) File.Delete(_dbPath);
@@ -21,12 +24,18 @@ public class IndexDirectionTests : IDisposable
// _db.Database.EnsureCreated(); // Not needed/doesn't exist? StorageEngine handles creation.
}
/// <summary>
/// Disposes test resources and deletes temporary files.
/// </summary>
public void Dispose()
{
_db.Dispose();
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Verifies forward range scans return values in ascending order.
/// </summary>
[Fact]
public void Range_Forward_ReturnsOrderedResults()
{
@@ -45,6 +54,9 @@ public class IndexDirectionTests : IDisposable
collection.FindByLocation(results.Last())!.Age.ShouldBe(20); // Last is 20
}
/// <summary>
/// Verifies backward range scans return values in descending order.
/// </summary>
[Fact]
public void Range_Backward_ReturnsReverseOrderedResults()
{
@@ -63,6 +75,9 @@ public class IndexDirectionTests : IDisposable
collection.FindByLocation(results.Last())!.Age.ShouldBe(10); // Last is 10
}
/// <summary>
/// Verifies backward scans across split index pages return complete result sets.
/// </summary>
[Fact]
public void Range_Backward_WithMultiplePages_ReturnsReverseOrderedResults()
{

View File

@@ -11,11 +11,23 @@ namespace ZB.MOM.WW.CBDD.Tests
{
public class TestEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// Gets or sets the age.
/// </summary>
public int Age { get; set; }
}
/// <summary>
/// Tests optimizer identifies equality.
/// </summary>
[Fact]
public void Optimizer_Identifies_Equality()
{
@@ -36,6 +48,9 @@ namespace ZB.MOM.WW.CBDD.Tests
result.IsRange.ShouldBeFalse();
}
/// <summary>
/// Tests optimizer identifies range greater than.
/// </summary>
[Fact]
public void Optimizer_Identifies_Range_GreaterThan()
{
@@ -56,6 +71,9 @@ namespace ZB.MOM.WW.CBDD.Tests
result.IsRange.ShouldBeTrue();
}
/// <summary>
/// Tests optimizer identifies range less than.
/// </summary>
[Fact]
public void Optimizer_Identifies_Range_LessThan()
{
@@ -76,6 +94,9 @@ namespace ZB.MOM.WW.CBDD.Tests
result.IsRange.ShouldBeTrue();
}
/// <summary>
/// Tests optimizer identifies range between simulated.
/// </summary>
[Fact]
public void Optimizer_Identifies_Range_Between_Simulated()
{
@@ -96,6 +117,9 @@ namespace ZB.MOM.WW.CBDD.Tests
result.IsRange.ShouldBeTrue();
}
/// <summary>
/// Tests optimizer identifies starts with.
/// </summary>
[Fact]
public void Optimizer_Identifies_StartsWith()
{
@@ -117,6 +141,9 @@ namespace ZB.MOM.WW.CBDD.Tests
result.IsRange.ShouldBeTrue();
}
/// <summary>
/// Tests optimizer ignores non indexed fields.
/// </summary>
[Fact]
public void Optimizer_Ignores_NonIndexed_Fields()
{

View File

@@ -13,17 +13,26 @@ public class InsertBulkTests : IDisposable
private readonly string _testFile;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="InsertBulkTests"/> class.
/// </summary>
public InsertBulkTests()
{
_testFile = Path.GetTempFileName();
_db = new Shared.TestDbContext(_testFile);
}
/// <summary>
/// Disposes test resources.
/// </summary>
public void Dispose()
{
_db.Dispose();
}
/// <summary>
/// Verifies bulk inserts are immediately persisted and visible.
/// </summary>
[Fact]
public void InsertBulk_PersistsData_ImmediatelyVisible()
{
@@ -41,6 +50,9 @@ public class InsertBulkTests : IDisposable
insertedUsers.Count.ShouldBe(50);
}
/// <summary>
/// Verifies bulk inserts spanning multiple pages persist correctly.
/// </summary>
[Fact]
public void InsertBulk_SpanningMultiplePages_PersistsCorrectly()
{

View File

@@ -17,6 +17,9 @@ namespace ZB.MOM.WW.CBDD.Tests
private readonly string _testFile;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="LinqTests"/> class.
/// </summary>
public LinqTests()
{
_testFile = Path.Combine(Path.GetTempPath(), $"linq_tests_{Guid.NewGuid()}.db");
@@ -35,6 +38,9 @@ namespace ZB.MOM.WW.CBDD.Tests
_db.SaveChanges();
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_db.Dispose();
@@ -43,6 +49,9 @@ namespace ZB.MOM.WW.CBDD.Tests
if (File.Exists(wal)) File.Delete(wal);
}
/// <summary>
/// Verifies where filters return matching documents.
/// </summary>
[Fact]
public void Where_FiltersDocuments()
{
@@ -53,6 +62,9 @@ namespace ZB.MOM.WW.CBDD.Tests
results.ShouldNotContain(d => d.Name == "Bob");
}
/// <summary>
/// Verifies order by returns sorted documents.
/// </summary>
[Fact]
public void OrderBy_SortsDocuments()
{
@@ -64,6 +76,9 @@ namespace ZB.MOM.WW.CBDD.Tests
results.Last().Name.ShouldBe("Eve"); // 40
}
/// <summary>
/// Verifies skip and take support pagination.
/// </summary>
[Fact]
public void SkipTake_Pagination()
{
@@ -78,6 +93,9 @@ namespace ZB.MOM.WW.CBDD.Tests
results[1].Name.ShouldBe("Alice"); // 30
}
/// <summary>
/// Verifies select supports projections.
/// </summary>
[Fact]
public void Select_Projections()
{
@@ -91,6 +109,9 @@ namespace ZB.MOM.WW.CBDD.Tests
names[0].ShouldBe("Dave");
names[1].ShouldBe("Bob");
}
/// <summary>
/// Verifies indexed where queries use index-backed filtering.
/// </summary>
[Fact]
public void IndexedWhere_UsedIndex()
{
@@ -104,6 +125,9 @@ namespace ZB.MOM.WW.CBDD.Tests
results.ShouldNotContain(d => d.Name == "Bob"); // Age 25 (filtered out by strict >)
results.ShouldNotContain(d => d.Name == "Dave"); // Age 20
}
/// <summary>
/// Verifies starts-with predicates can use an index.
/// </summary>
[Fact]
public void StartsWith_UsedIndex()
{
@@ -118,6 +142,9 @@ namespace ZB.MOM.WW.CBDD.Tests
results[0].Name.ShouldBe("Charlie");
}
/// <summary>
/// Verifies range predicates can use an index.
/// </summary>
[Fact]
public void Between_UsedIndex()
{

View File

@@ -7,6 +7,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class MaintenanceDiagnosticsAndMigrationTests
{
/// <summary>
/// Verifies diagnostics APIs return page usage, compression, and fragmentation data.
/// </summary>
[Fact]
public void DiagnosticsApis_ShouldReturnPageUsageCompressionAndFragmentationData()
{
@@ -61,6 +64,9 @@ public class MaintenanceDiagnosticsAndMigrationTests
}
}
/// <summary>
/// Verifies compression migration dry-run and apply modes return deterministic stats and preserve data.
/// </summary>
[Fact]
public void MigrateCompression_DryRunAndApply_ShouldReturnDeterministicStatsAndPreserveData()
{

View File

@@ -14,12 +14,18 @@ public class MetadataPersistenceTests : IDisposable
private readonly string _dbPath;
private readonly string _walPath;
/// <summary>
/// Initializes a new instance of the <see cref="MetadataPersistenceTests"/> class.
/// </summary>
public MetadataPersistenceTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"docdb_meta_{Guid.NewGuid()}.db");
_walPath = Path.ChangeExtension(_dbPath, ".wal");
}
/// <summary>
/// Tests index definitions are persisted and reloaded.
/// </summary>
[Fact]
public void IndexDefinitions_ArePersisted_AndReloaded()
{
@@ -59,6 +65,9 @@ public class MetadataPersistenceTests : IDisposable
}
}
/// <summary>
/// Tests ensure index does not recreate if index exists.
/// </summary>
[Fact]
public void EnsureIndex_DoesNotRecreate_IfIndexExists()
{
@@ -91,6 +100,9 @@ public class MetadataPersistenceTests : IDisposable
}
}
/// <summary>
/// Disposes the resources used by this instance.
/// </summary>
public void Dispose()
{
if (File.Exists(_dbPath)) File.Delete(_dbPath);

View File

@@ -12,8 +12,17 @@ namespace ZB.MOM.WW.CBDD.Shared
public class User
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// Gets or sets the age.
/// </summary>
public int Age { get; set; }
}
@@ -21,32 +30,62 @@ namespace ZB.MOM.WW.CBDD.Shared
public class ComplexUser
{
/// <summary>
/// Gets or sets the id.
/// </summary>
[BsonId]
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = "";
// Direct nested object
/// <summary>
/// Gets or sets the main address.
/// </summary>
public Address MainAddress { get; set; } = new();
// Collection of nested objects
/// <summary>
/// Gets or sets the other addresses.
/// </summary>
public List<Address> OtherAddresses { get; set; } = new();
// Primitive collection
/// <summary>
/// Gets or sets the tags.
/// </summary>
public List<string> Tags { get; set; } = new();
/// <summary>
/// Gets or sets the secret.
/// </summary>
[BsonIgnore]
public string Secret { get; set; } = "";
}
public class Address
{
/// <summary>
/// Gets or sets the street.
/// </summary>
public string Street { get; set; } = "";
/// <summary>
/// Gets or sets the city.
/// </summary>
public City City { get; set; } = new(); // Depth 2
}
public class City
{
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// Gets or sets the zip code.
/// </summary>
public string ZipCode { get; set; } = "";
}
@@ -54,19 +93,37 @@ namespace ZB.MOM.WW.CBDD.Shared
public class IntEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string? Name { get; set; }
}
public class StringEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public required string Id { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
public string? Value { get; set; }
}
public class GuidEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string? Name { get; set; }
}
@@ -75,8 +132,14 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class CustomKeyEntity
{
/// <summary>
/// Gets or sets the code.
/// </summary>
[System.ComponentModel.DataAnnotations.Key]
public required string Code { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
public string? Description { get; set; }
}
@@ -84,121 +147,252 @@ namespace ZB.MOM.WW.CBDD.Shared
public class AutoInitEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
}
public class Person
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// Gets or sets the age.
/// </summary>
public int Age { get; set; }
}
public class Product
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the title.
/// </summary>
public string Title { get; set; } = "";
/// <summary>
/// Gets or sets the price.
/// </summary>
public decimal Price { get; set; }
}
public class AsyncDoc
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = "";
}
public class SchemaUser
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// Gets or sets the address.
/// </summary>
public Address Address { get; set; } = new();
}
public class VectorEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the title.
/// </summary>
public string Title { get; set; } = "";
/// <summary>
/// Gets or sets the embedding.
/// </summary>
public float[] Embedding { get; set; } = Array.Empty<float>();
}
public class GeoEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = "";
/// <summary>
/// Gets or sets the location.
/// </summary>
public (double Latitude, double Longitude) Location { get; set; }
}
public record OrderId(string Value)
{
/// <summary>
/// Initializes a new instance.
/// </summary>
public OrderId() : this(string.Empty) { }
}
public class OrderIdConverter : ValueConverter<OrderId, string>
{
public override string ConvertToProvider(OrderId model) => model?.Value ?? string.Empty;
public override OrderId ConvertFromProvider(string provider) => new OrderId(provider);
/// <inheritdoc />
public override string ConvertToProvider(OrderId model) => model?.Value ?? string.Empty;
/// <inheritdoc />
public override OrderId ConvertFromProvider(string provider) => new OrderId(provider);
}
public class Order
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public OrderId Id { get; set; } = null!;
/// <summary>
/// Gets or sets the customer name.
/// </summary>
public string CustomerName { get; set; } = "";
}
public class TestDocument
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the category.
/// </summary>
public string Category { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the amount.
/// </summary>
public int Amount { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
}
public class OrderDocument
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the item name.
/// </summary>
public string ItemName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the quantity.
/// </summary>
public int Quantity { get; set; }
}
public class OrderItem
{
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the price.
/// </summary>
public int Price { get; set; }
}
public class ComplexDocument
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the title.
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the shipping address.
/// </summary>
public Address ShippingAddress { get; set; } = new();
/// <summary>
/// Gets or sets the items.
/// </summary>
public List<OrderItem> Items { get; set; } = new();
}
[Table("custom_users", Schema = "test")]
public class AnnotatedUser
{
/// <summary>
/// Gets or sets the id.
/// </summary>
[Key]
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[Required]
[Column("display_name")]
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; } = "";
/// <summary>
/// Gets or sets the age.
/// </summary>
[Range(0, 150)]
public int Age { get; set; }
/// <summary>
/// Gets the computed info.
/// </summary>
[NotMapped]
public string ComputedInfo => $"{Name} ({Age})";
/// <summary>
/// Gets or sets the location.
/// </summary>
[Column(TypeName = "geopoint")]
public (double Lat, double Lon) Location { get; set; }
}
public class PersonV2
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the age.
/// </summary>
public int Age { get; set; }
}
@@ -207,8 +401,17 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class ExtendedEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the created at.
/// </summary>
public DateTime CreatedAt { get; set; }
}
@@ -219,7 +422,13 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class BaseEntityWithId
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the created at.
/// </summary>
public DateTime CreatedAt { get; set; }
}
@@ -228,7 +437,13 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class DerivedEntity : BaseEntityWithId
{
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; } = string.Empty;
}
@@ -237,14 +452,35 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class EntityWithComputedProperties
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the first name.
/// </summary>
public string FirstName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the last name.
/// </summary>
public string LastName { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the birth year.
/// </summary>
public int BirthYear { get; set; }
// Computed properties - should NOT be serialized
/// <summary>
/// Gets the full name.
/// </summary>
public string FullName => $"{FirstName} {LastName}";
/// <summary>
/// Gets the age.
/// </summary>
public int Age => DateTime.Now.Year - BirthYear;
/// <summary>
/// Gets the display info.
/// </summary>
public string DisplayInfo => $"{FullName} (Age: {Age})";
}
@@ -253,18 +489,45 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class EntityWithAdvancedCollections
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
// Various collection types that should all be recognized
/// <summary>
/// Gets or sets the tags.
/// </summary>
public HashSet<string> Tags { get; set; } = new();
/// <summary>
/// Gets or sets the numbers.
/// </summary>
public ISet<int> Numbers { get; set; } = new HashSet<int>();
/// <summary>
/// Gets or sets the history.
/// </summary>
public LinkedList<string> History { get; set; } = new();
/// <summary>
/// Gets or sets the pending items.
/// </summary>
public Queue<string> PendingItems { get; set; } = new();
/// <summary>
/// Gets or sets the undo stack.
/// </summary>
public Stack<string> UndoStack { get; set; } = new();
// Nested objects in collections
/// <summary>
/// Gets or sets the addresses.
/// </summary>
public HashSet<Address> Addresses { get; set; } = new();
/// <summary>
/// Gets or sets the favorite cities.
/// </summary>
public ISet<City> FavoriteCities { get; set; } = new HashSet<City>();
}
@@ -273,13 +536,30 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class EntityWithPrivateSetters
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; private set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; private set; } = string.Empty;
/// <summary>
/// Gets or sets the age.
/// </summary>
public int Age { get; private set; }
/// <summary>
/// Gets or sets the created at.
/// </summary>
public DateTime CreatedAt { get; private set; }
// Factory method for creation
public static EntityWithPrivateSetters Create(string name, int age)
/// <summary>
/// Executes the create operation.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="age">The age.</param>
public static EntityWithPrivateSetters Create(string name, int age)
{
return new EntityWithPrivateSetters
{
@@ -296,9 +576,21 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class EntityWithInitSetters
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; init; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Gets or sets the age.
/// </summary>
public int Age { get; init; }
/// <summary>
/// Gets or sets the created at.
/// </summary>
public DateTime CreatedAt { get; init; }
}
@@ -313,10 +605,25 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class Employee
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the department.
/// </summary>
public string Department { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the manager id.
/// </summary>
public ObjectId? ManagerId { get; set; } // Reference to manager
/// <summary>
/// Gets or sets the direct report ids.
/// </summary>
public List<ObjectId>? DirectReportIds { get; set; } // References to direct reports (best practice)
}
@@ -327,9 +634,21 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class CategoryRef
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the description.
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the product ids.
/// </summary>
public List<ObjectId>? ProductIds { get; set; } // Only IDs - no embedding
}
@@ -340,9 +659,21 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class ProductRef
{
/// <summary>
/// Gets or sets the id.
/// </summary>
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the price.
/// </summary>
public decimal Price { get; set; }
/// <summary>
/// Gets or sets the category ids.
/// </summary>
public List<ObjectId>? CategoryIds { get; set; } // Only IDs - no embedding
}
@@ -358,12 +689,22 @@ namespace ZB.MOM.WW.CBDD.Shared
where TId : IEquatable<TId>
where TEntity : class
{
/// <summary>
/// Gets or sets the id.
/// </summary>
[System.ComponentModel.DataAnnotations.Key]
public virtual TId? Id { get; set; }
/// <summary>
/// Initializes a new instance.
/// </summary>
protected MockBaseEntity() { }
protected MockBaseEntity(TId? id)
/// <summary>
/// Initializes a new instance.
/// </summary>
/// <param name="id">The id.</param>
protected MockBaseEntity(TId? id)
{
Id = id;
}
@@ -377,9 +718,16 @@ namespace ZB.MOM.WW.CBDD.Shared
public abstract class MockUuidEntity<TEntity> : MockBaseEntity<string, TEntity>
where TEntity : class
{
/// <summary>
/// Initializes a new instance.
/// </summary>
protected MockUuidEntity() : base() { }
protected MockUuidEntity(string? id) : base(id) { }
/// <summary>
/// Initializes a new instance.
/// </summary>
/// <param name="id">The id.</param>
protected MockUuidEntity(string? id) : base(id) { }
}
/// <summary>
@@ -388,11 +736,24 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class MockCounter : MockUuidEntity<MockCounter>
{
/// <summary>
/// Initializes a new instance.
/// </summary>
public MockCounter() : base() { }
/// <summary>
/// Initializes a new instance.
/// </summary>
/// <param name="id">The id.</param>
public MockCounter(string? id) : base(id) { }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the value.
/// </summary>
public int Value { get; set; }
}
@@ -401,25 +762,58 @@ namespace ZB.MOM.WW.CBDD.Shared
/// </summary>
public class TemporalEntity
{
/// <summary>
/// Gets or sets the id.
/// </summary>
[Key]
public ObjectId Id { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } = string.Empty;
// DateTime types
/// <summary>
/// Gets or sets the created at.
/// </summary>
public DateTime CreatedAt { get; set; }
/// <summary>
/// Gets or sets the updated at.
/// </summary>
public DateTimeOffset UpdatedAt { get; set; }
/// <summary>
/// Gets or sets the last accessed at.
/// </summary>
public DateTimeOffset? LastAccessedAt { get; set; }
// TimeSpan
/// <summary>
/// Gets or sets the duration.
/// </summary>
public TimeSpan Duration { get; set; }
/// <summary>
/// Gets or sets the optional duration.
/// </summary>
public TimeSpan? OptionalDuration { get; set; }
// DateOnly and TimeOnly (.NET 6+)
/// <summary>
/// Gets or sets the birth date.
/// </summary>
public DateOnly BirthDate { get; set; }
/// <summary>
/// Gets or sets the anniversary.
/// </summary>
public DateOnly? Anniversary { get; set; }
/// <summary>
/// Gets or sets the opening time.
/// </summary>
public TimeOnly OpeningTime { get; set; }
/// <summary>
/// Gets or sets the closing time.
/// </summary>
public TimeOnly? ClosingTime { get; set; }
}
}

View File

@@ -11,16 +11,25 @@ namespace ZB.MOM.WW.CBDD.Tests
{
private const string DbPath = "nullable_string_id.db";
/// <summary>
/// Initializes a new instance of the <see cref="NullableStringIdTests"/> class.
/// </summary>
public NullableStringIdTests()
{
if (File.Exists(DbPath)) File.Delete(DbPath);
}
/// <summary>
/// Disposes test resources.
/// </summary>
public void Dispose()
{
if (File.Exists(DbPath)) File.Delete(DbPath);
}
/// <summary>
/// Verifies the mock counter collection is initialized.
/// </summary>
[Fact]
public void MockCounter_Collection_IsInitialized()
{
@@ -30,6 +39,9 @@ namespace ZB.MOM.WW.CBDD.Tests
db.MockCounters.ShouldNotBeNull();
}
/// <summary>
/// Verifies insert and find-by-id operations work for string identifiers.
/// </summary>
[Fact]
public void MockCounter_Insert_And_FindById_Works()
{
@@ -52,6 +64,9 @@ namespace ZB.MOM.WW.CBDD.Tests
stored.Value.ShouldBe(42);
}
/// <summary>
/// Verifies update operations work for string identifiers.
/// </summary>
[Fact]
public void MockCounter_Update_Works()
{
@@ -77,6 +92,9 @@ namespace ZB.MOM.WW.CBDD.Tests
updated.Value.ShouldBe(20);
}
/// <summary>
/// Verifies delete operations work for string identifiers.
/// </summary>
[Fact]
public void MockCounter_Delete_Works()
{
@@ -99,6 +117,9 @@ namespace ZB.MOM.WW.CBDD.Tests
deleted.ShouldBeNull();
}
/// <summary>
/// Verifies query operations work for string identifiers.
/// </summary>
[Fact]
public void MockCounter_Query_Works()
{
@@ -121,6 +142,9 @@ namespace ZB.MOM.WW.CBDD.Tests
highValues[0].Name.ShouldBe("Second");
}
/// <summary>
/// Verifies inherited string identifiers are stored and retrieved correctly.
/// </summary>
[Fact]
public void MockCounter_InheritedId_IsStoredCorrectly()
{

View File

@@ -5,6 +5,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class ObjectIdTests
{
/// <summary>
/// Verifies new object identifiers are 12 bytes long.
/// </summary>
[Fact]
public void NewObjectId_ShouldCreate12ByteId()
{
@@ -16,6 +19,9 @@ public class ObjectIdTests
bytes.Length.ShouldBe(12);
}
/// <summary>
/// Verifies object identifiers round-trip from their binary form.
/// </summary>
[Fact]
public void ObjectId_ShouldRoundTrip()
{
@@ -29,6 +35,9 @@ public class ObjectIdTests
restored.ShouldBe(original);
}
/// <summary>
/// Verifies object identifier equality behavior.
/// </summary>
[Fact]
public void ObjectId_Equals_ShouldWork()
{
@@ -40,6 +49,9 @@ public class ObjectIdTests
oid3.ShouldNotBe(oid1);
}
/// <summary>
/// Verifies object identifier timestamps are recent UTC values.
/// </summary>
[Fact]
public void ObjectId_Timestamp_ShouldBeRecentUtc()
{

View File

@@ -12,16 +12,25 @@ public class PrimaryKeyTests : IDisposable
{
private readonly string _dbPath = "primary_key_tests.db";
/// <summary>
/// Initializes a new instance of the <see cref="PrimaryKeyTests"/> class.
/// </summary>
public PrimaryKeyTests()
{
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Executes Dispose.
/// </summary>
public void Dispose()
{
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Executes Test_Int_PrimaryKey.
/// </summary>
[Fact]
public void Test_Int_PrimaryKey()
{
@@ -46,6 +55,9 @@ public class PrimaryKeyTests : IDisposable
db.IntEntities.FindById(1).ShouldBeNull();
}
/// <summary>
/// Executes Test_String_PrimaryKey.
/// </summary>
[Fact]
public void Test_String_PrimaryKey()
{
@@ -65,6 +77,9 @@ public class PrimaryKeyTests : IDisposable
db.StringEntities.FindById("key1").ShouldBeNull();
}
/// <summary>
/// Executes Test_Guid_PrimaryKey.
/// </summary>
[Fact]
public void Test_Guid_PrimaryKey()
{
@@ -84,6 +99,9 @@ public class PrimaryKeyTests : IDisposable
db.GuidEntities.FindById(id).ShouldBeNull();
}
/// <summary>
/// Executes Test_String_PrimaryKey_With_Custom_Name.
/// </summary>
[Fact]
public void Test_String_PrimaryKey_With_Custom_Name()
{

View File

@@ -11,6 +11,9 @@ public class QueryPrimitivesTests : IDisposable
private readonly StorageEngine _storage;
private readonly BTreeIndex _index;
/// <summary>
/// Initializes a new instance of the <see cref="QueryPrimitivesTests"/> class.
/// </summary>
public QueryPrimitivesTests()
{
_testFile = Path.Combine(Path.GetTempPath(), $"docdb_test_{Guid.NewGuid()}.db");
@@ -55,6 +58,9 @@ public class QueryPrimitivesTests : IDisposable
_index.Insert(key, new DocumentLocation(1, 1), txnId);
}
/// <summary>
/// Executes Equal_ShouldFindExactMatch.
/// </summary>
[Fact]
public void Equal_ShouldFindExactMatch()
{
@@ -65,6 +71,9 @@ public class QueryPrimitivesTests : IDisposable
result[0].Key.ShouldBe(key);
}
/// <summary>
/// Executes Equal_ShouldReturnEmpty_WhenNotFound.
/// </summary>
[Fact]
public void Equal_ShouldReturnEmpty_WhenNotFound()
{
@@ -74,6 +83,9 @@ public class QueryPrimitivesTests : IDisposable
result.ShouldBeEmpty();
}
/// <summary>
/// Executes GreaterThan_ShouldReturnMatches.
/// </summary>
[Fact]
public void GreaterThan_ShouldReturnMatches()
{
@@ -85,6 +97,9 @@ public class QueryPrimitivesTests : IDisposable
result[1].Key.ShouldBe(IndexKey.Create(50));
}
/// <summary>
/// Executes GreaterThanOrEqual_ShouldReturnMatches.
/// </summary>
[Fact]
public void GreaterThanOrEqual_ShouldReturnMatches()
{
@@ -97,6 +112,9 @@ public class QueryPrimitivesTests : IDisposable
result[2].Key.ShouldBe(IndexKey.Create(50));
}
/// <summary>
/// Executes LessThan_ShouldReturnMatches.
/// </summary>
[Fact]
public void LessThan_ShouldReturnMatches()
{
@@ -110,6 +128,9 @@ public class QueryPrimitivesTests : IDisposable
result[1].Key.ShouldBe(IndexKey.Create(10));
}
/// <summary>
/// Executes Between_ShouldReturnRange.
/// </summary>
[Fact]
public void Between_ShouldReturnRange()
{
@@ -123,6 +144,9 @@ public class QueryPrimitivesTests : IDisposable
result[2].Key.ShouldBe(IndexKey.Create(40));
}
/// <summary>
/// Executes StartsWith_ShouldReturnPrefixMatches.
/// </summary>
[Fact]
public void StartsWith_ShouldReturnPrefixMatches()
{
@@ -133,6 +157,9 @@ public class QueryPrimitivesTests : IDisposable
result[1].Key.ShouldBe(IndexKey.Create("ABC"));
}
/// <summary>
/// Executes Like_ShouldSupportWildcards.
/// </summary>
[Fact]
public void Like_ShouldSupportWildcards()
{
@@ -148,6 +175,9 @@ public class QueryPrimitivesTests : IDisposable
// AB ok. ABC ok. B ok.
}
/// <summary>
/// Executes Like_Underscore_ShouldMatchSingleChar.
/// </summary>
[Fact]
public void Like_Underscore_ShouldMatchSingleChar()
{
@@ -157,6 +187,9 @@ public class QueryPrimitivesTests : IDisposable
result[0].Key.ShouldBe(IndexKey.Create("AB"));
}
/// <summary>
/// Executes In_ShouldReturnSpecificKeys.
/// </summary>
[Fact]
public void In_ShouldReturnSpecificKeys()
{
@@ -169,6 +202,9 @@ public class QueryPrimitivesTests : IDisposable
result[2].Key.ShouldBe(IndexKey.Create(50));
}
/// <summary>
/// Executes Dispose.
/// </summary>
public void Dispose()
{
_storage.Dispose();

View File

@@ -11,19 +11,43 @@ public class RobustnessTests
{
public struct Point
{
/// <summary>
/// Gets or sets the X.
/// </summary>
public int X { get; set; }
/// <summary>
/// Gets or sets the Y.
/// </summary>
public int Y { get; set; }
}
public class RobustEntity
{
/// <summary>
/// Gets or sets the NullableInts.
/// </summary>
public List<int?> NullableInts { get; set; } = new();
/// <summary>
/// Gets or sets the Map.
/// </summary>
public Dictionary<string, int> Map { get; set; } = new();
/// <summary>
/// Gets or sets the EnumerableStrings.
/// </summary>
public IEnumerable<string> EnumerableStrings { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the Location.
/// </summary>
public Point Location { get; set; }
/// <summary>
/// Gets or sets the NullableLocation.
/// </summary>
public Point? NullableLocation { get; set; }
}
/// <summary>
/// Executes GenerateSchema_RobustnessChecks.
/// </summary>
[Fact]
public void GenerateSchema_RobustnessChecks()
{

View File

@@ -17,6 +17,9 @@ namespace ZB.MOM.WW.CBDD.Tests
private readonly string _testFile;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="ScanTests"/> class.
/// </summary>
public ScanTests()
{
_testFile = Path.Combine(Path.GetTempPath(), $"scan_tests_{Guid.NewGuid()}.db");
@@ -27,6 +30,9 @@ namespace ZB.MOM.WW.CBDD.Tests
_db = new Shared.TestDbContext(_testFile);
}
/// <summary>
/// Executes Dispose.
/// </summary>
public void Dispose()
{
_db.Dispose();
@@ -35,6 +41,9 @@ namespace ZB.MOM.WW.CBDD.Tests
if (File.Exists(wal)) File.Delete(wal);
}
/// <summary>
/// Executes Scan_FindsMatchingDocuments.
/// </summary>
[Fact]
public void Scan_FindsMatchingDocuments()
{
@@ -53,6 +62,9 @@ namespace ZB.MOM.WW.CBDD.Tests
results.ShouldContain(d => d.Name == "Charlie");
}
/// <summary>
/// Executes Repro_Insert_Loop_Hang.
/// </summary>
[Fact]
public void Repro_Insert_Loop_Hang()
{
@@ -65,6 +77,9 @@ namespace ZB.MOM.WW.CBDD.Tests
_db.SaveChanges();
}
/// <summary>
/// Executes ParallelScan_FindsMatchingDocuments.
/// </summary>
[Fact]
public void ParallelScan_FindsMatchingDocuments()
{

View File

@@ -17,18 +17,27 @@ public class SchemaPersistenceTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="SchemaPersistenceTests"/> class.
/// </summary>
public SchemaPersistenceTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"schema_test_{Guid.NewGuid()}.db");
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Disposes test resources and removes temporary files.
/// </summary>
public void Dispose()
{
_db.Dispose();
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Verifies BSON schema serialization and deserialization round-trips correctly.
/// </summary>
[Fact]
public void BsonSchema_Serialization_RoundTrip()
{
@@ -81,6 +90,9 @@ public class SchemaPersistenceTests : IDisposable
schema.Equals(roundTrip).ShouldBeTrue();
}
/// <summary>
/// Verifies collection metadata is persisted and reloaded correctly.
/// </summary>
[Fact]
public void StorageEngine_Collections_Metadata_Persistence()
{
@@ -103,6 +115,9 @@ public class SchemaPersistenceTests : IDisposable
loaded.Indexes[0].Name.ShouldBe("age");
}
/// <summary>
/// Verifies schema versioning appends new schema versions correctly.
/// </summary>
[Fact]
public void StorageEngine_Schema_Versioning()
{
@@ -125,6 +140,9 @@ public class SchemaPersistenceTests : IDisposable
schemas[1].Title.ShouldBe("V2");
}
/// <summary>
/// Verifies collection startup integrates schema versioning behavior.
/// </summary>
[Fact]
public void DocumentCollection_Integrates_Schema_Versioning_On_Startup()
{
@@ -186,6 +204,9 @@ public class SchemaPersistenceTests : IDisposable
}
}
/// <summary>
/// Verifies persisted documents include the schema version field.
/// </summary>
[Fact]
public void Document_Contains_Schema_Version_Field()
{

View File

@@ -16,6 +16,9 @@ public class SchemaTests
foreach (var k in new[] { "_id", "name", "mainaddress", "otheraddresses", "tags", "secret", "street", "city" }) _testKeyMap[k] = id++;
}
/// <summary>
/// Executes UsedKeys_ShouldReturnAllKeys.
/// </summary>
[Fact]
public void UsedKeys_ShouldReturnAllKeys()
{
@@ -33,6 +36,9 @@ public class SchemaTests
}
/// <summary>
/// Executes GetSchema_ShouldReturnBsonSchema.
/// </summary>
[Fact]
public void GetSchema_ShouldReturnBsonSchema()
{

View File

@@ -9,18 +9,27 @@ public class SetMethodTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="SetMethodTests"/> class.
/// </summary>
public SetMethodTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_set_{Guid.NewGuid()}.db");
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Disposes the resources used by this instance.
/// </summary>
public void Dispose()
{
_db.Dispose();
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Tests set object id returns correct collection.
/// </summary>
[Fact]
public void Set_ObjectId_ReturnsCorrectCollection()
{
@@ -29,6 +38,9 @@ public class SetMethodTests : IDisposable
collection.ShouldBeSameAs(_db.Users);
}
/// <summary>
/// Tests set shorthand returns correct collection.
/// </summary>
[Fact]
public void Set_Shorthand_ReturnsCorrectCollection()
{
@@ -37,6 +49,9 @@ public class SetMethodTests : IDisposable
collection.ShouldBeSameAs(_db.Users);
}
/// <summary>
/// Tests set int returns correct collection.
/// </summary>
[Fact]
public void Set_Int_ReturnsCorrectCollection()
{
@@ -45,6 +60,9 @@ public class SetMethodTests : IDisposable
collection.ShouldBeSameAs(_db.People);
}
/// <summary>
/// Tests set string returns correct collection.
/// </summary>
[Fact]
public void Set_String_ReturnsCorrectCollection()
{
@@ -53,6 +71,9 @@ public class SetMethodTests : IDisposable
collection.ShouldBeSameAs(_db.StringEntities);
}
/// <summary>
/// Tests set guid returns correct collection.
/// </summary>
[Fact]
public void Set_Guid_ReturnsCorrectCollection()
{
@@ -61,6 +82,9 @@ public class SetMethodTests : IDisposable
collection.ShouldBeSameAs(_db.GuidEntities);
}
/// <summary>
/// Tests set custom key returns correct collection.
/// </summary>
[Fact]
public void Set_CustomKey_ReturnsCorrectCollection()
{
@@ -69,6 +93,9 @@ public class SetMethodTests : IDisposable
collection.ShouldBeSameAs(_db.Orders);
}
/// <summary>
/// Tests set all object id collections return correct instances.
/// </summary>
[Fact]
public void Set_AllObjectIdCollections_ReturnCorrectInstances()
{
@@ -82,6 +109,9 @@ public class SetMethodTests : IDisposable
_db.Set<ObjectId, GeoEntity>().ShouldBeSameAs(_db.GeoItems);
}
/// <summary>
/// Tests set all int collections return correct instances.
/// </summary>
[Fact]
public void Set_AllIntCollections_ReturnCorrectInstances()
{
@@ -92,24 +122,36 @@ public class SetMethodTests : IDisposable
_db.Set<int, SchemaUser>().ShouldBeSameAs(_db.SchemaUsers);
}
/// <summary>
/// Tests set string key collections return correct instances.
/// </summary>
[Fact]
public void Set_StringKeyCollections_ReturnCorrectInstances()
{
_db.Set<string, CustomKeyEntity>().ShouldBeSameAs(_db.CustomKeyEntities);
}
/// <summary>
/// Tests set unregistered entity throws invalid operation exception.
/// </summary>
[Fact]
public void Set_UnregisteredEntity_ThrowsInvalidOperationException()
{
Should.Throw<InvalidOperationException>(() => _db.Set<ObjectId, Address>());
}
/// <summary>
/// Tests set wrong key type throws invalid operation exception.
/// </summary>
[Fact]
public void Set_WrongKeyType_ThrowsInvalidOperationException()
{
Should.Throw<InvalidOperationException>(() => _db.Set<string, User>());
}
/// <summary>
/// Tests set can perform operations.
/// </summary>
[Fact]
public void Set_CanPerformOperations()
{
@@ -124,6 +166,9 @@ public class SetMethodTests : IDisposable
found.Age.ShouldBe(30);
}
/// <summary>
/// Tests set with int key can perform operations.
/// </summary>
[Fact]
public void Set_WithIntKey_CanPerformOperations()
{
@@ -144,18 +189,27 @@ public class SetMethodInheritanceTests : IDisposable
private readonly string _dbPath;
private readonly Shared.TestExtendedDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="SetMethodInheritanceTests"/> class.
/// </summary>
public SetMethodInheritanceTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_set_inherit_{Guid.NewGuid()}.db");
_db = new Shared.TestExtendedDbContext(_dbPath);
}
/// <summary>
/// Disposes the resources used by this instance.
/// </summary>
public void Dispose()
{
_db.Dispose();
if (File.Exists(_dbPath)) File.Delete(_dbPath);
}
/// <summary>
/// Tests set own collection returns correct instance.
/// </summary>
[Fact]
public void Set_OwnCollection_ReturnsCorrectInstance()
{
@@ -164,6 +218,9 @@ public class SetMethodInheritanceTests : IDisposable
collection.ShouldBeSameAs(_db.ExtendedEntities);
}
/// <summary>
/// Tests set parent collection returns correct instance.
/// </summary>
[Fact]
public void Set_ParentCollection_ReturnsCorrectInstance()
{
@@ -172,6 +229,9 @@ public class SetMethodInheritanceTests : IDisposable
collection.ShouldBeSameAs(_db.Users);
}
/// <summary>
/// Tests set parent shorthand returns correct instance.
/// </summary>
[Fact]
public void Set_ParentShorthand_ReturnsCorrectInstance()
{
@@ -180,6 +240,9 @@ public class SetMethodInheritanceTests : IDisposable
collection.ShouldBeSameAs(_db.Users);
}
/// <summary>
/// Tests set parent int collection returns correct instance.
/// </summary>
[Fact]
public void Set_ParentIntCollection_ReturnsCorrectInstance()
{
@@ -187,6 +250,9 @@ public class SetMethodInheritanceTests : IDisposable
_db.Set<int, Product>().ShouldBeSameAs(_db.Products);
}
/// <summary>
/// Tests set parent custom key returns correct instance.
/// </summary>
[Fact]
public void Set_ParentCustomKey_ReturnsCorrectInstance()
{
@@ -195,12 +261,18 @@ public class SetMethodInheritanceTests : IDisposable
collection.ShouldBeSameAs(_db.Orders);
}
/// <summary>
/// Tests set unregistered entity throws invalid operation exception.
/// </summary>
[Fact]
public void Set_UnregisteredEntity_ThrowsInvalidOperationException()
{
Should.Throw<InvalidOperationException>(() => _db.Set<ObjectId, Address>());
}
/// <summary>
/// Tests set own collection can perform operations.
/// </summary>
[Fact]
public void Set_OwnCollection_CanPerformOperations()
{
@@ -214,6 +286,9 @@ public class SetMethodInheritanceTests : IDisposable
found.Description.ShouldBe("Test");
}
/// <summary>
/// Tests set parent collection can perform operations.
/// </summary>
[Fact]
public void Set_ParentCollection_CanPerformOperations()
{

View File

@@ -16,6 +16,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
private readonly string _walPath;
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="SourceGeneratorFeaturesTests"/> class.
/// </summary>
public SourceGeneratorFeaturesTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"test_sg_features_{Guid.NewGuid()}.db");
@@ -26,6 +29,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
#region Inheritance Tests
/// <summary>
/// Tests derived entity inherits id from base class.
/// </summary>
[Fact]
public void DerivedEntity_InheritsId_FromBaseClass()
{
@@ -50,6 +56,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
retrieved.CreatedAt.Date.ShouldBe(entity.CreatedAt.Date); // Compare just date part
}
/// <summary>
/// Tests derived entity update works with inherited id.
/// </summary>
[Fact]
public void DerivedEntity_Update_WorksWithInheritedId()
{
@@ -80,6 +89,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
updated.Description.ShouldBe("Updated Description");
}
/// <summary>
/// Tests derived entity query works with inherited properties.
/// </summary>
[Fact]
public void DerivedEntity_Query_WorksWithInheritedProperties()
{
@@ -107,6 +119,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
#region Computed Properties Tests
/// <summary>
/// Tests computed properties are not serialized.
/// </summary>
[Fact]
public void ComputedProperties_AreNotSerialized()
{
@@ -135,6 +150,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
retrieved.DisplayInfo.ShouldContain("John Doe");
}
/// <summary>
/// Tests computed properties update does not break.
/// </summary>
[Fact]
public void ComputedProperties_UpdateDoesNotBreak()
{
@@ -170,6 +188,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
#region Advanced Collections Tests
/// <summary>
/// Tests hash set serializes and deserializes.
/// </summary>
[Fact]
public void HashSet_SerializesAndDeserializes()
{
@@ -197,6 +218,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
retrieved.Tags.ShouldContain("tag3");
}
/// <summary>
/// Tests iset serializes and deserializes.
/// </summary>
[Fact]
public void ISet_SerializesAndDeserializes()
{
@@ -225,6 +249,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
retrieved.Numbers.ShouldContain(30);
}
/// <summary>
/// Tests linked list serializes and deserializes.
/// </summary>
[Fact]
public void LinkedList_SerializesAndDeserializes()
{
@@ -253,6 +280,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
historyList[2].ShouldBe("third");
}
/// <summary>
/// Tests queue serializes and deserializes.
/// </summary>
[Fact]
public void Queue_SerializesAndDeserializes()
{
@@ -280,6 +310,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
items.ShouldContain("item3");
}
/// <summary>
/// Tests stack serializes and deserializes.
/// </summary>
[Fact]
public void Stack_SerializesAndDeserializes()
{
@@ -307,6 +340,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
items.ShouldContain("action3");
}
/// <summary>
/// Tests hash set with nested objects serializes and deserializes.
/// </summary>
[Fact]
public void HashSet_WithNestedObjects_SerializesAndDeserializes()
{
@@ -334,6 +370,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
addressList.ShouldContain(a => a.Street == "456 Oak Ave" && a.City.Name == "LA");
}
/// <summary>
/// Tests iset with nested objects serializes and deserializes.
/// </summary>
[Fact]
public void ISet_WithNestedObjects_SerializesAndDeserializes()
{
@@ -363,6 +402,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
cityNames.ShouldContain("London");
}
/// <summary>
/// Tests advanced collections all types in single entity.
/// </summary>
[Fact]
public void AdvancedCollections_AllTypesInSingleEntity()
{
@@ -411,6 +453,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
#region Private Setters Tests
/// <summary>
/// Tests entity with private setters can be deserialized.
/// </summary>
[Fact]
public void EntityWithPrivateSetters_CanBeDeserialized()
{
@@ -429,6 +474,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
retrieved.Age.ShouldBe(30);
}
/// <summary>
/// Tests entity with private setters update works.
/// </summary>
[Fact]
public void EntityWithPrivateSetters_Update_Works()
{
@@ -452,6 +500,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
retrieved.Age.ShouldBe(35);
}
/// <summary>
/// Tests entity with private setters query works.
/// </summary>
[Fact]
public void EntityWithPrivateSetters_Query_Works()
{
@@ -478,6 +529,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
#region Init-Only Setters Tests
/// <summary>
/// Tests entity with init setters can be deserialized.
/// </summary>
[Fact]
public void EntityWithInitSetters_CanBeDeserialized()
{
@@ -502,6 +556,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
retrieved.Age.ShouldBe(28);
}
/// <summary>
/// Tests entity with init setters query works.
/// </summary>
[Fact]
public void EntityWithInitSetters_Query_Works()
{
@@ -526,6 +583,9 @@ public class SourceGeneratorFeaturesTests : IDisposable
#endregion
/// <summary>
/// Disposes the resources used by this instance.
/// </summary>
public void Dispose()
{
_db?.Dispose();

View File

@@ -13,6 +13,9 @@ public class StorageEngineDictionaryTests
if (File.Exists(Path.ChangeExtension(path, ".wal"))) File.Delete(Path.ChangeExtension(path, ".wal"));
}
/// <summary>
/// Verifies dictionary pages are initialized and return normalized keys.
/// </summary>
[Fact]
public void StorageEngine_ShouldInitializeDictionary()
{
@@ -32,6 +35,9 @@ public class StorageEngineDictionaryTests
finally { Cleanup(path); }
}
/// <summary>
/// Verifies dictionary entries persist across reopen.
/// </summary>
[Fact]
public void StorageEngine_ShouldPersistDictionary()
{
@@ -61,6 +67,9 @@ public class StorageEngineDictionaryTests
finally { Cleanup(path); }
}
/// <summary>
/// Verifies dictionary handling scales to many keys and remains durable.
/// </summary>
[Fact]
public void StorageEngine_ShouldHandleManyKeys()
{

View File

@@ -5,6 +5,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class StorageEngineTransactionProtocolTests
{
/// <summary>
/// Verifies preparing an unknown transaction returns false.
/// </summary>
[Fact]
public void PrepareTransaction_Should_ReturnFalse_For_Unknown_Transaction()
{
@@ -20,6 +23,9 @@ public class StorageEngineTransactionProtocolTests
}
}
/// <summary>
/// Verifies committing a detached transaction object throws.
/// </summary>
[Fact]
public void CommitTransaction_With_TransactionObject_Should_Throw_When_Not_Active()
{
@@ -37,6 +43,9 @@ public class StorageEngineTransactionProtocolTests
}
}
/// <summary>
/// Verifies committing a transaction object persists writes and clears active state.
/// </summary>
[Fact]
public void CommitTransaction_With_TransactionObject_Should_Commit_Writes()
{
@@ -65,6 +74,9 @@ public class StorageEngineTransactionProtocolTests
}
}
/// <summary>
/// Verifies committing by identifier with no writes does not throw.
/// </summary>
[Fact]
public void CommitTransaction_ById_With_NoWrites_Should_Not_Throw()
{
@@ -80,6 +92,9 @@ public class StorageEngineTransactionProtocolTests
}
}
/// <summary>
/// Verifies committed transaction cache moves into readable state and active count is cleared.
/// </summary>
[Fact]
public void MarkTransactionCommitted_Should_Move_Cache_And_Clear_ActiveCount()
{
@@ -108,6 +123,9 @@ public class StorageEngineTransactionProtocolTests
}
}
/// <summary>
/// Verifies rollback discards uncommitted page writes.
/// </summary>
[Fact]
public void RollbackTransaction_Should_Discard_Uncommitted_Write()
{
@@ -140,6 +158,9 @@ public class StorageEngineTransactionProtocolTests
}
}
/// <summary>
/// Verifies marking a transaction committed transitions state correctly.
/// </summary>
[Fact]
public void Transaction_MarkCommitted_Should_Transition_State()
{
@@ -169,6 +190,9 @@ public class StorageEngineTransactionProtocolTests
}
}
/// <summary>
/// Verifies preparing then committing writes WAL data and updates transaction state.
/// </summary>
[Fact]
public void Transaction_Prepare_Should_Write_Wal_And_Transition_State()
{

View File

@@ -11,12 +11,18 @@ namespace ZB.MOM.WW.CBDD.Tests
private readonly Shared.TestDbContext _db;
private readonly string _dbPath;
/// <summary>
/// Initializes a new instance of the <see cref="TemporalTypesTests"/> class.
/// </summary>
public TemporalTypesTests()
{
_dbPath = $"temporal_test_{Guid.NewGuid()}.db";
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Releases test resources.
/// </summary>
public void Dispose()
{
_db?.Dispose();
@@ -24,12 +30,18 @@ namespace ZB.MOM.WW.CBDD.Tests
File.Delete(_dbPath);
}
/// <summary>
/// Verifies temporal entity collection initialization.
/// </summary>
[Fact]
public void TemporalEntity_Collection_IsInitialized()
{
_db.TemporalEntities.ShouldNotBeNull();
}
/// <summary>
/// Verifies temporal fields round-trip through insert and lookup.
/// </summary>
[Fact]
public void TemporalEntity_Insert_And_FindById_Works()
{
@@ -87,6 +99,9 @@ namespace ZB.MOM.WW.CBDD.Tests
retrieved.ClosingTime!.Value.ShouldBe(entity.ClosingTime!.Value);
}
/// <summary>
/// Verifies insert behavior when optional temporal fields are null.
/// </summary>
[Fact]
public void TemporalEntity_Insert_WithNullOptionalFields_Works()
{
@@ -120,6 +135,9 @@ namespace ZB.MOM.WW.CBDD.Tests
retrieved.ClosingTime.ShouldBeNull();
}
/// <summary>
/// Verifies temporal entity updates persist correctly.
/// </summary>
[Fact]
public void TemporalEntity_Update_Works()
{
@@ -155,6 +173,9 @@ namespace ZB.MOM.WW.CBDD.Tests
retrieved.OpeningTime.ShouldBe(entity.OpeningTime);
}
/// <summary>
/// Verifies querying temporal entities by temporal fields.
/// </summary>
[Fact]
public void TemporalEntity_Query_Works()
{
@@ -197,6 +218,9 @@ namespace ZB.MOM.WW.CBDD.Tests
results[0].Name.ShouldBe("Person 1");
}
/// <summary>
/// Verifies edge-case TimeSpan values are persisted correctly.
/// </summary>
[Fact]
public void TimeSpan_EdgeCases_Work()
{

View File

@@ -1,10 +1,10 @@
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Compression;
using ZB.MOM.WW.CBDD.Core.Indexing;
using ZB.MOM.WW.CBDD.Core.Metadata;
using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Core;
using ZB.MOM.WW.CBDD.Core.Collections;
using ZB.MOM.WW.CBDD.Core.Compression;
using ZB.MOM.WW.CBDD.Core.Indexing;
using ZB.MOM.WW.CBDD.Core.Metadata;
using ZB.MOM.WW.CBDD.Core.Storage;
namespace ZB.MOM.WW.CBDD.Shared;
@@ -14,69 +14,178 @@ namespace ZB.MOM.WW.CBDD.Shared;
/// </summary>
public partial class TestDbContext : DocumentDbContext
{
/// <summary>
/// Gets or sets the AnnotatedUsers.
/// </summary>
public DocumentCollection<ObjectId, AnnotatedUser> AnnotatedUsers { get; set; } = null!;
/// <summary>
/// Gets or sets the Orders.
/// </summary>
public DocumentCollection<OrderId, Order> Orders { get; set; } = null!;
/// <summary>
/// Gets or sets the TestDocuments.
/// </summary>
public DocumentCollection<ObjectId, TestDocument> TestDocuments { get; set; } = null!;
/// <summary>
/// Gets or sets the OrderDocuments.
/// </summary>
public DocumentCollection<ObjectId, OrderDocument> OrderDocuments { get; set; } = null!;
/// <summary>
/// Gets or sets the ComplexDocuments.
/// </summary>
public DocumentCollection<ObjectId, ComplexDocument> ComplexDocuments { get; set; } = null!;
/// <summary>
/// Gets or sets the Users.
/// </summary>
public DocumentCollection<ObjectId, User> Users { get; set; } = null!;
/// <summary>
/// Gets or sets the ComplexUsers.
/// </summary>
public DocumentCollection<ObjectId, ComplexUser> ComplexUsers { get; set; } = null!;
/// <summary>
/// Gets or sets the AutoInitEntities.
/// </summary>
public DocumentCollection<int, AutoInitEntity> AutoInitEntities { get; set; } = null!;
/// <summary>
/// Gets or sets the People.
/// </summary>
public DocumentCollection<int, Person> People { get; set; } = null!;
/// <summary>
/// Gets or sets the PeopleV2.
/// </summary>
public DocumentCollection<ObjectId, PersonV2> PeopleV2 { get; set; } = null!;
/// <summary>
/// Gets or sets the Products.
/// </summary>
public DocumentCollection<int, Product> Products { get; set; } = null!;
/// <summary>
/// Gets or sets the IntEntities.
/// </summary>
public DocumentCollection<int, IntEntity> IntEntities { get; set; } = null!;
/// <summary>
/// Gets or sets the StringEntities.
/// </summary>
public DocumentCollection<string, StringEntity> StringEntities { get; set; } = null!;
/// <summary>
/// Gets or sets the GuidEntities.
/// </summary>
public DocumentCollection<Guid, GuidEntity> GuidEntities { get; set; } = null!;
/// <summary>
/// Gets or sets the CustomKeyEntities.
/// </summary>
public DocumentCollection<string, CustomKeyEntity> CustomKeyEntities { get; set; } = null!;
/// <summary>
/// Gets or sets the AsyncDocs.
/// </summary>
public DocumentCollection<int, AsyncDoc> AsyncDocs { get; set; } = null!;
/// <summary>
/// Gets or sets the SchemaUsers.
/// </summary>
public DocumentCollection<int, SchemaUser> SchemaUsers { get; set; } = null!;
/// <summary>
/// Gets or sets the VectorItems.
/// </summary>
public DocumentCollection<ObjectId, VectorEntity> VectorItems { get; set; } = null!;
public DocumentCollection<ObjectId, GeoEntity> GeoItems { get; set; } = null!;
// Source Generator Feature Tests
/// <summary>
/// Gets or sets the GeoItems.
/// </summary>
public DocumentCollection<ObjectId, GeoEntity> GeoItems { get; set; } = null!;
// Source Generator Feature Tests
/// <summary>
/// Gets or sets the DerivedEntities.
/// </summary>
public DocumentCollection<ObjectId, DerivedEntity> DerivedEntities { get; set; } = null!;
/// <summary>
/// Gets or sets the ComputedPropertyEntities.
/// </summary>
public DocumentCollection<ObjectId, EntityWithComputedProperties> ComputedPropertyEntities { get; set; } = null!;
/// <summary>
/// Gets or sets the AdvancedCollectionEntities.
/// </summary>
public DocumentCollection<ObjectId, EntityWithAdvancedCollections> AdvancedCollectionEntities { get; set; } = null!;
/// <summary>
/// Gets or sets the PrivateSetterEntities.
/// </summary>
public DocumentCollection<ObjectId, EntityWithPrivateSetters> PrivateSetterEntities { get; set; } = null!;
public DocumentCollection<ObjectId, EntityWithInitSetters> InitSetterEntities { get; set; } = null!;
// Circular Reference Tests
/// <summary>
/// Gets or sets the InitSetterEntities.
/// </summary>
public DocumentCollection<ObjectId, EntityWithInitSetters> InitSetterEntities { get; set; } = null!;
// Circular Reference Tests
/// <summary>
/// Gets or sets the Employees.
/// </summary>
public DocumentCollection<ObjectId, Employee> Employees { get; set; } = null!;
/// <summary>
/// Gets or sets the CategoryRefs.
/// </summary>
public DocumentCollection<ObjectId, CategoryRef> CategoryRefs { get; set; } = null!;
public DocumentCollection<ObjectId, ProductRef> ProductRefs { get; set; } = null!;
// Nullable String Id Test (UuidEntity scenario with inheritance)
public DocumentCollection<string, MockCounter> MockCounters { get; set; } = null!;
// Temporal Types Test (DateTimeOffset, TimeSpan, DateOnly, TimeOnly)
/// <summary>
/// Gets or sets the ProductRefs.
/// </summary>
public DocumentCollection<ObjectId, ProductRef> ProductRefs { get; set; } = null!;
// Nullable String Id Test (UuidEntity scenario with inheritance)
/// <summary>
/// Gets or sets the MockCounters.
/// </summary>
public DocumentCollection<string, MockCounter> MockCounters { get; set; } = null!;
// Temporal Types Test (DateTimeOffset, TimeSpan, DateOnly, TimeOnly)
/// <summary>
/// Gets or sets the TemporalEntities.
/// </summary>
public DocumentCollection<ObjectId, TemporalEntity> TemporalEntities { get; set; } = null!;
/// <summary>
/// Initializes a new instance of the <see cref="TestDbContext"/> class.
/// </summary>
/// <param name="databasePath">The database path.</param>
public TestDbContext(string databasePath)
: this(databasePath, PageFileConfig.Default, CompressionOptions.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TestDbContext"/> class.
/// </summary>
/// <param name="databasePath">The database path.</param>
/// <param name="compressionOptions">The compression options.</param>
public TestDbContext(string databasePath, CompressionOptions compressionOptions)
: this(databasePath, PageFileConfig.Default, compressionOptions)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TestDbContext"/> class.
/// </summary>
/// <param name="databasePath">The database path.</param>
/// <param name="pageFileConfig">The page file configuration.</param>
public TestDbContext(string databasePath, PageFileConfig pageFileConfig)
: this(databasePath, pageFileConfig, CompressionOptions.Default)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TestDbContext"/> class.
/// </summary>
/// <param name="databasePath">The database path.</param>
/// <param name="pageFileConfig">The page file configuration.</param>
/// <param name="compressionOptions">The compression options.</param>
/// <param name="maintenanceOptions">The maintenance options.</param>
public TestDbContext(
string databasePath,
PageFileConfig pageFileConfig,
CompressionOptions? compressionOptions,
MaintenanceOptions? maintenanceOptions = null)
: base(databasePath, pageFileConfig, compressionOptions, maintenanceOptions)
{
}
: base(databasePath, pageFileConfig, compressionOptions, maintenanceOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnnotatedUser>();
modelBuilder.Entity<User>().ToCollection("users");
@@ -93,8 +202,8 @@ public partial class TestDbContext : DocumentDbContext
modelBuilder.Entity<SchemaUser>().ToCollection("schema_users").HasKey(e => e.Id);
modelBuilder.Entity<TestDocument>();
modelBuilder.Entity<OrderDocument>();
modelBuilder.Entity<ComplexDocument>();
modelBuilder.Entity<ComplexDocument>();
modelBuilder.Entity<VectorEntity>()
.ToCollection("vector_items")
.HasVectorIndex(x => x.Embedding, dimensions: 3, metric: VectorMetric.L2, name: "idx_vector");
@@ -112,24 +221,30 @@ public partial class TestDbContext : DocumentDbContext
modelBuilder.Entity<EntityWithComputedProperties>().ToCollection("computed_property_entities");
modelBuilder.Entity<EntityWithAdvancedCollections>().ToCollection("advanced_collection_entities");
modelBuilder.Entity<EntityWithPrivateSetters>().ToCollection("private_setter_entities");
modelBuilder.Entity<EntityWithInitSetters>().ToCollection("init_setter_entities");
// Circular Reference Tests
modelBuilder.Entity<EntityWithInitSetters>().ToCollection("init_setter_entities");
// Circular Reference Tests
modelBuilder.Entity<Employee>().ToCollection("employees");
modelBuilder.Entity<CategoryRef>().ToCollection("category_refs");
modelBuilder.Entity<ProductRef>().ToCollection("product_refs");
// Nullable String Id Test (UuidEntity scenario)
modelBuilder.Entity<MockCounter>().ToCollection("mock_counters").HasKey(e => e.Id);
// Temporal Types Test
modelBuilder.Entity<ProductRef>().ToCollection("product_refs");
// Nullable String Id Test (UuidEntity scenario)
modelBuilder.Entity<MockCounter>().ToCollection("mock_counters").HasKey(e => e.Id);
// Temporal Types Test
modelBuilder.Entity<TemporalEntity>().ToCollection("temporal_entities").HasKey(e => e.Id);
}
public void ForceCheckpoint()
{
Engine.Checkpoint();
}
public StorageEngine Storage => Engine;
}
/// <summary>
/// Executes ForceCheckpoint.
/// </summary>
public void ForceCheckpoint()
{
Engine.Checkpoint();
}
/// <summary>
/// Gets or sets the Storage.
/// </summary>
public StorageEngine Storage => Engine;
}

View File

@@ -9,17 +9,25 @@ namespace ZB.MOM.WW.CBDD.Shared;
/// </summary>
public partial class TestExtendedDbContext : TestDbContext
{
/// <summary>
/// Gets or sets the extended entities.
/// </summary>
public DocumentCollection<int, ExtendedEntity> ExtendedEntities { get; set; } = null!;
public TestExtendedDbContext(string databasePath) : base(databasePath)
/// <summary>
/// Initializes a new instance of the <see cref="TestExtendedDbContext"/> class.
/// </summary>
/// <param name="databasePath">Database file path.</param>
public TestExtendedDbContext(string databasePath) : base(databasePath)
{
InitializeCollections();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ExtendedEntity>()
.ToCollection("extended_entities")
.HasKey(e => e.Id);

View File

@@ -12,12 +12,18 @@ public class ValueObjectIdTests : IDisposable
private readonly string _dbPath = "value_object_ids.db";
private readonly Shared.TestDbContext _db;
/// <summary>
/// Initializes a new instance of the <see cref="ValueObjectIdTests"/> class.
/// </summary>
public ValueObjectIdTests()
{
if (File.Exists(_dbPath)) File.Delete(_dbPath);
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Executes Should_Support_ValueObject_Id_Conversion.
/// </summary>
[Fact]
public void Should_Support_ValueObject_Id_Conversion()
{
@@ -36,6 +42,9 @@ public class ValueObjectIdTests : IDisposable
retrieved.CustomerName.ShouldBe("John Doe");
}
/// <summary>
/// Executes Dispose.
/// </summary>
public void Dispose()
{
_db.Dispose();

View File

@@ -4,6 +4,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class VectorMathTests
{
/// <summary>
/// Verifies distance calculations across all supported vector metrics.
/// </summary>
[Fact]
public void Distance_Should_Cover_All_Metrics()
{
@@ -21,6 +24,9 @@ public class VectorMathTests
MathF.Abs(cosineDistance - expectedCosine).ShouldBeLessThan(0.0001f);
}
/// <summary>
/// Verifies cosine similarity returns zero when one vector has zero magnitude.
/// </summary>
[Fact]
public void CosineSimilarity_Should_Return_Zero_For_ZeroMagnitude_Vector()
{
@@ -30,6 +36,9 @@ public class VectorMathTests
VectorMath.CosineSimilarity(v1, v2).ShouldBe(0f);
}
/// <summary>
/// Verifies dot product throws for mismatched vector lengths.
/// </summary>
[Fact]
public void DotProduct_Should_Throw_For_Length_Mismatch()
{
@@ -39,6 +48,9 @@ public class VectorMathTests
Should.Throw<ArgumentException>(() => VectorMath.DotProduct(v1, v2));
}
/// <summary>
/// Verifies squared Euclidean distance throws for mismatched vector lengths.
/// </summary>
[Fact]
public void EuclideanDistanceSquared_Should_Throw_For_Length_Mismatch()
{

View File

@@ -8,6 +8,9 @@ namespace ZB.MOM.WW.CBDD.Tests;
public class VectorSearchTests
{
/// <summary>
/// Verifies basic vector-search query behavior.
/// </summary>
[Fact]
public void Test_VectorSearch_Basic()
{

View File

@@ -10,12 +10,21 @@ public class VisibilityTests
public class VisibilityEntity
{
// Should be included
/// <summary>
/// Gets or sets the normal prop.
/// </summary>
public int NormalProp { get; set; }
// Should be included (serialization usually writes it)
/// <summary>
/// Gets or sets the private set prop.
/// </summary>
public int PrivateSetProp { get; private set; }
// Should be included
/// <summary>
/// Gets or sets the init prop.
/// </summary>
public int InitProp { get; init; }
// Fields - typically included in BSON if public, but reflection need GetFields
@@ -25,9 +34,16 @@ public class VisibilityTests
private int _privateField;
// Helper to set private
/// <summary>
/// Tests set private.
/// </summary>
/// <param name="val">Value assigned to the private field.</param>
public void SetPrivate(int val) => _privateField = val;
}
/// <summary>
/// Tests generate schema visibility checks.
/// </summary>
[Fact]
public void GenerateSchema_VisibilityChecks()
{

View File

@@ -18,6 +18,10 @@ public class WalIndexTests : IDisposable
private readonly Shared.TestDbContext _db;
private readonly ITestOutputHelper _output;
/// <summary>
/// Initializes a new instance of the <see cref="WalIndexTests"/> class.
/// </summary>
/// <param name="output">Test output sink.</param>
public WalIndexTests(ITestOutputHelper output)
{
_output = output;
@@ -28,6 +32,9 @@ public class WalIndexTests : IDisposable
_db = new Shared.TestDbContext(_dbPath);
}
/// <summary>
/// Verifies index writes are recorded in the WAL.
/// </summary>
[Fact]
public void IndexWritesAreLoggedToWal()
{
@@ -87,6 +94,9 @@ public class WalIndexTests : IDisposable
return (PageType)pageData[4]; // Casting byte to PageType
}
/// <summary>
/// Verifies offline compaction leaves the WAL empty.
/// </summary>
[Fact]
public void Compact_ShouldLeaveWalEmpty_AfterOfflineRun()
{
@@ -110,6 +120,9 @@ public class WalIndexTests : IDisposable
new FileInfo(_walPath).Length.ShouldBe(0);
}
/// <summary>
/// Verifies WAL recovery followed by compaction preserves data.
/// </summary>
[Fact]
public void Recover_WithCommittedWal_ThenCompact_ShouldPreserveData()
{
@@ -153,6 +166,9 @@ public class WalIndexTests : IDisposable
}
}
/// <summary>
/// Releases test resources.
/// </summary>
public void Dispose()
{
try

File diff suppressed because it is too large Load Diff