using ZB.MOM.WW.CBDD.Core.Storage; using ZB.MOM.WW.CBDD.Core.Indexing; using ZB.MOM.WW.CBDD.Bson; using Xunit; namespace ZB.MOM.WW.CBDD.Tests; public class CursorTests : IDisposable { private readonly string _testFile; private readonly StorageEngine _storage; private readonly BTreeIndex _index; public CursorTests() { _testFile = Path.Combine(Path.GetTempPath(), $"docdb_cursor_test_{Guid.NewGuid()}.db"); _storage = new StorageEngine(_testFile, PageFileConfig.Default); var options = IndexOptions.CreateBTree("test"); _index = new BTreeIndex(_storage, options); SeedData(); } private void SeedData() { var txnId = _storage.BeginTransaction().TransactionId; // Insert 10, 20, 30 _index.Insert(IndexKey.Create(10), new DocumentLocation(1, 0), txnId); _index.Insert(IndexKey.Create(20), new DocumentLocation(2, 0), txnId); _index.Insert(IndexKey.Create(30), new DocumentLocation(3, 0), txnId); _storage.CommitTransaction(txnId); } [Fact] public void MoveToFirst_ShouldPositionAtFirst() { using var cursor = _index.CreateCursor(0); cursor.MoveToFirst().ShouldBeTrue(); cursor.Current.Key.ShouldBe(IndexKey.Create(10)); } [Fact] public void MoveToLast_ShouldPositionAtLast() { using var cursor = _index.CreateCursor(0); cursor.MoveToLast().ShouldBeTrue(); cursor.Current.Key.ShouldBe(IndexKey.Create(30)); } [Fact] public void MoveNext_ShouldTraverseForward() { using var cursor = _index.CreateCursor(0); cursor.MoveToFirst(); cursor.MoveNext().ShouldBeTrue(); cursor.Current.Key.ShouldBe(IndexKey.Create(20)); cursor.MoveNext().ShouldBeTrue(); cursor.Current.Key.ShouldBe(IndexKey.Create(30)); cursor.MoveNext().ShouldBeFalse(); // End } [Fact] public void MovePrev_ShouldTraverseBackward() { using var cursor = _index.CreateCursor(0); cursor.MoveToLast(); cursor.MovePrev().ShouldBeTrue(); cursor.Current.Key.ShouldBe(IndexKey.Create(20)); cursor.MovePrev().ShouldBeTrue(); cursor.Current.Key.ShouldBe(IndexKey.Create(10)); cursor.MovePrev().ShouldBeFalse(); // Start } [Fact] public void Seek_ShouldPositionExact_OrNext() { using var cursor = _index.CreateCursor(0); // Exact cursor.Seek(IndexKey.Create(20)).ShouldBeTrue(); cursor.Current.Key.ShouldBe(IndexKey.Create(20)); // Non-exact (15 -> should land on 20) cursor.Seek(IndexKey.Create(15)).ShouldBeFalse(); cursor.Current.Key.ShouldBe(IndexKey.Create(20)); // Non-exact (35 -> should be invalid/end) cursor.Seek(IndexKey.Create(35)).ShouldBeFalse(); // Current should throw invalid Should.Throw(() => cursor.Current); } public void Dispose() { _storage.Dispose(); if (File.Exists(_testFile)) File.Delete(_testFile); } }