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();
         }
     }
 }