using ZB.MOM.WW.CBDD.Bson; using Xunit; using System.Collections.Concurrent; namespace ZB.MOM.WW.CBDD.Tests; public class BsonSpanReaderWriterTests { private readonly ConcurrentDictionary _keyMap = new(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _keys = new(); public BsonSpanReaderWriterTests() { ushort id = 1; string[] initialKeys = ["name", "age", "active", "_id", "val", "dec", "timestamp", "int32", "int64", "double", "data", "child", "value", "0", "1"]; foreach (var key in initialKeys) { _keyMap[key] = id; _keys[id] = key; id++; } } [Fact] public void WriteAndRead_SimpleDocument() { Span buffer = stackalloc byte[256]; var writer = new BsonSpanWriter(buffer, _keyMap); var sizePos = writer.BeginDocument(); writer.WriteString("name", "John"); writer.WriteInt32("age", 30); writer.WriteBoolean("active", true); writer.EndDocument(sizePos); var documentBytes = buffer[..writer.Position]; var reader = new BsonSpanReader(documentBytes, _keys); var size = reader.ReadDocumentSize(); size.ShouldBe(writer.Position); var type1 = reader.ReadBsonType(); var name1 = reader.ReadElementHeader(); var value1 = reader.ReadString(); type1.ShouldBe(BsonType.String); name1.ShouldBe("name"); value1.ShouldBe("John"); var type2 = reader.ReadBsonType(); var name2 = reader.ReadElementHeader(); var value2 = reader.ReadInt32(); type2.ShouldBe(BsonType.Int32); name2.ShouldBe("age"); value2.ShouldBe(30); var type3 = reader.ReadBsonType(); var name3 = reader.ReadElementHeader(); var value3 = reader.ReadBoolean(); type3.ShouldBe(BsonType.Boolean); name3.ShouldBe("active"); value3.ShouldBeTrue(); } [Fact] public void WriteAndRead_ObjectId() { Span buffer = stackalloc byte[256]; var writer = new BsonSpanWriter(buffer, _keyMap); var oid = ObjectId.NewObjectId(); var sizePos = writer.BeginDocument(); writer.WriteObjectId("_id", oid); writer.EndDocument(sizePos); var documentBytes = buffer[..writer.Position]; var reader = new BsonSpanReader(documentBytes, _keys); reader.ReadDocumentSize(); var type = reader.ReadBsonType(); var name = reader.ReadElementHeader(); var readOid = reader.ReadObjectId(); type.ShouldBe(BsonType.ObjectId); name.ShouldBe("_id"); readOid.ShouldBe(oid); } [Fact] public void ReadWrite_Double() { var buffer = new byte[256]; var writer = new BsonSpanWriter(buffer, _keyMap); writer.WriteDouble("val", 123.456); var reader = new BsonSpanReader(buffer, _keys); var type = reader.ReadBsonType(); var name = reader.ReadElementHeader(); var val = reader.ReadDouble(); type.ShouldBe(BsonType.Double); name.ShouldBe("val"); val.ShouldBe(123.456); } [Fact] public void ReadWrite_Decimal128_RoundTrip() { var buffer = new byte[256]; var writer = new BsonSpanWriter(buffer, _keyMap); decimal original = 123456.789m; writer.WriteDecimal128("dec", original); var reader = new BsonSpanReader(buffer, _keys); var type = reader.ReadBsonType(); var name = reader.ReadElementHeader(); var val = reader.ReadDecimal128(); type.ShouldBe(BsonType.Decimal128); name.ShouldBe("dec"); val.ShouldBe(original); } [Fact] public void WriteAndRead_DateTime() { Span buffer = stackalloc byte[256]; var writer = new BsonSpanWriter(buffer, _keyMap); var now = DateTime.UtcNow; // Round to milliseconds as BSON only stores millisecond precision var expectedTime = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond, DateTimeKind.Utc); var sizePos = writer.BeginDocument(); writer.WriteDateTime("timestamp", expectedTime); writer.EndDocument(sizePos); var documentBytes = buffer[..writer.Position]; var reader = new BsonSpanReader(documentBytes, _keys); reader.ReadDocumentSize(); var type = reader.ReadBsonType(); var name = reader.ReadElementHeader(); var readTime = reader.ReadDateTime(); type.ShouldBe(BsonType.DateTime); name.ShouldBe("timestamp"); readTime.ShouldBe(expectedTime); } [Fact] public void WriteAndRead_NumericTypes() { Span buffer = stackalloc byte[256]; var writer = new BsonSpanWriter(buffer, _keyMap); var sizePos = writer.BeginDocument(); writer.WriteInt32("int32", int.MaxValue); writer.WriteInt64("int64", long.MaxValue); writer.WriteDouble("double", 3.14159); writer.EndDocument(sizePos); var documentBytes = buffer[..writer.Position]; var reader = new BsonSpanReader(documentBytes, _keys); reader.ReadDocumentSize(); reader.ReadBsonType(); reader.ReadElementHeader(); reader.ReadInt32().ShouldBe(int.MaxValue); reader.ReadBsonType(); reader.ReadElementHeader(); reader.ReadInt64().ShouldBe(long.MaxValue); reader.ReadBsonType(); reader.ReadElementHeader(); Math.Round(reader.ReadDouble(), 5).ShouldBe(Math.Round(3.14159, 5)); } [Fact] public void WriteAndRead_Binary() { Span buffer = stackalloc byte[256]; var writer = new BsonSpanWriter(buffer, _keyMap); byte[] testData = [1, 2, 3, 4, 5]; var sizePos = writer.BeginDocument(); writer.WriteBinary("data", testData); writer.EndDocument(sizePos); var documentBytes = buffer[..writer.Position]; var reader = new BsonSpanReader(documentBytes, _keys); reader.ReadDocumentSize(); var type = reader.ReadBsonType(); var name = reader.ReadElementHeader(); var readData = reader.ReadBinary(out var subtype); type.ShouldBe(BsonType.Binary); name.ShouldBe("data"); subtype.ShouldBe((byte)0); testData.AsSpan().SequenceEqual(readData).ShouldBeTrue(); } [Fact] public void WriteAndRead_NestedDocument() { Span buffer = stackalloc byte[512]; var writer = new BsonSpanWriter(buffer, _keyMap); var rootSizePos = writer.BeginDocument(); writer.WriteString("name", "Parent"); var childSizePos = writer.BeginDocument("child"); writer.WriteString("name", "Child"); writer.WriteInt32("value", 42); writer.EndDocument(childSizePos); writer.EndDocument(rootSizePos); var documentBytes = buffer[..writer.Position]; var reader = new BsonSpanReader(documentBytes, _keys); var rootSize = reader.ReadDocumentSize(); rootSize.ShouldBe(writer.Position); reader.ReadBsonType(); // String reader.ReadElementHeader().ShouldBe("name"); reader.ReadString().ShouldBe("Parent"); reader.ReadBsonType(); // Document reader.ReadElementHeader().ShouldBe("child"); reader.ReadDocumentSize(); reader.ReadBsonType(); // String reader.ReadElementHeader().ShouldBe("name"); reader.ReadString().ShouldBe("Child"); reader.ReadBsonType(); // Int32 reader.ReadElementHeader().ShouldBe("value"); reader.ReadInt32().ShouldBe(42); } }