From fca5d61ad888b958623ca2018a9bf0a7d1fc58cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?DELAGE=20Rapha=C3=ABl?= <36408929+delager@users.noreply.github.com> Date: Mon, 5 Jun 2023 15:56:35 +0200 Subject: [PATCH] #2075 Sync AWS IoT Edge Device (#2148) * #2075 Sync AWS IoT Edge Device - Sync AWS IoT Edge Device - TU sync IoT Edge Device - GetByName async for DeviceModel and EdgeDeviceModel * #2075 Add GetByNameAsync TU * #2075 Add greengras devices job to quartz * #2075 Fix GetByNameAsync * #2075 Add EdgeDevice properties - NbModules / NbDevices / ConnectionState --- .../Mappers/AWS/AWSDeviceThingProfile.cs | 10 + .../Repositories/IDeviceModelRepository.cs | 2 +- .../IEdgeDeviceModelRepository.cs | 1 + .../Jobs/AWS/SyncGreenGrassDevicesJob.cs | 199 ++++++ .../Jobs/AWS/SyncThingsJob.cs | 4 +- .../Repositories/DeviceModelRepository.cs | 6 +- .../Repositories/EdgeDeviceModelRepository.cs | 7 + .../Services/AWS/AWSEdgeDevicesService.cs | 2 + .../Startup/AWSServiceCollectionExtension.cs | 8 + .../Jobs/AWS/SyncGreenGrassDevicesJobTests.cs | 597 ++++++++++++++++++ .../Jobs/AWS/SyncThingsJobTests.cs | 32 +- .../DeviceModelRepositoryTests.cs | 19 + .../EdgeDeviceModelRepositoryTests.cs | 19 + 13 files changed, 881 insertions(+), 25 deletions(-) create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs diff --git a/src/AzureIoTHub.Portal.Application/Mappers/AWS/AWSDeviceThingProfile.cs b/src/AzureIoTHub.Portal.Application/Mappers/AWS/AWSDeviceThingProfile.cs index 63605c5b2..98ba36af8 100644 --- a/src/AzureIoTHub.Portal.Application/Mappers/AWS/AWSDeviceThingProfile.cs +++ b/src/AzureIoTHub.Portal.Application/Mappers/AWS/AWSDeviceThingProfile.cs @@ -44,6 +44,16 @@ public AWSDeviceThingProfile() Name = att.Key, Value = att.Value }))); + + _ = CreateMap() + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.ThingId)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.ThingName)) + .ForMember(dest => dest.Version, opts => opts.MapFrom(src => src.Version)) + .ForMember(dest => dest.Tags, opts => opts.MapFrom(src => src.Attributes.Select(att => new DeviceTagValue + { + Name = att.Key, + Value = att.Value + }))); } private static MemoryStream EmptyPayload() diff --git a/src/AzureIoTHub.Portal.Domain/Repositories/IDeviceModelRepository.cs b/src/AzureIoTHub.Portal.Domain/Repositories/IDeviceModelRepository.cs index 3fc23d94b..13f1681ab 100644 --- a/src/AzureIoTHub.Portal.Domain/Repositories/IDeviceModelRepository.cs +++ b/src/AzureIoTHub.Portal.Domain/Repositories/IDeviceModelRepository.cs @@ -7,6 +7,6 @@ namespace AzureIoTHub.Portal.Domain.Repositories public interface IDeviceModelRepository : IRepository { - public DeviceModel? GetByName(string modelName); + Task GetByNameAsync(string modelName); } } diff --git a/src/AzureIoTHub.Portal.Domain/Repositories/IEdgeDeviceModelRepository.cs b/src/AzureIoTHub.Portal.Domain/Repositories/IEdgeDeviceModelRepository.cs index 5bf6d3fed..5f6516547 100644 --- a/src/AzureIoTHub.Portal.Domain/Repositories/IEdgeDeviceModelRepository.cs +++ b/src/AzureIoTHub.Portal.Domain/Repositories/IEdgeDeviceModelRepository.cs @@ -7,5 +7,6 @@ namespace AzureIoTHub.Portal.Domain.Repositories public interface IEdgeDeviceModelRepository : IRepository { + Task GetByNameAsync(string edgeModelDevice); } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs new file mode 100644 index 000000000..08ca42ce0 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJob.cs @@ -0,0 +1,199 @@ +// 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.Jobs.AWS +{ + using System.Linq; + using System.Net; + using System.Threading.Tasks; + using Amazon.GreengrassV2; + using Amazon.GreengrassV2.Model; + using Amazon.IoT; + using Amazon.IoT.Model; + using Amazon.SecretsManager.Model; + using AutoMapper; + using AzureIoTHub.Portal.Application.Services; + using AzureIoTHub.Portal.Application.Services.AWS; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Models.v10; + using Microsoft.Extensions.Logging; + using Quartz; + using Quartz.Util; + + [DisallowConcurrentExecution] + public class SyncGreenGrassDevicesJob : IJob + { + + private readonly ILogger logger; + private readonly IMapper mapper; + private readonly IUnitOfWork unitOfWork; + private readonly IEdgeDeviceRepository edgeDeviceRepository; + private readonly IEdgeDeviceModelRepository edgeDeviceModelRepository; + private readonly IDeviceTagValueRepository deviceTagValueRepository; + private readonly IAmazonIoT amazonIoTClient; + private readonly IAmazonGreengrassV2 amazonGreenGrass; + private readonly IConfigService configService; + private readonly IAWSExternalDeviceService awsExternalDevicesService; + + public SyncGreenGrassDevicesJob( + ILogger logger, + IMapper mapper, + IUnitOfWork unitOfWork, + IEdgeDeviceRepository edgeDeviceRepository, + IEdgeDeviceModelRepository edgeDeviceModelRepository, + IDeviceTagValueRepository deviceTagValueRepository, + IAmazonIoT amazonIoTClient, + IAmazonGreengrassV2 amazonGreenGrass, + IConfigService configService, + IAWSExternalDeviceService awsExternalDevicesService) + { + this.mapper = mapper; + this.unitOfWork = unitOfWork; + this.edgeDeviceRepository = edgeDeviceRepository; + this.edgeDeviceModelRepository = edgeDeviceModelRepository; + this.deviceTagValueRepository = deviceTagValueRepository; + this.amazonIoTClient = amazonIoTClient; + this.amazonGreenGrass = amazonGreenGrass; + this.configService = configService; + this.awsExternalDevicesService = awsExternalDevicesService; + this.logger = logger; + } + + + public async Task Execute(IJobExecutionContext context) + { + try + { + this.logger.LogInformation("Start of sync GreenGrass Devices job"); + + await SyncGreenGrassDevicesAsEdgeDevices(); + + this.logger.LogInformation("End of sync GreenGrass Devices job"); + } + catch (Exception e) + { + this.logger.LogError(e, "Sync GreenGrass Devices job has failed"); + } + } + + private async Task SyncGreenGrassDevicesAsEdgeDevices() + { + var things = await GetAllGreenGrassDevices(); + + foreach (var thing in things) + { + //Thing error + if (thing.HttpStatusCode != HttpStatusCode.OK) + { + this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error in the Amazon IoT API : {thing.HttpStatusCode}"); + continue; + } + + //ThingType not specified + if (thing.ThingTypeName.IsNullOrWhiteSpace()) + { + this.logger.LogInformation($"Cannot import Greengrass device '{thing.ThingName}' since it doesn't have related thing type."); + continue; + } + + //EdgeDeviceModel not find in DB + var edgeDeviceModel = await this.edgeDeviceModelRepository.GetByNameAsync(thing.ThingTypeName); + if (edgeDeviceModel == null) + { + this.logger.LogWarning($"Cannot import Greengrass device '{thing.ThingName}'. The EdgeDeviceModel '{thing.ThingTypeName}' doesn't exist"); + continue; + } + + //Map with EdgeDevice + var edgeDevice = this.mapper.Map(thing); + edgeDevice.DeviceModelId = edgeDeviceModel.Id; + //EdgeDevices properties that are not present in the thing + try + { + var modules = await this.configService.GetConfigModuleList(edgeDevice.DeviceModelId); + edgeDevice.NbDevices = await this.awsExternalDevicesService.GetEdgeDeviceNbDevices(this.mapper.Map(edgeDevice)); + edgeDevice.NbModules = modules.Count; + var coreDevice = await amazonGreenGrass.GetCoreDeviceAsync(new GetCoreDeviceRequest() { CoreDeviceThingName = thing.ThingName }); + if (coreDevice.HttpStatusCode != HttpStatusCode.OK) + { + this.logger.LogWarning($"Cannot import Greengrass device '{thing.ThingName}' due to an error retrieving core device in the Amazon IoT Data API : {coreDevice.HttpStatusCode}"); + continue; + } + edgeDevice.ConnectionState = coreDevice.Status == CoreDeviceStatus.HEALTHY ? "Connected" : "Disconnected"; + } + catch (Exception e) + { + this.logger.LogWarning($"Cannot import Greengrass device '{thing.ThingName}' due to an error retrieving Greengrass device properties in the Amazon IoT Data API.", e); + continue; + } + + //Create or update the Edge Device + await CreateOrUpdateGreenGrassDevice(edgeDevice); + } + + foreach (var item in (await this.edgeDeviceRepository.GetAllAsync( + edgeDevice => !things.Select(x => x.ThingId).Contains(edgeDevice.Id), + default, + d => d.Tags, + d => d.Labels + ))) + { + this.edgeDeviceRepository.Delete(item.Id); + } + + await this.unitOfWork.SaveAsync(); + } + + private async Task> GetAllGreenGrassDevices() + { + var devices = new List(); + + var nextToken = string.Empty; + + var response = await amazonGreenGrass.ListCoreDevicesAsync( + new ListCoreDevicesRequest + { + NextToken = nextToken + }); + + foreach (var requestDescribeThing in response.CoreDevices.Select(device => new DescribeThingRequest { ThingName = device.CoreDeviceThingName })) + { + try + { + devices.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing)); + } + catch (AmazonIoTException e) + { + this.logger.LogWarning($"Cannot import Greengrass device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e); + continue; + } + } + + return devices; + } + + private async Task CreateOrUpdateGreenGrassDevice(EdgeDevice edgeDevice) + { + var edgeDeviceEntity = await this.edgeDeviceRepository.GetByIdAsync(edgeDevice.Id, d => d.Tags); + + if (edgeDeviceEntity == null) + { + await this.edgeDeviceRepository.InsertAsync(edgeDevice); + } + else + { + if (edgeDeviceEntity.Version >= edgeDevice.Version) return; + + foreach (var deviceTagEntity in edgeDeviceEntity.Tags) + { + this.deviceTagValueRepository.Delete(deviceTagEntity.Id); + } + + _ = this.mapper.Map(edgeDevice, edgeDeviceEntity); + this.edgeDeviceRepository.Update(edgeDeviceEntity); + } + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs index eb0bc47a1..4ba7d346a 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs @@ -88,8 +88,8 @@ private async Task SyncThingsAsDevices() continue; } - //ThingType not find in DB - var deviceModel = this.deviceModelRepository.GetByName(thing.ThingTypeName); + //DeviceModel not find in DB + var deviceModel = await this.deviceModelRepository.GetByNameAsync(thing.ThingTypeName); if (deviceModel == null) { this.logger.LogWarning($"Cannot import device '{thing.ThingName}'. The ThingType '{thing.ThingTypeName}' doesn't exist"); diff --git a/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceModelRepository.cs b/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceModelRepository.cs index dfb1748a3..f1fb8ca13 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceModelRepository.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceModelRepository.cs @@ -5,6 +5,7 @@ namespace AzureIoTHub.Portal.Infrastructure.Repositories { using AzureIoTHub.Portal.Domain.Repositories; using Domain.Entities; + using Microsoft.EntityFrameworkCore; public class DeviceModelRepository : GenericRepository, IDeviceModelRepository { @@ -12,9 +13,10 @@ public DeviceModelRepository(PortalDbContext context) : base(context) { } - public DeviceModel? GetByName(string modelName) + public async Task GetByNameAsync(string modelName) { - return GetAll().FirstOrDefault(model => model.Name.Equals(modelName, StringComparison.Ordinal)); + return await this.context.Set() + .FirstOrDefaultAsync(deviceModel => deviceModel.Name == modelName); } } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Repositories/EdgeDeviceModelRepository.cs b/src/AzureIoTHub.Portal.Infrastructure/Repositories/EdgeDeviceModelRepository.cs index 940be2f00..76210805f 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Repositories/EdgeDeviceModelRepository.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Repositories/EdgeDeviceModelRepository.cs @@ -6,11 +6,18 @@ namespace AzureIoTHub.Portal.Infrastructure.Repositories { using Domain.Entities; using Domain.Repositories; + using Microsoft.EntityFrameworkCore; public class EdgeDeviceModelRepository : GenericRepository, IEdgeDeviceModelRepository { public EdgeDeviceModelRepository(PortalDbContext context) : base(context) { } + + public async Task GetByNameAsync(string edgeModelDevice) + { + return await this.context.Set() + .FirstOrDefaultAsync(edgeDeviceModel => edgeDeviceModel.Name == edgeModelDevice); + } } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs index 0ce7a2aae..e0a55dea9 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Services/AWS/AWSEdgeDevicesService.cs @@ -5,6 +5,7 @@ namespace AzureIoTHub.Portal.Infrastructure.Services { using System; using System.Threading.Tasks; + using Amazon.GreengrassV2; using Amazon.IoT.Model; using AutoMapper; using AzureIoTHub.Portal.Application.Managers; @@ -137,6 +138,7 @@ public async Task GetEdgeDevice(string edgeDeviceId) deviceDto.Modules = await this.configService.GetConfigModuleList(model.ExternalIdentifier!); deviceDto.NbDevices = await this.awsExternalDevicesService.GetEdgeDeviceNbDevices(deviceDto); deviceDto.NbModules = deviceDto.Modules.Count; + deviceDto.ConnectionState = deviceDto.RuntimeResponse == CoreDeviceStatus.HEALTHY ? "Connected" : "Disconnected"; return deviceDto; } diff --git a/src/AzureIoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs b/src/AzureIoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs index 7b67ec3dd..390aacc4f 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Startup/AWSServiceCollectionExtension.cs @@ -102,6 +102,14 @@ private static IServiceCollection ConfigureAWSSyncJobs(this IServiceCollection s .WithSimpleSchedule(s => s .WithIntervalInMinutes(configuration.SyncDatabaseJobRefreshIntervalInMinutes) .RepeatForever())); + + _ = q.AddJob(j => j.WithIdentity(nameof(SyncGreenGrassDevicesJob))) + .AddTrigger(t => t + .WithIdentity($"{nameof(SyncGreenGrassDevicesJob)}") + .ForJob(nameof(SyncGreenGrassDevicesJob)) + .WithSimpleSchedule(s => s + .WithIntervalInMinutes(configuration.SyncDatabaseJobRefreshIntervalInMinutes) + .RepeatForever())); }); } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs new file mode 100644 index 000000000..71ce10f32 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncGreenGrassDevicesJobTests.cs @@ -0,0 +1,597 @@ +// 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.Infrastructure.Jobs.AWS +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + using Amazon.GreengrassV2; + using Amazon.GreengrassV2.Model; + using Amazon.IoT; + using Amazon.IoT.Model; + using AutoFixture; + using AzureIoTHub.Portal.Application.Services.AWS; + using AzureIoTHub.Portal.Application.Services; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Infrastructure.Jobs.AWS; + using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using NUnit.Framework; + using Quartz; + using AzureIoTHub.Portal.Models.v10; + + public class SyncGreenGrassDevicesJobTests : BackendUnitTest + { + private IJob syncGreenGrassDevicesJob; + + private Mock amazonIoTClient; + private Mock amazonGreenGrass; + private Mock mockUnitOfWork; + private Mock mockDeviceRepository; + private Mock mockDeviceModelRepository; + private Mock mockDeviceTagValueRepository; + private Mock mockConfigService; + private Mock mockAwsExternalDevicesService; + + public override void Setup() + { + base.Setup(); + + this.mockUnitOfWork = MockRepository.Create(); + this.mockDeviceRepository = MockRepository.Create(); + this.mockDeviceModelRepository = MockRepository.Create(); + this.mockDeviceTagValueRepository = MockRepository.Create(); + this.amazonIoTClient = MockRepository.Create(); + this.amazonGreenGrass = MockRepository.Create(); + this.mockConfigService = MockRepository.Create(); + this.mockAwsExternalDevicesService = MockRepository.Create(); + + _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); + _ = ServiceCollection.AddSingleton(this.mockDeviceRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockDeviceModelRepository.Object); + _ = ServiceCollection.AddSingleton(this.mockDeviceTagValueRepository.Object); + _ = ServiceCollection.AddSingleton(this.amazonIoTClient.Object); + _ = ServiceCollection.AddSingleton(this.amazonGreenGrass.Object); + _ = ServiceCollection.AddSingleton(this.mockConfigService.Object); + _ = ServiceCollection.AddSingleton(this.mockAwsExternalDevicesService.Object); + _ = ServiceCollection.AddSingleton(); + + + Services = ServiceCollection.BuildServiceProvider(); + + this.syncGreenGrassDevicesJob = Services.GetRequiredService(); + } + + [Test] + public async Task ExecuteNewEdgeDeviceEdgeDeviceCreated() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var newDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 1 + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = newDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = newDevice.Id, + ThingName = newDevice.Name, + ThingTypeName = newDevice.DeviceModel.Name, + Version = newDevice.Version, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockDeviceModelRepository + .Setup(x => x.GetByNameAsync(newDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(expectedDeviceModel.Id)) + .ReturnsAsync(new List()); + + _ = this.mockAwsExternalDevicesService.Setup(x => x.GetEdgeDeviceNbDevices(It.IsAny())) + .ReturnsAsync(0); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK }); + + _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(newDevice.Id, d => d.Tags)) + .ReturnsAsync((EdgeDevice)null); + + _ = this.mockDeviceRepository.Setup(repository => repository.InsertAsync(It.IsAny())) + .Returns(Task.CompletedTask); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List + { + new EdgeDevice + { + Id = Guid.NewGuid().ToString() + } + }); + + this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 1, + Tags = new List() + { + new() + { + Id = Fixture.Create(), + Name = Fixture.Create(), + Value = Fixture.Create() + } + } + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = existingDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = existingDevice.Id, + ThingName = existingDevice.Name, + ThingTypeName = existingDevice.DeviceModel.Name, + Version = 2, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockDeviceModelRepository + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(expectedDeviceModel.Id)) + .ReturnsAsync(new List()); + + _ = this.mockAwsExternalDevicesService.Setup(x => x.GetEdgeDeviceNbDevices(It.IsAny())) + .ReturnsAsync(0); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK }); + + _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(existingDevice.Id, d => d.Tags)) + .ReturnsAsync(existingDevice); + + this.mockDeviceTagValueRepository.Setup(repository => repository.Delete(It.IsAny())).Verifiable(); + + this.mockDeviceRepository.Setup(repository => repository.Update(It.IsAny())).Verifiable(); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List + { + new EdgeDevice + { + Id = Guid.NewGuid().ToString() + } + }); + + this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteExistingEdgeDeviceWithOlderVersionEdgeDeviceNotUpdated() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2, + Tags = new List() + { + new() + { + Id = Fixture.Create(), + Name = Fixture.Create(), + Value = Fixture.Create() + } + } + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = existingDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = existingDevice.Id, + ThingName = existingDevice.Name, + ThingTypeName = existingDevice.DeviceModel.Name, + Version = 1, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockDeviceModelRepository + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(expectedDeviceModel.Id)) + .ReturnsAsync(new List()); + + _ = this.mockAwsExternalDevicesService.Setup(x => x.GetEdgeDeviceNbDevices(It.IsAny())) + .ReturnsAsync(0); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.OK }); + + _ = this.mockDeviceRepository.Setup(repository => repository.GetByIdAsync(existingDevice.Id, d => d.Tags)) + .ReturnsAsync(existingDevice); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List + { + new EdgeDevice + { + Id = Guid.NewGuid().ToString() + } + }); + + this.mockDeviceRepository.Setup(x => x.Delete(It.IsAny())).Verifiable(); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteNewEdgeDeviceWithDescribeThingErrorSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2 + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = existingDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + HttpStatusCode = HttpStatusCode.RequestTimeout + }); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteNewEdgeDeviceWithGetCoreDeviceErrorSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 1 + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = existingDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = existingDevice.Id, + ThingName = existingDevice.Name, + ThingTypeName = existingDevice.DeviceModel.Name, + Version = 1, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockDeviceModelRepository + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); + + _ = this.mockConfigService.Setup(x => x.GetConfigModuleList(expectedDeviceModel.Id)) + .ReturnsAsync(new List()); + + _ = this.mockAwsExternalDevicesService.Setup(x => x.GetEdgeDeviceNbDevices(It.IsAny())) + .ReturnsAsync(0); + + _ = this.amazonGreenGrass.Setup(client => client.GetCoreDeviceAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetCoreDeviceResponse() { HttpStatusCode = HttpStatusCode.RequestTimeout }); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteNewDeviceWithoutThingTypeSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2 + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = existingDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = existingDevice.Id, + ThingName = existingDevice.Name, + Version = 1, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + [Test] + public async Task ExecuteNewDeviceWithUnknownThingTypeSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2 + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = existingDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new DescribeThingResponse() + { + ThingId = existingDevice.Id, + ThingName = existingDevice.Name, + ThingTypeName = existingDevice.DeviceModel.Name, + Version = 1, + HttpStatusCode = HttpStatusCode.OK + }); + + _ = this.mockDeviceModelRepository + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync((EdgeDeviceModel)null); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + public async Task ExecuteNewEdgeDeviceWithAmazonIotExceptionSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new EdgeDevice + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2 + }; + + var greenGrassDevices = new ListCoreDevicesResponse + { + CoreDevices = new List() + { + new CoreDevice + { + CoreDeviceThingName = existingDevice.Name + } + } + }; + + _ = this.amazonGreenGrass.Setup(client => client.ListCoreDevicesAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(greenGrassDevices); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .Throws(new AmazonIoTException("")); + + _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) + .ReturnsAsync(new List()); + + _ = this.mockUnitOfWork.Setup(work => work.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await this.syncGreenGrassDevicesJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs index cc98c976e..f7ed62260 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs @@ -14,7 +14,6 @@ namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure.Jobs.AWS using Amazon.IotData; using Amazon.IotData.Model; using AutoFixture; - using AzureIoTHub.Portal.Application.Managers; using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Domain.Repositories; @@ -31,7 +30,6 @@ public class SyncThingsJobTests : BackendUnitTest private Mock amazonIoTClient; private Mock amazonIoTDataClient; - private Mock mockAWSImageManager; private Mock mockUnitOfWork; private Mock mockDeviceRepository; private Mock mockDeviceModelRepository; @@ -41,7 +39,6 @@ public override void Setup() { base.Setup(); - this.mockAWSImageManager = MockRepository.Create(); this.mockUnitOfWork = MockRepository.Create(); this.mockDeviceRepository = MockRepository.Create(); this.mockDeviceModelRepository = MockRepository.Create(); @@ -49,7 +46,6 @@ public override void Setup() this.amazonIoTClient = MockRepository.Create(); this.amazonIoTDataClient = MockRepository.Create(); - _ = ServiceCollection.AddSingleton(this.mockAWSImageManager.Object); _ = ServiceCollection.AddSingleton(this.mockUnitOfWork.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceRepository.Object); _ = ServiceCollection.AddSingleton(this.mockDeviceModelRepository.Object); @@ -105,8 +101,8 @@ public async Task ExecuteNewDeviceDeviceCreated() }); _ = this.mockDeviceModelRepository - .Setup(x => x.GetByName(newDevice.DeviceModel.Name)) - .Returns(expectedDeviceModel); + .Setup(x => x.GetByNameAsync(newDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); _ = this.amazonIoTDataClient.Setup(client => client.GetThingShadowAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new GetThingShadowResponse() { HttpStatusCode = HttpStatusCode.OK }); @@ -188,8 +184,8 @@ public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() }); _ = this.mockDeviceModelRepository - .Setup(x => x.GetByName(existingDevice.DeviceModel.Name)) - .Returns(expectedDeviceModel); + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); _ = this.amazonIoTDataClient.Setup(client => client.GetThingShadowAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new GetThingShadowResponse() { HttpStatusCode = HttpStatusCode.OK }); @@ -255,8 +251,8 @@ public async Task ExecuteExistingDeviceWithOlderVersionDeviceNotUpdated() }); _ = this.mockDeviceModelRepository - .Setup(x => x.GetByName(existingDevice.DeviceModel.Name)) - .Returns(expectedDeviceModel); + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); _ = this.amazonIoTDataClient.Setup(client => client.GetThingShadowAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new GetThingShadowResponse() { HttpStatusCode = HttpStatusCode.OK }); @@ -310,10 +306,6 @@ public async Task ExecuteNewDeviceWithDescribeThingErrorSkipped() _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new DescribeThingResponse() { - ThingId = existingDevice.Id, - ThingName = existingDevice.Name, - ThingTypeName = existingDevice.DeviceModel.Name, - Version = 1, HttpStatusCode = HttpStatusCode.RequestTimeout }); @@ -423,8 +415,8 @@ public async Task ExecuteNewDeviceWithUnknownThingTypeSkipped() }); _ = this.mockDeviceModelRepository - .Setup(x => x.GetByName(existingDevice.DeviceModel.Name)) - .Returns((DeviceModel)null); + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync((DeviceModel)null); _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List()); @@ -481,8 +473,8 @@ public async Task ExecuteNewDeviceWithoutThingShadowSkipped(HttpStatusCode thing }); _ = this.mockDeviceModelRepository - .Setup(x => x.GetByName(existingDevice.DeviceModel.Name)) - .Returns(expectedDeviceModel); + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); _ = this.amazonIoTDataClient.Setup(client => client.GetThingShadowAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new GetThingShadowResponse() { HttpStatusCode = thingShadowCode }); @@ -585,8 +577,8 @@ public async Task ExecuteNewDeviceWithAmazonIotDataExceptionSkipped() }); _ = this.mockDeviceModelRepository - .Setup(x => x.GetByName(existingDevice.DeviceModel.Name)) - .Returns(expectedDeviceModel); + .Setup(x => x.GetByNameAsync(existingDevice.DeviceModel.Name)) + .ReturnsAsync(expectedDeviceModel); _ = this.amazonIoTDataClient.Setup(client => client.GetThingShadowAsync(It.IsAny(), It.IsAny())) .Throws(new AmazonIotDataException("")); diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceModelRepositoryTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceModelRepositoryTests.cs index 9397d1eb9..d1fe2979a 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceModelRepositoryTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceModelRepositoryTests.cs @@ -39,5 +39,24 @@ public async Task GetAllShouldReturnExpectedDeviceModelCommands() // Assert _ = result.Should().BeEquivalentTo(expectedDeviceModels); } + + [Test] + public async Task GetByNameShouldReturnExpectedDeviceModel() + { + // Arrange + var deviceModels = Fixture.CreateMany(5).ToList(); + var searchDeviceModel = Fixture.Create(); + deviceModels.Add(searchDeviceModel); + + await DbContext.AddRangeAsync(deviceModels); + + _ = await DbContext.SaveChangesAsync(); + + // Act + var result = await this.deviceModelRepository.GetByNameAsync(searchDeviceModel.Name); + + // Assert + _ = result.Should().BeEquivalentTo(searchDeviceModel); + } } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/EdgeDeviceModelRepositoryTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/EdgeDeviceModelRepositoryTests.cs index cdb90f590..d36a21b39 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/EdgeDeviceModelRepositoryTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/EdgeDeviceModelRepositoryTests.cs @@ -39,5 +39,24 @@ public async Task GetAll_ExitingEdgeDeviceModels_EdgeDeviceModelsReturned() // Assert _ = result.Should().BeEquivalentTo(expectedEdgeDeviceModels); } + + [Test] + public async Task GetByNameShouldReturnExpectedEdgeDeviceModel() + { + // Arrange + var edgeDeviceModels = Fixture.CreateMany(5).ToList(); + var searchEdgeDeviceModel = Fixture.Create(); + edgeDeviceModels.Add(searchEdgeDeviceModel); + + await DbContext.AddRangeAsync(edgeDeviceModels); + + _ = await DbContext.SaveChangesAsync(); + + // Act + var result = await this.edgeDeviceModelRepository.GetByNameAsync(searchEdgeDeviceModel.Name); + + // Assert + _ = result.Should().BeEquivalentTo(searchEdgeDeviceModel); + } } }