From 0504066a81fb741ca4a505014003f0a396303fd5 Mon Sep 17 00:00:00 2001 From: delager Date: Thu, 1 Jun 2023 10:03:52 +0200 Subject: [PATCH] #1924 Amazon Exception Handling --- .../Jobs/AWS/SyncThingsJob.cs | 40 +++- .../Jobs/AWS/SyncThingsJobTests.cs | 182 +++++++++++++++++- 2 files changed, 206 insertions(+), 16 deletions(-) diff --git a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs index b4117ac2b..eb0bc47a1 100644 --- a/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/Jobs/AWS/SyncThingsJob.cs @@ -56,15 +56,15 @@ public async Task Execute(IJobExecutionContext context) { try { - this.logger.LogInformation("Start of sync Thing Types job"); + this.logger.LogInformation("Start of sync Things job"); await SyncThingsAsDevices(); - this.logger.LogInformation("End of sync Thing Types job"); + this.logger.LogInformation("End of sync Things job"); } catch (Exception e) { - this.logger.LogError(e, "Sync Thing Types job has failed"); + this.logger.LogError(e, "Sync Things job has failed"); } } @@ -74,6 +74,13 @@ private async Task SyncThingsAsDevices() 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()) { @@ -94,10 +101,21 @@ private async Task SyncThingsAsDevices() { ThingName = thing.ThingName }; - var thingShadow = await this.amazonIoTDataClient.GetThingShadowAsync(thingShadowRequest); - if (thingShadow.HttpStatusCode.Equals(HttpStatusCode.NotFound)) + try + { + var thingShadow = await this.amazonIoTDataClient.GetThingShadowAsync(thingShadowRequest); + if (thingShadow.HttpStatusCode != HttpStatusCode.OK) + { + if (thingShadow.HttpStatusCode.Equals(HttpStatusCode.NotFound)) + this.logger.LogInformation($"Cannot import device '{thing.ThingName}' since it doesn't have related classic thing shadow"); + else + this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT API : {thingShadow.HttpStatusCode}"); + continue; + } + } + catch (AmazonIotDataException e) { - this.logger.LogInformation($"Cannot import device '{thing.ThingName}' since it doesn't have related thing shadow"); + this.logger.LogWarning($"Cannot import device '{thing.ThingName}' due to an error retrieving thing shadow in the Amazon IoT Data API.", e); continue; } @@ -126,7 +144,15 @@ private async Task> GetAllThings() foreach (var requestDescribeThing in response.Things.Select(thing => new DescribeThingRequest { ThingName = thing.ThingName })) { - things.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing)); + try + { + things.Add(await this.amazonIoTClient.DescribeThingAsync(requestDescribeThing)); + } + catch (AmazonIoTException e) + { + this.logger.LogWarning($"Cannot import device '{requestDescribeThing.ThingName}' due to an error in the Amazon IoT API.", e); + continue; + } } return things; 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 faacb3d15..9bf5468ee 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Jobs/AWS/SyncThingsJobTests.cs @@ -100,7 +100,8 @@ public async Task ExecuteNewDeviceDeviceCreated() ThingId = newDevice.Id, ThingName = newDevice.Name, ThingTypeName = newDevice.DeviceModel.Name, - Version = newDevice.Version + Version = newDevice.Version, + HttpStatusCode = HttpStatusCode.OK }); _ = this.mockDeviceModelRepository @@ -182,7 +183,8 @@ public async Task ExecuteExistingDeviceWithHigherVersionDeviceUpdated() ThingId = existingDevice.Id, ThingName = existingDevice.Name, ThingTypeName = existingDevice.DeviceModel.Name, - Version = 2 + Version = 2, + HttpStatusCode = HttpStatusCode.OK }); _ = this.mockDeviceModelRepository @@ -248,7 +250,8 @@ public async Task ExecuteExistingDeviceWithOlderVersionDeviceNotUpdated() ThingId = existingDevice.Id, ThingName = existingDevice.Name, ThingTypeName = existingDevice.DeviceModel.Name, - Version = 1 + Version = 1, + HttpStatusCode = HttpStatusCode.OK }); _ = this.mockDeviceModelRepository @@ -274,6 +277,59 @@ public async Task ExecuteExistingDeviceWithOlderVersionDeviceNotUpdated() MockRepository.VerifyAll(); } + [Test] + public async Task ExecuteNewDeviceWithDescribeThingErrorSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new Device + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2 + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = existingDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = 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 + }); + + _ = 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.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + [Test] public async Task ExecuteNewDeviceWithoutThingTypeSkipped() { @@ -309,7 +365,8 @@ public async Task ExecuteNewDeviceWithoutThingTypeSkipped() { ThingId = existingDevice.Id, ThingName = existingDevice.Name, - Version = 1 + Version = 1, + HttpStatusCode = HttpStatusCode.OK }); _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) @@ -361,7 +418,8 @@ public async Task ExecuteNewDeviceWithUnknownThingTypeSkipped() ThingId = existingDevice.Id, ThingName = existingDevice.Name, ThingTypeName = existingDevice.DeviceModel.Name, - Version = 1 + Version = 1, + HttpStatusCode = HttpStatusCode.OK }); _ = this.mockDeviceModelRepository @@ -381,8 +439,113 @@ public async Task ExecuteNewDeviceWithUnknownThingTypeSkipped() MockRepository.VerifyAll(); } - [Test] - public async Task ExecuteNewDeviceWithoutThingShadowSkipped() + [TestCase(HttpStatusCode.NotFound)] + [TestCase(HttpStatusCode.BadRequest)] + public async Task ExecuteNewDeviceWithoutThingShadowSkipped(HttpStatusCode thingShadowCode) + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new Device + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2 + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = existingDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = 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.GetByName(existingDevice.DeviceModel.Name)) + .Returns(expectedDeviceModel); + + _ = this.amazonIoTDataClient.Setup(client => client.GetThingShadowAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new GetThingShadowResponse() { HttpStatusCode = thingShadowCode }); + + _ = 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.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + public async Task ExecuteNewDeviceWithAmazonIotExceptionSkipped() + { + // Arrange + var mockJobExecutionContext = MockRepository.Create(); + + var expectedDeviceModel = Fixture.Create(); + var existingDevice = new Device + { + Id = Fixture.Create(), + Name = Fixture.Create(), + DeviceModel = expectedDeviceModel, + DeviceModelId = expectedDeviceModel.Id, + Version = 2 + }; + + var thingsListing = new ListThingsResponse + { + Things = new List() + { + new ThingAttribute + { + ThingName = existingDevice.Name + } + } + }; + + _ = this.amazonIoTClient.Setup(client => client.ListThingsAsync(It.IsAny())) + .ReturnsAsync(thingsListing); + + _ = this.amazonIoTClient.Setup(client => client.DescribeThingAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(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.syncThingsJob.Execute(mockJobExecutionContext.Object); + + // Assert + MockRepository.VerifyAll(); + } + + public async Task ExecuteNewDeviceWithAmazonIotDataExceptionSkipped() { // Arrange var mockJobExecutionContext = MockRepository.Create(); @@ -417,7 +580,8 @@ public async Task ExecuteNewDeviceWithoutThingShadowSkipped() ThingId = existingDevice.Id, ThingName = existingDevice.Name, ThingTypeName = existingDevice.DeviceModel.Name, - Version = 1 + Version = 1, + HttpStatusCode = HttpStatusCode.OK }); _ = this.mockDeviceModelRepository @@ -425,7 +589,7 @@ public async Task ExecuteNewDeviceWithoutThingShadowSkipped() .Returns(expectedDeviceModel); _ = this.amazonIoTDataClient.Setup(client => client.GetThingShadowAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new GetThingShadowResponse() { HttpStatusCode = HttpStatusCode.NotFound }); + .ThrowsAsync(new AmazonIotDataException("")); _ = this.mockDeviceRepository.Setup(x => x.GetAllAsync(It.IsAny>>(), It.IsAny(), d => d.Tags, d => d.Labels)) .ReturnsAsync(new List());