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; /// /// Initializes a new instance of the class. /// public AsyncTests() { _dbPath = Path.Combine(Path.GetTempPath(), $"cbdd_async_{Guid.NewGuid()}.db"); } /// /// Executes Dispose. /// public void Dispose() { if (File.Exists(_dbPath)) File.Delete(_dbPath); if (File.Exists(Path.ChangeExtension(_dbPath, ".wal"))) File.Delete(Path.ChangeExtension(_dbPath, ".wal")); } /// /// Executes Async_Transaction_Commit_Should_Persist_Data. /// [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"); } /// /// Executes Async_Transaction_Rollback_Should_Discard_Data. /// [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(); } /// /// Executes Bulk_Async_Insert_Should_Persist_Data. /// [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"); } /// /// Executes Bulk_Async_Update_Should_Persist_Changes. /// [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"); } /// /// Executes High_Concurrency_Async_Commits. /// [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); } }