using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Text; using ZB.MOM.WW.CBDD.Bson; namespace ZB.MOM.WW.CBDD.Tests; public class BsonDocumentAndBufferWriterTests { [Fact] public void BsonDocument_Create_And_TryGet_RoundTrip() { var keyMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); var reverseMap = new ConcurrentDictionary(); RegisterKey(keyMap, reverseMap, 1, "name"); RegisterKey(keyMap, reverseMap, 2, "age"); RegisterKey(keyMap, reverseMap, 3, "_id"); var expectedId = ObjectId.NewObjectId(); var doc = BsonDocument.Create(keyMap, b => { b.AddString("name", "Alice"); b.AddInt32("age", 32); b.AddObjectId("_id", expectedId); }); var wrapped = new BsonDocument(doc.RawData.ToArray(), reverseMap); wrapped.TryGetString("name", out var name).ShouldBeTrue(); name.ShouldBe("Alice"); wrapped.TryGetInt32("age", out var age).ShouldBeTrue(); age.ShouldBe(32); wrapped.TryGetObjectId("_id", out var id).ShouldBeTrue(); id.ShouldBe(expectedId); var reader = wrapped.GetReader(); reader.ReadDocumentSize().ShouldBeGreaterThan(0); } [Fact] public void BsonDocument_TryGet_Should_Return_False_For_Missing_Or_Wrong_Type() { var keyMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); var reverseMap = new ConcurrentDictionary(); RegisterKey(keyMap, reverseMap, 1, "name"); RegisterKey(keyMap, reverseMap, 2, "age"); var doc = BsonDocument.Create(keyMap, b => { b.AddString("name", "Bob"); b.AddInt32("age", 28); }); var wrapped = new BsonDocument(doc.RawData.ToArray(), reverseMap); wrapped.TryGetInt32("name", out _).ShouldBeFalse(); wrapped.TryGetString("missing", out _).ShouldBeFalse(); wrapped.TryGetObjectId("age", out _).ShouldBeFalse(); } [Fact] public void BsonDocumentBuilder_Should_Grow_Buffer_When_Document_Is_Large() { var keyMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); var reverseMap = new ConcurrentDictionary(); for (ushort i = 1; i <= 180; i++) { var key = $"k{i}"; RegisterKey(keyMap, reverseMap, i, key); } var builder = new BsonDocumentBuilder(keyMap); for (int i = 1; i <= 180; i++) { builder.AddInt32($"k{i}", i); } var doc = builder.Build(); doc.Size.ShouldBeGreaterThan(1024); var wrapped = new BsonDocument(doc.RawData.ToArray(), reverseMap); wrapped.TryGetInt32("k180", out var value).ShouldBeTrue(); value.ShouldBe(180); } [Fact] public void BsonBufferWriter_Should_Write_Nested_Document_And_Array() { var output = new ArrayBufferWriter(); var writer = new BsonBufferWriter(output); int rootSizePos = writer.BeginDocument(); int childSizePos = writer.BeginDocument("child"); writer.WriteString("name", "nested"); writer.WriteBoolean("active", true); writer.EndDocument(childSizePos); int childEnd = writer.Position; int arraySizePos = writer.BeginArray("nums"); writer.WriteInt32("0", 1); writer.WriteInt32("1", 2); writer.EndArray(arraySizePos); int arrayEnd = writer.Position; writer.EndDocument(rootSizePos); int rootEnd = writer.Position; var bytes = output.WrittenSpan.ToArray(); PatchDocumentSize(bytes, childSizePos, childEnd); PatchDocumentSize(bytes, arraySizePos, arrayEnd); PatchDocumentSize(bytes, rootSizePos, rootEnd); var reader = new BsonSpanReader(bytes, new ConcurrentDictionary()); reader.ReadDocumentSize().ShouldBe(bytes.Length); reader.ReadBsonType().ShouldBe(BsonType.Document); reader.ReadCString().ShouldBe("child"); reader.ReadDocumentSize().ShouldBeGreaterThan(8); reader.ReadBsonType().ShouldBe(BsonType.String); reader.ReadCString().ShouldBe("name"); reader.ReadString().ShouldBe("nested"); reader.ReadBsonType().ShouldBe(BsonType.Boolean); reader.ReadCString().ShouldBe("active"); reader.ReadBoolean().ShouldBeTrue(); reader.ReadBsonType().ShouldBe(BsonType.EndOfDocument); reader.ReadBsonType().ShouldBe(BsonType.Array); reader.ReadCString().ShouldBe("nums"); reader.ReadDocumentSize().ShouldBeGreaterThan(8); reader.ReadBsonType().ShouldBe(BsonType.Int32); reader.ReadCString().ShouldBe("0"); reader.ReadInt32().ShouldBe(1); reader.ReadBsonType().ShouldBe(BsonType.Int32); reader.ReadCString().ShouldBe("1"); reader.ReadInt32().ShouldBe(2); reader.ReadBsonType().ShouldBe(BsonType.EndOfDocument); reader.ReadBsonType().ShouldBe(BsonType.EndOfDocument); } [Fact] public void BsonSpanReader_ReadByte_And_ReadCStringSpan_Should_Work() { var singleByteReader = new BsonSpanReader(new byte[] { 0x2A }, new ConcurrentDictionary()); singleByteReader.ReadByte().ShouldBe((byte)0x2A); var cstring = Encoding.UTF8.GetBytes("hello\0"); var cstringReader = new BsonSpanReader(cstring, new ConcurrentDictionary()); var destination = new char[16]; var written = cstringReader.ReadCString(destination); new string(destination, 0, written).ShouldBe("hello"); } private static void RegisterKey( ConcurrentDictionary keyMap, ConcurrentDictionary reverseMap, ushort id, string key) { keyMap[key] = id; reverseMap[id] = key; } private static void PatchDocumentSize(byte[] output, int sizePosition, int endPosition) { BinaryPrimitives.WriteInt32LittleEndian(output.AsSpan(sizePosition, 4), endPosition - sizePosition); } }