From 790f1233b360b1638fdec17c73a2cb67710b488e Mon Sep 17 00:00:00 2001 From: rstam Date: Wed, 25 Jan 2023 20:53:59 -0800 Subject: [PATCH] CSHARP-4475: Add an AllowedTypes filter to ObjectSerializer. --- .../Serialization/BsonSerializer.cs | 22 ++ .../Serialization/BsonSerializerRegistry.cs | 61 +++++- .../Serializers/BsonClassMapSerializer.cs | 18 +- .../Serializers/DictionarySerializerBase.cs | 3 +- .../DiscriminatedInterfaceSerializer.cs | 2 +- .../Serializers/ObjectSerializer.cs | 207 +++++++++++++++++- .../RegisterObjectSerializerFixture.cs | 55 +++++ .../Jira/CSharp1559Tests.cs | 1 + .../MongoDB.Bson.Tests/Jira/CSharp263Tests.cs | 3 +- .../MongoDB.Bson.Tests/Jira/CSharp301Tests.cs | 3 +- .../MongoDB.Bson.Tests/Jira/CSharp313Tests.cs | 3 +- .../Jira/CSharp4475Tests.cs | 95 ++++++++ .../MongoDB.Bson.Tests/Jira/CSharp515Tests.cs | 3 +- .../MongoDB.Bson.Tests/Jira/CSharp564Tests.cs | 3 +- .../MongoDB.Bson.Tests/Jira/CSharp783Tests.cs | 4 + .../RegisterObjectSerializerCollection.cs | 25 +++ .../BsonSerializerRegistryTests.cs | 193 ++++++++++++++++ .../CollectionSerializerGenericTests.cs | 2 + .../Serializers/CollectionSerializerTests.cs | 2 + .../DictionaryGenericSerializerTests.cs | 29 +++ .../Serializers/DictionarySerializerTests.cs | 27 +++ .../Serializers/DiscriminatorTests.cs | 3 +- .../ExpandoObjectSerializerTests.cs | 7 +- .../Serializers/ObjectSerializerTests.cs | 53 ++++- .../Serializers/SerializeInterfaceTests.cs | 3 +- .../Jira/CSharp140Tests.cs | 2 + .../Jira/CSharp247Tests.cs | 3 +- .../Jira/CSharp958Tests.cs | 5 +- .../RegisterObjectSerializerCollection.cs | 25 +++ .../Jira/CSharp2113Tests.cs | 2 + .../RegisterObjectSerializerCollection.cs | 25 +++ .../UpdateDefinitionBuilderTests.cs | 5 +- 32 files changed, 845 insertions(+), 49 deletions(-) create mode 100644 tests/MongoDB.Bson.TestHelpers/RegisterObjectSerializerFixture.cs create mode 100644 tests/MongoDB.Bson.Tests/Jira/CSharp4475Tests.cs create mode 100644 tests/MongoDB.Bson.Tests/RegisterObjectSerializerCollection.cs create mode 100644 tests/MongoDB.Bson.Tests/Serialization/BsonSerializerRegistryTests.cs create mode 100644 tests/MongoDB.Driver.Legacy.Tests/RegisterObjectSerializerCollection.cs create mode 100644 tests/MongoDB.Driver.Tests/RegisterObjectSerializerCollection.cs diff --git a/src/MongoDB.Bson/Serialization/BsonSerializer.cs b/src/MongoDB.Bson/Serialization/BsonSerializer.cs index 15d4858c3e7..5c027786d87 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializer.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializer.cs @@ -678,6 +678,28 @@ public static void RegisterSerializer(Type type, IBsonSerializer serializer) serializer.Serialize(context, args, value); } + /// + /// Tries to register a serializer for a type. + /// + /// The serializer. + /// The type. + /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. + public static bool TryRegisterSerializer(Type type, IBsonSerializer serializer) + { + return __serializerRegistry.TryRegisterSerializer(type, serializer); + } + + /// + /// Tries to register a serializer for a type. + /// + /// The type. + /// The serializer. + /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. + public static bool TryRegisterSerializer(IBsonSerializer serializer) + { + return TryRegisterSerializer(typeof(T), serializer); + } + // internal static methods internal static void EnsureKnownTypesAreRegistered(Type nominalType) { diff --git a/src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs b/src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs index 4ff54ec6134..429386f6019 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializerRegistry.cs @@ -93,17 +93,7 @@ public void RegisterSerializer(Type type, IBsonSerializer serializer) { throw new ArgumentNullException("serializer"); } - var typeInfo = type.GetTypeInfo(); - if (typeof(BsonValue).GetTypeInfo().IsAssignableFrom(type)) - { - var message = string.Format("A serializer cannot be registered for type {0} because it is a subclass of BsonValue.", BsonUtils.GetFriendlyTypeName(type)); - throw new BsonSerializationException(message); - } - if (typeInfo.IsGenericType && typeInfo.ContainsGenericParameters) - { - var message = string.Format("Generic type {0} has unassigned type parameters.", BsonUtils.GetFriendlyTypeName(type)); - throw new ArgumentException(message, "type"); - } + EnsureRegisteringASerializerForThisTypeIsAllowed(type); if (!_cache.TryAdd(type, serializer)) { @@ -127,6 +117,40 @@ public void RegisterSerializationProvider(IBsonSerializationProvider serializati _serializationProviders.Push(serializationProvider); } + /// + /// Tries to register the serializer. + /// + /// The type. + /// The serializer. + /// True if the serializer was registered on this call, false if the same serializer was already registered on a previous call, throws an exception if a different serializer was already registered. + public bool TryRegisterSerializer(Type type, IBsonSerializer serializer) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + if (serializer == null) + { + throw new ArgumentNullException(nameof(serializer)); + } + EnsureRegisteringASerializerForThisTypeIsAllowed(type); + + if (_cache.TryAdd(type, serializer)) + { + return true; + } + else + { + var existingSerializer = _cache[type]; + if (!existingSerializer.Equals(serializer)) + { + var message = $"There is already a different serializer registered for type {BsonUtils.GetFriendlyTypeName(type)}."; + throw new BsonSerializationException(message); + } + return false; + } + } + // private methods private IBsonSerializer CreateSerializer(Type type) { @@ -153,5 +177,20 @@ private IBsonSerializer CreateSerializer(Type type) var message = string.Format("No serializer found for type {0}.", type.FullName); throw new BsonSerializationException(message); } + + private void EnsureRegisteringASerializerForThisTypeIsAllowed(Type type) + { + var typeInfo = type.GetTypeInfo(); + if (typeof(BsonValue).GetTypeInfo().IsAssignableFrom(type)) + { + var message = string.Format("A serializer cannot be registered for type {0} because it is a subclass of BsonValue.", BsonUtils.GetFriendlyTypeName(type)); + throw new BsonSerializationException(message); + } + if (typeInfo.IsGenericType && typeInfo.ContainsGenericParameters) + { + var message = string.Format("Generic type {0} has unassigned type parameters.", BsonUtils.GetFriendlyTypeName(type)); + throw new ArgumentException(message, "type"); + } + } } } diff --git a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs index c38430fdb2e..52265b9fd15 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs @@ -651,13 +651,23 @@ private void SerializeDiscriminator(BsonSerializationContext context, Type nomin private void SerializeMember(BsonSerializationContext context, object obj, BsonMemberMap memberMap) { - if (memberMap != _classMap.ExtraElementsMemberMap) + try { - SerializeNormalMember(context, obj, memberMap); + if (memberMap != _classMap.ExtraElementsMemberMap) + { + SerializeNormalMember(context, obj, memberMap); + } + else + { + SerializeExtraElements(context, obj, memberMap); + } } - else + catch (Exception ex) { - SerializeExtraElements(context, obj, memberMap); + var message = string.Format( + "An error occurred while serializing the {0} {1} of class {2}: {3}", // terminating period provided by nested message + memberMap.MemberName, (memberMap.MemberInfo is FieldInfo) ? "field" : "property", memberMap.ClassMap.ClassType.FullName, ex.Message); + throw new BsonSerializationException(message, ex); } } diff --git a/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs b/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs index 73b45873c5c..9b04916444a 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/DictionarySerializerBase.cs @@ -16,7 +16,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq.Expressions; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization.Options; @@ -59,7 +58,7 @@ public DictionarySerializerBase() /// /// The dictionary representation. public DictionarySerializerBase(DictionaryRepresentation dictionaryRepresentation) - : this(dictionaryRepresentation, new ObjectSerializer(), new ObjectSerializer()) + : this(dictionaryRepresentation, BsonSerializer.LookupSerializer(), BsonSerializer.LookupSerializer()) { } diff --git a/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs index 760b03197ff..f59caa60836 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs @@ -81,7 +81,7 @@ public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorCo _interfaceType = typeof(TInterface); _discriminatorConvention = discriminatorConvention; - _objectSerializer = new ObjectSerializer(_discriminatorConvention); + _objectSerializer = ((ObjectSerializer)BsonSerializer.LookupSerializer()).WithDiscriminatorConvention(_discriminatorConvention); _interfaceSerializer = interfaceSerializer; } diff --git a/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs index 1415d0160fe..cdfce696933 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/ObjectSerializer.cs @@ -14,8 +14,11 @@ */ using System; +using System.Collections.Generic; +using System.Linq; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Shared; namespace MongoDB.Bson.Serialization.Serializers { @@ -24,10 +27,36 @@ namespace MongoDB.Bson.Serialization.Serializers /// public class ObjectSerializer : ClassSerializerBase { + #region static // private static fields + private static readonly Func __allAllowedTypes = t => true; private static readonly ObjectSerializer __instance = new ObjectSerializer(); + private static readonly Func __noAllowedTypes = t => false; + + // public static properties + /// + /// An allowed types function that returns true for all types. + /// + public static Func AllAllowedTypes => __allAllowedTypes; + + /// + /// An allowed types function that returns true for framework types known to be safe. + /// + public static Func DefaultAllowedTypes => DefaultFrameworkAllowedTypes.AllowedTypes; + + /// + /// Gets the standard instance. + /// + public static ObjectSerializer Instance => __instance; + + /// + /// An allowed types function that returns false for all types. + /// + public static Func NoAllowedTypes => __noAllowedTypes; + #endregion // private fields + private readonly Func _allowedTypes; private readonly IDiscriminatorConvention _discriminatorConvention; private readonly GuidRepresentation _guidRepresentation; private readonly GuidSerializer _guidSerializer; @@ -57,27 +86,50 @@ public ObjectSerializer(IDiscriminatorConvention discriminatorConvention) /// The discriminator convention. /// The Guid representation. public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation) + : this(discriminatorConvention, guidRepresentation, DefaultFrameworkAllowedTypes.AllowedTypes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A delegate that determines what types are allowed. + public ObjectSerializer(Func allowedTypes) + : this(BsonSerializer.LookupDiscriminatorConvention(typeof(object)), allowedTypes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The discriminator convention. + /// A delegate that determines what types are allowed. + public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, Func allowedTypes) + : this(discriminatorConvention, GuidRepresentation.Unspecified, allowedTypes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The discriminator convention. + /// The Guid representation. + /// A delegate that determines what types are allowed. + public ObjectSerializer(IDiscriminatorConvention discriminatorConvention, GuidRepresentation guidRepresentation, Func allowedTypes) { if (discriminatorConvention == null) { throw new ArgumentNullException("discriminatorConvention"); } + if (allowedTypes == null) + { + throw new ArgumentNullException(nameof(allowedTypes)); + } _discriminatorConvention = discriminatorConvention; _guidRepresentation = guidRepresentation; _guidSerializer = new GuidSerializer(_guidRepresentation); - } - - // public static properties - /// - /// Gets the standard instance. - /// - /// - /// The standard instance. - /// - public static ObjectSerializer Instance - { - get { return __instance; } + _allowedTypes = allowedTypes; } // public methods @@ -165,6 +217,21 @@ public override object Deserialize(BsonDeserializationContext context, BsonDeser } } + /// + public override bool Equals(object obj) => + obj is ObjectSerializer other && + GetType() == other.GetType() && + _allowedTypes.Equals(other._allowedTypes) && + _discriminatorConvention.Equals(other._discriminatorConvention) && + _guidRepresentation == other._guidRepresentation; + + /// + public override int GetHashCode() => + new Hasher() + .Hash(_discriminatorConvention) + .Hash(_guidRepresentation) + .GetHashCode(); + /// /// Serializes a value. /// @@ -264,12 +331,27 @@ public override void Serialize(BsonSerializationContext context, BsonSerializati } } + /// + /// Returns a new ObjectSerializer configured the same but with the specified discriminator convention. + /// + /// The discriminator convention. + /// An ObjectSerializer with the specified discriminator convention. + public ObjectSerializer WithDiscriminatorConvention(IDiscriminatorConvention discriminatorConvention) + { + return new ObjectSerializer(discriminatorConvention, _guidRepresentation, _allowedTypes); + } + // private methods private object DeserializeDiscriminatedValue(BsonDeserializationContext context, BsonDeserializationArgs args) { var bsonReader = context.Reader; var actualType = _discriminatorConvention.GetActualType(bsonReader, typeof(object)); + if (!_allowedTypes(actualType)) + { + throw new BsonSerializationException($"Type {actualType.FullName} is not configured as an allowed type for this instance of ObjectSerializer."); + } + if (actualType == typeof(object)) { var type = bsonReader.GetCurrentBsonType(); @@ -333,6 +415,11 @@ private object DeserializeDiscriminatedValue(BsonDeserializationContext context, private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonSerializationArgs args, object value, Type actualType) { + if (!_allowedTypes(actualType)) + { + throw new BsonSerializationException($"Type {actualType.FullName} is not configured as an allowed type for this instance of ObjectSerializer."); + } + var serializer = BsonSerializer.LookupSerializer(actualType); var polymorphicSerializer = serializer as IBsonPolymorphicSerializer; @@ -361,5 +448,101 @@ private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonS } } } + + // nested types + private static class DefaultFrameworkAllowedTypes + { + private readonly static HashSet __allowedTypes = new HashSet + { + typeof(System.Boolean), + typeof(System.Byte), + typeof(System.Char), + typeof(System.Collections.ArrayList), + typeof(System.Collections.BitArray), + typeof(System.Collections.Hashtable), + typeof(System.Collections.Queue), + typeof(System.Collections.SortedList), + typeof(System.Collections.Specialized.ListDictionary), + typeof(System.Collections.Specialized.OrderedDictionary), + typeof(System.Collections.Stack), + typeof(System.DateTime), + typeof(System.DateTimeOffset), + typeof(System.Decimal), + typeof(System.Double), + typeof(System.Dynamic.ExpandoObject), + typeof(System.Guid), + typeof(System.Int16), + typeof(System.Int32), + typeof(System.Int64), + typeof(System.Net.DnsEndPoint), + typeof(System.Net.EndPoint), + typeof(System.Net.IPAddress), + typeof(System.Net.IPEndPoint), + typeof(System.Net.IPHostEntry), + typeof(System.Object), + typeof(System.SByte), + typeof(System.Single), + typeof(System.String), + typeof(System.Text.RegularExpressions.Regex), + typeof(System.TimeSpan), + typeof(System.UInt16), + typeof(System.UInt32), + typeof(System.UInt64), + typeof(System.Uri), + typeof(System.Version) + }; + + private readonly static HashSet __allowedGenericTypes = new HashSet + { + typeof(System.Collections.Generic.Dictionary<,>), + typeof(System.Collections.Generic.HashSet<>), + typeof(System.Collections.Generic.KeyValuePair<,>), + typeof(System.Collections.Generic.LinkedList<>), + typeof(System.Collections.Generic.List<>), + typeof(System.Collections.Generic.Queue<>), + typeof(System.Collections.Generic.SortedDictionary<,>), + typeof(System.Collections.Generic.SortedList<,>), + typeof(System.Collections.Generic.SortedSet<>), + typeof(System.Collections.Generic.Stack<>), + typeof(System.Collections.ObjectModel.Collection<>), + typeof(System.Collections.ObjectModel.KeyedCollection<,>), + typeof(System.Collections.ObjectModel.ObservableCollection<>), + typeof(System.Collections.ObjectModel.ReadOnlyCollection<>), + typeof(System.Collections.ObjectModel.ReadOnlyDictionary<,>), + typeof(System.Collections.ObjectModel.ReadOnlyObservableCollection<>), + typeof(System.Nullable<>), + typeof(System.Tuple<>), + typeof(System.Tuple<,>), + typeof(System.Tuple<,,>), + typeof(System.Tuple<,,,>), + typeof(System.Tuple<,,,,>), + typeof(System.Tuple<,,,,,>), + typeof(System.Tuple<,,,,,,>), + typeof(System.Tuple<,,,,,,,>), + typeof(System.ValueTuple<,,,,,,,>), + typeof(System.ValueTuple<>), + typeof(System.ValueTuple<,>), + typeof(System.ValueTuple<,,>), + typeof(System.ValueTuple<,,,>), + typeof(System.ValueTuple<,,,,>), + typeof(System.ValueTuple<,,,,,>), + typeof(System.ValueTuple<,,,,,,>), + typeof(System.ValueTuple<,,,,,,,>) + }; + + public static bool AllowedTypes(Type type) + { + return type.IsConstructedGenericType ? IsAllowedGenericType(type) : IsAllowedType(type); + + static bool IsAllowedType(Type type) => + __allowedTypes.Contains(type) || + type.IsArray && AllowedTypes(type.GetElementType()) || + type.IsEnum; + + static bool IsAllowedGenericType(Type type) => + __allowedGenericTypes.Contains(type.GetGenericTypeDefinition()) && + type.GetGenericArguments().All(AllowedTypes); + } + } } } diff --git a/tests/MongoDB.Bson.TestHelpers/RegisterObjectSerializerFixture.cs b/tests/MongoDB.Bson.TestHelpers/RegisterObjectSerializerFixture.cs new file mode 100644 index 00000000000..0f0ac43d303 --- /dev/null +++ b/tests/MongoDB.Bson.TestHelpers/RegisterObjectSerializerFixture.cs @@ -0,0 +1,55 @@ +/* Copyright 2010-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 System.Collections.Concurrent; +using System.Reflection; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; + +namespace MongoDB.Bson.TestHelpers +{ + public sealed class RegisterObjectSerializerFixture + { + public const string CollectionName = "RegisterObjectSerializer"; + + public RegisterObjectSerializerFixture() + { + // note: because xUnit has no way of letting us run some initialization code before any tests are run + // and because xUnit runs tests in random order it's very likely some other test has had the side effect + // of already registering a standard ObjectSerializer, so we have to use reflection below to replace + // any existing ObjectSerializer with one configured with AllowedTypes for testing + + var testObjectSerializer = new ObjectSerializer(TestAllowedTypes); + RegisterOrReplaceExistingSerializer(testObjectSerializer); + } + + public static bool TestAllowedTypes(Type type) + { + return + ObjectSerializer.DefaultAllowedTypes(type) || + type.FullName.StartsWith("MongoDB"); + } + + private static void RegisterOrReplaceExistingSerializer(IBsonSerializer serializer) + { + // we have to use reflection because the serialization API specifically prohibits this operation + var serializerRegistry = BsonSerializer.SerializerRegistry; + var cacheInfo = typeof(BsonSerializerRegistry).GetField("_cache", BindingFlags.NonPublic | BindingFlags.Instance); + var cache = (ConcurrentDictionary)cacheInfo.GetValue(serializerRegistry); + cache.AddOrUpdate(typeof(object), serializer, (objectType, existingObjectSerializer) => serializer); // might replace existing serializer! + } + } +} diff --git a/tests/MongoDB.Bson.Tests/Jira/CSharp1559Tests.cs b/tests/MongoDB.Bson.Tests/Jira/CSharp1559Tests.cs index f42fc63eff0..7e9526b8fe7 100644 --- a/tests/MongoDB.Bson.Tests/Jira/CSharp1559Tests.cs +++ b/tests/MongoDB.Bson.Tests/Jira/CSharp1559Tests.cs @@ -24,6 +24,7 @@ namespace MongoDB.Bson.Tests.Jira { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp1559Tests { [Theory] diff --git a/tests/MongoDB.Bson.Tests/Jira/CSharp263Tests.cs b/tests/MongoDB.Bson.Tests/Jira/CSharp263Tests.cs index bfad065dc53..a871463e4d5 100644 --- a/tests/MongoDB.Bson.Tests/Jira/CSharp263Tests.cs +++ b/tests/MongoDB.Bson.Tests/Jira/CSharp263Tests.cs @@ -13,12 +13,13 @@ * limitations under the License. */ -using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Jira { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp263Tests { public class C diff --git a/tests/MongoDB.Bson.Tests/Jira/CSharp301Tests.cs b/tests/MongoDB.Bson.Tests/Jira/CSharp301Tests.cs index 6c1645f0e6a..23a12e00faa 100644 --- a/tests/MongoDB.Bson.Tests/Jira/CSharp301Tests.cs +++ b/tests/MongoDB.Bson.Tests/Jira/CSharp301Tests.cs @@ -15,12 +15,13 @@ using System.Collections; using System.Collections.Generic; -using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Jira { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp301Tests { public class C diff --git a/tests/MongoDB.Bson.Tests/Jira/CSharp313Tests.cs b/tests/MongoDB.Bson.Tests/Jira/CSharp313Tests.cs index 23b0da39f9d..a7c5ee9e4cf 100644 --- a/tests/MongoDB.Bson.Tests/Jira/CSharp313Tests.cs +++ b/tests/MongoDB.Bson.Tests/Jira/CSharp313Tests.cs @@ -14,12 +14,13 @@ */ using System; -using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Jira { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp313Tests { private static object[] __scalarValues = new object[] diff --git a/tests/MongoDB.Bson.Tests/Jira/CSharp4475Tests.cs b/tests/MongoDB.Bson.Tests/Jira/CSharp4475Tests.cs new file mode 100644 index 00000000000..980893e1149 --- /dev/null +++ b/tests/MongoDB.Bson.Tests/Jira/CSharp4475Tests.cs @@ -0,0 +1,95 @@ +/* Copyright 2010-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.Serialization; +using MongoDB.Bson.Serialization.Serializers; +using Xunit; + +namespace MongoDB.Bson.Tests.Jira +{ + public class CSharp4475Tests + { + static CSharp4475Tests() + { + var objectSerializerWithAllowedTypes = new ObjectSerializer(t => t == typeof(Allowed)); + + BsonClassMap.RegisterClassMap(classMap => + { + classMap.AutoMap(); + classMap.MapProperty(x => x.Object).SetSerializer(objectSerializerWithAllowedTypes); + }); + } + + [Fact] + public void Deserialize_should_return_expected_result_when_deserializing_an_allowed_type() + { + var json = "{ Object : { _t : 'MongoDB.Bson.Tests.Jira.CSharp4475Tests+Allowed, MongoDB.Bson.Tests', X : 1 } }"; + + var result = BsonSerializer.Deserialize(json); + + var allowed = result.Object.Should().BeOfType().Subject; + allowed.X.Should().Be(1); + } + + [Fact] + public void Deserialize_should_throw_when_deserializing_a_not_allowed_type() + { + var json = "{ Object : { _t : 'MongoDB.Bson.Tests.Jira.CSharp4475Tests+NotAllowed, MongoDB.Bson.Tests', X : 1 } }"; + + var exception = Record.Exception(() => BsonSerializer.Deserialize(json)); + + exception.Should().BeOfType(); + exception.Message.Should().Be("An error occurred while deserializing the Object property of class MongoDB.Bson.Tests.Jira.CSharp4475Tests+C: Type MongoDB.Bson.Tests.Jira.CSharp4475Tests+NotAllowed is not configured as an allowed type for this instance of ObjectSerializer."); + } + + [Fact] + public void Serialize_should_have_expected_result_when_serializing_an_allowed_type() + { + var c = new C { Object = new Allowed { X = 1 } }; + + var result = c.ToJson(); + + result.Should().Be("{ \"Object\" : { \"_t\" : \"Allowed\", \"X\" : 1 } }"); + } + + [Fact] + public void Serialize_should_throw_when_serializing_a_not_allowed_type() + { + var c = new C { Object = new NotAllowed { X = 1 } }; + + var exception = Record.Exception(() => c.ToJson()); + + exception.Should().BeOfType(); + exception.Message.Should().Be("An error occurred while serializing the Object property of class MongoDB.Bson.Tests.Jira.CSharp4475Tests+C: Type MongoDB.Bson.Tests.Jira.CSharp4475Tests+NotAllowed is not configured as an allowed type for this instance of ObjectSerializer."); + } + + public class C + { + public object Object { get; set; } + } + + public class Allowed + { + public int X { get; set; } + } + + public class NotAllowed + { + public int X { get; set; } + } + } +} diff --git a/tests/MongoDB.Bson.Tests/Jira/CSharp515Tests.cs b/tests/MongoDB.Bson.Tests/Jira/CSharp515Tests.cs index 4fda6e577f5..54920f37ef1 100644 --- a/tests/MongoDB.Bson.Tests/Jira/CSharp515Tests.cs +++ b/tests/MongoDB.Bson.Tests/Jira/CSharp515Tests.cs @@ -16,12 +16,13 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Reflection; -using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Jira.CSharp515 { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp515Tests { public class C diff --git a/tests/MongoDB.Bson.Tests/Jira/CSharp564Tests.cs b/tests/MongoDB.Bson.Tests/Jira/CSharp564Tests.cs index b385151e1d9..8c670ad3502 100644 --- a/tests/MongoDB.Bson.Tests/Jira/CSharp564Tests.cs +++ b/tests/MongoDB.Bson.Tests/Jira/CSharp564Tests.cs @@ -13,12 +13,13 @@ * limitations under the License. */ -using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Jira.CSharp564 { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp564Tests { interface IWhatever { } diff --git a/tests/MongoDB.Bson.Tests/Jira/CSharp783Tests.cs b/tests/MongoDB.Bson.Tests/Jira/CSharp783Tests.cs index 82d983a7589..8408afab25f 100644 --- a/tests/MongoDB.Bson.Tests/Jira/CSharp783Tests.cs +++ b/tests/MongoDB.Bson.Tests/Jira/CSharp783Tests.cs @@ -19,11 +19,13 @@ using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Jira.CSharp783 { #if WINDOWS + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp783DiscriminatedInterfaceTests { // nested types @@ -144,6 +146,7 @@ public void TestSortedSetTwoInts(int x, int y) } #endif + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp783ImpliedHashSetImplementationTests { // nested types @@ -262,6 +265,7 @@ public void TestSortedSetTwoInts(int x, int y) } } + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp783ImpliedSortedSetImplementationTests { // nested types diff --git a/tests/MongoDB.Bson.Tests/RegisterObjectSerializerCollection.cs b/tests/MongoDB.Bson.Tests/RegisterObjectSerializerCollection.cs new file mode 100644 index 00000000000..5ea624b4564 --- /dev/null +++ b/tests/MongoDB.Bson.Tests/RegisterObjectSerializerCollection.cs @@ -0,0 +1,25 @@ +/* Copyright 2010-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 MongoDB.Bson.TestHelpers; +using Xunit; + +namespace MongoDB.Bson.Tests +{ + [CollectionDefinition(RegisterObjectSerializerFixture.CollectionName)] + public sealed class RegisterObjectSerializerCollection : ICollectionFixture + { + } +} diff --git a/tests/MongoDB.Bson.Tests/Serialization/BsonSerializerRegistryTests.cs b/tests/MongoDB.Bson.Tests/Serialization/BsonSerializerRegistryTests.cs new file mode 100644 index 00000000000..6989565923c --- /dev/null +++ b/tests/MongoDB.Bson.Tests/Serialization/BsonSerializerRegistryTests.cs @@ -0,0 +1,193 @@ +/* Copyright 2010-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 System.Collections.Generic; +using FluentAssertions; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; +using Moq; +using Xunit; + +namespace MongoDB.Bson.Tests.Serialization +{ + public class BsonSerializerRegistryTests + { + [Fact] + public void RegisterSerializer_should_work() + { + var subject = new BsonSerializerRegistry(); + var serializer = new ObjectSerializer(); + + subject.RegisterSerializer(typeof(object), serializer); + + subject.GetSerializer(typeof(object)).Should().BeSameAs(serializer); + } + + [Fact] + public void RegisterSerializer_should_throw_when_type_is_null() + { + var subject = new BsonSerializerRegistry(); + var serializer = new ObjectSerializer(); + + var exception = Record.Exception(() => subject.RegisterSerializer(type: null, serializer)); + + var argumentNullException = exception.Should().BeOfType().Subject; + argumentNullException.ParamName.Should().Be("type"); + } + + [Fact] + public void RegisterSerializer_should_throw_when_type_is_BsonValue() + { + var subject = new BsonSerializerRegistry(); + var serializer = BsonValueSerializer.Instance; + + var exception = Record.Exception(() => subject.RegisterSerializer(typeof(BsonValue), serializer)); + + exception.Should().BeOfType(); + exception.Message.Should().Contain("A serializer cannot be registered for type BsonValue because it is a subclass of BsonValue"); + } + + [Fact] + public void RegisterSerializer_should_throw_when_type_is_not_closed_generic_type() + { + var subject = new BsonSerializerRegistry(); + var serializer = Mock.Of>(); + + var exception = Record.Exception(() => subject.RegisterSerializer(typeof(List<>), serializer)); + + var argumentException = exception.Should().BeOfType().Subject; + argumentException.ParamName.Should().Be("type"); + argumentException.Message.Should().Contain("Generic type List has unassigned type parameters"); + } + + [Fact] + public void RegisterSerializer_should_throw_when_serializer_is_null() + { + var subject = new BsonSerializerRegistry(); + + var exception = Record.Exception(() => subject.RegisterSerializer(typeof(object), serializer: null)); + + var argumentNullException = exception.Should().BeOfType().Subject; + argumentNullException.ParamName.Should().Be("serializer"); + } + + [Fact] + public void RegisterSerializer_should_throw_when_serializer_is_already_registered() + { + var subject = new BsonSerializerRegistry(); + var serializer = new ObjectSerializer(); + + subject.RegisterSerializer(typeof(object), serializer); + var exception = Record.Exception(() => subject.RegisterSerializer(typeof(object), serializer)); + + exception.Should().BeOfType(); + exception.Message.Should().Contain("There is already a serializer registered for type Object"); + } + + [Fact] + public void TryRegisterSerializer_should_return_true_when_serializer_is_not_already_registered() + { + var subject = new BsonSerializerRegistry(); + var serializer = new ObjectSerializer(); + + var result = subject.TryRegisterSerializer(typeof(object), serializer); + + result.Should().BeTrue(); + subject.GetSerializer(typeof(object)).Should().BeSameAs(serializer); + } + + [Fact] + public void TryRegisterSerializer_should_return_false_when_equivalent_serializer_is_already_registered() + { + var subject = new BsonSerializerRegistry(); + var serializer1 = new ObjectSerializer(ObjectSerializer.DefaultAllowedTypes); + var serializer2 = new ObjectSerializer(ObjectSerializer.DefaultAllowedTypes); + + var result1 = subject.TryRegisterSerializer(typeof(object), serializer1); + var result2 = subject.TryRegisterSerializer(typeof(object), serializer2); + + result1.Should().BeTrue(); + result2.Should().BeFalse(); + subject.GetSerializer(typeof(object)).Should().BeSameAs(serializer1); + subject.GetSerializer(typeof(object)).Should().NotBeSameAs(serializer2); + } + + [Fact] + public void TryRegisterSerializer_should_throw_when_type_is_null() + { + var subject = new BsonSerializerRegistry(); + var serializer = new ObjectSerializer(); + + var exception = Record.Exception(() => subject.TryRegisterSerializer(type: null, serializer)); + + var argumentNullException = exception.Should().BeOfType().Subject; + argumentNullException.ParamName.Should().Be("type"); + } + + [Fact] + public void TryRegisterSerializer_should_throw_when_type_is_BsonValue() + { + var subject = new BsonSerializerRegistry(); + var serializer = BsonValueSerializer.Instance; + + var exception = Record.Exception(() => subject.TryRegisterSerializer(typeof(BsonValue), serializer)); + + exception.Should().BeOfType(); + exception.Message.Should().Contain("A serializer cannot be registered for type BsonValue because it is a subclass of BsonValue"); + } + + [Fact] + public void TryRegisterSerializer_should_throw_when_type_is_not_closed_generic_type() + { + var subject = new BsonSerializerRegistry(); + var serializer = Mock.Of>(); + + var exception = Record.Exception(() => subject.TryRegisterSerializer(typeof(List<>), serializer)); + + var argumentException = exception.Should().BeOfType().Subject; + argumentException.ParamName.Should().Be("type"); + argumentException.Message.Should().Contain("Generic type List has unassigned type parameters"); + } + + [Fact] + public void TryRegisterSerializer_should_throw_when_serializer_is_null() + { + var subject = new BsonSerializerRegistry(); + + var exception = Record.Exception(() => subject.TryRegisterSerializer(typeof(object), serializer: null)); + + var argumentNullException = exception.Should().BeOfType().Subject; + argumentNullException.ParamName.Should().Be("serializer"); + } + + [Fact] + public void TryRegisterSerializer_should_throw_when_different_serializer_is_already_registered() + { + var subject = new BsonSerializerRegistry(); + var serializer1 = new ObjectSerializer(ObjectSerializer.DefaultAllowedTypes); + var serializer2 = new ObjectSerializer(ObjectSerializer.AllAllowedTypes); + + var result1 = subject.TryRegisterSerializer(typeof(object), serializer1); + var exception = Record.Exception(() => subject.TryRegisterSerializer(typeof(object), serializer2)); + + result1.Should().BeTrue(); + subject.GetSerializer(typeof(object)).Should().BeSameAs(serializer1); + subject.GetSerializer(typeof(object)).Should().NotBeSameAs(serializer2); + exception.Should().BeOfType(); + exception.Message.Should().Contain("There is already a different serializer registered for type Object"); + } + } +} diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/CollectionSerializerGenericTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/CollectionSerializerGenericTests.cs index ff17df7b79f..6d6a33a06ad 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/CollectionSerializerGenericTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/CollectionSerializerGenericTests.cs @@ -32,6 +32,7 @@ public class C public string P { get; set; } } + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class EnumerableSerializerTests { public class T @@ -277,6 +278,7 @@ public void TestTwoStrings() } #if WINDOWS + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class EnumerableSerializerNominalTypeObjectTests { public class T diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/CollectionSerializerTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/CollectionSerializerTests.cs index ab5a2a99954..2f97d67c933 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/CollectionSerializerTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/CollectionSerializerTests.cs @@ -32,6 +32,7 @@ public class C public string P { get; set; } } + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CollectionSerializerTests { public class T @@ -274,6 +275,7 @@ public void TestTwoStrings() } } + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CollectionSerializerNominalTypeObjectTests { public class T diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/DictionaryGenericSerializerTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/DictionaryGenericSerializerTests.cs index e7646e5a181..42f64421431 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/DictionaryGenericSerializerTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/DictionaryGenericSerializerTests.cs @@ -22,6 +22,8 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Options; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Bson.TestHelpers; using MongoDB.TestHelpers.XunitExtensions; using Xunit; @@ -34,8 +36,35 @@ public class C public string P { get; set; } } + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class DictionaryGenericSerializerTests { + static DictionaryGenericSerializerTests() + { + _ = new RegisterObjectSerializerFixture(); // ensure correct ObjectSerializer is registered + + var objectSerializer = BsonSerializer.LookupSerializer(); + var dictionaryRepresentation = DictionaryRepresentation.Document; + var keySerializer = objectSerializer; + var valueSerializer = objectSerializer; + var dictionarySerializer = new DictionaryInterfaceImplementerSerializer, object, object>(dictionaryRepresentation, keySerializer, valueSerializer); + var idictionarySerializer = new ImpliedImplementationInterfaceSerializer, Dictionary>(dictionarySerializer); + var readOnlyDictionarySerializer = new ReadOnlyDictionaryInterfaceImplementerSerializer, object, object>(dictionaryRepresentation, keySerializer, valueSerializer); + var ireadOnlyDictionarySerializer = new ImpliedImplementationInterfaceSerializer, ReadOnlyDictionary>(readOnlyDictionarySerializer); + var sortedDictionarySerializer = new DictionaryInterfaceImplementerSerializer, object, object>(dictionaryRepresentation, keySerializer, valueSerializer); + var sortedListSerializer = new DictionaryInterfaceImplementerSerializer, object, object>(dictionaryRepresentation, keySerializer, valueSerializer); + + BsonClassMap.RegisterClassMap(cm => + { + cm.MapProperty(t => t.D).SetSerializer(dictionarySerializer); + cm.MapProperty(t => t.ID).SetSerializer(idictionarySerializer); + cm.MapProperty(t => t.IROD).SetSerializer(ireadOnlyDictionarySerializer); + cm.MapProperty(t => t.ROD).SetSerializer(readOnlyDictionarySerializer); + cm.MapProperty(t => t.SD).SetSerializer(sortedDictionarySerializer); + cm.MapProperty(t => t.SL).SetSerializer(sortedListSerializer); + }); + } + public class T { public Dictionary D { get; set; } diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/DictionarySerializerTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/DictionarySerializerTests.cs index 5f8a493abc4..e8ba363c16d 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/DictionarySerializerTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/DictionarySerializerTests.cs @@ -23,6 +23,8 @@ using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Options; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Bson.TestHelpers; using MongoDB.TestHelpers.XunitExtensions; using Xunit; @@ -51,8 +53,33 @@ public override int GetHashCode() } } + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class DictionarySerializerTests { + static DictionarySerializerTests() + { + _ = new RegisterObjectSerializerFixture(); // ensure correct ObjectSerializer is registered + + var objectSerializer = BsonSerializer.LookupSerializer(); + var dictionaryRepresentation = DictionaryRepresentation.Document; + var keySerializer = objectSerializer; + var valueSerializer = objectSerializer; + var hashTableSerializer = new DictionaryInterfaceImplementerSerializer(dictionaryRepresentation, keySerializer, valueSerializer); + var iDictionarySerializer = new ImpliedImplementationInterfaceSerializer(hashTableSerializer); + var listDictionarySerializer = new DictionaryInterfaceImplementerSerializer(dictionaryRepresentation, keySerializer, valueSerializer); + var orderedDictionarySerializer = new DictionaryInterfaceImplementerSerializer(dictionaryRepresentation, keySerializer, valueSerializer); + var sortedListDictionarySerializer = new DictionaryInterfaceImplementerSerializer(dictionaryRepresentation, keySerializer, valueSerializer); + + BsonClassMap.RegisterClassMap(cm => + { + cm.MapProperty(t => t.HT).SetSerializer(hashTableSerializer); + cm.MapProperty(t => t.ID).SetSerializer(iDictionarySerializer); + cm.MapProperty(t => t.LD).SetSerializer(listDictionarySerializer); + cm.MapProperty(t => t.OD).SetSerializer(orderedDictionarySerializer); + cm.MapProperty(t => t.SL).SetSerializer(sortedListDictionarySerializer); + }); + } + public class T { public Hashtable HT { get; set; } diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/DiscriminatorTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/DiscriminatorTests.cs index 900cdd69277..826b0969da6 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/DiscriminatorTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/DiscriminatorTests.cs @@ -14,13 +14,14 @@ */ using System.Linq; -using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Serialization { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class DiscriminatorTests { [BsonDiscriminator("A~")] // make discriminators unique with respect to object diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/ExpandoObjectSerializerTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/ExpandoObjectSerializerTests.cs index 69248b92b3e..4d489ed4c08 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/ExpandoObjectSerializerTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/ExpandoObjectSerializerTests.cs @@ -16,16 +16,13 @@ using System.Collections.Generic; using System.Dynamic; using System.Linq; -using System.IO; -using System.Text; -using MongoDB.Bson; -using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; -using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Serialization { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class ExpandoSerializerTests { [Fact] diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/ObjectSerializerTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/ObjectSerializerTests.cs index a7da6c543fd..75eedcca133 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/ObjectSerializerTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/ObjectSerializerTests.cs @@ -31,6 +31,7 @@ namespace MongoDB.Bson.Tests.Serialization { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class ObjectSerializerTests { public class C @@ -294,7 +295,7 @@ public void constructor_with_discriminator_convention_should_initialize_instance [Fact] public void constructor_with_discriminator_convention_should_throw_when_discriminator_convention_is_null() { - var exception = Record.Exception(() => new ObjectSerializer(null)); + var exception = Record.Exception(() => new ObjectSerializer(discriminatorConvention: null)); var e = exception.Should().BeOfType().Subject; e.ParamName.Should().Be("discriminatorConvention"); @@ -365,6 +366,56 @@ public void constructor_with_discriminator_convention_and_guid_representation_sh #pragma warning restore 618 } + [Fact] + public void Equals_should_return_true_when_instances_are_equal() + { + var discriminatorConvention = new ScalarDiscriminatorConvention("_t"); + var subject1 = new ObjectSerializer(discriminatorConvention, GuidRepresentation.Standard, ObjectSerializer.DefaultAllowedTypes); + var subject2 = new ObjectSerializer(discriminatorConvention, GuidRepresentation.Standard, ObjectSerializer.DefaultAllowedTypes); + + var result = subject1.Equals(subject2); + var hashCode1 = subject1.GetHashCode(); + var hashCode2 = subject2.GetHashCode(); + + result.Should().BeTrue(); + hashCode2.Should().Be(hashCode1); // required by the contract of Equals + } + + [Theory] + [ParameterAttributeData] + public void Equals_should_return_false_when_instances_are_not_equal( + [Values("allowedTypes", "discriminatorConvention", "guidRepresentation")] + string notEqualFieldName) + { + IDiscriminatorConvention discriminatorConvention = new ScalarDiscriminatorConvention("_t"); + var guidRepresentation = GuidRepresentation.Standard; + var allowedTypes = ObjectSerializer.DefaultAllowedTypes; + var subject1 = new ObjectSerializer(discriminatorConvention, guidRepresentation, allowedTypes); + + switch (notEqualFieldName) + { + case "allowedTypes": allowedTypes = ObjectSerializer.NoAllowedTypes; break; + case "discriminatorConvention": discriminatorConvention = new HierarchicalDiscriminatorConvention("_t"); break; + case "guidRepresentation": guidRepresentation = GuidRepresentation.CSharpLegacy; break; + default: throw new ArgumentException($"Invalid notEqualFieldName: {notEqualFieldName}.", nameof(notEqualFieldName)); + } + var subject2 = new ObjectSerializer(discriminatorConvention, guidRepresentation, allowedTypes); + + var result = subject1.Equals(subject2); + var hashCode1 = subject1.GetHashCode(); + var hashCode2 = subject2.GetHashCode(); + + result.Should().BeFalse(); + if (notEqualFieldName == "allowedTypes") + { + hashCode2.Should().Be(hashCode1); // because allowedTypes is not part of the hash code computation + } + else + { + hashCode2.Should().NotBe(hashCode1); // not strictly required but desirable + } + } + [Theory] [ParameterAttributeData] [ResetGuidModeAfterTest] diff --git a/tests/MongoDB.Bson.Tests/Serialization/Serializers/SerializeInterfaceTests.cs b/tests/MongoDB.Bson.Tests/Serialization/Serializers/SerializeInterfaceTests.cs index 3d297c0b9a5..5d6123fbbec 100644 --- a/tests/MongoDB.Bson.Tests/Serialization/Serializers/SerializeInterfaceTests.cs +++ b/tests/MongoDB.Bson.Tests/Serialization/Serializers/SerializeInterfaceTests.cs @@ -14,12 +14,13 @@ */ using System.Linq; -using MongoDB.Bson; using MongoDB.Bson.Serialization; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Bson.Tests.Serialization { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class SerializeInterfaceTests { private interface IX diff --git a/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp140Tests.cs b/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp140Tests.cs index 44f003d6e6e..a1b983f07a3 100644 --- a/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp140Tests.cs +++ b/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp140Tests.cs @@ -14,12 +14,14 @@ */ using MongoDB.Bson; +using MongoDB.Bson.TestHelpers; using MongoDB.Driver.Builders; using MongoDB.Driver.Wrappers; using Xunit; namespace MongoDB.Driver.Tests.Jira.CSharp140 { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp140Tests { private class C diff --git a/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp247Tests.cs b/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp247Tests.cs index db2fbbe018c..7a7b482f7a4 100644 --- a/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp247Tests.cs +++ b/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp247Tests.cs @@ -14,11 +14,12 @@ */ using MongoDB.Bson; -using MongoDB.Driver; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Driver.Tests.Jira.CSharp247 { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp247Tests { public interface I diff --git a/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp958Tests.cs b/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp958Tests.cs index 5e53d73a7df..a02e1f5d77b 100644 --- a/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp958Tests.cs +++ b/tests/MongoDB.Driver.Legacy.Tests/Jira/CSharp958Tests.cs @@ -13,14 +13,13 @@ * limitations under the License. */ -using System; -using System.Linq; using MongoDB.Bson; -using MongoDB.Driver; +using MongoDB.Bson.TestHelpers; using Xunit; namespace MongoDB.Driver.Tests.Jira { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp958Tests { private interface IPerson { } diff --git a/tests/MongoDB.Driver.Legacy.Tests/RegisterObjectSerializerCollection.cs b/tests/MongoDB.Driver.Legacy.Tests/RegisterObjectSerializerCollection.cs new file mode 100644 index 00000000000..bd4fcb327db --- /dev/null +++ b/tests/MongoDB.Driver.Legacy.Tests/RegisterObjectSerializerCollection.cs @@ -0,0 +1,25 @@ +/* Copyright 2010-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 MongoDB.Bson.TestHelpers; +using Xunit; + +namespace MongoDB.Driver.Legacy.Tests +{ + [CollectionDefinition(RegisterObjectSerializerFixture.CollectionName)] + public sealed class RegisterObjectSerializerCollection : ICollectionFixture + { + } +} diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp2113Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp2113Tests.cs index 957b371d13b..fbd12010793 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp2113Tests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3ImplementationTests/Jira/CSharp2113Tests.cs @@ -15,11 +15,13 @@ using System.Linq; using FluentAssertions; +using MongoDB.Bson.TestHelpers; using MongoDB.Driver.Linq; using Xunit; namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Jira { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class CSharp2113Tests : Linq3IntegrationTest { [Fact] diff --git a/tests/MongoDB.Driver.Tests/RegisterObjectSerializerCollection.cs b/tests/MongoDB.Driver.Tests/RegisterObjectSerializerCollection.cs new file mode 100644 index 00000000000..69d2852f291 --- /dev/null +++ b/tests/MongoDB.Driver.Tests/RegisterObjectSerializerCollection.cs @@ -0,0 +1,25 @@ +/* Copyright 2010-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 MongoDB.Bson.TestHelpers; +using Xunit; + +namespace MongoDB.Driver.Tests +{ + [CollectionDefinition(RegisterObjectSerializerFixture.CollectionName)] + public sealed class RegisterObjectSerializerCollection : ICollectionFixture + { + } +} diff --git a/tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs index d142e8ca17d..24d9773465d 100644 --- a/tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs @@ -16,17 +16,18 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using FluentAssertions; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; -using MongoDB.TestHelpers.XunitExtensions; +using MongoDB.Bson.TestHelpers; using MongoDB.Driver.Linq; +using MongoDB.TestHelpers.XunitExtensions; using Xunit; namespace MongoDB.Driver.Tests { + [Collection(RegisterObjectSerializerFixture.CollectionName)] public class UpdateDefinitionBuilderTests { [Fact]