From 22fb8659ef3c96fe7f9ced8dfcb2ac2147b71530 Mon Sep 17 00:00:00 2001 From: Abhipsa Misra Date: Mon, 19 Jul 2021 17:05:05 -0700 Subject: [PATCH] * fix(iot-device): TryGetValue methods should check for the component identifier when applicable and should not throw any exceptions (#2111) --- iothub/device/src/ClientPropertyCollection.cs | 74 ++++++--- iothub/device/src/PayloadCollection.cs | 32 ++-- .../tests/ClientPropertyCollectionTests.cs | 149 ++++++++++++++++-- ...ClientPropertyCollectionTestsNewtonsoft.cs | 85 ++++++++++ 4 files changed, 298 insertions(+), 42 deletions(-) diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index e23246d6a9..214889e2ac 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -170,8 +170,9 @@ public bool Contains(string componentName, string propertyName) /// The type to cast the object to. /// The component which holds the required property. /// The property to get. - /// The value of the component-level property. - /// true if the property collection contains a component level property with the specified key; otherwise, false. + /// When this method returns true, this contains the value of the component-level property. + /// When this method returns false, this contains the default value of the type T passed in. + /// True if a component-level property of type T with the specified key was found; otherwise, it returns false. public virtual bool TryGetValue(string componentName, string propertyName, out T propertyValue) { if (Logging.IsEnabled && Convention == null) @@ -180,35 +181,72 @@ public virtual bool TryGetValue(string componentName, string propertyName, ou $"TryGetValue will attempt to get the property value but may not behave as expected.", nameof(TryGetValue)); } + // If either the component name or the property name is null, empty or whitespace, + // then return false with the default value of the type passed in. + if (string.IsNullOrWhiteSpace(componentName) || string.IsNullOrWhiteSpace(propertyName)) + { + propertyValue = default; + return false; + } + if (Contains(componentName, propertyName)) { object componentProperties = Collection[componentName]; + // If the ClientPropertyCollection was constructed by the user application (eg. for updating the client properties) + // then the componentProperties are retrieved as a dictionary. + // The required property value can be fetched from the dictionary directly. if (componentProperties is IDictionary nestedDictionary) { - if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement)) + // First verify that the retrieved dictionary contains the component identifier { "__t": "c" }. + // If not, then the retrieved nested dictionary is actually a root-level property of type map. + if (nestedDictionary.TryGetValue(ConventionBasedConstants.ComponentIdentifierKey, out object componentIdentifierValue) + && componentIdentifierValue.ToString() == ConventionBasedConstants.ComponentIdentifierValue) { - // If the value is null, go ahead and return it. - if (dictionaryElement == null) - { - propertyValue = default; - return true; - } - - // If the object is of type T or can be cast to type T, go ahead and return it. - if (dictionaryElement is T valueRef - || NumericHelpers.TryCastNumericTo(dictionaryElement, out valueRef)) + if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement)) { - propertyValue = valueRef; - return true; + // If the value associated with the key is null, then return true with the default value of the type passed in. + if (dictionaryElement == null) + { + propertyValue = default; + return true; + } + + // If the object is of type T or can be cast to type T, go ahead and return it. + if (dictionaryElement is T valueRef + || NumericHelpers.TryCastNumericTo(dictionaryElement, out valueRef)) + { + propertyValue = valueRef; + return true; + } } } } else { - // If it's not, we need to try to convert it using the serializer. - Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); - return true; + // If the ClientPropertyCollection was constructed by the SDK (eg. when retrieving the client properties) + // then the componentProperties are retrieved as the json object that is defined in the PayloadConvention. + // The required property value then needs to be deserialized accordingly. + try + { + // First verify that the retrieved dictionary contains the component identifier { "__t": "c" }. + // If not, then the retrieved nested dictionary is actually a root-level property of type map. + if (Convention + .PayloadSerializer + .TryGetNestedObjectValue(componentProperties, ConventionBasedConstants.ComponentIdentifierKey, out string componentIdentifierValue) + && componentIdentifierValue == ConventionBasedConstants.ComponentIdentifierValue) + { + // Since the value cannot be cast to directly, we need to try to convert it using the serializer. + // If it can be successfully converted, go ahead and return it. + Convention.PayloadSerializer.TryGetNestedObjectValue(componentProperties, propertyName, out propertyValue); + return true; + } + } + catch + { + // In case the value cannot be converted using the serializer, + // then return false with the default value of the type passed in. + } } } diff --git a/iothub/device/src/PayloadCollection.cs b/iothub/device/src/PayloadCollection.cs index 1943264358..a6ab45581a 100644 --- a/iothub/device/src/PayloadCollection.cs +++ b/iothub/device/src/PayloadCollection.cs @@ -107,13 +107,11 @@ public bool Contains(string key) /// /// Gets the value of the object from the collection. /// - /// - /// This class is used for both sending and receiving properties for the device. - /// /// The type to cast the object to. /// The key of the property to get. - /// The value of the object from the collection. - /// True if the collection contains an element with the specified key; otherwise, it returns false. + /// When this method returns true, this contains the value of the object from the collection. + /// When this method returns false, this contains the default value of the type T passed in. + /// True if a value of type T with the specified key was found; otherwise, it returns false. public bool TryGetValue(string key, out T value) { if (Logging.IsEnabled && Convention == null) @@ -122,9 +120,16 @@ public bool TryGetValue(string key, out T value) $"TryGetValue will attempt to get the property value but may not behave as expected.", nameof(TryGetValue)); } + // If the key is null, empty or whitespace, then return false with the default value of the type passed in. + if (string.IsNullOrWhiteSpace(key)) + { + value = default; + return false; + } + if (Collection.ContainsKey(key)) { - // If the value is null, go ahead and return it. + // If the value associated with the key is null, then return true with the default value of the type passed in. if (Collection[key] == null) { value = default; @@ -139,9 +144,18 @@ public bool TryGetValue(string key, out T value) return true; } - // If it's not, we need to try to convert it using the serializer. - value = Convention.PayloadSerializer.ConvertFromObject(Collection[key]); - return true; + try + { + // If the value cannot be cast to directly, we need to try to convert it using the serializer. + // If it can be successfully converted, go ahead and return it. + value = Convention.PayloadSerializer.ConvertFromObject(Collection[key]); + return true; + } + catch + { + // In case the value cannot be converted using the serializer, + // then return false with the default value of the type passed in. + } } value = default; diff --git a/iothub/device/tests/ClientPropertyCollectionTests.cs b/iothub/device/tests/ClientPropertyCollectionTests.cs index f9ea6caa2e..125b792bd2 100644 --- a/iothub/device/tests/ClientPropertyCollectionTests.cs +++ b/iothub/device/tests/ClientPropertyCollectionTests.cs @@ -56,6 +56,7 @@ public class ClientPropertyCollectionTests [TestMethod] public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection { { StringPropertyName, StringPropertyValue }, @@ -70,6 +71,8 @@ public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceC { DateTimePropertyName, s_dateTimePropertyValue } }; + // act, assert + clientProperties.TryGetValue(StringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); @@ -107,26 +110,35 @@ public void ClientPropertyCollection_CanAddSimpleObjectsAndGetBackWithoutDeviceC [TestMethod] public void ClientPropertyCollection_AddSimpleObjectAgainThrowsException() { + // arrange var clientProperties = new ClientPropertyCollection { { StringPropertyName, StringPropertyValue } }; + // act Action act = () => clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + + // assert act.Should().Throw("\"Add\" method does not support adding a key that already exists in the collection."); } [TestMethod] public void ClientPropertyCollection_CanUpdateSimpleObjectAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection { { StringPropertyName, StringPropertyValue } }; + + // act, assert + clientProperties.TryGetValue(StringPropertyName, out string outValue); outValue.Should().Be(StringPropertyValue); clientProperties.AddOrUpdateRootProperty(StringPropertyName, UpdatedPropertyValue); + clientProperties.TryGetValue(StringPropertyName, out string outValueChanged); outValueChanged.Should().Be(UpdatedPropertyValue, "\"AddOrUpdate\" should overwrite the value if the key already exists in the collection."); } @@ -134,10 +146,12 @@ public void ClientPropertyCollection_CanUpdateSimpleObjectAndGetBackWithoutDevic [TestMethod] public void ClientPropertyCollection_CanAddNullPropertyAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); clientProperties.AddRootProperty(IntPropertyName, null); + // act, assert clientProperties.TryGetValue(StringPropertyName, out string outStringValue); outStringValue.Should().Be(StringPropertyValue); @@ -149,10 +163,13 @@ public void ClientPropertyCollection_CanAddNullPropertyAndGetBackWithoutDeviceCl [TestMethod] public void ClientPropertyCollection_CanAddMultiplePropertyAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); clientProperties.AddRootProperty(IntPropertyName, IntPropertyValue); + // act, assert + clientProperties.TryGetValue(StringPropertyName, out string outStringValue); outStringValue.Should().Be(StringPropertyValue); @@ -160,24 +177,57 @@ public void ClientPropertyCollection_CanAddMultiplePropertyAndGetBackWithoutDevi outIntValue.Should().Be(IntPropertyValue); } + [TestMethod] + public void ClientPropertyCollection_TryGetValueShouldReturnFalseIfValueNotFound() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(IntPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollection_TryGetValueShouldReturnFalseIfValueCouldNotBeDeserialized() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddRootProperty(StringPropertyName, StringPropertyValue); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(StringPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + [TestMethod] public void ClientPropertyCollection_CanAddSimpleObjectWithComponentAndGetBackWithoutDeviceClient() { - var clientProperties = new ClientPropertyCollection + // arrange + var componentLevelProperties = new Dictionary { - { ComponentName, new Dictionary { - { StringPropertyName, StringPropertyValue }, - { BoolPropertyName, BoolPropertyValue }, - { DoublePropertyName, DoublePropertyValue }, - { FloatPropertyName, FloatPropertyValue }, - { IntPropertyName, IntPropertyValue }, - { ShortPropertyName, ShortPropertyValue }, - { ObjectPropertyName, s_objectPropertyValue }, - { ArrayPropertyName, s_arrayPropertyValue }, - { MapPropertyName, s_mapPropertyValue }, - { DateTimePropertyName, s_dateTimePropertyValue } } - } + { StringPropertyName, StringPropertyValue }, + { BoolPropertyName, BoolPropertyValue }, + { DoublePropertyName, DoublePropertyValue }, + { FloatPropertyName, FloatPropertyValue }, + { IntPropertyName, IntPropertyValue }, + { ShortPropertyName, ShortPropertyValue }, + { ObjectPropertyName, s_objectPropertyValue }, + { ArrayPropertyName, s_arrayPropertyValue }, + { MapPropertyName, s_mapPropertyValue }, + { DateTimePropertyName, s_dateTimePropertyValue } }; + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperties(ComponentName, componentLevelProperties); + + // act, assert clientProperties.TryGetValue(ComponentName, StringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); @@ -216,19 +266,26 @@ public void ClientPropertyCollection_CanAddSimpleObjectWithComponentAndGetBackWi [TestMethod] public void ClientPropertyCollection_AddSimpleObjectWithComponentAgainThrowsException() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + // act Action act = () => clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + // assert act.Should().Throw("\"Add\" method does not support adding a key that already exists in the collection."); } [TestMethod] public void ClientPropertyCollection_CanUpdateSimpleObjectWithComponentAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + // act, assert + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); outValue.Should().Be(StringPropertyValue); @@ -240,10 +297,13 @@ public void ClientPropertyCollection_CanUpdateSimpleObjectWithComponentAndGetBac [TestMethod] public void ClientPropertyCollection_CanAddNullPropertyWithComponentAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); clientProperties.AddComponentProperty(ComponentName, IntPropertyName, null); + // act, assert + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outStringValue); outStringValue.Should().Be(StringPropertyValue); @@ -255,10 +315,13 @@ public void ClientPropertyCollection_CanAddNullPropertyWithComponentAndGetBackWi [TestMethod] public void ClientPropertyCollection_CanAddMultiplePropertyWithComponentAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); clientProperties.AddComponentProperty(ComponentName, IntPropertyName, IntPropertyValue); + // act, assert + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outStringValue); outStringValue.Should().Be(StringPropertyValue); @@ -269,12 +332,15 @@ public void ClientPropertyCollection_CanAddMultiplePropertyWithComponentAndGetBa [TestMethod] public void ClientPropertyCollection_CanAddSimpleWritablePropertyAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); - var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, CommonClientResponseCodes.OK, 2, WritablePropertyDescription); clientProperties.AddRootProperty(StringPropertyName, writableResponse); + // act clientProperties.TryGetValue(StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + + // assert outValue.Value.Should().Be(writableResponse.Value); outValue.AckCode.Should().Be(writableResponse.AckCode); outValue.AckVersion.Should().Be(writableResponse.AckVersion); @@ -284,12 +350,15 @@ public void ClientPropertyCollection_CanAddSimpleWritablePropertyAndGetBackWitho [TestMethod] public void ClientPropertyCollection_CanAddWritablePropertyWithComponentAndGetBackWithoutDeviceClient() { + // arrange var clientProperties = new ClientPropertyCollection(); - var writableResponse = new NewtonsoftJsonWritablePropertyResponse(StringPropertyValue, CommonClientResponseCodes.OK, 2, WritablePropertyDescription); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, writableResponse); + // act clientProperties.TryGetValue(ComponentName, StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + + // assert outValue.Value.Should().Be(writableResponse.Value); outValue.AckCode.Should().Be(writableResponse.AckCode); outValue.AckVersion.Should().Be(writableResponse.AckVersion); @@ -299,15 +368,65 @@ public void ClientPropertyCollection_CanAddWritablePropertyWithComponentAndGetBa [TestMethod] public void ClientPropertyCollection_AddingComponentAddsComponentIdentifier() { + // arrange var clientProperties = new ClientPropertyCollection(); clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + // act clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); clientProperties.TryGetValue(ComponentName, ConventionBasedConstants.ComponentIdentifierKey, out string componentOut); + // assert outValue.Should().Be(StringPropertyValue); componentOut.Should().Be(ConventionBasedConstants.ComponentIdentifierValue); } + + [TestMethod] + public void ClientPropertyCollection_TryGetValueWithComponentShouldReturnFalseIfValueNotFound() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, IntPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollection_TryGetValueWithComponentShouldReturnFalseIfValueCouldNotBeDeserialized() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddComponentProperty(ComponentName, StringPropertyName, StringPropertyValue); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, StringPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollection_TryGetValueWithComponentShouldReturnFalseIfNotAComponent() + { + // arrange + var clientProperties = new ClientPropertyCollection(); + clientProperties.AddRootProperty(MapPropertyName, s_mapPropertyValue); + string incorrectlyMappedComponentName = MapPropertyName; + string incorrectlyMappedComponentPropertyName = "key1"; + + // act + bool isValueRetrieved = clientProperties.TryGetValue(incorrectlyMappedComponentName, incorrectlyMappedComponentPropertyName, out object propertyValue); + + // assert + isValueRetrieved.Should().BeFalse(); + propertyValue.Should().Be(default); + } } internal class CustomClientProperty diff --git a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs index 07a820febe..7e12e2c9b8 100644 --- a/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs +++ b/iothub/device/tests/ClientPropertyCollectionTestsNewtonsoft.cs @@ -121,8 +121,11 @@ public class ClientPropertyCollectionTestsNewtonsoft [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanGetValue() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + // act, assert + clientProperties.TryGetValue(StringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); @@ -162,8 +165,11 @@ public void ClientPropertyCollectionNewtonsoft_CanGetValue() [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanGetValueWithComponent() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + // act, assert + clientProperties.TryGetValue(ComponentName, StringPropertyName, out string stringOutValue); stringOutValue.Should().Be(StringPropertyValue); @@ -203,9 +209,13 @@ public void ClientPropertyCollectionNewtonsoft_CanGetValueWithComponent() [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanAddSimpleWritablePropertyAndGetBack() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWritablePropertyToRoundTrip, DefaultPayloadConvention.Instance); + // act clientProperties.TryGetValue(StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + + // assert outValue.Value.Should().Be(StringPropertyValue); outValue.AckCode.Should().Be(s_writablePropertyResponse.AckCode); outValue.AckVersion.Should().Be(s_writablePropertyResponse.AckVersion); @@ -215,9 +225,13 @@ public void ClientPropertyCollectionNewtonsoft_CanAddSimpleWritablePropertyAndGe [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanAddWritablePropertyWithComponentAndGetBack() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWritablePropertyWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + // act clientProperties.TryGetValue(ComponentName, StringPropertyName, out NewtonsoftJsonWritablePropertyResponse outValue); + + // assert outValue.Value.Should().Be(StringPropertyValue); outValue.AckCode.Should().Be(s_writablePropertyResponse.AckCode); outValue.AckVersion.Should().Be(s_writablePropertyResponse.AckVersion); @@ -227,14 +241,85 @@ public void ClientPropertyCollectionNewtonsoft_CanAddWritablePropertyWithCompone [TestMethod] public void ClientPropertyCollectionNewtonsoft_CanGetComponentIdentifier() { + // arrange var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + // act clientProperties.TryGetValue(ComponentName, StringPropertyName, out string outValue); clientProperties.TryGetValue(ComponentName, ConventionBasedConstants.ComponentIdentifierKey, out string componentOut); + // assert outValue.Should().Be(StringPropertyValue); componentOut.Should().Be(ConventionBasedConstants.ComponentIdentifierValue); } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueShouldReturnFalseIfValueNotFound() + { + // arrange + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + + // act + bool isValueRetrieved = clientProperties.TryGetValue("thisPropertyDoesNotExist", out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfValueNotFound() + { + // arrange + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, "thisPropertyDoesNotExist", out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueShouldReturnFalseIfValueCouldNotBeDeserialized() + { + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + + bool isValueRetrieved = clientProperties.TryGetValue(StringPropertyName, out int outIntValue); + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfValueCouldNotBeDeserialized() + { + // arrange + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionWithComponentToRoundTrip, DefaultPayloadConvention.Instance); + + // act + bool isValueRetrieved = clientProperties.TryGetValue(ComponentName, StringPropertyName, out int outIntValue); + + // assert + isValueRetrieved.Should().BeFalse(); + outIntValue.Should().Be(default); + } + + [TestMethod] + public void ClientPropertyCollectionNewtonSoft_TryGetValueWithComponentShouldReturnFalseIfNotAComponent() + { + // arrange + var clientProperties = ClientPropertyCollection.FromTwinCollection(collectionToRoundTrip, DefaultPayloadConvention.Instance); + string incorrectlyMappedComponentName = MapPropertyName; + string incorrectlyMappedComponentPropertyName = "key1"; + + // act + bool isValueRetrieved = clientProperties.TryGetValue(incorrectlyMappedComponentName, incorrectlyMappedComponentPropertyName, out object propertyValue); + + // assert + isValueRetrieved.Should().BeFalse(); + propertyValue.Should().Be(default); + } } internal class RootLevelProperties