using ZB.MOM.WW.CBDD.Bson; using ZB.MOM.WW.CBDD.Core.Compression; using ZB.MOM.WW.CBDD.Core.Storage; using ZB.MOM.WW.CBDD.Shared; using System.Security.Cryptography; using System.IO.Compression; using System.IO.MemoryMappedFiles; namespace ZB.MOM.WW.CBDD.Tests; public class DbContextTests : IDisposable { private string _dbPath; public DbContextTests() { _dbPath = Path.Combine(Path.GetTempPath(), $"test_dbcontext_{Guid.NewGuid()}.db"); } [Fact] public void DbContext_BasicLifecycle_Works() { using var db = new Shared.TestDbContext(_dbPath); var user = new User { Name = "Alice", Age = 30 }; var id = db.Users.Insert(user); var found = db.Users.FindById(id); found.ShouldNotBeNull(); found.Name.ShouldBe("Alice"); found.Age.ShouldBe(30); } [Fact] public void DbContext_MultipleOperations_Work() { using var db = new Shared.TestDbContext(_dbPath); // Insert var alice = new User { Name = "Alice", Age = 30 }; var bob = new User { Name = "Bob", Age = 25 }; var id1 = db.Users.Insert(alice); var id2 = db.Users.Insert(bob); // FindAll var all = db.Users.FindAll().ToList(); all.Count.ShouldBe(2); // Update alice.Age = 31; db.Users.Update(alice).ShouldBeTrue(); var updated = db.Users.FindById(id1); updated!.Age.ShouldBe(31); // Delete db.Users.Delete(id2).ShouldBeTrue(); db.Users.Count().ShouldBe(1); } [Fact] public void DbContext_Dispose_ReleasesResources() { _dbPath = Path.Combine(Path.GetTempPath(), $"test_dbcontext_reopen.db"); var totalUsers = 0; // First context - insert and dispose (auto-checkpoint) using (var db = new Shared.TestDbContext(_dbPath)) { db.Users.Insert(new User { Name = "Test", Age = 20 }); db.SaveChanges(); // Explicitly save changes to ensure data is in WAL var beforeCheckpointTotalUsers = db.Users.FindAll().Count(); db.ForceCheckpoint(); // Force checkpoint to ensure data is persisted to main file totalUsers = db.Users.FindAll().Count(); var countedUsers = db.Users.Count(); totalUsers.ShouldBe(beforeCheckpointTotalUsers); } // Dispose → Commit → ForceCheckpoint → Write to PageFile // Should be able to open again and see persisted data using var db2 = new Shared.TestDbContext(_dbPath); totalUsers.ShouldBe(1); db2.Users.FindAll().Count().ShouldBe(totalUsers); db2.Users.Count().ShouldBe(totalUsers); } private static string ComputeFileHash(string path) { using var stream = File.OpenRead(path); using var sha256 = SHA256.Create(); return Convert.ToHexString(sha256.ComputeHash(stream)); } [Fact] public void DatabaseFile_SizeAndContent_ChangeAfterInsert() { var dbPath = Path.Combine(Path.GetTempPath(), $"test_dbfile_{Guid.NewGuid()}.db"); // 1. Crea e chiudi database vuoto using (var db = new Shared.TestDbContext(dbPath)) { db.Users.Insert(new User { Name = "Pippo", Age = 42 }); } var initialSize = new FileInfo(dbPath).Length; var initialHash = ComputeFileHash(dbPath); // 2. Riapri, inserisci, chiudi using (var db = new Shared.TestDbContext(dbPath)) { db.Users.Insert(new User { Name = "Test", Age = 42 }); db.ForceCheckpoint(); // Forza persistenza } var afterInsertSize = new FileInfo(dbPath).Length; var afterInsertHash = ComputeFileHash(dbPath); // 3. Verifica che dimensione e hash siano cambiati afterInsertSize.ShouldNotBe(initialSize); afterInsertHash.ShouldNotBe(initialHash); } [Fact] public void DbContext_AutoDerivesWalPath() { using var db = new Shared.TestDbContext(_dbPath); db.Users.Insert(new User { Name = "Test", Age = 20 }); var walPath = Path.ChangeExtension(_dbPath, ".wal"); File.Exists(walPath).ShouldBeTrue(); } [Fact] public void DbContext_WithCustomPageFileAndCompressionOptions_ShouldSupportRoundTrip() { var dbPath = Path.Combine(Path.GetTempPath(), $"test_dbcontext_compression_{Guid.NewGuid():N}.db"); var options = new CompressionOptions { EnableCompression = true, MinSizeBytes = 0, MinSavingsPercent = 0, Codec = CompressionCodec.Brotli, Level = CompressionLevel.Fastest }; var config = new PageFileConfig { PageSize = 16 * 1024, InitialFileSize = 1024 * 1024, Access = MemoryMappedFileAccess.ReadWrite }; try { using var db = new Shared.TestDbContext(dbPath, config, options); var payload = string.Concat(Enumerable.Repeat("compressible-", 3000)); var id = db.Users.Insert(new User { Name = payload, Age = 77 }); db.SaveChanges(); var loaded = db.Users.FindById(id); loaded.ShouldNotBeNull(); loaded.Name.ShouldBe(payload); db.GetCompressionStats().CompressedDocumentCount.ShouldBeGreaterThanOrEqualTo(1); } finally { CleanupDbFiles(dbPath); } } [Fact] public void DbContext_CompactApi_ShouldReturnStatsAndPreserveData() { var dbPath = Path.Combine(Path.GetTempPath(), $"test_dbcontext_compact_{Guid.NewGuid():N}.db"); try { using var db = new Shared.TestDbContext(dbPath); for (var i = 0; i < 120; i++) { db.Users.Insert(new User { Name = $"compact-{i:D3}", Age = i % 20 }); } db.SaveChanges(); db.Users.Count().ShouldBe(120); db.SaveChanges(); var stats = db.Compact(new CompactionOptions { EnableTailTruncation = true, DefragmentSlottedPages = true, NormalizeFreeList = true }); stats.OnlineMode.ShouldBeFalse(); db.Users.Count().ShouldBe(120); } finally { CleanupDbFiles(dbPath); } } public void Dispose() { try { CleanupDbFiles(_dbPath); } catch { // Ignore cleanup errors } } private static void CleanupDbFiles(string dbPath) { if (File.Exists(dbPath)) File.Delete(dbPath); var walPath = Path.ChangeExtension(dbPath, ".wal"); if (File.Exists(walPath)) File.Delete(walPath); var markerPath = $"{dbPath}.compact.state"; if (File.Exists(markerPath)) File.Delete(markerPath); } }