Browse Source

CSHARP-2239: Refactor ChangeStreamDocument to be a BsonDocumentBackedClass.

pull/327/head
rstam 7 years ago
parent
commit
2a79ee4279
  1. 34
      src/MongoDB.Bson/Serialization/Serializers/BsonDocumentBackedClassSerializer.cs
  2. 64
      src/MongoDB.Driver.Core/ChangeStreamDocument.cs
  3. 76
      src/MongoDB.Driver.Core/ChangeStreamDocumentCollectionNamespaceSerializer.cs
  4. 158
      src/MongoDB.Driver.Core/ChangeStreamDocumentSerializer.cs
  5. 14
      src/MongoDB.Driver.Core/ChangeStreamOperationTypeSerializer.cs
  6. 26
      src/MongoDB.Driver.Core/ChangeStreamUpdateDescription.cs
  7. 11
      src/MongoDB.Driver.Core/ChangeStreamUpdateDescriptionSerializer.cs
  8. 1
      src/MongoDB.Driver.Core/MongoDB.Driver.Core.csproj
  9. 12
      tests/MongoDB.Bson.TestHelpers/Reflector.cs
  10. 2
      tests/MongoDB.Bson.Tests/MongoDB.Bson.Tests.csproj
  11. 30
      tests/MongoDB.Bson.Tests/Serialization/BsonDocumentBackedClassTests.cs
  12. 32
      tests/MongoDB.Bson.Tests/Serialization/Serializers/BsonDocumentBackedClassSerializerTests.cs
  13. 1
      tests/MongoDB.Driver.Core.Tests.Dotnet/MongoDB.Driver.Core.Tests.Dotnet.csproj
  14. 156
      tests/MongoDB.Driver.Core.Tests/ChangeStreamDocumentSerializerTests.cs
  15. 262
      tests/MongoDB.Driver.Core.Tests/ChangeStreamDocumentTests.cs
  16. 5
      tests/MongoDB.Driver.Core.Tests/MongoDB.Driver.Core.Tests.csproj

34
src/MongoDB.Bson/Serialization/Serializers/BsonDocumentBackedClassSerializer.cs

@ -39,18 +39,6 @@ namespace MongoDB.Bson.Serialization
}
// public methods
/// <summary>
/// Deserializes a value.
/// </summary>
/// <param name="context">The deserialization context.</param>
/// <param name="args">The deserialization args.</param>
/// <returns>A deserialized value.</returns>
public override TClass Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var backingDocument = BsonDocumentSerializer.Instance.Deserialize(context);
return CreateInstance(backingDocument);
}
/// <summary>
/// Tries to get the serialization info for a member.
/// </summary>
@ -64,19 +52,14 @@ namespace MongoDB.Bson.Serialization
return _memberSerializationInfo.TryGetValue(memberName, out serializationInfo);
}
/// <summary>
/// Serializes a value.
/// </summary>
/// <param name="context">The serialization context.</param>
/// <param name="args">The serialization args.</param>
/// <param name="value">The object.</param>
protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, TClass value)
// protected methods
/// <inheritdoc />
protected override TClass DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var backingDocument = ((BsonDocumentBackedClass)value).BackingDocument;
BsonDocumentSerializer.Instance.Serialize(context, backingDocument);
var backingDocument = BsonDocumentSerializer.Instance.Deserialize(context);
return CreateInstance(backingDocument);
}
// protected methods
/// <summary>
/// Registers a member.
/// </summary>
@ -102,6 +85,13 @@ namespace MongoDB.Bson.Serialization
_memberSerializationInfo.Add(memberName, info);
}
/// <inheritdoc />
protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, TClass value)
{
var backingDocument = ((BsonDocumentBackedClass)value).BackingDocument;
BsonDocumentSerializer.Instance.Serialize(context, backingDocument);
}
/// <summary>
/// Creates the instance.
/// </summary>

64
src/MongoDB.Driver.Core/ChangeStreamDocument.cs

@ -14,7 +14,8 @@
*/
using MongoDB.Bson;
using MongoDB.Driver.Core.Misc;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Attributes;
namespace MongoDB.Driver
{
@ -22,50 +23,43 @@ namespace MongoDB.Driver
/// An output document from a $changeStream pipeline stage.
/// </summary>
/// <typeparam name="TDocument">The type of the document.</typeparam>
public sealed class ChangeStreamDocument<TDocument>
[BsonSerializer(typeof(ChangeStreamDocumentSerializer<>))]
public sealed class ChangeStreamDocument<TDocument> : BsonDocumentBackedClass
{
// private fields
private readonly CollectionNamespace _collectionNamespace;
private readonly BsonDocument _documentKey;
private readonly TDocument _fullDocument;
private readonly ChangeStreamOperationType _operationType;
private readonly BsonDocument _resumeToken;
private readonly ChangeStreamUpdateDescription _updateDescription;
// constructors
/// <summary>
/// Initializes a new instance of the <see cref="ChangeStreamDocument{TDocument}" /> class.
/// Initializes a new instance of the <see cref="ChangeStreamDocument{TDocument}"/> class.
/// </summary>
/// <param name="resumeToken">The resume token.</param>
/// <param name="operationType">Type of the operation.</param>
/// <param name="collectionNamespace">Namespace of the collection.</param>
/// <param name="documentKey">The document key.</param>
/// <param name="updateDescription">The update description.</param>
/// <param name="fullDocument">The full document.</param>
/// <param name="backingDocument">The backing document.</param>
/// <param name="documentSerializer">The document serializer.</param>
public ChangeStreamDocument(
BsonDocument resumeToken,
ChangeStreamOperationType operationType,
CollectionNamespace collectionNamespace,
BsonDocument documentKey,
ChangeStreamUpdateDescription updateDescription,
TDocument fullDocument)
BsonDocument backingDocument,
IBsonSerializer<TDocument> documentSerializer)
: base(backingDocument, new ChangeStreamDocumentSerializer<TDocument>(documentSerializer))
{
_resumeToken = Ensure.IsNotNull(resumeToken, nameof(resumeToken));
_operationType = operationType;
_collectionNamespace = collectionNamespace; // can be null when operationType is Invalidate
_documentKey = documentKey; // can be null
_updateDescription = updateDescription; // can be null
_fullDocument = fullDocument; // can be null
}
// public properties
/// <summary>
/// Gets the backing document.
/// </summary>
new public BsonDocument BackingDocument => base.BackingDocument;
/// <summary>
/// Gets the cluster time.
/// </summary>
/// <value>
/// The cluster time.
/// </value>
public BsonDocument ClusterTime => GetValue<BsonDocument>(nameof(ClusterTime), null);
/// <summary>
/// Gets the namespace of the collection.
/// </summary>
/// <value>
/// The namespace of the collection.
/// </value>
public CollectionNamespace CollectionNamespace => _collectionNamespace;
public CollectionNamespace CollectionNamespace => GetValue<CollectionNamespace>(nameof(CollectionNamespace), null);
/// <summary>
/// Gets the document key.
@ -73,7 +67,7 @@ namespace MongoDB.Driver
/// <value>
/// The document key.
/// </value>
public BsonDocument DocumentKey => _documentKey;
public BsonDocument DocumentKey => GetValue<BsonDocument>(nameof(DocumentKey), null);
/// <summary>
/// Gets the full document.
@ -81,7 +75,7 @@ namespace MongoDB.Driver
/// <value>
/// The full document.
/// </value>
public TDocument FullDocument => _fullDocument;
public TDocument FullDocument => GetValue<TDocument>(nameof(FullDocument), default(TDocument));
/// <summary>
/// Gets the type of the operation.
@ -89,7 +83,7 @@ namespace MongoDB.Driver
/// <value>
/// The type of the operation.
/// </value>
public ChangeStreamOperationType OperationType => _operationType;
public ChangeStreamOperationType OperationType => GetValue<ChangeStreamOperationType>(nameof(OperationType), (ChangeStreamOperationType)(-1));
/// <summary>
/// Gets the resume token.
@ -97,7 +91,7 @@ namespace MongoDB.Driver
/// <value>
/// The resume token.
/// </value>
public BsonDocument ResumeToken => _resumeToken;
public BsonDocument ResumeToken => GetValue<BsonDocument>(nameof(ResumeToken), null);
/// <summary>
/// Gets the update description.
@ -105,6 +99,6 @@ namespace MongoDB.Driver
/// <value>
/// The update description.
/// </value>
public ChangeStreamUpdateDescription UpdateDescription => _updateDescription;
public ChangeStreamUpdateDescription UpdateDescription => GetValue<ChangeStreamUpdateDescription>(nameof(UpdateDescription), null);
}
}

76
src/MongoDB.Driver.Core/ChangeStreamDocumentCollectionNamespaceSerializer.cs

@ -0,0 +1,76 @@
/* Copyright 2018-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
namespace MongoDB.Driver
{
internal class ChangeStreamDocumentCollectionNamespaceSerializer : SealedClassSerializerBase<CollectionNamespace>
{
#region static
// private static fields
private static readonly ChangeStreamDocumentCollectionNamespaceSerializer __instance = new ChangeStreamDocumentCollectionNamespaceSerializer();
// public static properties
public static ChangeStreamDocumentCollectionNamespaceSerializer Instance => __instance;
#endregion
// public methods
public override CollectionNamespace Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var reader = context.Reader;
string collectionName = null;
string databaseName = null;
reader.ReadStartDocument();
while (reader.ReadBsonType() != 0)
{
var fieldName = reader.ReadName();
switch (fieldName)
{
case "db":
databaseName = reader.ReadString();
break;
case "coll":
collectionName = reader.ReadString();
break;
default:
throw new FormatException($"Invalid field name: \"{fieldName}\".");
}
}
reader.ReadEndDocument();
var databaseNamespace = new DatabaseNamespace(databaseName);
return new CollectionNamespace(databaseNamespace, collectionName);
}
protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, CollectionNamespace value)
{
var writer = context.Writer;
writer.WriteStartDocument();
writer.WriteName("db");
writer.WriteString(value.DatabaseNamespace.DatabaseName);
writer.WriteName("coll");
writer.WriteString(value.CollectionName);
writer.WriteEndDocument();
}
}
}

158
src/MongoDB.Driver.Core/ChangeStreamDocumentSerializer.cs

@ -14,11 +14,9 @@
*/
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Core.Misc;
using System;
namespace MongoDB.Driver
{
@ -27,14 +25,8 @@ namespace MongoDB.Driver
/// A serializer for ChangeStreamDocument instances.
/// </summary>
/// <typeparam name="TDocument">The type of the document.</typeparam>
public class ChangeStreamDocumentSerializer<TDocument> : SealedClassSerializerBase<ChangeStreamDocument<TDocument>>
public class ChangeStreamDocumentSerializer<TDocument> : BsonDocumentBackedClassSerializer<ChangeStreamDocument<TDocument>>
{
#region static
// private static fields
private static readonly IBsonSerializer<ChangeStreamOperationType> __operationTypeSerializer = new ChangeStreamOperationTypeSerializer();
private readonly ChangeStreamUpdateDescriptionSerializer __updateDescriptionSerializer = new ChangeStreamUpdateDescriptionSerializer();
#endregion
// private fields
private readonly IBsonSerializer<TDocument> _documentSerializer;
@ -47,149 +39,21 @@ namespace MongoDB.Driver
IBsonSerializer<TDocument> documentSerializer)
{
_documentSerializer = Ensure.IsNotNull(documentSerializer, nameof(documentSerializer));
}
// public methods
/// <inheritdoc />
protected override ChangeStreamDocument<TDocument> DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var reader = context.Reader;
CollectionNamespace collectionNamespace = null;
BsonDocument documentKey = null;
TDocument fullDocument = default(TDocument);
BsonDocument resumeToken = null;
ChangeStreamOperationType? operationType = null;
ChangeStreamUpdateDescription updateDescription = null;
reader.ReadStartDocument();
while (reader.ReadBsonType() != 0)
{
var fieldName = reader.ReadName();
switch (fieldName)
{
case "_id":
resumeToken = BsonDocumentSerializer.Instance.Deserialize(context);
break;
case "ns":
collectionNamespace = DeserializeCollectionNamespace(reader);
break;
case "documentKey":
documentKey = BsonDocumentSerializer.Instance.Deserialize(context);
break;
case "fullDocument":
if (reader.CurrentBsonType == BsonType.Null)
{
reader.ReadNull();
fullDocument = default(TDocument);
}
else
{
fullDocument = _documentSerializer.Deserialize(context);
}
break;
case "operationType":
operationType = __operationTypeSerializer.Deserialize(context);
break;
case "updateDescription":
updateDescription = __updateDescriptionSerializer.Deserialize(context);
break;
case "clusterTime":
// TODO: decide what to do about clusterTime
reader.SkipValue();
break;
default:
throw new FormatException($"Invalid field name: \"{fieldName}\".");
}
}
reader.ReadEndDocument();
return new ChangeStreamDocument<TDocument>(
resumeToken,
operationType.Value,
collectionNamespace,
documentKey,
updateDescription,
fullDocument);
RegisterMember("ClusterTime", "clusterTime", BsonDocumentSerializer.Instance);
RegisterMember("CollectionNamespace", "ns", ChangeStreamDocumentCollectionNamespaceSerializer.Instance);
RegisterMember("DocumentKey", "documentKey", BsonDocumentSerializer.Instance);
RegisterMember("FullDocument", "fullDocument", _documentSerializer);
RegisterMember("OperationType", "operationType", ChangeStreamOperationTypeSerializer.Instance);
RegisterMember("ResumeToken", "_id", BsonDocumentSerializer.Instance);
RegisterMember("UpdateDescription", "updateDescription", ChangeStreamUpdateDescriptionSerializer.Instance);
}
// protected methods
/// <inheritdoc />
protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, ChangeStreamDocument<TDocument> value)
{
var writer = context.Writer;
writer.WriteStartDocument();
writer.WriteName("_id");
BsonDocumentSerializer.Instance.Serialize(context, value.ResumeToken);
writer.WriteName("operationType");
__operationTypeSerializer.Serialize(context, value.OperationType);
if (value.CollectionNamespace != null)
{
writer.WriteName("ns");
SerializeCollectionNamespace(writer, value.CollectionNamespace);
}
if (value.DocumentKey != null)
{
writer.WriteName("documentKey");
BsonDocumentSerializer.Instance.Serialize(context, value.DocumentKey);
}
if (value.UpdateDescription != null)
{
writer.WriteName("updateDescription");
__updateDescriptionSerializer.Serialize(context, value.UpdateDescription);
}
if (value.FullDocument != null)
{
writer.WriteName("fullDocument");
_documentSerializer.Serialize(context, value.FullDocument);
}
writer.WriteEndDocument();
}
// private methods
private CollectionNamespace DeserializeCollectionNamespace(IBsonReader reader)
{
string collectionName = null;
string databaseName = null;
reader.ReadStartDocument();
while (reader.ReadBsonType() != 0)
{
var fieldName = reader.ReadName();
switch (fieldName)
{
case "db":
databaseName = reader.ReadString();
break;
case "coll":
collectionName = reader.ReadString();
break;
default:
throw new FormatException($"Invalid field name: \"{fieldName}\".");
}
}
reader.ReadEndDocument();
var databaseNamespace = new DatabaseNamespace(databaseName);
return new CollectionNamespace(databaseNamespace, collectionName);
}
private void SerializeCollectionNamespace(IBsonWriter writer, CollectionNamespace value)
protected override ChangeStreamDocument<TDocument> CreateInstance(BsonDocument backingDocument)
{
writer.WriteStartDocument();
writer.WriteName("db");
writer.WriteString(value.DatabaseNamespace.DatabaseName);
writer.WriteName("coll");
writer.WriteString(value.CollectionName);
writer.WriteEndDocument();
return new ChangeStreamDocument<TDocument>(backingDocument, _documentSerializer);
}
}
}

14
src/MongoDB.Driver.Core/ChangeStreamOperationTypeSerializer.cs

@ -24,6 +24,20 @@ namespace MongoDB.Driver
/// </summary>
public class ChangeStreamOperationTypeSerializer : StructSerializerBase<ChangeStreamOperationType>
{
#region static
// private static fields
private static readonly ChangeStreamOperationTypeSerializer __instance = new ChangeStreamOperationTypeSerializer();
// public static properties
/// <summary>
/// Gets a ChangeStreamOperationTypeSerializer.
/// </summary>
/// <value>
/// A ChangeStreamOperationTypeSerializer.
/// </value>
public static ChangeStreamOperationTypeSerializer Instance => __instance;
#endregion
/// <inheritdoc />
public override ChangeStreamOperationType Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{

26
src/MongoDB.Driver.Core/ChangeStreamUpdateDescription.cs

@ -13,8 +13,10 @@
* limitations under the License.
*/
using System.Linq;
using MongoDB.Bson;
using MongoDB.Driver.Core.Misc;
using MongoDB.Shared;
namespace MongoDB.Driver
{
@ -57,5 +59,29 @@ namespace MongoDB.Driver
/// The updated fields.
/// </value>
public BsonDocument UpdatedFields => _updatedFields;
// public methods
/// <inheritdoc />
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != typeof(ChangeStreamUpdateDescription))
{
return false;
}
var other = (ChangeStreamUpdateDescription)obj;
return
_removedFields.SequenceEqual(other._removedFields) &&
_updatedFields.Equals(other._updatedFields);
}
/// <inheritdoc />
public override int GetHashCode()
{
return new Hasher()
.HashElements(_removedFields)
.Hash(_updatedFields)
.GetHashCode();
}
}
}

11
src/MongoDB.Driver.Core/ChangeStreamUpdateDescriptionSerializer.cs

@ -28,7 +28,18 @@ namespace MongoDB.Driver
public class ChangeStreamUpdateDescriptionSerializer : SealedClassSerializerBase<ChangeStreamUpdateDescription>
{
#region static
// private static fields
private static readonly ChangeStreamUpdateDescriptionSerializer __instance = new ChangeStreamUpdateDescriptionSerializer();
private static readonly IBsonSerializer<string[]> __stringArraySerializer = new ArraySerializer<string>();
// public static properties
/// <summary>
/// Gets a ChangeStreamUpdateDescriptionSerializer.
/// </summary>
/// <value>
/// A ChangeStreamUpdateDescriptionSerializer.
/// </value>
public static ChangeStreamUpdateDescriptionSerializer Instance => __instance;
#endregion
/// <inheritdoc />

1
src/MongoDB.Driver.Core/MongoDB.Driver.Core.csproj

@ -62,6 +62,7 @@
<Compile Include="..\MongoDB.Shared\MaxTimeHelper.cs">
<Link>Core\Operations\MaxTimeHelper.cs</Link>
</Compile>
<Compile Include="ChangeStreamDocumentCollectionNamespaceSerializer.cs" />
<Compile Include="ChangeStreamFullDocumentOptions.cs" />
<Compile Include="ChangeStreamOperationType.cs" />
<Compile Include="ChangeStreamOperationTypeSerializer.cs" />

12
tests/MongoDB.Bson.TestHelpers/Reflector.cs

@ -13,6 +13,7 @@
* limitations under the License.
*/
using System;
using System.Linq;
using System.Reflection;
@ -22,7 +23,7 @@ namespace MongoDB.Bson.TestHelpers
{
public static object GetFieldValue(object obj, string name, BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance)
{
var fieldInfo = obj.GetType().GetField(name, flags);
var fieldInfo = GetDeclaredOrInheritedField(obj.GetType(), name, flags);
return fieldInfo.GetValue(obj);
}
@ -48,5 +49,14 @@ namespace MongoDB.Bson.TestHelpers
.Single();
return methodInfo.Invoke(obj, new object[] { arg1 });
}
// private methods
private static FieldInfo GetDeclaredOrInheritedField(Type type, string name, BindingFlags bindingFlags)
{
return
type == null ?
null :
type.GetField(name, bindingFlags) ?? GetDeclaredOrInheritedField(type.GetTypeInfo().BaseType, name, bindingFlags);
}
}
}

2
tests/MongoDB.Bson.Tests/MongoDB.Bson.Tests.csproj

@ -139,6 +139,7 @@
<Compile Include="Serialization\BsonClassMapAutoMappingTests.cs" />
<Compile Include="Serialization\BsonClassMapSetOrderTests.cs" />
<Compile Include="Serialization\BsonClassMapTests.cs" />
<Compile Include="Serialization\BsonDocumentBackedClassTests.cs" />
<Compile Include="Serialization\BsonMemberMapDefaultValueCreatorTests.cs" />
<Compile Include="Serialization\BsonMemberMapTests.cs" />
<Compile Include="Serialization\Conventions\AttributeConventionPackTests.cs" />
@ -168,6 +169,7 @@
<Compile Include="Serialization\IdGenerators\AscendingGuidGeneratorTests.cs" />
<Compile Include="Serialization\IdGenerators\CombGuidGeneratorTests.cs" />
<Compile Include="Serialization\Options\RepresentationConverterTests.cs" />
<Compile Include="Serialization\Serializers\BsonDocumentBackedClassSerializerTests.cs" />
<Compile Include="Serialization\Serializers\DictionarySerializerBaseSubclassTests.cs" />
<Compile Include="Serialization\Serializers\ElementAppendingSerializerTests.cs" />
<Compile Include="Serialization\Serializers\EnumerableInterfaceImplementerSerializerTests.cs" />

30
tests/MongoDB.Bson.Tests/Serialization/BsonDocumentBackedClassTests.cs

@ -0,0 +1,30 @@
/* Copyright 2018-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Reflection;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.TestHelpers;
namespace MongoDB.Bson.Tests.Serialization
{
public class BsonDocumentBackedClassTests
{
}
public static class BsonDocumentBackedClassReflector
{
public static IBsonDocumentSerializer _serializer(this BsonDocumentBackedClass obj) => (IBsonDocumentSerializer)Reflector.GetFieldValue(obj, nameof(_serializer), BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
}
}

32
tests/MongoDB.Bson.Tests/Serialization/Serializers/BsonDocumentBackedClassSerializerTests.cs

@ -0,0 +1,32 @@
/* Copyright 2018-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.TestHelpers;
namespace MongoDB.Bson.Tests.Serialization.Serializers
{
public class BsonDocumentBackedClassSerializerTests
{
}
public static class BsonDocumentBackedClassSerializerReflector
{
public static Dictionary<string, BsonSerializationInfo> _memberSerializationInfo<TClass>(this BsonDocumentBackedClassSerializer<TClass> obj)
where TClass : BsonDocumentBackedClass
=> (Dictionary<string, BsonSerializationInfo>)Reflector.GetFieldValue(obj, nameof(_memberSerializationInfo));
}
}

1
tests/MongoDB.Driver.Core.Tests.Dotnet/MongoDB.Driver.Core.Tests.Dotnet.csproj

@ -26,6 +26,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\MongoDB.Bson.Dotnet\MongoDB.Bson.Dotnet.csproj" />
<ProjectReference Include="..\MongoDB.Bson.TestHelpers.Dotnet\MongoDB.Bson.TestHelpers.Dotnet.csproj" />
<ProjectReference Include="..\MongoDB.Bson.Tests.Dotnet\MongoDB.Bson.Tests.Dotnet.csproj" />
<ProjectReference Include="..\MongoDB.Driver.Core.TestHelpers.Dotnet\MongoDB.Driver.Core.TestHelpers.Dotnet.csproj" />
</ItemGroup>

156
tests/MongoDB.Driver.Core.Tests/ChangeStreamDocumentSerializerTests.cs

@ -13,69 +13,53 @@
* limitations under the License.
*/
using System;
using System.IO;
using FluentAssertions;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Bson.TestHelpers;
using MongoDB.Bson.Tests.Serialization.Serializers;
using Xunit;
namespace MongoDB.Driver
{
public class ChangeStreamDocumentSerializerTests
{
[Theory]
[InlineData("{ _id : { id : 1 }, operationType : \"delete\", ns : { db : \"db\", coll : \"collection\" }, documentKey : { k : 1 } }", "{ id : 1 }", ChangeStreamOperationType.Delete, "db", "collection", "{ k : 1 }", null, null)]
[InlineData("{ _id : { id : 1 }, operationType : \"insert\", ns : { db : \"db\", coll : \"collection\" }, documentKey : { k : 1 }, fullDocument : { _id : 2 } }", "{ id : 1 }", ChangeStreamOperationType.Insert, "db", "collection", "{ k : 1 }", null, "{ _id : 2 }")]
[InlineData("{ _id : { id : 1 }, operationType : \"invalidate\" }", "{ id : 1 }", ChangeStreamOperationType.Invalidate, null, null, null, null, null)]
[InlineData("{ _id : { id : 1 }, operationType : \"replace\", ns : { db : \"db\", coll : \"collection\" }, documentKey : { k : 1 } }", "{ id : 1 }", ChangeStreamOperationType.Replace, "db", "collection", "{ k : 1 }", null, null)]
[InlineData("{ _id : { id : 1 }, operationType : \"update\", ns : { db : \"db\", coll : \"collection\" }, documentKey : { k : 1 }, updateDescription : { updatedFields : { f : 1 }, removedFields : [\"r\"] } }", "{ id : 1 }", ChangeStreamOperationType.Update, "db", "collection", "{ k : 1 }", "{ updatedFields : { f : 1 }, removedFields : [\"r\"] }", null)]
[InlineData("{ _id : { id : 1 }, operationType : \"update\", ns : { db : \"db\", coll : \"collection\" }, documentKey : { k : 1 }, updateDescription : { updatedFields : { f : 1 }, removedFields : [\"r\"] }, fullDocument : null }", "{ id : 1 }", ChangeStreamOperationType.Update, "db", "collection", "{ k : 1 }", "{ updatedFields : { f : 1 }, removedFields : [\"r\"] }", null)]
[InlineData("{ _id : { id : 1 }, operationType : \"update\", ns : { db : \"db\", coll : \"collection\" }, documentKey : { k : 1 }, updateDescription : { updatedFields : { f : 1 }, removedFields : [\"r\"] }, fullDocument : { _id : 2 } }", "{ id : 1 }", ChangeStreamOperationType.Update, "db", "collection", "{ k : 1 }", "{ updatedFields : { f : 1 }, removedFields : [\"r\"] }", "{ _id : 2 }")]
public void Deserialize_should_return_expected_result(
string json,
string expectedResumeTokenJson,
ChangeStreamOperationType expectedOperationType,
string expectedDatabaseName,
string expectedCollectionName,
string expectedDocumentKeyJson,
string expectedUpdateDescriptionJson,
string expectedFullDocumentJson
)
[Fact]
public void constructor_should_initialize_instance()
{
var subject = CreateSubject();
var expectedCollectionNamespace = CreateCollectionNamespace(expectedDatabaseName, expectedCollectionName);
var expectedDocumentKey = ParseBsonDocument(expectedDocumentKeyJson);
var expectedFullDocument = ParseBsonDocument(expectedFullDocumentJson);
var expectedResumeToken = ParseBsonDocument(expectedResumeTokenJson);
var expectedUpdateDescription = ParseUpdateDescription(expectedUpdateDescriptionJson);
var documentSerializer = new BsonDocumentSerializer();
var result = new ChangeStreamDocumentSerializer<BsonDocument>(documentSerializer);
result._documentSerializer().Should().BeSameAs(documentSerializer);
result._memberSerializationInfo().Count.Should().Be(7);
AssertRegisteredMember(result, "ClusterTime", "clusterTime", BsonDocumentSerializer.Instance);
AssertRegisteredMember(result, "CollectionNamespace", "ns", ChangeStreamDocumentCollectionNamespaceSerializer.Instance);
AssertRegisteredMember(result, "DocumentKey", "documentKey", BsonDocumentSerializer.Instance);
AssertRegisteredMember(result, "FullDocument", "fullDocument", documentSerializer);
AssertRegisteredMember(result, "OperationType", "operationType", ChangeStreamOperationTypeSerializer.Instance);
AssertRegisteredMember(result, "ResumeToken", "_id", BsonDocumentSerializer.Instance);
AssertRegisteredMember(result, "UpdateDescription", "updateDescription", ChangeStreamUpdateDescriptionSerializer.Instance);
}
ChangeStreamDocument<BsonDocument> result;
using (var reader = new JsonReader(json))
{
var context = BsonDeserializationContext.CreateRoot(reader);
result = subject.Deserialize(context);
}
[Fact]
public void constructor_should_throw_when_documentSerializer_is_null()
{
var exception = Record.Exception(() => new ChangeStreamDocumentSerializer<BsonDocument>(null));
result.CollectionNamespace.Should().Be(expectedCollectionNamespace);
result.DocumentKey.Should().Be(expectedDocumentKey);
result.FullDocument.Should().Be(expectedFullDocument);
result.OperationType.Should().Be(expectedOperationType);
result.ResumeToken.Should().Be(expectedResumeToken);
result.UpdateDescription.ShouldBeEquivalentTo(expectedUpdateDescription);
var e = exception.Should().BeOfType<ArgumentNullException>().Subject;
e.ParamName.Should().Be("documentSerializer");
}
[Fact]
public void Deserialize_should_return_expected_result_when_value_is_null()
public void Deserialize_should_return_expected_result()
{
var json = "{ x : 1 }";
var subject = CreateSubject();
var json = "null";
ChangeStreamDocument<BsonDocument> result;
using (var reader = new JsonReader(json))
@ -84,51 +68,31 @@ namespace MongoDB.Driver
result = subject.Deserialize(context);
}
result.Should().BeNull();
result.BackingDocument.Should().Be(json);
}
[Fact]
public void Deserialize_should_throw_when_an_invalid_element_name_is_present()
public void Deserialize_should_return_expected_result_when_value_is_null()
{
var json = "null";
var subject = CreateSubject();
var json = "{ x : 1 }";
Exception exception;
ChangeStreamDocument<BsonDocument> result;
using (var reader = new JsonReader(json))
{
var context = BsonDeserializationContext.CreateRoot(reader);
exception = Record.Exception(() => subject.Deserialize(context));
result = subject.Deserialize(context);
}
var formatException = exception.Should().BeOfType<FormatException>().Subject;
formatException.Message.Should().Be("Invalid field name: \"x\".");
result.Should().BeNull();
}
[Theory]
[InlineData(ChangeStreamOperationType.Delete, "{ _id : 1 }", "db", "collection", "{ k : 1 }", null, null, "{ \"_id\" : { \"_id\" : 1 }, \"operationType\" : \"delete\", \"ns\" : { \"db\" : \"db\", \"coll\" : \"collection\" }, \"documentKey\" : { \"k\" : 1 } }")]
[InlineData(ChangeStreamOperationType.Insert, "{ _id : 1 }", "db", "collection", "{ k : 1 }", null, "{ _id : 2 }", "{ \"_id\" : { \"_id\" : 1 }, \"operationType\" : \"insert\", \"ns\" : { \"db\" : \"db\", \"coll\" : \"collection\" }, \"documentKey\" : { \"k\" : 1 }, \"fullDocument\" : { \"_id\" : 2 } }")]
[InlineData(ChangeStreamOperationType.Invalidate, "{ _id : 1 }", null, null, null, null, null, "{ \"_id\" : { \"_id\" : 1 }, \"operationType\" : \"invalidate\" }")]
[InlineData(ChangeStreamOperationType.Replace, "{ _id : 1 }", "db", "collection", "{ k : 1 }", null, "{ _id : 2 }", "{ \"_id\" : { \"_id\" : 1 }, \"operationType\" : \"replace\", \"ns\" : { \"db\" : \"db\", \"coll\" : \"collection\" }, \"documentKey\" : { \"k\" : 1 }, \"fullDocument\" : { \"_id\" : 2 } }")]
[InlineData(ChangeStreamOperationType.Update, "{ _id : 1 }", "db", "collection", "{ k : 1 }", "{ updatedFields : { f : 1 }, removedFields : [\"f\"] }", null, "{ \"_id\" : { \"_id\" : 1 }, \"operationType\" : \"update\", \"ns\" : { \"db\" : \"db\", \"coll\" : \"collection\" }, \"documentKey\" : { \"k\" : 1 }, \"updateDescription\" : { \"updatedFields\" : { \"f\" : 1 }, \"removedFields\" : [\"f\"] } }")]
[InlineData(ChangeStreamOperationType.Update, "{ _id : 1 }", "db", "collection", "{ k : 1 }", "{ updatedFields : { f : 1 }, removedFields : [\"f\"] }", "{ _id : 2 }", "{ \"_id\" : { \"_id\" : 1 }, \"operationType\" : \"update\", \"ns\" : { \"db\" : \"db\", \"coll\" : \"collection\" }, \"documentKey\" : { \"k\" : 1 }, \"updateDescription\" : { \"updatedFields\" : { \"f\" : 1 }, \"removedFields\" : [\"f\"] }, \"fullDocument\" : { \"_id\" : 2 } }")]
public void Serialize_should_have_expected_result(
ChangeStreamOperationType operationType,
string resumeTokenJson,
string databaseName,
string collectionName,
string documentKeyJson,
string updateDescriptionJson,
string fullDocumentJson,
string expectedJson)
[Fact]
public void Serialize_should_have_expected_result()
{
var subject = CreateSubject();
var value = new ChangeStreamDocument<BsonDocument>(
ParseBsonDocument(resumeTokenJson),
operationType,
CreateCollectionNamespace(databaseName, collectionName),
ParseBsonDocument(documentKeyJson),
ParseUpdateDescription(updateDescriptionJson),
ParseBsonDocument(fullDocumentJson));
var backingDocument = BsonDocument.Parse("{ x : 1 }");
var value = new ChangeStreamDocument<BsonDocument>(backingDocument, BsonDocumentSerializer.Instance);
string json;
using (var textWriter = new StringWriter())
@ -139,21 +103,20 @@ namespace MongoDB.Driver
json = textWriter.ToString();
}
json.Should().Be(expectedJson);
json.Should().Be("{ \"x\" : 1 }");
}
[Fact]
public void Serialize_should_have_expected_result_when_value_is_null()
{
var subject = CreateSubject();
ChangeStreamDocument<BsonDocument> value = null;
string json;
using (var textWriter = new StringWriter())
using (var writer = new JsonWriter(textWriter))
{
var context = BsonSerializationContext.CreateRoot(writer);
subject.Serialize(context, value);
subject.Serialize(context, null);
json = textWriter.ToString();
}
@ -161,43 +124,22 @@ namespace MongoDB.Driver
}
// private methods
private CollectionNamespace CreateCollectionNamespace(string databaseName, string collectionName)
private void AssertRegisteredMember(ChangeStreamDocumentSerializer<BsonDocument> changeStreamDocumentSerializer, string memberName, string elementName, IBsonSerializer memberSerializer)
{
if (databaseName == null && collectionName == null)
{
return null;
}
else
{
var databaseNamespace = new DatabaseNamespace(databaseName);
return new CollectionNamespace(databaseNamespace, collectionName);
}
var serializationInfo = changeStreamDocumentSerializer._memberSerializationInfo()[memberName];
serializationInfo.ElementName.Should().Be(elementName);
serializationInfo.Serializer.Should().BeSameAs(memberSerializer);
serializationInfo.NominalType.Should().Be(memberSerializer.ValueType);
}
private ChangeStreamDocumentSerializer<BsonDocument> CreateSubject()
{
return new ChangeStreamDocumentSerializer<BsonDocument>(BsonDocumentSerializer.Instance);
}
}
private BsonDocument ParseBsonDocument(string json)
{
return json == null ? null : BsonDocument.Parse(json);
}
private ChangeStreamUpdateDescription ParseUpdateDescription(string json)
{
if (json == null)
{
return null;
}
else
{
var document = BsonDocument.Parse(json);
var updatedFields = document["updatedFields"].AsBsonDocument;
var removedFields = document["removedFields"].AsBsonArray.Select(f => f.AsString).ToArray();
return new ChangeStreamUpdateDescription(updatedFields, removedFields);
}
}
public static class ChangeStreamDocumentSerializerReflector
{
public static IBsonSerializer<BsonDocument> _documentSerializer(this ChangeStreamDocumentSerializer<BsonDocument> obj) => (IBsonSerializer<BsonDocument>)Reflector.GetFieldValue(obj, nameof(_documentSerializer));
}
}

262
tests/MongoDB.Driver.Core.Tests/ChangeStreamDocumentTests.cs

@ -0,0 +1,262 @@
/* Copyright 2018-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using FluentAssertions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Bson.Tests.Serialization;
using Moq;
using Xunit;
namespace MongoDB.Driver
{
public class ChangeStreamDocumentTests
{
[Fact]
public void constructor_should_initialize_instance()
{
var backingDocument = new BsonDocument();
var documentSerializer = Mock.Of<IBsonSerializer<BsonDocument>>();
var result = new ChangeStreamDocument<BsonDocument>(backingDocument, documentSerializer);
result.BackingDocument.Should().BeSameAs(backingDocument);
var changeStreamDocumentSerializer = result._serializer().Should().BeOfType<ChangeStreamDocumentSerializer<BsonDocument>>().Subject;
changeStreamDocumentSerializer._documentSerializer().Should().BeSameAs(documentSerializer);
}
[Fact]
public void constructor_should_throw_when_backingDocument_is_null()
{
var documentSerializer = Mock.Of<IBsonSerializer<BsonDocument>>();
var exception = Record.Exception(() => new ChangeStreamDocument<BsonDocument>(null, documentSerializer));
var e = exception.Should().BeOfType<ArgumentNullException>().Subject;
e.ParamName.Should().Be("backingDocument");
}
[Fact]
public void constructor_should_throw_when_documentSerializer_is_null()
{
var backingDocument = new BsonDocument();
var exception = Record.Exception(() => new ChangeStreamDocument<BsonDocument>(backingDocument, null));
var e = exception.Should().BeOfType<ArgumentNullException>().Subject;
e.ParamName.Should().Be("documentSerializer");
}
[Fact]
public void BackingDocument_should_return_expected_result()
{
var backingDocument = new BsonDocument();
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.BackingDocument;
result.Should().BeSameAs(backingDocument);
}
[Fact]
public void ClusterTime_should_return_expected_result()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 }, { "clusterTime", value } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.ClusterTime;
result.Should().Be(value);
}
[Fact]
public void ClusterTime_should_return_null_when_not_present()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.ClusterTime;
result.Should().BeNull();
}
[Fact]
public void CollectionNamespace_should_return_expected_result()
{
var value = new CollectionNamespace(new DatabaseNamespace("database"), "collection");
var ns = new BsonDocument { { "db", value.DatabaseNamespace.DatabaseName }, { "coll", value.CollectionName } };
var backingDocument = new BsonDocument { { "other", 1 }, { "ns", ns } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.CollectionNamespace;
result.Should().Be(value);
}
[Fact]
public void CollectionNamespace_should_return_null_when_not_present()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.CollectionNamespace;
result.Should().BeNull();
}
[Fact]
public void DocumentKey_should_return_expected_result()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 }, { "documentKey", value } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.DocumentKey;
result.Should().Be(value);
}
[Fact]
public void DocumentKey_should_return_null_when_not_present()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.DocumentKey;
result.Should().BeNull();
}
[Fact]
public void FullDocument_should_return_expected_result()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 }, { "fullDocument", value } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.FullDocument;
result.Should().Be(value);
}
[Fact]
public void FullDocument_should_return_null_when_not_present()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.FullDocument;
result.Should().BeNull();
}
[Theory]
[InlineData("insert", ChangeStreamOperationType.Insert)]
[InlineData("update", ChangeStreamOperationType.Update)]
[InlineData("replace", ChangeStreamOperationType.Replace)]
[InlineData("delete", ChangeStreamOperationType.Delete)]
public void OperationType_should_return_expected_result(string operationTypeName, ChangeStreamOperationType expectedResult)
{
var backingDocument = new BsonDocument { { "other", 1 }, { "operationType", operationTypeName } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.OperationType;
result.Should().Be(expectedResult);
}
[Fact]
public void OperationType_should_return_minus_one_when_not_present()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.OperationType;
result.Should().Be((ChangeStreamOperationType)(-1));
}
[Fact]
public void ResumeToken_should_return_expected_result()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 }, { "_id", value } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.ResumeToken;
result.Should().Be(value);
}
[Fact]
public void ResumeToken_should_return_null_when_not_present()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.ResumeToken;
result.Should().BeNull();
}
[Theory]
[InlineData("{}", new string[0])]
[InlineData("{ x : 1 }", new[] { "a", "b", "c" })]
public void UpdateDescription_should_return_expected_result(string updatedFieldsJson, string[] removedFields)
{
var updatedFields = BsonDocument.Parse(updatedFieldsJson);
var updateDescriptionDocument = new BsonDocument { { "updatedFields", updatedFields }, { "removedFields", new BsonArray(removedFields) } };
var backingDocument = new BsonDocument { { "other", 1 }, { "updateDescription", updateDescriptionDocument } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.UpdateDescription;
var expectedResult = new ChangeStreamUpdateDescription(updatedFields, removedFields);
result.Should().Be(expectedResult);
}
[Fact]
public void UpdateDescription_should_return_null_when_not_present()
{
var value = new BsonDocument("x", 1234);
var backingDocument = new BsonDocument { { "other", 1 } };
var subject = CreateSubject(backingDocument: backingDocument);
var result = subject.UpdateDescription;
result.Should().BeNull();
}
// private methods
private ChangeStreamDocument<BsonDocument> CreateSubject(
BsonDocument backingDocument = null,
IBsonSerializer<BsonDocument> documentSerializer = null)
{
backingDocument = backingDocument ?? new BsonDocument();
documentSerializer = documentSerializer ?? BsonDocumentSerializer.Instance;
return new ChangeStreamDocument<BsonDocument>(backingDocument, documentSerializer);
}
}
}

5
tests/MongoDB.Driver.Core.Tests/MongoDB.Driver.Core.Tests.csproj

@ -65,6 +65,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ChangeStreamDocumentTests.cs" />
<Compile Include="ChangeStreamOperationTypeSerializerTests.cs" />
<Compile Include="ChangeStreamDocumentSerializerTests.cs" />
<Compile Include="ChangeStreamUpdateDescriptionSerializerTests.cs" />
@ -316,6 +317,10 @@
<Project>{0e9a3a2a-49cd-4f6c-847c-dc79b4b65ce6}</Project>
<Name>MongoDB.Bson</Name>
</ProjectReference>
<ProjectReference Include="..\MongoDB.Bson.Tests\MongoDB.Bson.Tests.csproj">
<Project>{10A5FAC2-E26F-4726-B888-26D5B849F805}</Project>
<Name>MongoDB.Bson.Tests</Name>
</ProjectReference>
<ProjectReference Include="..\MongoDB.Driver.Core.TestHelpers\MongoDB.Driver.Core.TestHelpers.csproj">
<Project>{f7b7d81a-ca16-4cd7-8b6c-444280ea37c1}</Project>
<Name>MongoDB.Driver.Core.TestHelpers</Name>

Loading…
Cancel
Save