diff --git a/iothub/device/src/ClientPropertyCollection.cs b/iothub/device/src/ClientPropertyCollection.cs index 17729ad2c0..f624c38c30 100644 --- a/iothub/device/src/ClientPropertyCollection.cs +++ b/iothub/device/src/ClientPropertyCollection.cs @@ -44,8 +44,16 @@ public void AddRootProperty(string propertyName, object propertyValue) /// The component with the property to add. /// The name of the property to add. /// The value of the property to add. + /// or is null. public void AddComponentProperty(string componentName, string propertyName, object propertyValue) - => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, false); + { + if (componentName == null) + { + throw new ArgumentNullException(nameof(componentName)); + } + + AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, false); + } /// /// @@ -55,8 +63,16 @@ public void AddComponentProperty(string componentName, string propertyName, obje /// The component with the properties to add. /// A collection of properties to add. /// A property name in already exists in the collection. + /// or a property name in is null. public void AddComponentProperties(string componentName, IDictionary properties) - => AddInternal(properties, componentName, true); + { + if (componentName == null) + { + throw new ArgumentNullException(nameof(componentName)); + } + + AddInternal(properties, componentName, false); + } /// /// @@ -100,9 +116,16 @@ public void AddOrUpdateRootProperty(string propertyName, object propertyValue) /// The component with the property to add or update. /// The name of the property to add or update. /// The value of the property to add or update. - /// is null. + /// or is null. public void AddOrUpdateComponentProperty(string componentName, string propertyName, object propertyValue) - => AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, true); + { + if (componentName == null) + { + throw new ArgumentNullException(nameof(componentName)); + } + + AddInternal(new Dictionary { { propertyName, propertyValue } }, componentName, true); + } /// /// @@ -116,8 +139,16 @@ public void AddOrUpdateComponentProperty(string componentName, string propertyNa /// /// The component with the properties to add or update. /// A collection of properties to add or update. + /// or a property name in is null. public void AddOrUpdateComponentProperties(string componentName, IDictionary properties) - => AddInternal(properties, componentName, true); + { + if (componentName == null) + { + throw new ArgumentNullException(nameof(componentName)); + } + + AddInternal(properties, componentName, true); + } /// /// @@ -133,9 +164,16 @@ public void AddOrUpdateComponentProperties(string componentName, IDictionaryA collection of properties to add or update. /// is null. public void AddOrUpdateRootProperties(IDictionary properties) - => properties + { + if (properties == null) + { + throw new ArgumentNullException(nameof(properties)); + } + + properties .ToList() .ForEach(entry => Collection[entry.Key] = entry.Value); + } /// /// Determines whether the specified property is present. @@ -426,14 +464,14 @@ internal static ClientPropertyCollection FromClientPropertiesAsDictionary(IDicti /// The component with the properties to add or update. /// Forces the collection to use the Add or Update behavior. /// Setting to true will simply overwrite the value. Setting to false will use - /// is null for a top-level property operation. + /// is null. private void AddInternal(IDictionary properties, string componentName = default, bool forceUpdate = false) { // If the componentName is null then simply add the key-value pair to Collection dictionary. // This will either insert a property or overwrite it if it already exists. if (componentName == null) { - // If both the component name and properties collection are null then throw a ArgumentNullException. + // If both the component name and properties collection are null then throw an ArgumentNullException. // This is not a valid use-case. if (properties == null) { @@ -442,6 +480,12 @@ private void AddInternal(IDictionary properties, string componen foreach (KeyValuePair entry in properties) { + // A null property key is not allowed. Throw an ArgumentNullException. + if (entry.Key == null) + { + throw new ArgumentNullException(nameof(entry.Key)); + } + if (forceUpdate) { Collection[entry.Key] = entry.Value; @@ -471,6 +515,12 @@ private void AddInternal(IDictionary properties, string componen foreach (KeyValuePair entry in properties) { + // A null property key is not allowed. Throw an ArgumentNullException. + if (entry.Key == null) + { + throw new ArgumentNullException(nameof(entry.Key)); + } + if (forceUpdate) { componentProperties[entry.Key] = entry.Value; diff --git a/iothub/device/src/TelemetryCollection.cs b/iothub/device/src/TelemetryCollection.cs index 99d0164aff..321bdbec7a 100644 --- a/iothub/device/src/TelemetryCollection.cs +++ b/iothub/device/src/TelemetryCollection.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Linq; namespace Microsoft.Azure.Devices.Client { @@ -32,5 +34,42 @@ public override void AddOrUpdate(string telemetryName, object telemetryValue) { base.AddOrUpdate(telemetryName, telemetryValue); } + + /// + /// Adds the telemetry values to the telemetry collection. + /// + /// + /// + /// An element with the same key already exists in the collection. + /// is null. + public void Add(IDictionary telemetryValues) + { + if (telemetryValues == null) + { + throw new ArgumentNullException(nameof(telemetryValues)); + } + + telemetryValues + .ToList() + .ForEach(entry => base.Add(entry.Key, entry.Value)); + } + + /// + /// Adds or updates the telemetry values in the telemetry collection. + /// + /// + /// + /// is null. + public void AddOrUpdate(IDictionary telemetryValues) + { + if (telemetryValues == null) + { + throw new ArgumentNullException(nameof(telemetryValues)); + } + + telemetryValues + .ToList() + .ForEach(entry => base.AddOrUpdate(entry.Key, entry.Value)); + } } } diff --git a/iothub/device/tests/ClientPropertyCollectionTests.cs b/iothub/device/tests/ClientPropertyCollectionTests.cs index 48e3cc6e95..bd0d1dddd4 100644 --- a/iothub/device/tests/ClientPropertyCollectionTests.cs +++ b/iothub/device/tests/ClientPropertyCollectionTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Microsoft.Azure.Devices.Shared; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; namespace Microsoft.Azure.Devices.Client.Test { @@ -427,6 +428,405 @@ public void ClientPropertyCollection_TryGetValueWithComponentShouldReturnFalseIf isValueRetrieved.Should().BeFalse(); propertyValue.Should().Be(default); } + + [TestMethod] + public void ClientPropertyCollection_AddNullPropertyNameThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddRootProperty(null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullPropertyNameThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddOrUpdateRootProperty(null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullPropertyValueSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove property "abc" from its properties. + testPropertyCollection.AddRootProperty("abc", null); + + // assert + bool isValueRetrieved = testPropertyCollection.TryGetValue("abc", out object propertyValue); + isValueRetrieved.Should().BeTrue(); + propertyValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullPropertyValueSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove property "abc" from its properties. + testPropertyCollection.AddOrUpdateRootProperty("abc", null); + + // assert + bool isValueRetrieved = testPropertyCollection.TryGetValue("abc", out object propertyValue); + isValueRetrieved.Should().BeTrue(); + propertyValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddPropertyValueAlreadyExistsThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddRootProperty("abc", 123); + + // act + Action testAction = () => testPropertyCollection.AddRootProperty("abc", 1); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdatePropertyValueAlreadyExistsSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddRootProperty("abc", 123); + + // act + testPropertyCollection.AddOrUpdateRootProperty("abc", 1); + + // assert + bool isValueRetrieved = testPropertyCollection.TryGetValue("abc", out int propertyValue); + isValueRetrieved.Should().BeTrue(); + propertyValue.Should().Be(1); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullClientPropertyCollectionThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddRootProperties(null); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullClientPropertyCollectionThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddOrUpdateRootProperties(null); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddClientPropertyCollectionAlreadyExistsThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddRootProperty("abc", 123); + var propertyValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + Action testAction = () => testPropertyCollection.AddRootProperties(propertyValues); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateClientPropertyCollectionAlreadyExistsSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddRootProperty("abc", 123); + var propertyValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + testPropertyCollection.AddOrUpdateRootProperties(propertyValues); + + // assert + bool isValue1Retrieved = testPropertyCollection.TryGetValue("qwe", out int value1Retrieved); + isValue1Retrieved.Should().BeTrue(); + value1Retrieved.Should().Be(98); + + bool isValue2Retrieved = testPropertyCollection.TryGetValue("abc", out int value2Retrieved); + isValue2Retrieved.Should().BeTrue(); + value2Retrieved.Should().Be(2); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullPropertyNameWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddComponentProperty("testComponent", null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullPropertyNameWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddOrUpdateComponentProperty("testComponent", null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullComponentNameThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddComponentProperty(null, "abc", 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullComponentNameThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + + // act + Action testAction = () => testPropertyCollection.AddOrUpdateComponentProperty(null, "abc", 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullPropertyValueWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "qwe", 123); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove property "abc" from its properties. + testPropertyCollection.AddComponentProperty("testComponent", "abc", null); + + // assert + bool isValue1Retrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int property1Value); + isValue1Retrieved.Should().BeTrue(); + property1Value.Should().Be(123); + + bool isValue2Retrieved = testPropertyCollection.TryGetValue("testComponent", "abc", out object property2Value); + isValue2Retrieved.Should().BeTrue(); + property2Value.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullPropertyValueWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "qwe", 123); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove property "abc" from its properties. + testPropertyCollection.AddOrUpdateComponentProperty("testComponent", "abc", null); + + // assert + bool isValue1Retrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int property1Value); + isValue1Retrieved.Should().BeTrue(); + property1Value.Should().Be(123); + + bool isValue2Retrieved = testPropertyCollection.TryGetValue("testComponent", "abc", out object property2Value); + isValue2Retrieved.Should().BeTrue(); + property2Value.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddNullClientPropertyCollectionWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.Convention = DefaultPayloadConvention.Instance; + testPropertyCollection.AddComponentProperty("testComponent", "qwe", 98); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove component "testComponent" from its properties. + testPropertyCollection.AddComponentProperties("testComponent", null); + + // assert + bool iscomponentValueRetrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int property2Value); + iscomponentValueRetrieved.Should().BeFalse(); + + bool iscomponentRetrieved = testPropertyCollection.TryGetValue("testComponent", out object componentValue); + iscomponentRetrieved.Should().BeTrue(); + componentValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateNullClientPropertyCollectionWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.Convention = DefaultPayloadConvention.Instance; + testPropertyCollection.AddComponentProperty("testComponent", "qwe", 98); + + // act + // This should add an entry in the dictionary with a null value. + // This patch would be interpreted by the service as the client wanting to remove component "testComponent" from its properties. + testPropertyCollection.AddOrUpdateComponentProperties("testComponent", null); + + // assert + bool iscomponentValueRetrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int property2Value); + iscomponentValueRetrieved.Should().BeFalse(); + + bool iscomponentRetrieved = testPropertyCollection.TryGetValue("testComponent", out object componentValue); + iscomponentRetrieved.Should().BeTrue(); + componentValue.Should().BeNull(); + } + + [TestMethod] + public void ClientPropertyCollection_AddPropertyValueAlreadyExistsWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "abc", 123); + + // act + Action testAction = () => testPropertyCollection.AddComponentProperty("testComponent", "abc", 1); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdatePropertyValueAlreadyExistsWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "abc", 123); + + // act + testPropertyCollection.AddOrUpdateComponentProperty("testComponent", "abc", 1); + + // assert + bool isValueRetrieved = testPropertyCollection.TryGetValue("testComponent", "abc", out int propertyValue); + isValueRetrieved.Should().BeTrue(); + propertyValue.Should().Be(1); + } + + [TestMethod] + public void ClientPropertyCollection_AddClientPropertyCollectionAlreadyExistsWithComponentThrows() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "abc", 123); + var propertyValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + Action testAction = () => testPropertyCollection.AddComponentProperties("testComponent", propertyValues); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void ClientPropertyCollection_AddOrUpdateClientPropertyCollectionAlreadyExistsWithComponentSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + testPropertyCollection.AddComponentProperty("testComponent", "abc", 123); + var propertyValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + testPropertyCollection.AddOrUpdateComponentProperties("testComponent", propertyValues); + + // assert + bool isValue1Retrieved = testPropertyCollection.TryGetValue("testComponent", "qwe", out int value1Retrieved); + isValue1Retrieved.Should().BeTrue(); + value1Retrieved.Should().Be(98); + + bool isValue2Retrieved = testPropertyCollection.TryGetValue("testComponent", "abc", out int value2Retrieved); + isValue2Retrieved.Should().BeTrue(); + value2Retrieved.Should().Be(2); + } + + [TestMethod] + public void ClientPropertyCollect_AddRawClassSuccess() + { + // arrange + var testPropertyCollection = new ClientPropertyCollection(); + var propertyValues = new CustomClientProperty + { + Id = 12, + Name = "testProperty" + }; + var propertyValuesAsDictionary = JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(propertyValues)); + + // act + testPropertyCollection.AddRootProperties(propertyValuesAsDictionary); + + // assert + bool isIdPresent = testPropertyCollection.TryGetValue("Id", out int id); + isIdPresent.Should().BeTrue(); + id.Should().Be(12); + + bool isNamePresent = testPropertyCollection.TryGetValue("Name", out string name); + isNamePresent.Should().BeTrue(); + name.Should().Be("testProperty"); + } } internal class CustomClientProperty diff --git a/iothub/device/tests/TelemetryCollectionTests.cs b/iothub/device/tests/TelemetryCollectionTests.cs new file mode 100644 index 0000000000..c27f864fcb --- /dev/null +++ b/iothub/device/tests/TelemetryCollectionTests.cs @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Text; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.Azure.Devices.Client.Test +{ + [TestClass] + [TestCategory("Unit")] + public class TelemetryCollectionTests + { + [TestMethod] + public void AddNullTelemetryNameThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + Action testAction = () => testTelemetryCollection.Add(null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddOrUpdateNullTelemetryNameThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + Action testAction = () => testTelemetryCollection.AddOrUpdate(null, 123); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddNullTelemetryValueSuccess() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + testTelemetryCollection.Add("abc", null); + + // assert + testTelemetryCollection["abc"].Should().BeNull(); + } + + [TestMethod] + public void AddOrUpdateNullTelemetryValueSuccess() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + testTelemetryCollection.AddOrUpdate("abc", null); + + // assert + testTelemetryCollection["abc"].Should().BeNull(); + } + + [TestMethod] + public void AddTelemetryValueAlreadyExistsThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + testTelemetryCollection.Add("abc", 123); + + // act + Action testAction = () => testTelemetryCollection.Add("abc", 1); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddOrUpdateTelemetryValueAlreadyExistsSuccess() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + testTelemetryCollection.Add("abc", 123); + + // act + testTelemetryCollection.AddOrUpdate("abc", 1); + + // assert + testTelemetryCollection["abc"].Should().Be(1); + } + + [TestMethod] + public void AddNullTelemetryCollectionThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + Action testAction = () => testTelemetryCollection.Add(null); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddOrUpdateNullTelemetryCollectionThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + + // act + Action testAction = () => testTelemetryCollection.AddOrUpdate(null); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddTelemetryCollectionAlreadyExistsThrows() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + testTelemetryCollection.AddOrUpdate("abc", 123); + var telemetryValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + Action testAction = () => testTelemetryCollection.Add(telemetryValues); + + // assert + testAction.Should().Throw(); + } + + [TestMethod] + public void AddOrUpdateTelemetryCollectionAlreadyExistsSuccess() + { + // arrange + var testTelemetryCollection = new TelemetryCollection(); + testTelemetryCollection.AddOrUpdate("abc", 123); + var telemetryValues = new Dictionary + { + { "qwe", 98 }, + { "abc", 2 }, + }; + + // act + testTelemetryCollection.AddOrUpdate(telemetryValues); + + // assert + testTelemetryCollection["qwe"].Should().Be(98); + testTelemetryCollection["abc"].Should().Be(2); + } + } +}