diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs index 735b41e1f..14493a4aa 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs @@ -12,10 +12,8 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 using Azure.Data.Tables; using Models.v10; using AzureIoTHub.Portal.Server.Controllers.V10; - using AzureIoTHub.Portal.Server.Entities; using AzureIoTHub.Portal.Server.Exceptions; using AzureIoTHub.Portal.Server.Factories; - using AzureIoTHub.Portal.Server.Helpers; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; @@ -42,6 +40,7 @@ public class DevicesControllerTests private Mock mockUrlHelper; private Mock> mockLogger; private Mock mockDeviceService; + private Mock mockDevicePropertyService; private Mock mockDeviceTagService; private Mock> mockDeviceTwinMapper; private Mock mockTableClientFactory; @@ -54,6 +53,7 @@ public void SetUp() this.mockProvisioningServiceManager = this.mockRepository.Create(); this.mockLogger = this.mockRepository.Create>(); this.mockDeviceService = this.mockRepository.Create(); + this.mockDevicePropertyService = this.mockRepository.Create(); this.mockDeviceTagService = this.mockRepository.Create(); this.mockDeviceTwinMapper = this.mockRepository.Create>(); this.mockTableClientFactory = this.mockRepository.Create(); @@ -68,7 +68,8 @@ private DevicesController CreateDevicesController() this.mockDeviceTagService.Object, this.mockProvisioningServiceManager.Object, this.mockDeviceTwinMapper.Object, - this.mockTableClientFactory.Object) + this.mockTableClientFactory.Object, + this.mockDevicePropertyService.Object) { Url = this.mockUrlHelper.Object }; @@ -395,413 +396,81 @@ public async Task WhenDeviceNotExistGetEnrollmentCredentialsShouldReturnNotFound } [Test] - public async Task WhenDeviceNotExistGetPropertiesShouldReturnNotFound() - { - // Arrange - var devicesController = CreateDevicesController(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync((Twin)null); - - // Act - var response = await devicesController.GetProperties("aaa"); - - // Assert - Assert.IsNotNull(response); - Assert.IsAssignableFrom(response.Result); - - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task WhenDeviceDoesntHaveModelIdNotExistGetPropertiesShouldReturnBadRequest() - { - // Arrange - var devicesController = CreateDevicesController(); - var twin = new Twin(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync(twin); - - // Act - var response = await devicesController.GetProperties("aaa"); - - // Assert - Assert.IsNotNull(response); - Assert.IsAssignableFrom(response.Result); - - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task WhenDeviceNotExistSetPropertiesShouldReturnNotFound() - { - // Arrange - var devicesController = CreateDevicesController(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync((Twin)null); - - // Act - var response = await devicesController.SetProperties("aaa", null); - - // Assert - Assert.IsNotNull(response); - Assert.IsAssignableFrom(response.Result); - - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task WhenDeviceDoesntHaveModelIdNotExistSetPropertiesShouldReturnBadRequest() - { - // Arrange - var devicesController = CreateDevicesController(); - var twin = new Twin(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync(twin); - - // Act - var response = await devicesController.SetProperties("aaa", null); - - // Assert - Assert.IsNotNull(response); - Assert.IsAssignableFrom(response.Result); - - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task GetPropertiesShouldReturnDeviceProperties() - { - // Arrange - var devicesController = CreateDevicesController(); - var twin = new Twin(); - - DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - DeviceHelper.SetDesiredProperty(twin, "writable", "ccc"); - - var mockTableClient = this.mockRepository.Create(); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync(twin); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] - { - Page.FromValues(new[] - { - new DeviceModelProperty - { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", - IsWritable = true, - Name = "writable", - }, - new DeviceModelProperty - { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", - IsWritable = false, - Name = "notwritable", - } - }, null, mockResponse.Object) - })); - - // Act - var response = await devicesController.GetProperties("aaa"); - - // Assert - Assert.IsNotNull(response); - Assert.IsNotNull(response.Value); - - Assert.AreEqual(2, response.Value.Count()); - Assert.AreEqual("ccc", response.Value.Single(x => x.Name == "writable").Value); - Assert.IsNull(response.Value.Single(x => x.Name == "notwritable").Value); - - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task GetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursOnGettingProperties() + public async Task CreateDeviceAsyncDuplicatedDeviceIdShouldThrowInternalServerErrorException() { // Arrange var devicesController = CreateDevicesController(); - var twin = new Twin(); - - DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - DeviceHelper.SetDesiredProperty(twin, "writable", "ccc"); - - var mockTableClient = this.mockRepository.Create(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync(twin); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new RequestFailedException("test")); + _ = this.mockDeviceService.Setup(c => c.GetDevice(It.IsAny())) + .ReturnsAsync(new Device()); // Act - var act = () => devicesController.GetProperties("aaa"); + var result = async () => await devicesController.CreateDeviceAsync(new DeviceDetails()); // Assert - _ = await act.Should().ThrowAsync(); + _ = await result.Should().ThrowAsync(); this.mockRepository.VerifyAll(); } [Test] - public async Task SetPropertiesShouldUpdateDesiredProperties() + public async Task GetPropertiesShouldReturnDevicePropertyValues() { // Arrange var devicesController = CreateDevicesController(); - var twin = new Twin(); - DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - - var mockTableClient = this.mockRepository.Create(); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync(twin); - - _ = this.mockDeviceService.Setup(c => c.UpdateDeviceTwin( - It.Is(c => c == twin))) - .ReturnsAsync(twin); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] - { - Page.FromValues(new[] - { - new DeviceModelProperty - { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", - IsWritable = true, - Name = "writable", - } - }, null, mockResponse.Object) - })); + var deviceId = Guid.NewGuid().ToString(); - // Act - var response = await devicesController.SetProperties("aaa", new[] + var expectedDevicePropertyValues = new List { - new DevicePropertyValue + new() { - Name = "writable", - Value = "ccc" + Name = Guid.NewGuid().ToString() } - }); - - // Assert - Assert.IsNotNull(response); - Assert.IsAssignableFrom(response.Result); - - Assert.AreEqual("ccc", twin.Properties.Desired["writable"].ToString()); - - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursOnGettingProperties() - { - // Arrange - var devicesController = CreateDevicesController(); - var twin = new Twin(); - - DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - - var mockTableClient = this.mockRepository.Create(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync(twin); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); + }; - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new RequestFailedException("test")); + _ = this.mockDevicePropertyService.Setup(c => c.GetProperties(deviceId)) + .ReturnsAsync(expectedDevicePropertyValues); // Act - var act = () => devicesController.SetProperties("aaa", new[] - { - new DevicePropertyValue - { - Name = "writable", - Value = "ccc" - } - }); + var result = await devicesController.GetProperties(deviceId); // Assert - _ = await act.Should().ThrowAsync(); + _ = result.Count().Should().Be(1); + _ = result.Should().BeEquivalentTo(expectedDevicePropertyValues); + this.mockRepository.VerifyAll(); } [Test] - public async Task WhenPropertyNotWrittableSetPropertiesShouldNotUpdateDesiredProperty() + public async Task SetPropertiesShouldSetDevicePropertyValues() { // Arrange var devicesController = CreateDevicesController(); - var twin = new Twin(); - - DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - - var mockTableClient = this.mockRepository.Create(); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync(twin); - - _ = this.mockDeviceService.Setup(c => c.UpdateDeviceTwin( - It.Is(c => c == twin))) - .ReturnsAsync(twin); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] - { - Page.FromValues(new[] - { - new DeviceModelProperty - { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", - IsWritable = false, - Name = "notwritable", - } - }, null, mockResponse.Object) - })); + var deviceId = Guid.NewGuid().ToString(); - // Act - var response = await devicesController.SetProperties("aaa", new[] + var devicePropertyValues = new List { - new DevicePropertyValue + new() { - Name = "notwritable", - Value = "ccc" + Name = Guid.NewGuid().ToString() } - }); - - // Assert - Assert.IsNotNull(response); - Assert.IsAssignableFrom(response.Result); - - Assert.IsFalse(twin.Properties.Desired.Contains("notwritable")); - - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task WhenPropertyNotInModelSetPropertiesShouldNotUpdateDesiredProperty() - { - // Arrange - var devicesController = CreateDevicesController(); - var twin = new Twin(); - - DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - - var mockTableClient = this.mockRepository.Create(); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) - .ReturnsAsync(twin); - - _ = this.mockDeviceService.Setup(c => c.UpdateDeviceTwin( - It.Is(c => c == twin))) - .ReturnsAsync(twin); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); + }; - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] - { - Page.FromValues(new[] - { - new DeviceModelProperty - { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", - IsWritable = true, - Name = "writable", - } - }, null, mockResponse.Object) - })); + _ = this.mockDevicePropertyService.Setup(c => c.SetProperties(deviceId, devicePropertyValues)) + .Returns(Task.CompletedTask); // Act - var response = await devicesController.SetProperties("aaa", new[] - { - new DevicePropertyValue - { - Name = "unknown", - Value = "eee" - } - }); + var result = await devicesController.SetProperties(deviceId, devicePropertyValues); // Assert - Assert.IsNotNull(response); - Assert.IsAssignableFrom(response.Result); - - Assert.IsFalse(twin.Properties.Desired.Contains("unknown")); - - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task CreateDeviceAsyncDuplicatedDeviceIdShouldThrowInternalServerErrorException() - { - // Arrange - var devicesController = CreateDevicesController(); - - _ = this.mockDeviceService.Setup(c => c.GetDevice(It.IsAny())) - .ReturnsAsync(new Device()); + Assert.IsNotNull(result); + Assert.IsAssignableFrom(result.Result); - // Act - //var result = await existingDevice.GetItem(deviceID); - var result = async () => await devicesController.CreateDeviceAsync(new DeviceDetails()); + var okResult = (OkResult)result.Result; + Assert.IsNotNull(okResult); - // Assert - //Assert.IsNotNull(result); - //Assert.IsAssignableFrom(result); - _ = await result.Should().ThrowAsync(); this.mockRepository.VerifyAll(); } - } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DevicePropertyServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DevicePropertyServiceTests.cs new file mode 100644 index 000000000..e738b5e94 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DevicePropertyServiceTests.cs @@ -0,0 +1,408 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Tests.Unit.Server.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Azure; + using Azure.Data.Tables; + using UnitTests.Bases; + using FluentAssertions; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + using AzureIoTHub.Portal.Server.Exceptions; + using AzureIoTHub.Portal.Server.Services; + using Microsoft.Azure.Devices.Shared; + using Models.v10; + using Portal.Server.Entities; + using Portal.Server.Factories; + using Portal.Server.Helpers; + + [TestFixture] + public class DevicePropertyServiceTests : BackendUnitTest + { + private Mock mockDeviceService; + private Mock mockTableClientFactory; + + private IDevicePropertyService devicePropertyService; + + public override void Setup() + { + base.Setup(); + + + this.mockDeviceService = MockRepository.Create(); + this.mockTableClientFactory = MockRepository.Create(); + + _ = ServiceCollection.AddSingleton(this.mockDeviceService.Object); + _ = ServiceCollection.AddSingleton(this.mockTableClientFactory.Object); + _ = ServiceCollection.AddSingleton(); + + Services = ServiceCollection.BuildServiceProvider(); + + this.devicePropertyService = Services.GetRequiredService(); + } + + [Test] + public async Task GetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursOnGettingProperties() + { + // Arrange + var twin = new Twin(); + + DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); + DeviceHelper.SetDesiredProperty(twin, "writable", "ccc"); + + var mockTableClient = MockRepository.Create(); + + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync(twin); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) + .Returns(mockTableClient.Object); + + _ = mockTableClient.Setup(c => c.QueryAsync( + It.Is(x => x == "PartitionKey eq 'bbb'"), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Throws(new RequestFailedException("test")); + + // Act + var act = () => this.devicePropertyService.GetProperties("aaa"); + + // Assert + _ = await act.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task WhenDeviceDoesntHaveModelIdNotExistGetPropertiesShouldThrowResourceNotFoundException() + { + // Arrange + var twin = new Twin(); + + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync(twin); + + // Act + var act = () => this.devicePropertyService.GetProperties("aaa"); + + // Assert + _ = await act.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task WhenDeviceNotExistGetPropertiesShouldThrowResourceNotFoundException() + { + // Arrange + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync((Twin)null); + + // Act + var act = () => this.devicePropertyService.GetProperties("aaa"); + + // Assert + _ = await act.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task GetPropertiesShouldReturnDeviceProperties() + { + // Arrange + var twin = new Twin(); + + DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); + DeviceHelper.SetDesiredProperty(twin, "writable", "ccc"); + + var mockTableClient = MockRepository.Create(); + var mockResponse = MockRepository.Create(); + + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync(twin); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) + .Returns(mockTableClient.Object); + + _ = mockTableClient.Setup(c => c.QueryAsync( + It.Is(x => x == "PartitionKey eq 'bbb'"), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(AsyncPageable.FromPages(new[] + { + Page.FromValues(new[] + { + new DeviceModelProperty + { + RowKey = Guid.NewGuid().ToString(), + PartitionKey = "bbb", + IsWritable = true, + Name = "writable", + }, + new DeviceModelProperty + { + RowKey = Guid.NewGuid().ToString(), + PartitionKey = "bbb", + IsWritable = false, + Name = "notwritable", + } + }, null, mockResponse.Object) + })); + + // Act + var devicePropertyValues = await this.devicePropertyService.GetProperties("aaa"); + + // Assert + _ = devicePropertyValues.Count().Should().Be(2); + _ = devicePropertyValues.Single(x => x.Name == "writable").Value.Should().Be("ccc"); + _ = devicePropertyValues.Single(x => x.Name == "notwritable").Value.Should().BeNull(); + + MockRepository.VerifyAll(); + } + + [Test] + public async Task WhenDeviceNotExistSetPropertiesShouldThrowResourceNotFoundException() + { + // Arrange + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync((Twin)null); + + // Act + var act = () => this.devicePropertyService.SetProperties("aaa", null); + + // Assert + _ = await act.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task WhenDeviceDoesntHaveModelIdNotExistSetPropertiesShouldThrowResourceNotFoundException() + { + // Arrange + var twin = new Twin(); + + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync(twin); + + // Act + var act = () => this.devicePropertyService.SetProperties("aaa", null); + + // Assert + _ = await act.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task SetPropertiesShouldUpdateDesiredProperties() + { + // Arrange + var twin = new Twin(); + + DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); + + var mockTableClient = MockRepository.Create(); + var mockResponse = MockRepository.Create(); + + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync(twin); + + _ = this.mockDeviceService.Setup(c => c.UpdateDeviceTwin( + It.Is(c => c == twin))) + .ReturnsAsync(twin); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) + .Returns(mockTableClient.Object); + + _ = mockTableClient.Setup(c => c.QueryAsync( + It.Is(x => x == "PartitionKey eq 'bbb'"), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(AsyncPageable.FromPages(new[] + { + Page.FromValues(new[] + { + new DeviceModelProperty + { + RowKey = Guid.NewGuid().ToString(), + PartitionKey = "bbb", + IsWritable = true, + Name = "writable", + } + }, null, mockResponse.Object) + })); + + // Act + await this.devicePropertyService.SetProperties("aaa", new[] + { + new DevicePropertyValue + { + Name = "writable", + Value = "ccc" + } + }); + + // Assert + Assert.AreEqual("ccc", twin.Properties.Desired["writable"].ToString()); + + MockRepository.VerifyAll(); + } + + [Test] + public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursOnGettingProperties() + { + // Arrange + var twin = new Twin(); + + DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); + + var mockTableClient = MockRepository.Create(); + + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync(twin); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) + .Returns(mockTableClient.Object); + + _ = mockTableClient.Setup(c => c.QueryAsync( + It.Is(x => x == "PartitionKey eq 'bbb'"), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Throws(new RequestFailedException("test")); + + // Act + var act = () => this.devicePropertyService.SetProperties("aaa", new[] + { + new DevicePropertyValue + { + Name = "writable", + Value = "ccc" + } + }); + + // Assert + _ = await act.Should().ThrowAsync(); + MockRepository.VerifyAll(); + } + + [Test] + public async Task WhenPropertyNotWrittableSetPropertiesShouldNotUpdateDesiredProperty() + { + // Arrange + var twin = new Twin(); + + DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); + + var mockTableClient = MockRepository.Create(); + var mockResponse = MockRepository.Create(); + + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync(twin); + + _ = this.mockDeviceService.Setup(c => c.UpdateDeviceTwin( + It.Is(c => c == twin))) + .ReturnsAsync(twin); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) + .Returns(mockTableClient.Object); + + _ = mockTableClient.Setup(c => c.QueryAsync( + It.Is(x => x == "PartitionKey eq 'bbb'"), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(AsyncPageable.FromPages(new[] + { + Page.FromValues(new[] + { + new DeviceModelProperty + { + RowKey = Guid.NewGuid().ToString(), + PartitionKey = "bbb", + IsWritable = false, + Name = "notwritable", + } + }, null, mockResponse.Object) + })); + + // Act + await this.devicePropertyService.SetProperties("aaa", new[] + { + new DevicePropertyValue + { + Name = "notwritable", + Value = "ccc" + } + }); + + // Assert + Assert.IsFalse(twin.Properties.Desired.Contains("notwritable")); + + MockRepository.VerifyAll(); + } + + [Test] + public async Task WhenPropertyNotInModelSetPropertiesShouldNotUpdateDesiredProperty() + { + // Arrange + var twin = new Twin(); + + DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); + + var mockTableClient = MockRepository.Create(); + var mockResponse = MockRepository.Create(); + + _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) + .ReturnsAsync(twin); + + _ = this.mockDeviceService.Setup(c => c.UpdateDeviceTwin( + It.Is(c => c == twin))) + .ReturnsAsync(twin); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) + .Returns(mockTableClient.Object); + + _ = mockTableClient.Setup(c => c.QueryAsync( + It.Is(x => x == "PartitionKey eq 'bbb'"), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(AsyncPageable.FromPages(new[] + { + Page.FromValues(new[] + { + new DeviceModelProperty + { + RowKey = Guid.NewGuid().ToString(), + PartitionKey = "bbb", + IsWritable = true, + Name = "writable", + } + }, null, mockResponse.Object) + })); + + // Act + await this.devicePropertyService.SetProperties("aaa", new[] + { + new DevicePropertyValue + { + Name = "unknown", + Value = "eee" + } + }); + + // Assert + Assert.IsFalse(twin.Properties.Desired.Contains("unknown")); + + MockRepository.VerifyAll(); + } + } +} diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesController.cs index 7f3e03ed9..4c795af21 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesController.cs @@ -4,22 +4,15 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 { using System.Collections.Generic; - using System.Linq; using System.Threading.Tasks; - using Azure; using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Server.Entities; - using AzureIoTHub.Portal.Server.Factories; - using AzureIoTHub.Portal.Server.Helpers; - using AzureIoTHub.Portal.Server.Managers; - using AzureIoTHub.Portal.Server.Mappers; - using AzureIoTHub.Portal.Server.Services; - using Exceptions; + using Factories; + using Managers; + using Mappers; + using Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; [Authorize] [ApiController] @@ -28,15 +21,7 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 [ApiExplorerSettings(GroupName = "IoT Devices")] public class DevicesController : DevicesControllerBase { - /// - /// The table client factory. - /// - private readonly ITableClientFactory tableClientFactory; - - /// - /// The devices service. - /// - private readonly IDeviceService devicesService; + private readonly IDevicePropertyService devicePropertyService; public DevicesController( ILogger logger, @@ -44,11 +29,10 @@ public DevicesController( IDeviceTagService deviceTagService, IDeviceProvisioningServiceManager deviceProvisioningServiceManager, IDeviceTwinMapper deviceTwinMapper, - ITableClientFactory tableClientFactory) + ITableClientFactory tableClientFactory, IDevicePropertyService devicePropertyService) : base(logger, devicesService, deviceTagService, deviceTwinMapper, deviceProvisioningServiceManager, tableClientFactory) { - this.devicesService = devicesService; - this.tableClientFactory = tableClientFactory; + this.devicePropertyService = devicePropertyService; } /// @@ -125,73 +109,9 @@ public override Task> GetCredentials(string /// /// The device identifier. [HttpGet("{deviceID}/properties", Name = "GET Device Properties")] - public async Task>> GetProperties(string deviceID) + public async Task> GetProperties(string deviceID) { - var device = await this.devicesService.GetDeviceTwin(deviceID); - - if (device == null) - { - return NotFound(); - } - - var modelId = DeviceHelper.RetrieveTagValue(device, nameof(DeviceDetails.ModelId)); - - if (string.IsNullOrEmpty(modelId)) - { - return BadRequest("Device has no modelId tag value"); - } - - AsyncPageable items; - - try - { - items = this.tableClientFactory - .GetDeviceTemplateProperties() - .QueryAsync($"PartitionKey eq '{modelId}'"); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to get templates properties fro device with id {deviceID}: {e.Message}", e); - } - - var result = new List(); - JObject desiredPropertiesAsJson; - JObject reportedPropertiesAsJson; - - try - { - desiredPropertiesAsJson = JObject.Parse(device.Properties.Desired.ToJson()); - } - catch (JsonReaderException e) - { - throw new InternalServerErrorException($"Unable to read desired properties for device with id {deviceID}", e); - } - - try - { - reportedPropertiesAsJson = JObject.Parse(device.Properties.Reported.ToJson()); - } - catch (JsonReaderException e) - { - throw new InternalServerErrorException($"Unable to read reported properties for device with id {deviceID}", e); - } - - await foreach (var item in items) - { - var value = item.IsWritable ? desiredPropertiesAsJson.SelectToken(item.Name)?.Value() : - reportedPropertiesAsJson.SelectToken(item.Name)?.Value(); - - result.Add(new DevicePropertyValue - { - DisplayName = item.DisplayName, - IsWritable = item.IsWritable, - Name = item.Name, - PropertyType = item.PropertyType, - Value = value - }); - } - - return result; + return await this.devicePropertyService.GetProperties(deviceID); } /// @@ -202,48 +122,7 @@ public async Task>> GetProperties( [HttpPost("{deviceID}/properties", Name = "POST Device Properties")] public async Task>> SetProperties(string deviceID, IEnumerable values) { - var device = await this.devicesService.GetDeviceTwin(deviceID); - - if (device == null) - { - return NotFound(); - } - - var modelId = DeviceHelper.RetrieveTagValue(device, nameof(DeviceDetails.ModelId)); - - if (string.IsNullOrEmpty(modelId)) - { - return BadRequest("Device has no modelId tag value"); - } - - AsyncPageable items; - - try - { - items = this.tableClientFactory - .GetDeviceTemplateProperties() - .QueryAsync($"PartitionKey eq '{modelId}'"); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to get templates properties fro device with id {deviceID}: {e.Message}", e); - } - - var desiredProperties = new Dictionary(); - - await foreach (var item in items) - { - if (!item.IsWritable) - { - continue; - } - - _ = desiredProperties.TryAdd(item.Name, values.FirstOrDefault(x => x.Name == item.Name)?.Value); - } - - device.Properties.Desired = DeviceHelper.PropertiesWithDotNotationToTwinCollection(desiredProperties); - - _ = await this.devicesService.UpdateDeviceTwin(device); + await this.devicePropertyService.SetProperties(deviceID, values); return Ok(); } diff --git a/src/AzureIoTHub.Portal/Server/Services/DevicePropertyService.cs b/src/AzureIoTHub.Portal/Server/Services/DevicePropertyService.cs new file mode 100644 index 000000000..676e3adb2 --- /dev/null +++ b/src/AzureIoTHub.Portal/Server/Services/DevicePropertyService.cs @@ -0,0 +1,145 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Server.Services +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Azure; + using Entities; + using Exceptions; + using Factories; + using Helpers; + using Models.v10; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + public class DevicePropertyService : IDevicePropertyService + { + private readonly IDeviceService devicesService; + private readonly ITableClientFactory tableClientFactory; + + public DevicePropertyService(IDeviceService devicesService, ITableClientFactory tableClientFactory) + { + this.devicesService = devicesService; + this.tableClientFactory = tableClientFactory; + } + + + public async Task> GetProperties(string deviceId) + { + var device = await this.devicesService.GetDeviceTwin(deviceId); + + if (device == null) + { + throw new ResourceNotFoundException($"Unable to find the device {deviceId}"); + } + + var modelId = DeviceHelper.RetrieveTagValue(device, nameof(DeviceDetails.ModelId)); + + if (string.IsNullOrEmpty(modelId)) + { + throw new ResourceNotFoundException($"Device {deviceId} has no modelId tag value"); + } + + AsyncPageable items; + + try + { + items = this.tableClientFactory + .GetDeviceTemplateProperties() + .QueryAsync($"PartitionKey eq '{modelId}'"); + } + catch (RequestFailedException e) + { + throw new InternalServerErrorException($"Unable to get templates properties fro device with id {deviceId}: {e.Message}", e); + } + + var result = new List(); + JObject desiredPropertiesAsJson; + JObject reportedPropertiesAsJson; + + try + { + desiredPropertiesAsJson = JObject.Parse(device.Properties.Desired.ToJson()); + } + catch (JsonReaderException e) + { + throw new InternalServerErrorException($"Unable to read desired properties for device with id {deviceId}", e); + } + + try + { + reportedPropertiesAsJson = JObject.Parse(device.Properties.Reported.ToJson()); + } + catch (JsonReaderException e) + { + throw new InternalServerErrorException($"Unable to read reported properties for device with id {deviceId}", e); + } + + await foreach (var item in items) + { + var value = item.IsWritable ? desiredPropertiesAsJson.SelectToken(item.Name)?.Value() : + reportedPropertiesAsJson.SelectToken(item.Name)?.Value(); + + result.Add(new DevicePropertyValue + { + DisplayName = item.DisplayName, + IsWritable = item.IsWritable, + Name = item.Name, + PropertyType = item.PropertyType, + Value = value + }); + } + + return result; + } + + public async Task SetProperties(string deviceId, IEnumerable devicePropertyValues) + { + var device = await this.devicesService.GetDeviceTwin(deviceId); + + if (device == null) + { + throw new ResourceNotFoundException($"Unable to find the device {deviceId}"); + } + + var modelId = DeviceHelper.RetrieveTagValue(device, nameof(DeviceDetails.ModelId)); + + if (string.IsNullOrEmpty(modelId)) + { + throw new ResourceNotFoundException($"Device {deviceId} has no modelId tag value"); + } + + AsyncPageable items; + + try + { + items = this.tableClientFactory + .GetDeviceTemplateProperties() + .QueryAsync($"PartitionKey eq '{modelId}'"); + } + catch (RequestFailedException e) + { + throw new InternalServerErrorException($"Unable to get templates properties fro device with id {deviceId}: {e.Message}", e); + } + + var desiredProperties = new Dictionary(); + + await foreach (var item in items) + { + if (!item.IsWritable) + { + continue; + } + + _ = desiredProperties.TryAdd(item.Name, devicePropertyValues.FirstOrDefault(x => x.Name == item.Name)?.Value); + } + + device.Properties.Desired = DeviceHelper.PropertiesWithDotNotationToTwinCollection(desiredProperties); + + _ = await this.devicesService.UpdateDeviceTwin(device); + } + } +} diff --git a/src/AzureIoTHub.Portal/Server/Services/IDevicePropertyService.cs b/src/AzureIoTHub.Portal/Server/Services/IDevicePropertyService.cs new file mode 100644 index 000000000..7fe0f0239 --- /dev/null +++ b/src/AzureIoTHub.Portal/Server/Services/IDevicePropertyService.cs @@ -0,0 +1,16 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace AzureIoTHub.Portal.Server.Services +{ + using Models.v10; + using System.Collections.Generic; + using System.Threading.Tasks; + + public interface IDevicePropertyService + { + Task> GetProperties(string deviceId); + + Task SetProperties(string deviceId, IEnumerable devicePropertyValues); + } +} diff --git a/src/AzureIoTHub.Portal/Server/Startup.cs b/src/AzureIoTHub.Portal/Server/Startup.cs index 6d4ba596c..65aff08c4 100644 --- a/src/AzureIoTHub.Portal/Server/Startup.cs +++ b/src/AzureIoTHub.Portal/Server/Startup.cs @@ -110,7 +110,6 @@ public void ConfigureServices(IServiceCollection services) _ = services.AddTransient(_ => new BlobServiceClient(configuration.StorageAccountConnectionString)); _ = services.AddTransient(_ => new TableClientFactory(configuration.StorageAccountConnectionString)); _ = services.AddTransient(); - _ = services.AddTransient(); _ = services.AddTransient(); _ = services.AddTransient(); _ = services.AddTransient(); @@ -125,6 +124,7 @@ public void ConfigureServices(IServiceCollection services) _ = services.AddTransient(); _ = services.AddTransient(); + _ = services.AddTransient(); _ = services.AddTransient(); _ = services.AddTransient(); _ = services.AddTransient(); @@ -132,6 +132,7 @@ public void ConfigureServices(IServiceCollection services) _ = services.AddTransient(); _ = services.AddTransient(); _ = services.AddTransient(); + _ = services.AddTransient(); _ = services.AddMudServices();