Implement checkpoint modes with docs/tests and reorganize project file layout
This commit is contained in:
152
tests/CBDD.Tests/Collections/AsyncTests.cs
Executable file
152
tests/CBDD.Tests/Collections/AsyncTests.cs
Executable file
@@ -0,0 +1,152 @@
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
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()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
|
||||
using (var db = new Shared.TestDbContext(_dbPath))
|
||||
{
|
||||
using (var txn = await db.BeginTransactionAsync(ct))
|
||||
{
|
||||
db.AsyncDocs.Insert(new AsyncDoc { Id = 1, Name = "Async1" });
|
||||
db.AsyncDocs.Insert(new AsyncDoc { Id = 2, Name = "Async2" });
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify with new storage engine instance
|
||||
using var db2 = new Shared.TestDbContext(_dbPath);
|
||||
var doc1 = db2.AsyncDocs.FindById(1);
|
||||
doc1.ShouldNotBeNull();
|
||||
doc1.Name.ShouldBe("Async1");
|
||||
|
||||
var doc2 = db2.AsyncDocs.FindById(2);
|
||||
doc2.ShouldNotBeNull();
|
||||
doc2.Name.ShouldBe("Async2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Async_Transaction_Rollback_Should_Discard_Data.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Async_Transaction_Rollback_Should_Discard_Data()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
|
||||
using var db = new Shared.TestDbContext(_dbPath);
|
||||
using (var txn = await db.BeginTransactionAsync(ct))
|
||||
{
|
||||
db.AsyncDocs.Insert(new AsyncDoc { Id = 3, Name = "RollbackMe" });
|
||||
}
|
||||
|
||||
var doc = db.AsyncDocs.FindById(3);
|
||||
doc.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Bulk_Async_Insert_Should_Persist_Data.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Bulk_Async_Insert_Should_Persist_Data()
|
||||
{
|
||||
using var db = new Shared.TestDbContext(_dbPath);
|
||||
var docs = Enumerable.Range(1, 100).Select(i => new AsyncDoc { Id = i + 5000, Name = $"Bulk{i}" });
|
||||
|
||||
var ids = await db.AsyncDocs.InsertBulkAsync(docs);
|
||||
|
||||
ids.Count.ShouldBe(100);
|
||||
|
||||
var doc50 = db.AsyncDocs.FindById(5050);
|
||||
doc50.ShouldNotBeNull();
|
||||
doc50.Name.ShouldBe("Bulk50");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Bulk_Async_Update_Should_Persist_Changes.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Bulk_Async_Update_Should_Persist_Changes()
|
||||
{
|
||||
using var db = new Shared.TestDbContext(_dbPath);
|
||||
// 1. Insert 100 docs
|
||||
var docs = Enumerable.Range(1, 100).Select(i => new AsyncDoc { Id = i + 6000, Name = $"Original{i}" }).ToList();
|
||||
await db.AsyncDocs.InsertBulkAsync(docs);
|
||||
|
||||
// 2. Update all docs
|
||||
foreach (var doc in docs)
|
||||
{
|
||||
doc.Name = $"Updated{doc.Id - 6000}";
|
||||
}
|
||||
|
||||
var count = await db.AsyncDocs.UpdateBulkAsync(docs);
|
||||
|
||||
count.ShouldBe(100);
|
||||
|
||||
// 3. Verify updates
|
||||
var doc50 = db.AsyncDocs.FindById(6050);
|
||||
doc50.ShouldNotBeNull();
|
||||
doc50.Name.ShouldBe("Updated50");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes High_Concurrency_Async_Commits.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task High_Concurrency_Async_Commits()
|
||||
{
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
|
||||
using var db = new Shared.TestDbContext(Path.Combine(Path.GetTempPath(), $"cbdd_async_concurrency_{Guid.NewGuid()}.db"));
|
||||
int threadCount = 2;
|
||||
int docsPerThread = 50;
|
||||
|
||||
var tasks = Enumerable.Range(0, threadCount).Select(async i =>
|
||||
{
|
||||
// Test mix of implicit and explicit transactions
|
||||
for (int j = 0; j < docsPerThread; j++)
|
||||
{
|
||||
int id = (i * docsPerThread) + j + 8000;
|
||||
await db.AsyncDocs.InsertAsync(new AsyncDoc { Id = id, Name = $"Thread{i}_Doc{j}" });
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
await db.SaveChangesAsync(ct);
|
||||
|
||||
// Verify count
|
||||
var count = db.AsyncDocs.Scan(_ => true).Count();
|
||||
count.ShouldBe(threadCount * docsPerThread);
|
||||
}
|
||||
}
|
||||
140
tests/CBDD.Tests/Collections/BulkOperationsTests.cs
Executable file
140
tests/CBDD.Tests/Collections/BulkOperationsTests.cs
Executable file
@@ -0,0 +1,140 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;
|
||||
using Xunit;
|
||||
using static ZB.MOM.WW.CBDD.Tests.SchemaTests;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class BulkOperationsTests : IDisposable
|
||||
{
|
||||
private readonly string _dbPath;
|
||||
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");
|
||||
_walPath = Path.Combine(Path.GetTempPath(), $"test_bulk_{Guid.NewGuid()}.wal");
|
||||
|
||||
_dbContext = new Shared.TestDbContext(_dbPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes Dispose.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_dbContext.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes UpdateBulk_UpdatesMultipleDocuments.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void UpdateBulk_UpdatesMultipleDocuments()
|
||||
{
|
||||
// Arrange: Insert 100 users
|
||||
var users = new List<User>();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
users.Add(new User { Id = ObjectId.NewObjectId(), Name = $"User {i}", Age = 20 });
|
||||
}
|
||||
_dbContext.Users.InsertBulk(users);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
// Modify users
|
||||
foreach (var u in users)
|
||||
{
|
||||
u.Age = 30; // In-place update (int is same size)
|
||||
if (u.Name.EndsWith("0")) u.Name += "_Modified_Longer"; // Force move update
|
||||
}
|
||||
|
||||
// Act
|
||||
var updatedCount = _dbContext.Users.UpdateBulk(users);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
// Assert
|
||||
updatedCount.ShouldBe(100);
|
||||
|
||||
// Verify changes
|
||||
foreach (var u in users)
|
||||
{
|
||||
var stored = _dbContext.Users.FindById(u.Id);
|
||||
stored.ShouldNotBeNull();
|
||||
stored.Age.ShouldBe(30);
|
||||
stored.Name.ShouldBe(u.Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes DeleteBulk_RemovesMultipleDocuments.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DeleteBulk_RemovesMultipleDocuments()
|
||||
{
|
||||
// Arrange: Insert 100 users
|
||||
var users = new List<User>();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
users.Add(new User { Id = ObjectId.NewObjectId(), Name = $"User {i}", Age = 20 });
|
||||
}
|
||||
_dbContext.Users.InsertBulk(users);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
var idsToDelete = users.Take(50).Select(u => u.Id).ToList();
|
||||
|
||||
// Act
|
||||
var deletedCount = _dbContext.Users.DeleteBulk(idsToDelete);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
// Assert
|
||||
deletedCount.ShouldBe(50);
|
||||
|
||||
// Verify deleted
|
||||
foreach (var id in idsToDelete)
|
||||
{
|
||||
_dbContext.Users.FindById(id).ShouldBeNull();
|
||||
}
|
||||
|
||||
// Verify remaining
|
||||
var remaining = users.Skip(50).ToList();
|
||||
foreach (var u in remaining)
|
||||
{
|
||||
_dbContext.Users.FindById(u.Id).ShouldNotBeNull();
|
||||
}
|
||||
|
||||
// Verify count
|
||||
// Note: Count() is not fully implemented efficiently yet (iterates everything), but FindAll().Count() works
|
||||
_dbContext.Users.FindAll().Count().ShouldBe(50);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes DeleteBulk_WithTransaction_Rollworks.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void DeleteBulk_WithTransaction_Rollworks()
|
||||
{
|
||||
// Arrange
|
||||
var user = new User { Id = ObjectId.NewObjectId(), Name = "Txn User", Age = 20 };
|
||||
_dbContext.Users.Insert(user);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
_dbContext.Users.FindById(user.Id).ShouldNotBeNull();
|
||||
|
||||
using (var txn = _dbContext.BeginTransaction())
|
||||
{
|
||||
_dbContext.Users.DeleteBulk(new[] { user.Id });
|
||||
txn.Rollback();
|
||||
}
|
||||
|
||||
// Assert: Should still exist
|
||||
_dbContext.Users.FindById(user.Id).ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
95
tests/CBDD.Tests/Collections/DocumentCollectionDeleteTests.cs
Executable file
95
tests/CBDD.Tests/Collections/DocumentCollectionDeleteTests.cs
Executable file
@@ -0,0 +1,95 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class DocumentCollectionDeleteTests : IDisposable
|
||||
{
|
||||
private readonly string _dbPath;
|
||||
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");
|
||||
_walPath = Path.Combine(Path.GetTempPath(), $"test_delete_{Guid.NewGuid()}.wal");
|
||||
|
||||
_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()
|
||||
{
|
||||
var user = new User { Id = ObjectId.NewObjectId(), Name = "To Delete", Age = 10 };
|
||||
_dbContext.Users.Insert(user);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
// Verify inserted
|
||||
_dbContext.Users.FindById(user.Id).ShouldNotBeNull();
|
||||
|
||||
// Delete
|
||||
var deleted = _dbContext.Users.Delete(user.Id);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
// Assert
|
||||
deleted.ShouldBeTrue("Delete returned false");
|
||||
|
||||
// Verify deleted from storage
|
||||
_dbContext.Users.FindById(user.Id).ShouldBeNull();
|
||||
|
||||
// Verify Index is clean (FindAll uses index scan)
|
||||
var all = _dbContext.Users.FindAll();
|
||||
all.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies delete returns false for a non-existent document.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Delete_NonExistent_ReturnsFalse()
|
||||
{
|
||||
var id = ObjectId.NewObjectId();
|
||||
var deleted = _dbContext.Users.Delete(id);
|
||||
_dbContext.SaveChanges();
|
||||
deleted.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies deletes inside a transaction commit successfully.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Delete_WithTransaction_CommitsSuccessfully()
|
||||
{
|
||||
var user = new User { Id = ObjectId.NewObjectId(), Name = "Txn Delete", Age = 20 };
|
||||
_dbContext.Users.Insert(user);
|
||||
_dbContext.SaveChanges();
|
||||
|
||||
using (var txn = _dbContext.BeginTransaction())
|
||||
{
|
||||
_dbContext.Users.Delete(user.Id);
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
// Verify
|
||||
_dbContext.Users.FindById(user.Id).ShouldBeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using ZB.MOM.WW.CBDD.Core.Indexing;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
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()
|
||||
{
|
||||
_db.VectorItems.Insert(new VectorEntity { Title = "A", Embedding = [1f, 1f, 1f] });
|
||||
_db.VectorItems.Insert(new VectorEntity { Title = "B", Embedding = [2f, 2f, 2f] });
|
||||
_db.SaveChanges();
|
||||
|
||||
_db.VectorItems.CreateVectorIndex(v => v.Embedding, 3, VectorMetric.DotProduct, "idx_vector_extra");
|
||||
|
||||
var indexNames = _db.VectorItems.GetIndexes().Select(x => x.Name).ToList();
|
||||
indexNames.ShouldContain("idx_vector_extra");
|
||||
|
||||
_db.VectorItems.DropIndex("idx_vector_extra").ShouldBeTrue();
|
||||
_db.VectorItems.DropIndex("idx_vector_extra").ShouldBeFalse();
|
||||
|
||||
_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()
|
||||
{
|
||||
var first = _db.People.EnsureIndex(p => p.Age, name: "idx_people_age");
|
||||
var second = _db.People.EnsureIndex(p => p.Age, name: "idx_people_age");
|
||||
|
||||
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();
|
||||
if (File.Exists(_dbPath)) File.Delete(_dbPath);
|
||||
var wal = Path.ChangeExtension(_dbPath, ".wal");
|
||||
if (File.Exists(wal)) File.Delete(wal);
|
||||
}
|
||||
}
|
||||
249
tests/CBDD.Tests/Collections/DocumentCollectionTests.cs
Executable file
249
tests/CBDD.Tests/Collections/DocumentCollectionTests.cs
Executable file
@@ -0,0 +1,249 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
public class DocumentCollectionTests : IDisposable
|
||||
{
|
||||
private readonly string _dbPath;
|
||||
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");
|
||||
_walPath = Path.Combine(Path.GetTempPath(), $"test_collection_{Guid.NewGuid()}.wal");
|
||||
|
||||
_db = new Shared.TestDbContext(_dbPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies insert and find-by-id operations.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Insert_And_FindById_Works()
|
||||
{
|
||||
// Arrange
|
||||
var user = new User { Name = "Alice", Age = 30 };
|
||||
|
||||
// Act
|
||||
var id = _db.Users.Insert(user);
|
||||
_db.SaveChanges();
|
||||
var found = _db.Users.FindById(id);
|
||||
|
||||
// Assert
|
||||
found.ShouldNotBeNull();
|
||||
found.Id.ShouldBe(id);
|
||||
found.Name.ShouldBe("Alice");
|
||||
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()
|
||||
{
|
||||
// Act
|
||||
var found = _db.Users.FindById(ObjectId.NewObjectId());
|
||||
|
||||
// Assert
|
||||
found.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies find-all returns all entities.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FindAll_Returns_All_Entities()
|
||||
{
|
||||
// Arrange
|
||||
_db.Users.Insert(new User { Name = "Alice", Age = 30 });
|
||||
_db.Users.Insert(new User { Name = "Bob", Age = 25 });
|
||||
_db.Users.Insert(new User { Name = "Charlie", Age = 35 });
|
||||
_db.SaveChanges();
|
||||
|
||||
// Act
|
||||
var all = _db.Users.FindAll().ToList();
|
||||
|
||||
// Assert
|
||||
all.Count.ShouldBe(3);
|
||||
all.ShouldContain(u => u.Name == "Alice");
|
||||
all.ShouldContain(u => u.Name == "Bob");
|
||||
all.ShouldContain(u => u.Name == "Charlie");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies update modifies an existing entity.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Update_Modifies_Entity()
|
||||
{
|
||||
// Arrange
|
||||
var user = new User { Name = "Alice", Age = 30 };
|
||||
var id = _db.Users.Insert(user);
|
||||
_db.SaveChanges();
|
||||
|
||||
// Act
|
||||
user.Age = 31;
|
||||
var updated = _db.Users.Update(user);
|
||||
_db.SaveChanges();
|
||||
|
||||
// Assert
|
||||
updated.ShouldBeTrue();
|
||||
|
||||
var found = _db.Users.FindById(id);
|
||||
found.ShouldNotBeNull();
|
||||
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()
|
||||
{
|
||||
// Arrange
|
||||
var user = new User { Id = ObjectId.NewObjectId(), Name = "Ghost", Age = 99 };
|
||||
|
||||
// Act
|
||||
var updated = _db.Users.Update(user);
|
||||
_db.SaveChanges();
|
||||
|
||||
// Assert
|
||||
updated.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies delete removes an entity.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Delete_Removes_Entity()
|
||||
{
|
||||
// Arrange
|
||||
var user = new User { Name = "Alice", Age = 30 };
|
||||
var id = _db.Users.Insert(user);
|
||||
_db.SaveChanges();
|
||||
|
||||
// Act
|
||||
var deleted = _db.Users.Delete(id);
|
||||
_db.SaveChanges();
|
||||
|
||||
// Assert
|
||||
deleted.ShouldBeTrue();
|
||||
_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()
|
||||
{
|
||||
// Act
|
||||
var deleted = _db.Users.Delete(ObjectId.NewObjectId());
|
||||
_db.SaveChanges();
|
||||
|
||||
// Assert
|
||||
deleted.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies count returns the correct entity count.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Count_Returns_Correct_Count()
|
||||
{
|
||||
// Arrange
|
||||
_db.Users.Insert(new User { Name = "Alice", Age = 30 });
|
||||
_db.Users.Insert(new User { Name = "Bob", Age = 25 });
|
||||
_db.SaveChanges();
|
||||
|
||||
// Act
|
||||
var count = _db.Users.Count();
|
||||
|
||||
// Assert
|
||||
count.ShouldBe(2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies predicate queries filter entities correctly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Find_With_Predicate_Filters_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
_db.Users.Insert(new User { Name = "Alice", Age = 30 });
|
||||
_db.Users.Insert(new User { Name = "Bob", Age = 25 });
|
||||
_db.Users.Insert(new User { Name = "Charlie", Age = 35 });
|
||||
_db.SaveChanges();
|
||||
|
||||
// Act
|
||||
var over30 = _db.Users.Find(u => u.Age > 30).ToList();
|
||||
|
||||
// Assert
|
||||
over30.Count().ShouldBe(1);
|
||||
over30[0].Name.ShouldBe("Charlie");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies bulk insert stores multiple entities.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void InsertBulk_Inserts_Multiple_Entities()
|
||||
{
|
||||
// Arrange
|
||||
var users = new[]
|
||||
{
|
||||
new User { Name = "User1", Age = 20 },
|
||||
new User { Name = "User2", Age = 21 },
|
||||
new User { Name = "User3", Age = 22 }
|
||||
};
|
||||
|
||||
// Act
|
||||
var count = _db.Users.InsertBulk(users);
|
||||
_db.SaveChanges();
|
||||
|
||||
// Assert
|
||||
count.Count.ShouldBe(3);
|
||||
_db.Users.Count().ShouldBe(3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies inserts preserve an explicitly assigned identifier.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Insert_With_SpecifiedId_RetainsId()
|
||||
{
|
||||
// Arrange
|
||||
var id = ObjectId.NewObjectId();
|
||||
var user = new User { Id = id, Name = "SpecifiedID", Age = 40 };
|
||||
|
||||
// Act
|
||||
var insertedId = _db.Users.Insert(user);
|
||||
_db.SaveChanges();
|
||||
|
||||
// Assert
|
||||
insertedId.ShouldBe(id);
|
||||
|
||||
var found = _db.Users.FindById(id);
|
||||
found.ShouldNotBeNull();
|
||||
found.Id.ShouldBe(id);
|
||||
found.Name.ShouldBe("SpecifiedID");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases test resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_db?.Dispose();
|
||||
}
|
||||
}
|
||||
71
tests/CBDD.Tests/Collections/InsertBulkTests.cs
Executable file
71
tests/CBDD.Tests/Collections/InsertBulkTests.cs
Executable file
@@ -0,0 +1,71 @@
|
||||
using ZB.MOM.WW.CBDD.Core;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Core.Storage;
|
||||
using ZB.MOM.WW.CBDD.Core.Transactions;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
using ZB.MOM.WW.CBDD.Shared.TestDbContext_TestDbContext_Mappers;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
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()
|
||||
{
|
||||
var users = new List<User>();
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
users.Add(new User { Id = ZB.MOM.WW.CBDD.Bson.ObjectId.NewObjectId(), Name = $"User {i}", Age = 20 });
|
||||
}
|
||||
|
||||
_db.Users.InsertBulk(users);
|
||||
_db.SaveChanges();
|
||||
|
||||
var insertedUsers = _db.Users.FindAll().ToList();
|
||||
|
||||
insertedUsers.Count.ShouldBe(50);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies bulk inserts spanning multiple pages persist correctly.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void InsertBulk_SpanningMultiplePages_PersistsCorrectly()
|
||||
{
|
||||
// 16KB page. User ~50 bytes. 400 users -> ~20KB -> 2 pages.
|
||||
var users = new List<User>();
|
||||
for (int i = 0; i < 400; i++)
|
||||
{
|
||||
users.Add(new User { Id = ZB.MOM.WW.CBDD.Bson.ObjectId.NewObjectId(), Name = $"User {i} with some long padding text to ensure we fill space {new string('x', 50)}", Age = 20 });
|
||||
}
|
||||
|
||||
_db.Users.InsertBulk(users);
|
||||
_db.SaveChanges();
|
||||
|
||||
_db.Users.Count().ShouldBe(400);
|
||||
}
|
||||
}
|
||||
304
tests/CBDD.Tests/Collections/SetMethodTests.cs
Executable file
304
tests/CBDD.Tests/Collections/SetMethodTests.cs
Executable file
@@ -0,0 +1,304 @@
|
||||
using ZB.MOM.WW.CBDD.Bson;
|
||||
using ZB.MOM.WW.CBDD.Core.Collections;
|
||||
using ZB.MOM.WW.CBDD.Shared;
|
||||
|
||||
namespace ZB.MOM.WW.CBDD.Tests;
|
||||
|
||||
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()
|
||||
{
|
||||
var collection = _db.Set<ObjectId, User>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.Users);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set shorthand returns correct collection.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_Shorthand_ReturnsCorrectCollection()
|
||||
{
|
||||
var collection = _db.Set<User>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.Users);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set int returns correct collection.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_Int_ReturnsCorrectCollection()
|
||||
{
|
||||
var collection = _db.Set<int, Person>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.People);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set string returns correct collection.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_String_ReturnsCorrectCollection()
|
||||
{
|
||||
var collection = _db.Set<string, StringEntity>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.StringEntities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set guid returns correct collection.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_Guid_ReturnsCorrectCollection()
|
||||
{
|
||||
var collection = _db.Set<Guid, GuidEntity>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.GuidEntities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set custom key returns correct collection.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_CustomKey_ReturnsCorrectCollection()
|
||||
{
|
||||
var collection = _db.Set<OrderId, Order>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.Orders);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set all object id collections return correct instances.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_AllObjectIdCollections_ReturnCorrectInstances()
|
||||
{
|
||||
_db.Set<ObjectId, AnnotatedUser>().ShouldBeSameAs(_db.AnnotatedUsers);
|
||||
_db.Set<ObjectId, ComplexUser>().ShouldBeSameAs(_db.ComplexUsers);
|
||||
_db.Set<ObjectId, TestDocument>().ShouldBeSameAs(_db.TestDocuments);
|
||||
_db.Set<ObjectId, OrderDocument>().ShouldBeSameAs(_db.OrderDocuments);
|
||||
_db.Set<ObjectId, ComplexDocument>().ShouldBeSameAs(_db.ComplexDocuments);
|
||||
_db.Set<ObjectId, PersonV2>().ShouldBeSameAs(_db.PeopleV2);
|
||||
_db.Set<ObjectId, VectorEntity>().ShouldBeSameAs(_db.VectorItems);
|
||||
_db.Set<ObjectId, GeoEntity>().ShouldBeSameAs(_db.GeoItems);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set all int collections return correct instances.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_AllIntCollections_ReturnCorrectInstances()
|
||||
{
|
||||
_db.Set<int, AutoInitEntity>().ShouldBeSameAs(_db.AutoInitEntities);
|
||||
_db.Set<int, Product>().ShouldBeSameAs(_db.Products);
|
||||
_db.Set<int, IntEntity>().ShouldBeSameAs(_db.IntEntities);
|
||||
_db.Set<int, AsyncDoc>().ShouldBeSameAs(_db.AsyncDocs);
|
||||
_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()
|
||||
{
|
||||
var users = _db.Set<User>();
|
||||
|
||||
var user = new User { Name = "Alice", Age = 30 };
|
||||
var id = users.Insert(user);
|
||||
|
||||
var found = users.FindById(id);
|
||||
found.ShouldNotBeNull();
|
||||
found.Name.ShouldBe("Alice");
|
||||
found.Age.ShouldBe(30);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set with int key can perform operations.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_WithIntKey_CanPerformOperations()
|
||||
{
|
||||
var products = _db.Set<int, Product>();
|
||||
|
||||
var product = new Product { Id = 1, Title = "Widget", Price = 9.99m };
|
||||
products.Insert(product);
|
||||
|
||||
var found = products.FindById(1);
|
||||
found.ShouldNotBeNull();
|
||||
found.Title.ShouldBe("Widget");
|
||||
found.Price.ShouldBe(9.99m);
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
var collection = _db.Set<int, ExtendedEntity>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.ExtendedEntities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set parent collection returns correct instance.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_ParentCollection_ReturnsCorrectInstance()
|
||||
{
|
||||
var collection = _db.Set<ObjectId, User>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.Users);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set parent shorthand returns correct instance.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_ParentShorthand_ReturnsCorrectInstance()
|
||||
{
|
||||
var collection = _db.Set<User>();
|
||||
collection.ShouldNotBeNull();
|
||||
collection.ShouldBeSameAs(_db.Users);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set parent int collection returns correct instance.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_ParentIntCollection_ReturnsCorrectInstance()
|
||||
{
|
||||
_db.Set<int, Person>().ShouldBeSameAs(_db.People);
|
||||
_db.Set<int, Product>().ShouldBeSameAs(_db.Products);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set parent custom key returns correct instance.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_ParentCustomKey_ReturnsCorrectInstance()
|
||||
{
|
||||
var collection = _db.Set<OrderId, Order>();
|
||||
collection.ShouldNotBeNull();
|
||||
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()
|
||||
{
|
||||
var entities = _db.Set<int, ExtendedEntity>();
|
||||
|
||||
var entity = new ExtendedEntity { Id = 1, Description = "Test", CreatedAt = DateTime.UtcNow };
|
||||
entities.Insert(entity);
|
||||
|
||||
var found = entities.FindById(1);
|
||||
found.ShouldNotBeNull();
|
||||
found.Description.ShouldBe("Test");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests set parent collection can perform operations.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Set_ParentCollection_CanPerformOperations()
|
||||
{
|
||||
var users = _db.Set<User>();
|
||||
|
||||
var user = new User { Name = "Bob", Age = 25 };
|
||||
var id = users.Insert(user);
|
||||
|
||||
var found = users.FindById(id);
|
||||
found.ShouldNotBeNull();
|
||||
found.Name.ShouldBe("Bob");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user