From 6320014cd9c3e8cf42e7302a5ad6f90d27633e8d Mon Sep 17 00:00:00 2001 From: Audrey Serra Date: Wed, 5 Oct 2022 15:35:28 +0200 Subject: [PATCH 1/6] WIP --- .../PortalDbContext.cs | 1 + .../Repositories/ConcentratorRepository.cs | 15 +++ .../ConcentratorDetailPageTests.cs | 8 +- .../Concentrator/ConcentratorListPageTests.cs | 16 +-- .../CreateConcentratorPageTest.cs | 16 +-- .../LoRaWanConcentratorsClientServiceTests.cs | 18 +-- .../Validators/ConcentratorValidatorTests.cs | 12 +- .../LoRaWANConcentratorsControllerTest.cs | 28 ++--- .../Mappers/ConcentratorTwinMapperTests.cs | 32 ++--- .../LoRaWANConcentratorServiceTests.cs | 30 ++--- .../Concentrator/ConcentratorDetailPage.razor | 2 +- .../Concentrator/ConcentratorListPage.razor | 16 +-- .../Concentrator/CreateConcentratorPage.razor | 8 +- .../ILoRaWanConcentratorsClientService.cs | 8 +- .../LoRaWanConcentratorsClientService.cs | 12 +- .../Validators/ConcentratorValidator.cs | 2 +- .../LoRaWAN/LoRaWANConcentratorsController.cs | 8 +- .../Server/Jobs/SyncConcentratorsJob.cs | 110 ++++++++++++++++++ .../Server/Mappers/ConcentratorProfile.cs | 68 +++++++++++ .../Server/Mappers/ConcentratorTwinMapper.cs | 14 +-- .../Server/Mappers/IConcentratorTwinMapper.cs | 4 +- .../Services/ILoRaWANConcentratorService.cs | 6 +- .../Services/LoRaWANConcentratorService.cs | 8 +- src/AzureIoTHub.Portal/Server/Startup.cs | 9 ++ .../{Concentrator.cs => ConcentratorDto.cs} | 2 +- .../Entities/Concentrator.cs | 44 +++++++ .../Repositories/IConcentratorRepository.cs | 11 ++ 27 files changed, 383 insertions(+), 125 deletions(-) create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Repositories/ConcentratorRepository.cs create mode 100644 src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs create mode 100644 src/AzureIoTHub.Portal/Server/Mappers/ConcentratorProfile.cs rename src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/{Concentrator.cs => ConcentratorDto.cs} (98%) create mode 100644 src/AzureIoTHubPortal.Domain/Entities/Concentrator.cs create mode 100644 src/AzureIoTHubPortal.Domain/Repositories/IConcentratorRepository.cs diff --git a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs index c1ba83b23..c8d95ea4d 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs @@ -22,6 +22,7 @@ public class PortalDbContext : DbContext public DbSet DeviceTagValues { get; set; } public DbSet EdgeDeviceModels { get; set; } public DbSet EdgeDeviceModelCommands { get; set; } + public DbSet Concentrators { get; set; } #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. public PortalDbContext(DbContextOptions options) diff --git a/src/AzureIoTHub.Portal.Infrastructure/Repositories/ConcentratorRepository.cs b/src/AzureIoTHub.Portal.Infrastructure/Repositories/ConcentratorRepository.cs new file mode 100644 index 000000000..811468e0e --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Repositories/ConcentratorRepository.cs @@ -0,0 +1,15 @@ +// 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.Infrastructure.Repositories +{ + using AzureIoTHub.Portal.Domain.Repositories; + using Domain.Entities; + + public class ConcentratorRepository : GenericRepository, IConcentratorRepository + { + public ConcentratorRepository(PortalDbContext context) : base(context) + { + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/ConcentratorDetailPageTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/ConcentratorDetailPageTests.cs index b23445c9e..ed3f2d56f 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/ConcentratorDetailPageTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/ConcentratorDetailPageTests.cs @@ -54,7 +54,7 @@ public void ReturnButtonMustNavigateToPreviousPage() { // Arrange _ = this.mockLoRaWanConcentratorsClientService.Setup(service => service.GetConcentrator(this.mockDeviceId)) - .ReturnsAsync(new Concentrator()); + .ReturnsAsync(new ConcentratorDto()); _ = this.mockLoRaWanConcentratorsClientService.Setup(service => service.GetFrequencyPlans()) .ReturnsAsync(new[] @@ -98,7 +98,7 @@ public void ConcentratorDetailPageShouldProcessProblemDetailsExceptionWhenIssueO public void ClickOnSaveShouldPutConcentratorDetails() { // Arrange - var mockConcentrator = new Concentrator() + var mockConcentrator = new ConcentratorDto() { DeviceId = "1234567890123456", DeviceName = Guid.NewGuid().ToString(), @@ -133,7 +133,7 @@ public void ClickOnSaveShouldPutConcentratorDetails() public void ConcentratorShouldNotBeUpdatedWhenModelIsNotValid() { // Arrange - var mockConcentrator = new Concentrator() + var mockConcentrator = new ConcentratorDto() { DeviceId = string.Empty, DeviceName = Guid.NewGuid().ToString(), @@ -163,7 +163,7 @@ public void ConcentratorShouldNotBeUpdatedWhenModelIsNotValid() public void ClickOnSaveShouldProcessProblemDetailsExceptionWhenIssueOccursOnUpdatingConcentratorDetails() { // Arrange - var mockConcentrator = new Concentrator() + var mockConcentrator = new ConcentratorDto() { DeviceId = "1234567890123456", DeviceName = Guid.NewGuid().ToString(), diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/ConcentratorListPageTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/ConcentratorListPageTests.cs index 02ff94ad7..f2d971ac0 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/ConcentratorListPageTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/ConcentratorListPageTests.cs @@ -50,9 +50,9 @@ public void ConcentratorListPageShouldLoadAndShowConcentrators() _ = this.mockLoRaWanConcentratorsClientService.Setup(service => service.GetConcentrators(It.Is(s => expectedUri.Equals(s, StringComparison.Ordinal)))) - .ReturnsAsync(new PaginationResult + .ReturnsAsync(new PaginationResult { - Items = new List + Items = new List { new(), new(), @@ -100,9 +100,9 @@ public void ClickToItemShouldRedirectToConcentratorDetailsPage() _ = this.mockLoRaWanConcentratorsClientService.Setup(service => service.GetConcentrators(It.Is(s => expectedUri.Equals(s, StringComparison.Ordinal)))) - .ReturnsAsync(new PaginationResult + .ReturnsAsync(new PaginationResult { - Items = new List + Items = new List { new() { @@ -130,9 +130,9 @@ public void ClickOnAddNewDeviceShouldNavigateToNewDevicePage() _ = this.mockLoRaWanConcentratorsClientService.Setup(service => service.GetConcentrators(It.Is(s => expectedUri.Equals(s, StringComparison.Ordinal)))) - .ReturnsAsync(new PaginationResult + .ReturnsAsync(new PaginationResult { - Items = new List + Items = new List { new(), new(), @@ -159,9 +159,9 @@ public void ClickOnRefreshShouldReloadConcentrators() _ = this.mockLoRaWanConcentratorsClientService.Setup(service => service.GetConcentrators(It.Is(s => expectedUri.Equals(s, StringComparison.Ordinal)))) - .ReturnsAsync(new PaginationResult + .ReturnsAsync(new PaginationResult { - Items = Array.Empty() + Items = Array.Empty() }); var cut = RenderComponent(); diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/CreateConcentratorPageTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/CreateConcentratorPageTest.cs index acec8f60e..66c6a5a96 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/CreateConcentratorPageTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Pages/LoRaWan/Concentrator/CreateConcentratorPageTest.cs @@ -54,7 +54,7 @@ public override void Setup() public void ClickOnSaveShouldPostConcentratorDetails() { // Arrange - var mockConcentrator = new Concentrator + var mockConcentrator = new ConcentratorDto { DeviceId = "1234567890123456", DeviceName = Guid.NewGuid().ToString(), @@ -68,7 +68,7 @@ public void ClickOnSaveShouldPostConcentratorDetails() }); _ = this.mockLoRaWanConcentratorsClientService.Setup(service => - service.CreateConcentrator(It.Is(concentrator => + service.CreateConcentrator(It.Is(concentrator => mockConcentrator.DeviceId.Equals(concentrator.DeviceId, StringComparison.Ordinal)))) .Returns(Task.CompletedTask); @@ -78,8 +78,8 @@ public void ClickOnSaveShouldPostConcentratorDetails() cut.WaitForAssertion(() => cut.Find("#saveButton")); - cut.Find($"#{nameof(Concentrator.DeviceId)}").Change(mockConcentrator.DeviceId); - cut.Find($"#{nameof(Concentrator.DeviceName)}").Change(mockConcentrator.DeviceName); + cut.Find($"#{nameof(ConcentratorDto.DeviceId)}").Change(mockConcentrator.DeviceId); + cut.Find($"#{nameof(ConcentratorDto.DeviceName)}").Change(mockConcentrator.DeviceName); cut.Instance.ChangeRegion(mockConcentrator.LoraRegion); // Act @@ -94,7 +94,7 @@ public void ClickOnSaveShouldPostConcentratorDetails() public void ClickOnSaveShouldProcessProblemDetailsExceptionWhenIssueOccursOnCreatingConcentratorDetails() { // Arrange - var mockConcentrator = new Concentrator + var mockConcentrator = new ConcentratorDto { DeviceId = "1234567890123456", DeviceName = Guid.NewGuid().ToString(), @@ -108,7 +108,7 @@ public void ClickOnSaveShouldProcessProblemDetailsExceptionWhenIssueOccursOnCrea }); _ = this.mockLoRaWanConcentratorsClientService.Setup(service => - service.CreateConcentrator(It.Is(concentrator => + service.CreateConcentrator(It.Is(concentrator => mockConcentrator.DeviceId.Equals(concentrator.DeviceId, StringComparison.Ordinal)))) .ThrowsAsync(new ProblemDetailsException(new ProblemDetailsWithExceptionDetails())); @@ -116,8 +116,8 @@ public void ClickOnSaveShouldProcessProblemDetailsExceptionWhenIssueOccursOnCrea cut.WaitForAssertion(() => cut.Find("#saveButton")); - cut.Find($"#{nameof(Concentrator.DeviceId)}").Change(mockConcentrator.DeviceId); - cut.Find($"#{nameof(Concentrator.DeviceName)}").Change(mockConcentrator.DeviceName); + cut.Find($"#{nameof(ConcentratorDto.DeviceId)}").Change(mockConcentrator.DeviceId); + cut.Find($"#{nameof(ConcentratorDto.DeviceName)}").Change(mockConcentrator.DeviceName); cut.Instance.ChangeRegion(mockConcentrator.LoraRegion); // Act diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/LoRaWanConcentratorsClientServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/LoRaWanConcentratorsClientServiceTests.cs index 51890b31c..e9f411e3c 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/LoRaWanConcentratorsClientServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Services/LoRaWanConcentratorsClientServiceTests.cs @@ -36,9 +36,9 @@ public override void Setup() public async Task GetConcentratorsShouldReturnConcentrators() { // Arrange - var expectedConcentrators = new PaginationResult + var expectedConcentrators = new PaginationResult { - Items = new List() + Items = new List() { new () } @@ -64,7 +64,7 @@ public async Task GetConcentratorShouldReturnConcentrator() // Arrange var deviceId = Fixture.Create(); - var expectedConcentrator = new Concentrator(); + var expectedConcentrator = new ConcentratorDto(); _ = MockHttpClient.When(HttpMethod.Get, $"/api/lorawan/concentrators/{deviceId}") .RespondJson(expectedConcentrator); @@ -82,13 +82,13 @@ public async Task GetConcentratorShouldReturnConcentrator() public async Task CreateConcentratorShouldCreateConcentrator() { // Arrange - var concentrator = new Concentrator(); + var concentrator = new ConcentratorDto(); _ = MockHttpClient.When(HttpMethod.Post, "/api/lorawan/concentrators") .With(m => { - _ = m.Content.Should().BeAssignableTo>(); - var body = m.Content as ObjectContent; + _ = m.Content.Should().BeAssignableTo>(); + var body = m.Content as ObjectContent; _ = body.Value.Should().BeEquivalentTo(concentrator); return true; }) @@ -106,13 +106,13 @@ public async Task CreateConcentratorShouldCreateConcentrator() public async Task UpdateConcentratorShouldUpdateConcentrator() { // Arrange - var concentrator = new Concentrator(); + var concentrator = new ConcentratorDto(); _ = MockHttpClient.When(HttpMethod.Put, "/api/lorawan/concentrators") .With(m => { - _ = m.Content.Should().BeAssignableTo>(); - var body = m.Content as ObjectContent; + _ = m.Content.Should().BeAssignableTo>(); + var body = m.Content as ObjectContent; _ = body.Value.Should().BeEquivalentTo(concentrator); return true; }) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Client/Validators/ConcentratorValidatorTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Client/Validators/ConcentratorValidatorTests.cs index 772810fc6..5aba646c6 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Client/Validators/ConcentratorValidatorTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Client/Validators/ConcentratorValidatorTests.cs @@ -15,7 +15,7 @@ public void ValidateValidConcentrator() { // Arrange var concentratorValidator = new ConcentratorValidator(); - var concentrator = new Concentrator() + var concentrator = new ConcentratorDto() { DeviceId = "0123456789abcdef", DeviceName = Guid.NewGuid().ToString(), @@ -39,7 +39,7 @@ public void ValidateMissingFieldShouldReturnError( { // Arrange var concentratorValidator = new ConcentratorValidator(); - var concentrator = new Concentrator() + var concentrator = new ConcentratorDto() { DeviceId = "0123456789abcdef", DeviceName =DeviceNameValue, @@ -60,7 +60,7 @@ public void ValidateMissingDeviceIdShouldReturnError() { // Arrange var concentratorValidator = new ConcentratorValidator(); - var concentrator = new Concentrator() + var concentrator = new ConcentratorDto() { DeviceId = "", DeviceName = Guid.NewGuid().ToString(), @@ -83,7 +83,7 @@ public void ValidateBadFormatDeviceIdShouldReturnError() { // Arrange var concentratorValidator = new ConcentratorValidator(); - var concentrator = new Concentrator() + var concentrator = new ConcentratorDto() { DeviceId = Guid.NewGuid().ToString(), DeviceName = Guid.NewGuid().ToString(), @@ -104,7 +104,7 @@ public void ValidateBadFormatClientThumbprintShouldReturnError() { // Arrange var concentratorValidator = new ConcentratorValidator(); - var concentrator = new Concentrator() + var concentrator = new ConcentratorDto() { DeviceId = "0123456789abcdef", DeviceName = Guid.NewGuid().ToString(), @@ -126,7 +126,7 @@ public void ValidateAllFieldsEmptyShouldReturnError() { // Arrange var concentratorValidator = new ConcentratorValidator(); - var concentrator = new Concentrator(); + var concentrator = new ConcentratorDto(); // Act var concentratorValidation = concentratorValidator.Validate(concentrator); diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsControllerTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsControllerTest.cs index c97875da4..17a61f1dd 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsControllerTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsControllerTest.cs @@ -83,9 +83,9 @@ public async Task GetAllDeviceConcentratorWithDeviceTypeShouldReturnNotEmptyList }); _ = this.mockLoRaWANConcentratorService.Setup(c => c.GetAllDeviceConcentrator(It.IsAny>(), It.IsAny())) - .Returns((PaginationResult r, IUrlHelper h) => new PaginationResult + .Returns((PaginationResult r, IUrlHelper h) => new PaginationResult { - Items = r.Items.Select(x => new Concentrator { DeviceId = x.DeviceId }), + Items = r.Items.Select(x => new ConcentratorDto { DeviceId = x.DeviceId }), NextPage = r.NextPage, TotalItems = r.TotalItems }); @@ -101,7 +101,7 @@ public async Task GetAllDeviceConcentratorWithDeviceTypeShouldReturnNotEmptyList Assert.IsNotNull(okObjectResult); Assert.AreEqual(200, okObjectResult.StatusCode); Assert.IsNotNull(okObjectResult.Value); - var deviceList = okObjectResult.Value as PaginationResult; + var deviceList = okObjectResult.Value as PaginationResult; Assert.IsNotNull(deviceList); Assert.AreEqual(count, deviceList.Items.Count()); @@ -116,9 +116,9 @@ public async Task GetAllDeviceConcentratorWithNoDeviceTypeShouldReturnEmptyList( var concentratorsController = CreateLoRaWANConcentratorsController(); _ = this.mockLoRaWANConcentratorService.Setup(c => c.GetAllDeviceConcentrator(It.IsAny>(), It.IsAny())) - .Returns((PaginationResult r, IUrlHelper h) => new PaginationResult + .Returns((PaginationResult r, IUrlHelper h) => new PaginationResult { - Items = r.Items.Select(x => new Concentrator { DeviceId = x.DeviceId }), + Items = r.Items.Select(x => new ConcentratorDto { DeviceId = x.DeviceId }), NextPage = r.NextPage, TotalItems = r.TotalItems }); @@ -147,7 +147,7 @@ public async Task GetAllDeviceConcentratorWithNoDeviceTypeShouldReturnEmptyList( Assert.IsNotNull(okObjectResult); Assert.AreEqual(200, okObjectResult.StatusCode); - var deviceList = okObjectResult.Value as IEnumerable; + var deviceList = okObjectResult.Value as IEnumerable; Assert.IsNull(deviceList); @@ -186,7 +186,7 @@ public async Task GetDeviceConcentratorWithValidArgumentShouldReturnConcentrator // Arrange var twin = new Twin("aaa"); twin.Tags["deviceType"] = "LoRa Concentrator"; - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = twin.DeviceId, DeviceType = "LoRa Concentrator" @@ -211,8 +211,8 @@ public async Task GetDeviceConcentratorWithValidArgumentShouldReturnConcentrator Assert.IsNotNull(okObjectResult); Assert.AreEqual(200, okObjectResult.StatusCode); Assert.IsNotNull(okObjectResult.Value); - Assert.IsAssignableFrom(okObjectResult.Value); - var device = okObjectResult.Value as Concentrator; + Assert.IsAssignableFrom(okObjectResult.Value); + var device = okObjectResult.Value as ConcentratorDto; Assert.IsNotNull(device); Assert.AreEqual(twin.DeviceId, device.DeviceId); Assert.AreEqual("LoRa Concentrator", device.DeviceType); @@ -246,7 +246,7 @@ public async Task CreateDeviceAsyncWithValidArgumentShouldReturnOkResult() { // Arrange var concentratorController = CreateLoRaWANConcentratorsController(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -272,7 +272,7 @@ public async Task WhenDeviceCreationFailedShouldReturnBadRequest() { // Arrange var concentratorController = CreateLoRaWANConcentratorsController(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -300,7 +300,7 @@ public async Task WhenDeviceAlreadyExistsShouldReturnBadRequestWithAltreadyExist { // Arrange var concentratorController = CreateLoRaWANConcentratorsController(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -329,7 +329,7 @@ public async Task UpdateDeviceAsyncWithValidArgumentShouldReturnOkResult() { // Arrange var concentratorController = CreateLoRaWANConcentratorsController(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -337,7 +337,7 @@ public async Task UpdateDeviceAsyncWithValidArgumentShouldReturnOkResult() RouterConfig = new RouterConfig() }; - _ = this.mockLoRaWANConcentratorService.Setup(x => x.UpdateDeviceAsync(It.Is(c => c == concentrator))) + _ = this.mockLoRaWANConcentratorService.Setup(x => x.UpdateDeviceAsync(It.Is(c => c == concentrator))) .ReturnsAsync(true); // Act diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/ConcentratorTwinMapperTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/ConcentratorTwinMapperTests.cs index a5ccfe7d2..42cc625e1 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/ConcentratorTwinMapperTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/ConcentratorTwinMapperTests.cs @@ -53,13 +53,13 @@ public void CreateDeviceDetailsStateUnderTestExpectedBehavior() DeviceId = Guid.NewGuid().ToString() }; - twin.Tags[nameof(Concentrator.DeviceType).ToCamelCase()] = Guid.NewGuid().ToString(); - twin.Tags[nameof(Concentrator.DeviceName).ToCamelCase()] = Guid.NewGuid().ToString(); - twin.Tags[nameof(Concentrator.LoraRegion).ToCamelCase()] = Guid.NewGuid().ToString(); + twin.Tags[nameof(ConcentratorDto.DeviceType).ToCamelCase()] = Guid.NewGuid().ToString(); + twin.Tags[nameof(ConcentratorDto.DeviceName).ToCamelCase()] = Guid.NewGuid().ToString(); + twin.Tags[nameof(ConcentratorDto.LoraRegion).ToCamelCase()] = Guid.NewGuid().ToString(); twin.Properties.Reported["DevAddr"] = Guid.NewGuid().ToString(); - twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()] = new List() { Guid.NewGuid().ToString() }; + twin.Properties.Desired[nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()] = new List() { Guid.NewGuid().ToString() }; // Act var result = concentratorTwinMapper.CreateDeviceDetails(twin); @@ -70,13 +70,13 @@ public void CreateDeviceDetailsStateUnderTestExpectedBehavior() Assert.IsFalse(result.IsConnected); Assert.IsFalse(result.IsEnabled); - Assert.AreEqual(twin.Tags[nameof(Concentrator.DeviceName).ToCamelCase()].ToString(), result.DeviceName); - Assert.AreEqual(twin.Tags[nameof(Concentrator.LoraRegion).ToCamelCase()].ToString(), result.LoraRegion); - Assert.AreEqual(twin.Tags[nameof(Concentrator.DeviceType).ToCamelCase()].ToString(), result.DeviceType); + Assert.AreEqual(twin.Tags[nameof(ConcentratorDto.DeviceName).ToCamelCase()].ToString(), result.DeviceName); + Assert.AreEqual(twin.Tags[nameof(ConcentratorDto.LoraRegion).ToCamelCase()].ToString(), result.LoraRegion); + Assert.AreEqual(twin.Tags[nameof(ConcentratorDto.DeviceType).ToCamelCase()].ToString(), result.DeviceType); Assert.IsTrue(result.AlreadyLoggedInOnce); - Assert.AreEqual(twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()][0].ToString(), result.ClientThumbprint); + Assert.AreEqual(twin.Properties.Desired[nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()][0].ToString(), result.ClientThumbprint); this.mockRepository.VerifyAll(); } @@ -108,7 +108,7 @@ public void CreateDeviceDetailsClientThumbprintEmptyArrayExpectedBehavior() { DeviceId = Guid.NewGuid().ToString() }; - twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()] = new List(); + twin.Properties.Desired[nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()] = new List(); // Act var result = concentratorTwinMapper.CreateDeviceDetails(twin); @@ -128,7 +128,7 @@ public void CreateDeviceDetailsClientThumbprintBadFormatExpectedBehavior() { DeviceId = Guid.NewGuid().ToString() }; - twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()] = Guid.NewGuid().ToString(); + twin.Properties.Desired[nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()] = Guid.NewGuid().ToString(); // Act var result = concentratorTwinMapper.CreateDeviceDetails(twin); @@ -147,7 +147,7 @@ public void UpdateTwinStateUnderTestExpectedBehavior() var twin = new Twin(); - var item = new Concentrator + var item = new ConcentratorDto { LoraRegion = Guid.NewGuid().ToString(), DeviceName = Guid.NewGuid().ToString(), @@ -173,17 +173,17 @@ public void UpdateTwinStateUnderTestExpectedBehavior() DeviceHelper.SetTagValue(twin, nameof(item.DeviceType), item.DeviceType); DeviceHelper.SetTagValue(twin, nameof(item.LoraRegion), item.LoraRegion); - twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()] = new List() { item.ClientThumbprint }; + twin.Properties.Desired[nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()] = new List() { item.ClientThumbprint }; // Act concentratorTwinMapper.UpdateTwin(twin, item); // Assert - Assert.AreEqual(item.DeviceName, twin.Tags[nameof(Concentrator.DeviceName).ToCamelCase()].ToString()); - Assert.AreEqual(item.DeviceType, twin.Tags[nameof(Concentrator.DeviceType).ToCamelCase()].ToString()); - Assert.AreEqual(item.LoraRegion, twin.Tags[nameof(Concentrator.LoraRegion).ToCamelCase()].ToString()); + Assert.AreEqual(item.DeviceName, twin.Tags[nameof(ConcentratorDto.DeviceName).ToCamelCase()].ToString()); + Assert.AreEqual(item.DeviceType, twin.Tags[nameof(ConcentratorDto.DeviceType).ToCamelCase()].ToString()); + Assert.AreEqual(item.LoraRegion, twin.Tags[nameof(ConcentratorDto.LoraRegion).ToCamelCase()].ToString()); - Assert.AreEqual(item.ClientThumbprint, twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()][0].ToString()); + Assert.AreEqual(item.ClientThumbprint, twin.Properties.Desired[nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()][0].ToString()); this.mockRepository.VerifyAll(); } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs index 792d0521b..feee15180 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs @@ -58,7 +58,7 @@ public async Task CreateDeviceAsyncWithValidArgumentShouldReturnOkResult() { // Arrange var concentratorController = CreateLoRaWANConcentratorService(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -86,7 +86,7 @@ public async Task CreateDeviceAsyncWithValidArgumentShouldReturnOkResult() _ = this.mockRouterConfigManager.Setup(x => x.GetRouterConfig(It.Is(c => c == concentrator.LoraRegion))) .ReturnsAsync(routerConfig); - _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); + _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); // Act var result = await concentratorController.CreateDeviceAsync(concentrator); @@ -103,7 +103,7 @@ public void WhenDeviceAlreadyExistCreateDeviceAsyncWithValidArgumentShouldThrowA { // Arrange var concentratorController = CreateLoRaWANConcentratorService(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -127,7 +127,7 @@ public void WhenDeviceAlreadyExistCreateDeviceAsyncWithValidArgumentShouldThrowA _ = this.mockRouterConfigManager.Setup(x => x.GetRouterConfig(It.Is(c => c == concentrator.LoraRegion))) .ReturnsAsync(routerConfig); - _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); + _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); _ = this.mockLogger.Setup(x => x.Log(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())); // Act @@ -142,7 +142,7 @@ public async Task WhenDeviceCreationFailedWithInvalidOperationExceptionShouldRet { // Arrange var concentratorController = CreateLoRaWANConcentratorService(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -166,7 +166,7 @@ public async Task WhenDeviceCreationFailedWithInvalidOperationExceptionShouldRet _ = this.mockRouterConfigManager.Setup(x => x.GetRouterConfig(It.Is(c => c == concentrator.LoraRegion))) .ReturnsAsync(routerConfig); - _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); + _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); _ = this.mockLogger.Setup(x => x.Log(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())); // Act @@ -184,7 +184,7 @@ public async Task WhenDeviceCreationFailedShouldReturnFalse() { // Arrange var concentratorController = CreateLoRaWANConcentratorService(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -213,7 +213,7 @@ public async Task WhenDeviceCreationFailedShouldReturnFalse() _ = this.mockRouterConfigManager.Setup(x => x.GetRouterConfig(It.Is(c => c == concentrator.LoraRegion))) .ReturnsAsync(routerConfig); - _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); + _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); _ = this.mockLogger.Setup(x => x.Log(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())); // Act @@ -231,7 +231,7 @@ public async Task UpdateDeviceAsyncWithValidArgument() { // Arrange var concentratorService = CreateLoRaWANConcentratorService(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -248,7 +248,7 @@ public async Task UpdateDeviceAsyncWithValidArgument() _ = this.mockRouterConfigManager.Setup(x => x.GetRouterConfig(It.Is(c => c == concentrator.LoraRegion))) .ReturnsAsync(concentrator.RouterConfig); - _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); + _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); _ = this.mockDeviceService.Setup(x => x.GetDevice(It.Is(c => c == concentrator.DeviceId))) .ReturnsAsync(device); @@ -271,7 +271,7 @@ public async Task WhenGetDeviceThrowAnErrorUpdateDeviceAsyncWithValidArgumentSho { // Arrange var concentratorController = CreateLoRaWANConcentratorService(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -296,7 +296,7 @@ public async Task WhenUpdateDeviceThrowAnErrorUpdateDeviceAsyncWithValidArgument { // Arrange var concentratorController = CreateLoRaWANConcentratorService(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -326,7 +326,7 @@ public async Task WhenUpdateDeviceTwinThrowAnErrorUpdateDeviceAsyncWithValidArgu { // Arrange var concentratorController = CreateLoRaWANConcentratorService(); - var concentrator = new Concentrator + var concentrator = new ConcentratorDto { DeviceId = "4512457896451156", LoraRegion = Guid.NewGuid().ToString(), @@ -343,7 +343,7 @@ public async Task WhenUpdateDeviceTwinThrowAnErrorUpdateDeviceAsyncWithValidArgu _ = this.mockRouterConfigManager.Setup(x => x.GetRouterConfig(It.Is(c => c == concentrator.LoraRegion))) .ReturnsAsync(concentrator.RouterConfig); - _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); + _ = this.mockConcentratorTwinMapper.Setup(x => x.UpdateTwin(It.Is(c => c.DeviceId == twin.DeviceId), It.Is(c => c.DeviceId == concentrator.DeviceId))); _ = this.mockDeviceService.Setup(x => x.GetDevice(It.Is(c => c == concentrator.DeviceId))) .ReturnsAsync(device); @@ -372,7 +372,7 @@ public void GetAllConcentratorsTest() twinCollection["deviceType"] = "LoRa Concentrator"; _ = this.mockConcentratorTwinMapper.Setup(c => c.CreateDeviceDetails(It.IsAny())) - .Returns(x => new Concentrator + .Returns(x => new ConcentratorDto { DeviceId = x.DeviceId }); diff --git a/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/ConcentratorDetailPage.razor b/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/ConcentratorDetailPage.razor index 23809580d..882e6200b 100644 --- a/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/ConcentratorDetailPage.razor +++ b/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/ConcentratorDetailPage.razor @@ -116,7 +116,7 @@ [Parameter] public string DeviceID { get; set; } - private Concentrator concentrator { get; set; } = new(); + private ConcentratorDto concentrator { get; set; } = new(); private MudForm form; private ConcentratorValidator concentratorValidator = new(); private List FrequencyPlans = new List(); diff --git a/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/ConcentratorListPage.razor b/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/ConcentratorListPage.razor index ee3c16446..1ec4ec968 100644 --- a/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/ConcentratorListPage.razor +++ b/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/ConcentratorListPage.razor @@ -10,7 +10,7 @@ - + @@ -97,17 +97,17 @@ [CascadingParameter] public Error Error {get; set;} - private MudTable table; + private MudTable table; private readonly Dictionary pages = new(); private bool IsLoading { get; set; } = true; - private async Task> LoadItems(TableState state) + private async Task> LoadItems(TableState state) { try { - PaginationResult result; + PaginationResult result; if (pages.Keys.Contains(state.Page)) { @@ -126,7 +126,7 @@ IsLoading = false; - return new TableData + return new TableData { Items = result.Items, TotalItems = result.TotalItems @@ -135,7 +135,7 @@ catch (ProblemDetailsException exception) { Error?.ProcessProblemDetails(exception); - return new TableData(); + return new TableData(); } } @@ -144,7 +144,7 @@ NavigationManager.NavigateTo("lorawan/concentrators/new"); } - private async Task DeleteDevice(Concentrator device) + private async Task DeleteDevice(ConcentratorDto device) { var parameters = new DialogParameters {{"deviceId", device.DeviceId}}; @@ -171,7 +171,7 @@ table.ReloadServerData(); } - private void GoToDetails(Concentrator item) + private void GoToDetails(ConcentratorDto item) { NavigationManager.NavigateTo($"/lorawan/concentrators/{item.DeviceId}"); } diff --git a/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/CreateConcentratorPage.razor b/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/CreateConcentratorPage.razor index bbffe8efd..52fc1153a 100644 --- a/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/CreateConcentratorPage.razor +++ b/src/AzureIoTHub.Portal/Client/Pages/LoRaWAN/Concentrator/CreateConcentratorPage.razor @@ -37,7 +37,7 @@ @@ -57,7 +57,7 @@ @bind-Value="@concentrator.ClientThumbprint" Label="Client Certificate Thumbprint" Variant="Variant.Outlined" /> - + @foreach (var frequencyPlan in FrequencyPlans.OrderBy(c => c.Name)) { @frequencyPlan.Name @@ -96,7 +96,7 @@ [CascadingParameter] public Error Error {get; set;} - private Concentrator concentrator = new(); + private ConcentratorDto concentrator = new(); private MudForm form; private ConcentratorValidator concentratorValidator = new(); diff --git a/src/AzureIoTHub.Portal/Client/Services/ILoRaWanConcentratorsClientService.cs b/src/AzureIoTHub.Portal/Client/Services/ILoRaWanConcentratorsClientService.cs index dc1edd36f..0257cddb5 100644 --- a/src/AzureIoTHub.Portal/Client/Services/ILoRaWanConcentratorsClientService.cs +++ b/src/AzureIoTHub.Portal/Client/Services/ILoRaWanConcentratorsClientService.cs @@ -10,13 +10,13 @@ namespace AzureIoTHub.Portal.Client.Services public interface ILoRaWanConcentratorsClientService { - Task> GetConcentrators(string continuationUri); + Task> GetConcentrators(string continuationUri); - Task GetConcentrator(string deviceId); + Task GetConcentrator(string deviceId); - Task CreateConcentrator(Concentrator concentrator); + Task CreateConcentrator(ConcentratorDto concentrator); - Task UpdateConcentrator(Concentrator concentrator); + Task UpdateConcentrator(ConcentratorDto concentrator); Task DeleteConcentrator(string deviceId); diff --git a/src/AzureIoTHub.Portal/Client/Services/LoRaWanConcentratorsClientService.cs b/src/AzureIoTHub.Portal/Client/Services/LoRaWanConcentratorsClientService.cs index 4be4ab19b..e39c87a30 100644 --- a/src/AzureIoTHub.Portal/Client/Services/LoRaWanConcentratorsClientService.cs +++ b/src/AzureIoTHub.Portal/Client/Services/LoRaWanConcentratorsClientService.cs @@ -19,22 +19,22 @@ public LoRaWanConcentratorsClientService(HttpClient http) this.http = http; } - public Task> GetConcentrators(string continuationUri) + public Task> GetConcentrators(string continuationUri) { - return this.http.GetFromJsonAsync>(continuationUri); + return this.http.GetFromJsonAsync>(continuationUri); } - public Task GetConcentrator(string deviceId) + public Task GetConcentrator(string deviceId) { - return this.http.GetFromJsonAsync($"api/lorawan/concentrators/{deviceId}"); + return this.http.GetFromJsonAsync($"api/lorawan/concentrators/{deviceId}"); } - public Task CreateConcentrator(Concentrator concentrator) + public Task CreateConcentrator(ConcentratorDto concentrator) { return this.http.PostAsJsonAsync("api/lorawan/concentrators", concentrator); } - public Task UpdateConcentrator(Concentrator concentrator) + public Task UpdateConcentrator(ConcentratorDto concentrator) { return this.http.PutAsJsonAsync("api/lorawan/concentrators", concentrator); } diff --git a/src/AzureIoTHub.Portal/Client/Validators/ConcentratorValidator.cs b/src/AzureIoTHub.Portal/Client/Validators/ConcentratorValidator.cs index a02fdcf7b..133187eb9 100644 --- a/src/AzureIoTHub.Portal/Client/Validators/ConcentratorValidator.cs +++ b/src/AzureIoTHub.Portal/Client/Validators/ConcentratorValidator.cs @@ -6,7 +6,7 @@ namespace AzureIoTHub.Portal.Client.Validators using AzureIoTHub.Portal.Models.v10.LoRaWAN; using FluentValidation; - public class ConcentratorValidator : AbstractValidator + public class ConcentratorValidator : AbstractValidator { public ConcentratorValidator() { diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsController.cs index 5a1a11b25..6919968d7 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANConcentratorsController.cs @@ -67,7 +67,7 @@ public LoRaWANConcentratorsController( /// [HttpGet(Name = "GET LoRaWAN Concentrator list")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetAllDeviceConcentrator( + public async Task>> GetAllDeviceConcentrator( string continuationToken = null, int pageSize = 10) { @@ -86,7 +86,7 @@ public async Task>> GetAllDeviceConc /// The device identifier. [HttpGet("{deviceId}", Name = "GET LoRaWAN Concentrator")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetDeviceConcentrator(string deviceId) + public async Task> GetDeviceConcentrator(string deviceId) { var item = await this.externalDevicesService.GetDeviceTwin(deviceId); return Ok(this.concentratorTwinMapper.CreateDeviceDetails(item)); @@ -99,7 +99,7 @@ public async Task> GetDeviceConcentrator(string devic [HttpPost(Name = "POST Create LoRaWAN concentrator")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task CreateDeviceAsync(Concentrator device) + public async Task CreateDeviceAsync(ConcentratorDto device) { if (!ModelState.IsValid) { @@ -130,7 +130,7 @@ public async Task CreateDeviceAsync(Concentrator device) [HttpPut(Name = "PUT Update LoRaWAN concentrator")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task UpdateDeviceAsync(Concentrator device) + public async Task UpdateDeviceAsync(ConcentratorDto device) { if (!ModelState.IsValid) { diff --git a/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs b/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs new file mode 100644 index 000000000..07961a8f5 --- /dev/null +++ b/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs @@ -0,0 +1,110 @@ +// 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.Jobs +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AutoMapper; + using Domain; + using AzureIoTHub.Portal.Domain.Entities; + using Domain.Repositories; + using Services; + using Microsoft.Azure.Devices.Shared; + using Microsoft.Extensions.Logging; + using Quartz; + + [DisallowConcurrentExecution] + public class SyncConcentratorsJob : IJob + { + private readonly ILoRaWANConcentratorService concentratorService; + private readonly IConcentratorRepository concentratorRepository; + private readonly IExternalDeviceService externalDeviceService; + private readonly IMapper mapper; + private readonly IUnitOfWork unitOfWork; + private readonly ILogger logger; + + //private const string ModelId = "modelId"; + + public SyncConcentratorsJob(ILoRaWANConcentratorService concentratorService, + IConcentratorRepository concentratorRepository, + IExternalDeviceService externalDeviceService, + IMapper mapper, + IUnitOfWork unitOfWork, + ILogger logger) + { + this.concentratorService = concentratorService; + this.concentratorRepository = concentratorRepository; + this.externalDeviceService = externalDeviceService; + this.mapper = mapper; + this.unitOfWork = unitOfWork; + this.logger = logger; + } + + public async Task Execute(IJobExecutionContext context) + { + try + { + this.logger.LogInformation("Start of sync concentrators job"); + + await SyncConcentrators(); + + this.logger.LogInformation("End of sync concentrators job"); + } + catch (Exception e) + { + this.logger.LogError(e, "Sync concentrators job has failed"); + } + } + + private async Task SyncConcentrators() + { + foreach (var twin in await GetTwinConcentrators()) + { + await CreateOrUpdateConcentrator(twin); + } + + await this.unitOfWork.SaveAsync(); + } + + private async Task> GetTwinConcentrators() + { + var twins = new List(); + var continuationToken = string.Empty; + + int totalTwinConcentrators; + do + { + var result = await this.externalDeviceService.GetAllDevice(continuationToken: continuationToken,filterDeviceType: "LoRa Concentrator", pageSize: 100); + //var pouet = this.concentratorService.GetAllDeviceConcentrator(result, this.Url); + + twins.AddRange(result.Items); + + totalTwinConcentrators = result.TotalItems; + continuationToken = result.NextPage; + + } while (totalTwinConcentrators > twins.Count); + + return twins; + } + + private async Task CreateOrUpdateConcentrator(Twin twin) + { + var concentrator = this.mapper.Map(twin); + + var concentratorEntity = await this.concentratorRepository.GetByIdAsync(concentrator.Id); + if (concentratorEntity == null) + { + await this.concentratorRepository.InsertAsync(concentrator); + } + else + { + if (concentratorEntity.Version >= concentratorEntity.Version) return; + + _ = this.mapper.Map(concentrator, concentratorEntity); + this.concentratorRepository.Update(concentratorEntity); + } + } + } +} diff --git a/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorProfile.cs b/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorProfile.cs new file mode 100644 index 000000000..d5cf0b3f3 --- /dev/null +++ b/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorProfile.cs @@ -0,0 +1,68 @@ +// 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.Mappers +{ + using AutoMapper; + using AzureIoTHub.Portal.Domain.Entities; + using Models.v10.LoRaWAN; + using Microsoft.Azure.Devices.Shared; + using AzureIoTHub.Portal.Server.Extensions; + using AzureIoTHub.Portal.Server.Helpers; + using Newtonsoft.Json; + + public class ConcentratorProfile : Profile + { + public ConcentratorProfile() + { + _ = CreateMap(); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceId)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.DeviceName)); + + _ = CreateMap() + .ForMember(dest => dest.DeviceId, opts => opts.MapFrom(src => src.Id)) + .ForMember(dest => dest.DeviceName, opts => opts.MapFrom(src => src.Name)); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.DeviceId)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.Tags["deviceName"])) + .ForMember(dest => dest.Version, opts => opts.MapFrom(src => src.Version)) + .ForMember(dest => dest.IsConnected, opts => opts.MapFrom(src => src.ConnectionState == Microsoft.Azure.Devices.DeviceConnectionState.Connected)) + .ForMember(dest => dest.IsEnabled, opts => opts.MapFrom(src => src.Status == Microsoft.Azure.Devices.DeviceStatus.Enabled)) + .ForMember(dest => dest.ClientThumbprint, opts => opts.MapFrom(src => RetrieveClientThumbprintValue(src))) + .ForMember(dest => dest.LoraRegion, opts => opts.MapFrom(src => src.Tags["loraRegion"])) + .ForMember(dest => dest.DeviceType, opts => opts.MapFrom(src => src.Tags["deviceType"])); + } + + private static string RetrieveClientThumbprintValue(Twin twin) + { + var serializedClientThumbprint = DeviceHelper.RetrieveDesiredPropertyValue(twin, nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()); + + if (serializedClientThumbprint == null) + { + // clientThumbprint does not exist in the device twin + return null; + } + + try + { + var clientThumbprintArray=JsonConvert.DeserializeObject(serializedClientThumbprint); + + if (clientThumbprintArray.Length == 0) + { + // clientThumbprint array is empty in the device twin + return null; + } + + return clientThumbprintArray[0]; + } + catch (JsonReaderException) + { + // clientThumbprint is not an array in the device twin + return null; + } + } + } +} diff --git a/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorTwinMapper.cs b/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorTwinMapper.cs index 000f73fe8..e3b88acd3 100644 --- a/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorTwinMapper.cs +++ b/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorTwinMapper.cs @@ -13,26 +13,26 @@ namespace AzureIoTHub.Portal.Server.Mappers public class ConcentratorTwinMapper : IConcentratorTwinMapper { - public Concentrator CreateDeviceDetails(Twin twin) + public ConcentratorDto CreateDeviceDetails(Twin twin) { ArgumentNullException.ThrowIfNull(twin, nameof(twin)); - return new Concentrator + return new ConcentratorDto { DeviceId = twin.DeviceId, - DeviceName = DeviceHelper.RetrieveTagValue(twin, nameof(Concentrator.DeviceName)), - LoraRegion = DeviceHelper.RetrieveTagValue(twin, nameof(Concentrator.LoraRegion)), + DeviceName = DeviceHelper.RetrieveTagValue(twin, nameof(ConcentratorDto.DeviceName)), + LoraRegion = DeviceHelper.RetrieveTagValue(twin, nameof(ConcentratorDto.LoraRegion)), ClientThumbprint = RetrieveClientThumbprintValue(twin), IsEnabled = twin.Status == DeviceStatus.Enabled, IsConnected = twin.ConnectionState == DeviceConnectionState.Connected, AlreadyLoggedInOnce = DeviceHelper.RetrieveReportedPropertyValue(twin, "DevAddr") != null, - DeviceType = DeviceHelper.RetrieveTagValue(twin, nameof(Concentrator.DeviceType)) + DeviceType = DeviceHelper.RetrieveTagValue(twin, nameof(ConcentratorDto.DeviceType)) }; } private static string RetrieveClientThumbprintValue(Twin twin) { - var serializedClientThumbprint = DeviceHelper.RetrieveDesiredPropertyValue(twin, nameof(Concentrator.ClientThumbprint).ToCamelCase()); + var serializedClientThumbprint = DeviceHelper.RetrieveDesiredPropertyValue(twin, nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()); if (serializedClientThumbprint == null) { @@ -59,7 +59,7 @@ private static string RetrieveClientThumbprintValue(Twin twin) } } - public void UpdateTwin(Twin twin, Concentrator item) + public void UpdateTwin(Twin twin, ConcentratorDto item) { ArgumentNullException.ThrowIfNull(twin, nameof(twin)); ArgumentNullException.ThrowIfNull(item, nameof(item)); diff --git a/src/AzureIoTHub.Portal/Server/Mappers/IConcentratorTwinMapper.cs b/src/AzureIoTHub.Portal/Server/Mappers/IConcentratorTwinMapper.cs index 0ac08fe2b..e2fb5d66b 100644 --- a/src/AzureIoTHub.Portal/Server/Mappers/IConcentratorTwinMapper.cs +++ b/src/AzureIoTHub.Portal/Server/Mappers/IConcentratorTwinMapper.cs @@ -8,8 +8,8 @@ namespace AzureIoTHub.Portal.Server.Mappers public interface IConcentratorTwinMapper { - Concentrator CreateDeviceDetails(Twin twin); + ConcentratorDto CreateDeviceDetails(Twin twin); - void UpdateTwin(Twin twin, Concentrator item); + void UpdateTwin(Twin twin, ConcentratorDto item); } } diff --git a/src/AzureIoTHub.Portal/Server/Services/ILoRaWANConcentratorService.cs b/src/AzureIoTHub.Portal/Server/Services/ILoRaWANConcentratorService.cs index 4e473e6fc..21c5d1ccb 100644 --- a/src/AzureIoTHub.Portal/Server/Services/ILoRaWANConcentratorService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/ILoRaWANConcentratorService.cs @@ -10,8 +10,8 @@ namespace AzureIoTHub.Portal.Server.Services public interface ILoRaWANConcentratorService { - Task CreateDeviceAsync(Concentrator device); - Task UpdateDeviceAsync(Concentrator device); - PaginationResult GetAllDeviceConcentrator(PaginationResult twinResults, IUrlHelper urlHelper); + Task CreateDeviceAsync(ConcentratorDto device); + Task UpdateDeviceAsync(ConcentratorDto device); + PaginationResult GetAllDeviceConcentrator(PaginationResult twinResults, IUrlHelper urlHelper); } } diff --git a/src/AzureIoTHub.Portal/Server/Services/LoRaWANConcentratorService.cs b/src/AzureIoTHub.Portal/Server/Services/LoRaWANConcentratorService.cs index 3eb1657ea..ab656655e 100644 --- a/src/AzureIoTHub.Portal/Server/Services/LoRaWANConcentratorService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/LoRaWANConcentratorService.cs @@ -51,7 +51,7 @@ public LoRaWANConcentratorService( this.routerConfigManager = routerConfigManager; } - public async Task CreateDeviceAsync(Concentrator device) + public async Task CreateDeviceAsync(ConcentratorDto device) { try { @@ -92,7 +92,7 @@ public async Task CreateDeviceAsync(Concentrator device) return true; } - public PaginationResult GetAllDeviceConcentrator(PaginationResult twinResults, IUrlHelper urlHelper) + public PaginationResult GetAllDeviceConcentrator(PaginationResult twinResults, IUrlHelper urlHelper) { string nextPage = null; @@ -107,7 +107,7 @@ public PaginationResult GetAllDeviceConcentrator(PaginationResult< }); } - return new PaginationResult + return new PaginationResult { Items = twinResults.Items.Select(this.concentratorTwinMapper.CreateDeviceDetails), TotalItems = twinResults.TotalItems, @@ -115,7 +115,7 @@ public PaginationResult GetAllDeviceConcentrator(PaginationResult< }; } - public async Task UpdateDeviceAsync(Concentrator device) + public async Task UpdateDeviceAsync(ConcentratorDto device) { ArgumentNullException.ThrowIfNull(device, nameof(device)); diff --git a/src/AzureIoTHub.Portal/Server/Startup.cs b/src/AzureIoTHub.Portal/Server/Startup.cs index f52399d62..2b015afc4 100644 --- a/src/AzureIoTHub.Portal/Server/Startup.cs +++ b/src/AzureIoTHub.Portal/Server/Startup.cs @@ -155,6 +155,7 @@ public void ConfigureServices(IServiceCollection services) _ = services.AddScoped(); _ = services.AddScoped(); _ = services.AddScoped(); + _ = services.AddScoped(); _ = services.AddMudServices(); @@ -314,6 +315,14 @@ Specify the authorization token got from your IDP as a header. .WithSimpleSchedule(s => s .WithIntervalInMinutes(configuration.SyncDatabaseJobRefreshIntervalInMinutes) .RepeatForever())); + + _ = q.AddJob(j => j.WithIdentity(nameof(SyncConcentratorsJob))) + .AddTrigger(t => t + .WithIdentity($"{nameof(SyncConcentratorsJob)}") + .ForJob(nameof(SyncConcentratorsJob)) + .WithSimpleSchedule(s => s + .WithIntervalInMinutes(configuration.SyncDatabaseJobRefreshIntervalInMinutes) + .RepeatForever())); }); diff --git a/src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/Concentrator.cs b/src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/ConcentratorDto.cs similarity index 98% rename from src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/Concentrator.cs rename to src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/ConcentratorDto.cs index f63401e86..a0423e68b 100644 --- a/src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/Concentrator.cs +++ b/src/AzureIoTHub.Portal/Shared/Models/v1.0/LoRaWAN/ConcentratorDto.cs @@ -8,7 +8,7 @@ namespace AzureIoTHub.Portal.Models.v10.LoRaWAN /// /// LoRaWAN Concentrator. /// - public class Concentrator + public class ConcentratorDto { /// /// The device identifier. diff --git a/src/AzureIoTHubPortal.Domain/Entities/Concentrator.cs b/src/AzureIoTHubPortal.Domain/Entities/Concentrator.cs new file mode 100644 index 000000000..09fe8208c --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/Entities/Concentrator.cs @@ -0,0 +1,44 @@ +// 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.Domain.Entities +{ + using System.ComponentModel.DataAnnotations; + using AzureIoTHub.Portal.Domain.Base; + + public class Concentrator : EntityBase + { + /// + /// The name of the device. + /// + public string Name { get; set; } + + /// + /// The lora region. + /// + [Required] + public string LoraRegion { get; set; } + + /// + /// The type of the device. + /// + public string DeviceType { get; set; } + + /// + /// The client certificate thumbprint. + /// + public string ClientThumbprint { get; set; } + + /// + /// true if this instance is connected; otherwise, false. + /// + public bool IsConnected { get; set; } + + /// + /// true if this instance is enabled; otherwise, false. + /// + public bool IsEnabled { get; set; } + + public int Version { get; set; } + } +} diff --git a/src/AzureIoTHubPortal.Domain/Repositories/IConcentratorRepository.cs b/src/AzureIoTHubPortal.Domain/Repositories/IConcentratorRepository.cs new file mode 100644 index 000000000..d87fb4885 --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/Repositories/IConcentratorRepository.cs @@ -0,0 +1,11 @@ +// 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.Domain.Repositories +{ + using Entities; + + public interface IConcentratorRepository : IRepository + { + } +} From adfaeceb52cedda59787990bcdf8e60be582d552 Mon Sep 17 00:00:00 2001 From: Audrey Serra Date: Thu, 6 Oct 2022 10:16:16 +0200 Subject: [PATCH 2/6] Fix typo in SyncConcentratorJobs --- src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs b/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs index 07961a8f5..1aa3eca67 100644 --- a/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs +++ b/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs @@ -77,7 +77,6 @@ private async Task> GetTwinConcentrators() do { var result = await this.externalDeviceService.GetAllDevice(continuationToken: continuationToken,filterDeviceType: "LoRa Concentrator", pageSize: 100); - //var pouet = this.concentratorService.GetAllDeviceConcentrator(result, this.Url); twins.AddRange(result.Items); @@ -100,7 +99,7 @@ private async Task CreateOrUpdateConcentrator(Twin twin) } else { - if (concentratorEntity.Version >= concentratorEntity.Version) return; + if (concentratorEntity.Version >= concentrator.Version) return; _ = this.mapper.Map(concentrator, concentratorEntity); this.concentratorRepository.Update(concentratorEntity); From 6c770cd6d81f84067f047ef7896d23507c850bf8 Mon Sep 17 00:00:00 2001 From: Audrey Serra Date: Thu, 6 Oct 2022 11:54:53 +0200 Subject: [PATCH 3/6] Add SynConcentratorsJobTests --- .../Server/Jobs/SyncConcentratorsJobTests.cs | 214 ++++++++++++++++++ ...viceJobTests.cs => SyncDevicesJobTests.cs} | 2 +- .../Server/Jobs/SyncConcentratorsJob.cs | 6 +- 3 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncConcentratorsJobTests.cs rename src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/{SyncDeviceJobTests.cs => SyncDevicesJobTests.cs} (99%) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncConcentratorsJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncConcentratorsJobTests.cs new file mode 100644 index 000000000..f1af63ff9 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncConcentratorsJobTests.cs @@ -0,0 +1,214 @@ +// 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.Jobs +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AutoFixture; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Server.Jobs; + using AzureIoTHub.Portal.Server.Services; + using Microsoft.Azure.Devices.Shared; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + using Portal.Domain.Entities; + using Quartz; + using UnitTests.Bases; + + public class SyncConcentratorsTests : BackendUnitTest + { + private IJob syncConcentratorsJob; + + private Mock mockExternalDeviceService; + private Mock mockConcentratorRepository; + private Mock mockUnitOfWork; + + public override void Setup() + { + base.Setup(); + + this.mockExternalDeviceService = MockRepository.Create(); + this.mockConcentratorRepository = MockRepository.Create(); + this.mockUnitOfWork = MockRepository.Create(); + + _ = ServiceCollection.AddSingleton(this.mockExternalDeviceService.Object); + _ = ServiceCollection.AddSingleton(this.mockConcentratorRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); + _ = ServiceCollection.AddSingleton(); + + Services = ServiceCollection.BuildServiceProvider(); + + this.syncConcentratorsJob = Services.GetRequiredService(); + } + + [Test] + public async Task ExecuteNewConcentratorShouldCreateEntity() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedTwinConcentrator = new Twin + { + DeviceId = Fixture.Create(), + Tags = new TwinCollection + { + ["deviceName"] = Fixture.Create(), + ["loraRegion"] = Fixture.Create(), + ["deviceType"] = Fixture.Create() + } + }; + + _ = this.mockExternalDeviceService + .Setup(x => x.GetAllDevice( + It.IsAny(), + It.Is(x => x == "LoRa Concentrator"), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.Is(x => x == 100))) + .ReturnsAsync(new PaginationResult + { + Items = new List + { + expectedTwinConcentrator + }, + TotalItems = 1 + }); + + _ = this.mockConcentratorRepository.Setup(repository => repository.GetByIdAsync(expectedTwinConcentrator.DeviceId)) + .ReturnsAsync((Concentrator)null); + + _ = this.mockConcentratorRepository.Setup(repository => repository.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncConcentratorsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteExistingConcentratorWithGreaterVersionShouldUpdateEntity() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedTwinConcentrator = new Twin + { + DeviceId = Fixture.Create(), + Tags = new TwinCollection + { + ["deviceName"] = Fixture.Create(), + ["loraRegion"] = Fixture.Create(), + ["deviceType"] = Fixture.Create() + }, + Version = 2 + }; + + var existingConcentrator = new Concentrator + { + Id = expectedTwinConcentrator.DeviceId, + Version = 1, + }; + + _ = this.mockExternalDeviceService + .Setup(x => x.GetAllDevice( + It.IsAny(), + It.Is(x => x == "LoRa Concentrator"), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.Is(x => x == 100))) + .ReturnsAsync(new PaginationResult + { + Items = new List + { + expectedTwinConcentrator + }, + TotalItems = 1 + }); + + _ = this.mockConcentratorRepository.Setup(repository => repository.GetByIdAsync(expectedTwinConcentrator.DeviceId)) + .ReturnsAsync(existingConcentrator); + + this.mockConcentratorRepository.Setup(repository => repository.Update(It.IsAny())) + .Verifiable(); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncConcentratorsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteExistingConcentratorWithLowerOrEqualVersionShouldReturn() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedTwinConcentrator = new Twin + { + DeviceId = Fixture.Create(), + Tags = new TwinCollection + { + ["deviceName"] = Fixture.Create(), + ["loraRegion"] = Fixture.Create(), + ["deviceType"] = Fixture.Create() + }, + Version = 1 + }; + + var existingConcentrator = new Concentrator + { + Id = expectedTwinConcentrator.DeviceId, + Version = 1, + }; + + _ = this.mockExternalDeviceService + .Setup(x => x.GetAllDevice( + It.IsAny(), + It.Is(x => x == "LoRa Concentrator"), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.Is(x => x == 100))) + .ReturnsAsync(new PaginationResult + { + Items = new List + { + expectedTwinConcentrator + }, + TotalItems = 1 + }); + + _ = this.mockConcentratorRepository.Setup(repository => repository.GetByIdAsync(expectedTwinConcentrator.DeviceId)) + .ReturnsAsync(existingConcentrator); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncConcentratorsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDeviceJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDevicesJobTests.cs similarity index 99% rename from src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDeviceJobTests.cs rename to src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDevicesJobTests.cs index 495236d18..8d47d9203 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDeviceJobTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncDevicesJobTests.cs @@ -18,7 +18,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Jobs using Quartz; using UnitTests.Bases; - public class SyncDeviceJobTests : BackendUnitTest + public class SyncDevicesJobTests : BackendUnitTest { private IJob syncDevicesJob; diff --git a/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs b/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs index 1aa3eca67..a8d116c93 100644 --- a/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs +++ b/src/AzureIoTHub.Portal/Server/Jobs/SyncConcentratorsJob.cs @@ -18,23 +18,19 @@ namespace AzureIoTHub.Portal.Server.Jobs [DisallowConcurrentExecution] public class SyncConcentratorsJob : IJob { - private readonly ILoRaWANConcentratorService concentratorService; private readonly IConcentratorRepository concentratorRepository; private readonly IExternalDeviceService externalDeviceService; private readonly IMapper mapper; private readonly IUnitOfWork unitOfWork; private readonly ILogger logger; - //private const string ModelId = "modelId"; - - public SyncConcentratorsJob(ILoRaWANConcentratorService concentratorService, + public SyncConcentratorsJob( IConcentratorRepository concentratorRepository, IExternalDeviceService externalDeviceService, IMapper mapper, IUnitOfWork unitOfWork, ILogger logger) { - this.concentratorService = concentratorService; this.concentratorRepository = concentratorRepository; this.externalDeviceService = externalDeviceService; this.mapper = mapper; From 1bb42fa110e3051c1a06e740313f24590e073f53 Mon Sep 17 00:00:00 2001 From: Audrey Serra Date: Thu, 6 Oct 2022 14:40:11 +0200 Subject: [PATCH 4/6] Add migration --- ...0221005134349_Add Concentrator.Designer.cs | 427 ++++++++++++++++++ .../20221005134349_Add Concentrator.cs | 39 ++ .../PortalDbContextModelSnapshot.cs | 35 ++ .../PortalDbContext.cs | 6 +- 4 files changed, 502 insertions(+), 5 deletions(-) create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Migrations/20221005134349_Add Concentrator.Designer.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Migrations/20221005134349_Add Concentrator.cs diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/20221005134349_Add Concentrator.Designer.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20221005134349_Add Concentrator.Designer.cs new file mode 100644 index 000000000..72a712257 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20221005134349_Add Concentrator.Designer.cs @@ -0,0 +1,427 @@ +// +using System; +using AzureIoTHub.Portal.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace AzureIoTHub.Portal.Infrastructure.Migrations +{ + [DbContext(typeof(PortalDbContext))] + [Migration("20221005134349_Add Concentrator")] + partial class AddConcentrator + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.Concentrator", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientThumbprint") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeviceType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConnected") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LoraRegion") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Concentrators"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.Device", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConnected") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("StatusUpdatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ABPRelaxMode") + .HasColumnType("boolean"); + + b.Property("AppEUI") + .HasColumnType("text"); + + b.Property("ClassType") + .HasColumnType("integer"); + + b.Property("Deduplication") + .HasColumnType("integer"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Downlink") + .HasColumnType("boolean"); + + b.Property("IsBuiltin") + .HasColumnType("boolean"); + + b.Property("KeepAliveTimeout") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PreferredWindow") + .HasColumnType("integer"); + + b.Property("RXDelay") + .HasColumnType("integer"); + + b.Property("SensorDecoder") + .HasColumnType("text"); + + b.Property("SupportLoRaFeatures") + .HasColumnType("boolean"); + + b.Property("UseOTAA") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DeviceModels"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Confirmed") + .HasColumnType("boolean"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Frame") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsBuiltin") + .HasColumnType("boolean"); + + b.Property("Port") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelCommands"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceModelProperty", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsWritable") + .HasColumnType("boolean"); + + b.Property("ModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("PropertyType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("DeviceModelProperties"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceTag", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.Property("Required") + .HasColumnType("boolean"); + + b.Property("Searchable") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("DeviceTags"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("DeviceId") + .HasColumnType("text"); + + b.Property("LorawanDeviceId") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("LorawanDeviceId"); + + b.ToTable("DeviceTagValues"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.EdgeDeviceModel", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModels"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.EdgeDeviceModelCommand", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("EdgeDeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModuleName") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("EdgeDeviceModelCommands"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ABPRelaxMode") + .HasColumnType("boolean"); + + b.Property("AlreadyLoggedInOnce") + .HasColumnType("boolean"); + + b.Property("AppEUI") + .HasColumnType("text"); + + b.Property("AppKey") + .HasColumnType("text"); + + b.Property("AppSKey") + .HasColumnType("text"); + + b.Property("ClassType") + .HasColumnType("integer"); + + b.Property("DataRate") + .HasColumnType("text"); + + b.Property("Deduplication") + .HasColumnType("integer"); + + b.Property("DevAddr") + .HasColumnType("text"); + + b.Property("DeviceModelId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Downlink") + .HasColumnType("boolean"); + + b.Property("FCntDownStart") + .HasColumnType("integer"); + + b.Property("FCntResetCounter") + .HasColumnType("integer"); + + b.Property("FCntUpStart") + .HasColumnType("integer"); + + b.Property("GatewayID") + .HasColumnType("text"); + + b.Property("IsConnected") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("KeepAliveTimeout") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("NbRep") + .HasColumnType("text"); + + b.Property("NwkSKey") + .HasColumnType("text"); + + b.Property("PreferredWindow") + .HasColumnType("integer"); + + b.Property("RX1DROffset") + .HasColumnType("integer"); + + b.Property("RX2DataRate") + .HasColumnType("integer"); + + b.Property("RXDelay") + .HasColumnType("integer"); + + b.Property("ReportedRX1DROffset") + .HasColumnType("text"); + + b.Property("ReportedRX2DataRate") + .HasColumnType("text"); + + b.Property("ReportedRXDelay") + .HasColumnType("text"); + + b.Property("SensorDecoder") + .HasColumnType("text"); + + b.Property("StatusUpdatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Supports32BitFCnt") + .HasColumnType("boolean"); + + b.Property("TxPower") + .HasColumnType("text"); + + b.Property("UseOTAA") + .HasColumnType("boolean"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("LorawanDevices"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.DeviceTagValue", b => + { + b.HasOne("AzureIoTHub.Portal.Domain.Entities.Device", null) + .WithMany("Tags") + .HasForeignKey("DeviceId"); + + b.HasOne("AzureIoTHub.Portal.Domain.Entities.LorawanDevice", null) + .WithMany("Tags") + .HasForeignKey("LorawanDeviceId"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.Device", b => + { + b.Navigation("Tags"); + }); + + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.LorawanDevice", b => + { + b.Navigation("Tags"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/20221005134349_Add Concentrator.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20221005134349_Add Concentrator.cs new file mode 100644 index 000000000..426cbddea --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20221005134349_Add Concentrator.cs @@ -0,0 +1,39 @@ +// Copyright (c) CGI France. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#nullable disable + +namespace AzureIoTHub.Portal.Infrastructure.Migrations +{ + using Microsoft.EntityFrameworkCore.Migrations; + + public partial class AddConcentrator : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.CreateTable( + name: "Concentrators", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + LoraRegion = table.Column(type: "text", nullable: false), + DeviceType = table.Column(type: "text", nullable: false), + ClientThumbprint = table.Column(type: "text", nullable: false), + IsConnected = table.Column(type: "boolean", nullable: false), + IsEnabled = table.Column(type: "boolean", nullable: false), + Version = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + _ = table.PrimaryKey("PK_Concentrators", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropTable( + name: "Concentrators"); + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs index 9636dcf32..195f59bb7 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs @@ -22,6 +22,41 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.Concentrator", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ClientThumbprint") + .IsRequired() + .HasColumnType("text"); + + b.Property("DeviceType") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsConnected") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LoraRegion") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("Concentrators"); + }); + modelBuilder.Entity("AzureIoTHub.Portal.Domain.Entities.Device", b => { b.Property("Id") diff --git a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs index c8d95ea4d..b84c9a7fc 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs @@ -3,13 +3,9 @@ namespace AzureIoTHub.Portal.Infrastructure { - using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Entities; - using AzureIoTHub.Portal.Infrastructure.Seeds; using Microsoft.EntityFrameworkCore; - using Microsoft.EntityFrameworkCore.Infrastructure; - using Microsoft.Extensions.Logging; - using Newtonsoft.Json; + public class PortalDbContext : DbContext { From 68fd52d1989324bfc70c3bb66f08db8a6e6d41c1 Mon Sep 17 00:00:00 2001 From: Audrey Serra Date: Thu, 6 Oct 2022 15:37:22 +0200 Subject: [PATCH 5/6] Add clientThumbprint property in SyncConcentratorJobTests --- .../Server/Jobs/SyncConcentratorsJobTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncConcentratorsJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncConcentratorsJobTests.cs index f1af63ff9..a19f81b66 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncConcentratorsJobTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Jobs/SyncConcentratorsJobTests.cs @@ -57,7 +57,8 @@ public async Task ExecuteNewConcentratorShouldCreateEntity() { ["deviceName"] = Fixture.Create(), ["loraRegion"] = Fixture.Create(), - ["deviceType"] = Fixture.Create() + ["deviceType"] = Fixture.Create(), + ["clientThumbprint"] = Fixture.Create() } }; From a1cef54022d868015cf736dd3eaaf9060a914bf0 Mon Sep 17 00:00:00 2001 From: Audrey Serra Date: Fri, 7 Oct 2022 10:12:44 +0200 Subject: [PATCH 6/6] RetrieveClientThumbprintValue() moved to DeviceHelper --- .../Server/Helpers/DeviceHelperTests.cs | 72 +++++++++++++++++++ .../Mappers/ConcentratorTwinMapperTests.cs | 59 --------------- .../Server/Helpers/DeviceHelper.cs | 30 ++++++++ .../Server/Mappers/ConcentratorProfile.cs | 33 +-------- .../Server/Mappers/ConcentratorTwinMapper.cs | 32 +-------- 5 files changed, 104 insertions(+), 122 deletions(-) diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Helpers/DeviceHelperTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Helpers/DeviceHelperTests.cs index 9957c0934..036d22f7a 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Helpers/DeviceHelperTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Helpers/DeviceHelperTests.cs @@ -5,6 +5,8 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Helpers { using System; using System.Collections.Generic; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Server.Extensions; using AzureIoTHub.Portal.Server.Helpers; using FluentAssertions; using Microsoft.Azure.Devices.Provisioning.Service; @@ -437,5 +439,75 @@ public void RetrievePropertyValueEmptyTwinReturnsNull() // Assert Assert.IsNull(result); } + + [Test] + public void RetrieveClientThumbprintShouldReturnClientThumbprintValue() + { + // Arrange + var expectedThumbprint = Guid.NewGuid().ToString(); + var twin = new Twin + { + DeviceId = Guid.NewGuid().ToString() + }; + twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()] = new List() { expectedThumbprint }; + + + // Act + var result = DeviceHelper.RetrieveClientThumbprintValue(twin); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedThumbprint, result); + } + + [Test] + public void RetrieveClientThumbprintValueNotExistShouldReturnNull() + { + // Arrange + var twin = new Twin + { + DeviceId = Guid.NewGuid().ToString() + }; + + // Act + var result = DeviceHelper.RetrieveClientThumbprintValue(twin); + + // Assert + Assert.IsNull(result); + } + + [Test] + public void RetrieveClientThumbprintValueEmptyArrayShouldReturnNull() + { + // Arrange + var twin = new Twin + { + DeviceId = Guid.NewGuid().ToString() + }; + twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()] = new List(); + + // Act + var result = DeviceHelper.RetrieveClientThumbprintValue(twin); + + // Assert + Assert.IsNull(result); + } + + [Test] + public void CreateDeviceDetailsClientThumbprintBadFormatShouldReturnNull() + { + // Arrange + var twin = new Twin + { + DeviceId = Guid.NewGuid().ToString() + }; + twin.Properties.Desired[nameof(Concentrator.ClientThumbprint).ToCamelCase()] = Guid.NewGuid().ToString(); + + // Act + var result = DeviceHelper.RetrieveClientThumbprintValue(twin); + + // Assert + Assert.IsNull(result); + } } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/ConcentratorTwinMapperTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/ConcentratorTwinMapperTests.cs index 42cc625e1..617441cdb 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/ConcentratorTwinMapperTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/ConcentratorTwinMapperTests.cs @@ -80,65 +80,6 @@ public void CreateDeviceDetailsStateUnderTestExpectedBehavior() this.mockRepository.VerifyAll(); } - [Test] - public void CreateDeviceDetailsClientThumbprintNotExistExpectedBehavior() - { - // Arrange - var concentratorTwinMapper = CreateConcentratorTwinMapper(); - var twin = new Twin - { - DeviceId = Guid.NewGuid().ToString() - }; - - // Act - var result = concentratorTwinMapper.CreateDeviceDetails(twin); - - // Assert - Assert.IsNotNull(result); - Assert.IsNull(result.ClientThumbprint); - this.mockRepository.VerifyAll(); - } - - [Test] - public void CreateDeviceDetailsClientThumbprintEmptyArrayExpectedBehavior() - { - // Arrange - var concentratorTwinMapper = CreateConcentratorTwinMapper(); - var twin = new Twin - { - DeviceId = Guid.NewGuid().ToString() - }; - twin.Properties.Desired[nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()] = new List(); - - // Act - var result = concentratorTwinMapper.CreateDeviceDetails(twin); - - // Assert - Assert.IsNotNull(result); - Assert.IsNull(result.ClientThumbprint); - this.mockRepository.VerifyAll(); - } - - [Test] - public void CreateDeviceDetailsClientThumbprintBadFormatExpectedBehavior() - { - // Arrange - var concentratorTwinMapper = CreateConcentratorTwinMapper(); - var twin = new Twin - { - DeviceId = Guid.NewGuid().ToString() - }; - twin.Properties.Desired[nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()] = Guid.NewGuid().ToString(); - - // Act - var result = concentratorTwinMapper.CreateDeviceDetails(twin); - - // Assert - Assert.IsNotNull(result); - Assert.IsNull(result.ClientThumbprint); - this.mockRepository.VerifyAll(); - } - [Test] public void UpdateTwinStateUnderTestExpectedBehavior() { diff --git a/src/AzureIoTHub.Portal/Server/Helpers/DeviceHelper.cs b/src/AzureIoTHub.Portal/Server/Helpers/DeviceHelper.cs index fe0634306..7a610a019 100644 --- a/src/AzureIoTHub.Portal/Server/Helpers/DeviceHelper.cs +++ b/src/AzureIoTHub.Portal/Server/Helpers/DeviceHelper.cs @@ -13,6 +13,7 @@ namespace AzureIoTHub.Portal.Server.Helpers using Microsoft.Azure.Devices.Shared; using Newtonsoft.Json.Linq; using Newtonsoft.Json; + using AzureIoTHub.Portal.Domain.Entities; public static class DeviceHelper { @@ -245,5 +246,34 @@ public static TwinCollection PropertiesWithDotNotationToTwinCollection(Dictionar return new TwinCollection(JsonConvert.SerializeObject(root)); } + + public static string RetrieveClientThumbprintValue(Twin twin) + { + var serializedClientThumbprint = RetrieveDesiredPropertyValue(twin, nameof(Concentrator.ClientThumbprint).ToCamelCase()); + + if (serializedClientThumbprint == null) + { + // clientThumbprint does not exist in the device twin + return null; + } + + try + { + var clientThumbprintArray=JsonConvert.DeserializeObject(serializedClientThumbprint); + + if (clientThumbprintArray.Length == 0) + { + // clientThumbprint array is empty in the device twin + return null; + } + + return clientThumbprintArray[0]; + } + catch (JsonReaderException) + { + // clientThumbprint is not an array in the device twin + return null; + } + } } } diff --git a/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorProfile.cs b/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorProfile.cs index d5cf0b3f3..f237c1c69 100644 --- a/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorProfile.cs +++ b/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorProfile.cs @@ -7,9 +7,7 @@ namespace AzureIoTHub.Portal.Server.Mappers using AzureIoTHub.Portal.Domain.Entities; using Models.v10.LoRaWAN; using Microsoft.Azure.Devices.Shared; - using AzureIoTHub.Portal.Server.Extensions; using AzureIoTHub.Portal.Server.Helpers; - using Newtonsoft.Json; public class ConcentratorProfile : Profile { @@ -31,38 +29,9 @@ public ConcentratorProfile() .ForMember(dest => dest.Version, opts => opts.MapFrom(src => src.Version)) .ForMember(dest => dest.IsConnected, opts => opts.MapFrom(src => src.ConnectionState == Microsoft.Azure.Devices.DeviceConnectionState.Connected)) .ForMember(dest => dest.IsEnabled, opts => opts.MapFrom(src => src.Status == Microsoft.Azure.Devices.DeviceStatus.Enabled)) - .ForMember(dest => dest.ClientThumbprint, opts => opts.MapFrom(src => RetrieveClientThumbprintValue(src))) + .ForMember(dest => dest.ClientThumbprint, opts => opts.MapFrom(src => DeviceHelper.RetrieveClientThumbprintValue(src))) .ForMember(dest => dest.LoraRegion, opts => opts.MapFrom(src => src.Tags["loraRegion"])) .ForMember(dest => dest.DeviceType, opts => opts.MapFrom(src => src.Tags["deviceType"])); } - - private static string RetrieveClientThumbprintValue(Twin twin) - { - var serializedClientThumbprint = DeviceHelper.RetrieveDesiredPropertyValue(twin, nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()); - - if (serializedClientThumbprint == null) - { - // clientThumbprint does not exist in the device twin - return null; - } - - try - { - var clientThumbprintArray=JsonConvert.DeserializeObject(serializedClientThumbprint); - - if (clientThumbprintArray.Length == 0) - { - // clientThumbprint array is empty in the device twin - return null; - } - - return clientThumbprintArray[0]; - } - catch (JsonReaderException) - { - // clientThumbprint is not an array in the device twin - return null; - } - } } } diff --git a/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorTwinMapper.cs b/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorTwinMapper.cs index e3b88acd3..28a55b110 100644 --- a/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorTwinMapper.cs +++ b/src/AzureIoTHub.Portal/Server/Mappers/ConcentratorTwinMapper.cs @@ -9,7 +9,6 @@ namespace AzureIoTHub.Portal.Server.Mappers using Extensions; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Shared; - using Newtonsoft.Json; public class ConcentratorTwinMapper : IConcentratorTwinMapper { @@ -22,7 +21,7 @@ public ConcentratorDto CreateDeviceDetails(Twin twin) DeviceId = twin.DeviceId, DeviceName = DeviceHelper.RetrieveTagValue(twin, nameof(ConcentratorDto.DeviceName)), LoraRegion = DeviceHelper.RetrieveTagValue(twin, nameof(ConcentratorDto.LoraRegion)), - ClientThumbprint = RetrieveClientThumbprintValue(twin), + ClientThumbprint = DeviceHelper.RetrieveClientThumbprintValue(twin), IsEnabled = twin.Status == DeviceStatus.Enabled, IsConnected = twin.ConnectionState == DeviceConnectionState.Connected, AlreadyLoggedInOnce = DeviceHelper.RetrieveReportedPropertyValue(twin, "DevAddr") != null, @@ -30,35 +29,6 @@ public ConcentratorDto CreateDeviceDetails(Twin twin) }; } - private static string RetrieveClientThumbprintValue(Twin twin) - { - var serializedClientThumbprint = DeviceHelper.RetrieveDesiredPropertyValue(twin, nameof(ConcentratorDto.ClientThumbprint).ToCamelCase()); - - if (serializedClientThumbprint == null) - { - // clientThumbprint does not exist in the device twin - return null; - } - - try - { - var clientThumbprintArray=JsonConvert.DeserializeObject(serializedClientThumbprint); - - if (clientThumbprintArray.Length == 0) - { - // clientThumbprint array is empty in the device twin - return null; - } - - return clientThumbprintArray[0]; - } - catch (Newtonsoft.Json.JsonReaderException) - { - // clientThumbprint is not an array in the device twin - return null; - } - } - public void UpdateTwin(Twin twin, ConcentratorDto item) { ArgumentNullException.ThrowIfNull(twin, nameof(twin));