From 0fcad6e3af2d01e62e6e19d430926ab60d1ce6a6 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf <hacherouf.hocine@gmail.com> Date: Mon, 8 Aug 2022 10:49:37 +0200 Subject: [PATCH 1/4] Send command with confirmed property #907 --- .../Server/Managers/LoraDeviceMethodManager.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/AzureIoTHub.Portal/Server/Managers/LoraDeviceMethodManager.cs b/src/AzureIoTHub.Portal/Server/Managers/LoraDeviceMethodManager.cs index ba99a97c8..6fce64e82 100644 --- a/src/AzureIoTHub.Portal/Server/Managers/LoraDeviceMethodManager.cs +++ b/src/AzureIoTHub.Portal/Server/Managers/LoraDeviceMethodManager.cs @@ -7,10 +7,10 @@ namespace AzureIoTHub.Portal.Server.Managers using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; + using System.Text; using System.Threading; using System.Threading.Tasks; using AzureIoTHub.Portal.Models.v10.LoRaWAN; - using AzureIoTHub.Portal.Server.Extensions; public class LoraDeviceMethodManager : ILoraDeviceMethodManager { @@ -26,7 +26,12 @@ public async Task<HttpResponseMessage> ExecuteLoRaDeviceMessage(string deviceId, ArgumentNullException.ThrowIfNull(deviceId, nameof(deviceId)); ArgumentNullException.ThrowIfNull(command, nameof(command)); - var body = command.ToDynamic(); + var body = new + { + rawPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(command.Frame)), + fport = command.Port, + confirmed = command.Confirmed + }; using var commandContent = JsonContent.Create(body); From 3ca2fde60c0579eef13323ed53c03426a6c09e39 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf <hacherouf.hocine@gmail.com> Date: Mon, 8 Aug 2022 10:53:59 +0200 Subject: [PATCH 2/4] Remove unused DeviceModelCommandExtensions #907 --- .../DeviceModelCommandExtensionsTests.cs | 67 ------------------- .../DeviceModelCommandExtensions.cs | 28 -------- 2 files changed, 95 deletions(-) delete mode 100644 src/AzureIoTHub.Portal.Server.Tests.Unit/Extensions/DeviceModelCommandExtensionsTests.cs delete mode 100644 src/AzureIoTHub.Portal/Server/Extensions/DeviceModelCommandExtensions.cs diff --git a/src/AzureIoTHub.Portal.Server.Tests.Unit/Extensions/DeviceModelCommandExtensionsTests.cs b/src/AzureIoTHub.Portal.Server.Tests.Unit/Extensions/DeviceModelCommandExtensionsTests.cs deleted file mode 100644 index a5588665a..000000000 --- a/src/AzureIoTHub.Portal.Server.Tests.Unit/Extensions/DeviceModelCommandExtensionsTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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.Tests.Unit.Extensions -{ - using System; - using System.Linq; - using System.Text; - using AzureIoTHub.Portal.Models.v10.LoRaWAN; - using FluentAssertions; - using NUnit.Framework; - using Server.Extensions; - - [TestFixture] - public class DeviceModelCommandExtensionsTests - { - - [Test] - public void ToDynamicMustNotIncludeConfirmedPropertyWhenConfirmedIsFalse() - { - // Arrange - var command = new DeviceModelCommand - { - Name = Guid.NewGuid().ToString(), - Frame = Guid.NewGuid().ToString(), - Port = 123, - Confirmed = false, - IsBuiltin = false - }; - - // Act - var result = command.ToDynamic(); - - // Assert - _ = ((Type)result.GetType()).GetProperties() - .Where(p => p.Name.Equals("confirmed", StringComparison.Ordinal)) - .Any() - .Should() - .BeFalse(); - - _ = ((string)result.rawPayload).Should().Be(Convert.ToBase64String(Encoding.UTF8.GetBytes(command.Frame))); - _ = ((int)result.fport).Should().Be(command.Port); - } - - [Test] - public void ToDynamicMustIncludeConfirmedPropertyWhenConfirmedIsTrue() - { - // Arrange - var command = new DeviceModelCommand - { - Name = Guid.NewGuid().ToString(), - Frame = Guid.NewGuid().ToString(), - Port = 123, - Confirmed = true, - IsBuiltin = false - }; - - // Act - var result = command.ToDynamic(); - - // Assert - _ = ((bool)result.confirmed).Should().BeTrue(); - _ = ((string)result.rawPayload).Should().Be(Convert.ToBase64String(Encoding.UTF8.GetBytes(command.Frame))); - _ = ((int)result.fport).Should().Be(command.Port); - } - } -} diff --git a/src/AzureIoTHub.Portal/Server/Extensions/DeviceModelCommandExtensions.cs b/src/AzureIoTHub.Portal/Server/Extensions/DeviceModelCommandExtensions.cs deleted file mode 100644 index 81c74d0b1..000000000 --- a/src/AzureIoTHub.Portal/Server/Extensions/DeviceModelCommandExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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.Extensions -{ - using System; - using System.Text; - using AzureIoTHub.Portal.Models.v10.LoRaWAN; - - public static class DeviceModelCommandExtensions - { - public static dynamic ToDynamic(this DeviceModelCommand command) - { - return command.Confirmed - ? (new - { - rawPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(command.Frame)), - fport = command.Port, - confirmed = command.Confirmed - }) - : (new - { - rawPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(command.Frame)), - fport = command.Port - }); - } - } -} From 05b237ff907805156718050b578acb7050274a57 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf <hacherouf.hocine@gmail.com> Date: Mon, 8 Aug 2022 11:54:17 +0200 Subject: [PATCH 3/4] Add LoRaCloudToDeviceMessage contact to send commands #907 --- .../Managers/LoraDeviceMethodManager.cs | 8 ++++---- .../v1.0/LoRaWAN/LoRaCloudToDeviceMessage.cs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/LoRaCloudToDeviceMessage.cs diff --git a/src/AzureIoTHub.Portal/Server/Managers/LoraDeviceMethodManager.cs b/src/AzureIoTHub.Portal/Server/Managers/LoraDeviceMethodManager.cs index 6fce64e82..3593756a2 100644 --- a/src/AzureIoTHub.Portal/Server/Managers/LoraDeviceMethodManager.cs +++ b/src/AzureIoTHub.Portal/Server/Managers/LoraDeviceMethodManager.cs @@ -26,11 +26,11 @@ public async Task<HttpResponseMessage> ExecuteLoRaDeviceMessage(string deviceId, ArgumentNullException.ThrowIfNull(deviceId, nameof(deviceId)); ArgumentNullException.ThrowIfNull(command, nameof(command)); - var body = new + var body = new LoRaCloudToDeviceMessage { - rawPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(command.Frame)), - fport = command.Port, - confirmed = command.Confirmed + RawPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(command.Frame)), + Fport = command.Port, + Confirmed = command.Confirmed }; using var commandContent = JsonContent.Create(body); diff --git a/src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/LoRaCloudToDeviceMessage.cs b/src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/LoRaCloudToDeviceMessage.cs new file mode 100644 index 000000000..94a4f01f0 --- /dev/null +++ b/src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/LoRaCloudToDeviceMessage.cs @@ -0,0 +1,19 @@ +// 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.Models.v10.LoRaWAN +{ + using System.Text.Json.Serialization; + + public class LoRaCloudToDeviceMessage + { + [JsonPropertyName("rawPayload")] + public string RawPayload { get; set; } + + [JsonPropertyName("fport")] + public int Fport { get; set; } + + [JsonPropertyName("confirmed")] + public bool Confirmed { get; set; } + } +} From 3abdd9d14688e08df045852f2cb28661d31da594 Mon Sep 17 00:00:00 2001 From: Hocine Hacherouf <hacherouf.hocine@gmail.com> Date: Mon, 8 Aug 2022 11:56:04 +0200 Subject: [PATCH 4/4] Update unit test LoraDeviceMethodManagerTests #907 --- .../Managers/LoraDeviceMethodManagerTests.cs | 89 +++++++++---------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/src/AzureIoTHub.Portal.Server.Tests.Unit/Managers/LoraDeviceMethodManagerTests.cs b/src/AzureIoTHub.Portal.Server.Tests.Unit/Managers/LoraDeviceMethodManagerTests.cs index ef00ca5aa..1a24acc0f 100644 --- a/src/AzureIoTHub.Portal.Server.Tests.Unit/Managers/LoraDeviceMethodManagerTests.cs +++ b/src/AzureIoTHub.Portal.Server.Tests.Unit/Managers/LoraDeviceMethodManagerTests.cs @@ -3,48 +3,41 @@ namespace AzureIoTHub.Portal.Server.Tests.Unit.Managers { - using AzureIoTHub.Portal.Models.v10.LoRaWAN; + using Models.v10.LoRaWAN; using AzureIoTHub.Portal.Server.Managers; using FluentAssertions; - using Moq; - using Moq.Protected; using NUnit.Framework; using System; + using System.Net; using System.Net.Http; + using System.Net.Http.Json; using System.Text; - using System.Threading; using System.Threading.Tasks; + using AutoFixture; + using Microsoft.Extensions.DependencyInjection; + using RichardSzalay.MockHttp; [TestFixture] - public class LoraDeviceMethodManagerTests : IDisposable + public class LoraDeviceMethodManagerTests : BackendUnitTest { -#pragma warning disable CA2213 // Disposable fields should be disposed - private HttpClient mockHttpClient; -#pragma warning restore CA2213 // Disposable fields should be disposed - private MockRepository mockRepository; + private ILoraDeviceMethodManager loraDeviceMethodManager; - private Mock<HttpMessageHandler> httpMessageHandlerMock; - - [SetUp] - public void SetUp() + public override void Setup() { - this.mockRepository = new MockRepository(MockBehavior.Strict); + base.Setup(); - this.httpMessageHandlerMock = this.mockRepository.Create<HttpMessageHandler>(); - this.mockHttpClient = new HttpClient(this.httpMessageHandlerMock.Object) - { - BaseAddress = new Uri("http://fake.local") - }; + _ = ServiceCollection.AddSingleton<ILoraDeviceMethodManager, LoraDeviceMethodManager>(); + + Services = ServiceCollection.BuildServiceProvider(); + + this.loraDeviceMethodManager = Services.GetRequiredService<ILoraDeviceMethodManager>(); } [Test] public async Task ExecuteLoRaDeviceMessageThrowsArgumentNullExceptionWhenDeviceIdIsNull() { - // Arrange - var loraDeviceMethodManager = new LoraDeviceMethodManager(this.mockHttpClient); - // Act - var act = () => loraDeviceMethodManager.ExecuteLoRaDeviceMessage(null, null); + var act = () => this.loraDeviceMethodManager.ExecuteLoRaDeviceMessage(null, null); // Assert _ = await act.Should().ThrowAsync<ArgumentNullException>(); @@ -54,11 +47,10 @@ public async Task ExecuteLoRaDeviceMessageThrowsArgumentNullExceptionWhenDeviceI public async Task ExecuteLoRaDeviceMessageThrowsArgumentNullExceptionWhenCommandIsNull() { // Arrange - var loraDeviceMethodManager = new LoraDeviceMethodManager(this.mockHttpClient); - var deviceId = Guid.NewGuid().ToString(); + var deviceId = Fixture.Create<string>(); // Act - var act = () => loraDeviceMethodManager.ExecuteLoRaDeviceMessage(deviceId, null); + var act = () => this.loraDeviceMethodManager.ExecuteLoRaDeviceMessage(deviceId, null); // Assert _ = await act.Should().ThrowAsync<ArgumentNullException>(); @@ -68,39 +60,38 @@ public async Task ExecuteLoRaDeviceMessageThrowsArgumentNullExceptionWhenCommand public async Task ExecuteLoRaDeviceMessageMustBeSuccessfullWhenParametersAreProvided() { // Arrange - var loraDeviceMethodManager = new LoraDeviceMethodManager(this.mockHttpClient); - var deviceId = Guid.NewGuid().ToString(); + var deviceId = Fixture.Create<string>(); var command = new DeviceModelCommand { - Frame = Guid.NewGuid().ToString() + Frame = Fixture.Create<string>(), + Confirmed = Fixture.Create<bool>(), + Port = Fixture.Create<int>() }; - using var deviceResponseMock = new HttpResponseMessage(); - - deviceResponseMock.Content = new StringContent("{}", Encoding.UTF8, "application/json"); - - this.httpMessageHandlerMock - .Protected() - .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.Is<HttpRequestMessage>(req => req.RequestUri.LocalPath.Equals($"/api/cloudtodevicemessage/{deviceId}", StringComparison.OrdinalIgnoreCase) && req.Method == HttpMethod.Post), ItExpr.IsAny<CancellationToken>()) - .ReturnsAsync((HttpRequestMessage _, CancellationToken _) => deviceResponseMock) - .Verifiable(); + var expectedRawPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(command.Frame)); + + _ = MockHttpClient.When(HttpMethod.Post, $"/api/cloudtodevicemessage/{deviceId}") + .With(m => + { + _ = m.Content.Should().BeAssignableTo<JsonContent>(); + var body = (JsonContent) m.Content; + var loRaCloudToDeviceMessage = (LoRaCloudToDeviceMessage)body?.Value; + _ = loRaCloudToDeviceMessage.Should().NotBeNull(); + _ = loRaCloudToDeviceMessage?.Fport.Should().Be(command.Port); + _ = loRaCloudToDeviceMessage?.Confirmed.Should().Be(command.Confirmed); + _ = loRaCloudToDeviceMessage?.RawPayload.Should().Be(expectedRawPayload); + return true; + }) + .Respond(HttpStatusCode.Created); // Act - var result = await loraDeviceMethodManager.ExecuteLoRaDeviceMessage(deviceId, command); + var result = await this.loraDeviceMethodManager.ExecuteLoRaDeviceMessage(deviceId, command); // Assert _ = result.Should().NotBeNull(); _ = result.IsSuccessStatusCode.Should().BeTrue(); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { + MockHttpClient.VerifyNoOutstandingRequest(); + MockHttpClient.VerifyNoOutstandingExpectation(); } } }