From 1fc869e276dd9ba4300767eda476357c9ddd3d8f Mon Sep 17 00:00:00 2001 From: Kevin BEAUGRAND <9513635+kbeaugrand@users.noreply.github.com> Date: Fri, 9 Sep 2022 19:06:42 +0200 Subject: [PATCH] Merge v3 Features to main branch (#1144) * Add target branche to CI actions * Add PostgreSQL database to deployment (#1112) * Add PostgreSQL database to deployment * Add ARM lint task to v3-dev branch * Add github actions to solution file * Feature/add pgsql database connection (#1121) * Add entityframework + PGSql nuget packages * Add PGSql database context + initial database creation * Add Quartz.NET Scheduler to server (#1135) * Ignore migration files to code coverage * Migrate v3-dev to v3/main branch * Add unit of work and generic repository (#1154) * Add unit of work and generic repository * Add infrastructure layer + domain layer * Move Device model property to database (#1155) * Fix #984 - Refactor DeviceModelPropertiesController (#1159) * Rebase from main * Remove start Azurite from the build pipeline * Add database servcie as adependency to iot hub portal #1175 * Fix PostgreSQL arm deployment on V3 (#1183) * Feature: Add PostgreSQL login/password fields to arm ui form #1181 * Fix postgsql arm deployment #1180 * Update temporary arm templates urls for testing * Update arm templates url to target main branch * Set pgsqlAdminPassword parameter as securestring (#1188) Co-authored-by: Hocine Hacherouf --- .github/workflows/arm-ttk.yml | 2 +- .github/workflows/build.yml | 8 +- .github/workflows/codeql.yml | 8 +- codecov.yml | 6 +- .../AzureIoTHub.Portal.Infrastructure.csproj | 27 + .../ConfigHandlerBase.cs} | 81 +- .../ConfigHandlerFactory.cs | 26 + .../DevelopmentConfigHandler.cs | 74 + .../Factories/TableClientFactory.cs | 71 + .../20220903171411_Initial Create.Designer.cs | 28 + .../20220903171411_Initial Create.cs | 22 + ...dd DeviceModelProperty support.Designer.cs | 56 + ...4153232_Add DeviceModelProperty support.cs | 38 + .../PortalDbContextModelSnapshot.cs | 54 + .../PortalDbContext.cs | 40 + .../PortalDbContextFactory.cs | 20 + .../ProductionConfigHandler.cs | 74 + .../DeviceModelPropertiesRepository.cs | 53 + .../Repositories/GenericRepository.cs | 54 + .../Seeds/DeviceModelPropoertySeeder.cs | 45 + .../UnitOfWork.cs | 44 + .../AzureIoTHub.Portal.Tests.Unit.csproj | 3 +- .../ConfigHandlerFactoryTest.cs | 57 + .../DevelopmentConfigHandlerTests.cs | 99 +- .../Factories/TableClientFactoryTests.cs | 160 + .../ProductionConfigHandlerTests.cs | 99 +- .../DeviceModelPropertiesRepositoryTests.cs | 126 + .../Repositories/GenericRepositoryTests.cs | 154 + .../Infrastructure/UnitOfWorkTests.cs | 58 + .../DeviceModelPropertiesControllerTests.cs | 345 +- .../v1.0/DeviceModelsControllerTests.cs | 6 +- .../v1.0/DevicesControllerTests.cs | 15 +- .../v1.0/EdgeDevicesControllerTests.cs | 10 +- .../LoRaWANConcentratorsControllerTest.cs | 2 +- .../LoRaWANDeviceModelsControllerTest.cs | 6 +- .../LoRaWAN/LoRaWANDevicesControllerTests.cs | 6 +- .../v1.0/SettingsControllerTest.cs | 4 +- .../InternalServerErrorExceptionTests.cs | 4 +- .../Factories/TableClientFactoryTests.cs | 42 - .../LoRaFeatureActiveFilterAttributeTest.cs | 2 +- .../DeviceModelCommandsManagerTests.cs | 4 +- .../Managers/DeviceModelImageManagerTest.cs | 4 +- .../DeviceProvisioningServiceManagerTests.cs | 2 +- .../Server/Mappers/DeviceTwinMapperTests.cs | 4 +- .../ConcentratorMetricExporterServiceTests.cs | 10 +- .../ConcentratorMetricLoaderServiceTests.cs | 10 +- .../Server/Services/ConfigServiceTests.cs | 12 +- .../DeviceConfigurationsServiceTest.cs | 186 +- .../DeviceMetricExporterServiceTests.cs | 10 +- .../DeviceMetricLoaderServiceTests.cs | 10 +- .../DeviceModelPropertiesServiceTests.cs | 193 + .../Services/DevicePropertyServiceTests.cs | 148 +- .../Server/Services/DeviceServiceTests.cs | 6 +- .../Server/Services/DeviceTagServiceTests.cs | 6 +- .../EdgeDeviceMetricExporterServiceTests.cs | 10 +- .../EdgeDeviceMetricLoaderServiceTests.cs | 10 +- .../Server/Services/EdgeDeviceServiceTest.cs | 16 +- .../Server/Services/EdgeModelServiceTest.cs | 4 +- .../Server/Services/IdeaServiceTests.cs | 12 +- .../Services/LoRaWANCommandServiceTests.cs | 8 +- .../LoRaWANConcentratorServiceTests.cs | 3 +- .../Services/LoRaWANDeviceServiceTests.cs | 22 +- .../TableStorageHealthCheckTest.cs | 4 +- .../UnitTests/Bases/RepositoryTestBase.cs | 22 + src/AzureIoTHub.Portal.sln | 18 + src/AzureIoTHub.Portal/Client/Program.cs | 1 + .../Client/assets/package-lock.json | 3233 +---------------- .../Server/AzureIoTHub.Portal.Server.csproj | 76 +- .../v1.0/DeviceModelControllerBase.cs | 6 +- .../v1.0/DeviceModelPropertiesController.cs | 13 +- .../DeviceModelPropertiesControllerBase.cs | 133 +- .../v1.0/DeviceModelsController.cs | 6 +- .../Controllers/v1.0/DevicesController.cs | 7 +- .../Controllers/v1.0/DevicesControllerBase.cs | 6 +- .../Controllers/v1.0/EdgeDevicesController.cs | 2 +- .../Controllers/v1.0/EdgeModelsController.cs | 2 +- .../LoRaWAN/LoRaWANDeviceModelsController.cs | 8 +- .../v1.0/LoRaWAN/LoRaWANDevicesController.cs | 2 +- .../Controllers/v1.0/SettingsController.cs | 1 + .../Server/DevelopmentConfigHandler.cs | 72 - .../Server/Entities/EntityBase.cs | 6 +- .../Server/Factories/ITableClientFactory.cs | 31 - .../Server/Factories/TableClientFactory.cs | 73 - .../LoRaFeatureActiveFilterAttribute.cs | 1 + .../Managers/DeviceModelCommandsManager.cs | 4 +- .../Managers/DeviceModelImageManager.cs | 3 +- .../DeviceProvisioningServiceManager.cs | 1 + .../Server/Mappers/DevicePropertyProfile.cs | 5 +- .../Server/ProductionConfigHandler.cs | 72 - .../ConcentratorMetricExporterService.cs | 3 +- .../ConcentratorMetricLoaderService.cs | 3 +- .../Server/Services/ConfigService.cs | 2 +- .../Services/DeviceConfigurationsService.cs | 21 +- .../Services/DeviceMetricExporterService.cs | 3 +- .../Services/DeviceMetricLoaderService.cs | 3 +- .../Services/DeviceModelPropertiesService.cs | 95 + .../Server/Services/DevicePropertyService.cs | 29 +- .../Server/Services/DeviceService.cs | 2 +- .../Server/Services/DeviceTagService.cs | 10 +- .../EdgeDeviceMetricExporterService.cs | 3 +- .../Services/EdgeDeviceMetricLoaderService.cs | 3 +- .../Server/Services/EdgeDevicesService.cs | 20 +- .../Server/Services/EdgeModelService.cs | 16 +- .../Services/IDeviceModelPropertiesService.cs | 15 + .../Server/Services/IdeaService.cs | 3 +- .../Server/Services/LoRaWANCommandService.cs | 4 +- .../Server/Services/LoRaWANDeviceService.cs | 4 +- .../TableStorageHealthCheck.cs | 2 +- src/AzureIoTHub.Portal/Server/Startup.cs | 57 +- .../AzureIoTHub.Portal.Domain.csproj | 17 + .../Base/EntityBase.cs | 13 + src/AzureIoTHubPortal.Domain/ConfigHandler.cs | 68 + .../Entities/DeviceModelProperty.cs | 14 +- .../Exceptions/BaseException.cs | 2 +- .../InternalServerErrorException.cs | 4 +- .../ResourceAlreadyExistsException.cs | 4 +- .../Exceptions/ResourceNotFoundException.cs | 4 +- src/AzureIoTHubPortal.Domain/IRepository.cs | 21 + .../ITableClientFactory.cs | 24 + src/AzureIoTHubPortal.Domain/IUnitOfWork.cs | 12 + .../IDeviceModelPropertiesRepository.cs | 15 + .../Shared}/Constants/ErrorTitles.cs | 2 +- .../Shared}/Constants/MetricName.cs | 2 +- src/docker-compose.dcproj | 18 + src/docker-compose.override.yml | 13 + src/docker-compose.yml | 17 + src/launchSettings.json | 15 + templates/azuredeploy.json | 24 + templates/azuredeployUI.json | 35 + templates/portalDeployWithLoRa.json | 44 + templates/portalDeployWithoutLoRa.json | 116 +- 131 files changed, 2687 insertions(+), 4622 deletions(-) create mode 100644 src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj rename src/{AzureIoTHub.Portal/Server/ConfigHandler.cs => AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs} (50%) create mode 100644 src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/DevelopmentConfigHandler.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Factories/TableClientFactory.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Migrations/20220903171411_Initial Create.Designer.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Migrations/20220903171411_Initial Create.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Migrations/20220904153232_Add DeviceModelProperty support.Designer.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Migrations/20220904153232_Add DeviceModelProperty support.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/PortalDbContextFactory.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/ProductionConfigHandler.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceModelPropertiesRepository.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Repositories/GenericRepository.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/Seeds/DeviceModelPropoertySeeder.cs create mode 100644 src/AzureIoTHub.Portal.Infrastructure/UnitOfWork.cs create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ConfigHandlerFactoryTest.cs rename src/AzureIoTHub.Portal.Tests.Unit/{Server => Infrastructure}/DevelopmentConfigHandlerTests.cs (52%) create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Factories/TableClientFactoryTests.cs rename src/AzureIoTHub.Portal.Tests.Unit/{Server => Infrastructure}/ProductionConfigHandlerTests.cs (56%) create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceModelPropertiesRepositoryTests.cs create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GenericRepositoryTests.cs create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/UnitOfWorkTests.cs delete mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Server/Factories/TableClientFactoryTests.cs create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceModelPropertiesServiceTests.cs create mode 100644 src/AzureIoTHub.Portal.Tests.Unit/UnitTests/Bases/RepositoryTestBase.cs delete mode 100644 src/AzureIoTHub.Portal/Server/DevelopmentConfigHandler.cs delete mode 100644 src/AzureIoTHub.Portal/Server/Factories/ITableClientFactory.cs delete mode 100644 src/AzureIoTHub.Portal/Server/Factories/TableClientFactory.cs delete mode 100644 src/AzureIoTHub.Portal/Server/ProductionConfigHandler.cs create mode 100644 src/AzureIoTHub.Portal/Server/Services/DeviceModelPropertiesService.cs create mode 100644 src/AzureIoTHub.Portal/Server/Services/IDeviceModelPropertiesService.cs create mode 100644 src/AzureIoTHubPortal.Domain/AzureIoTHub.Portal.Domain.csproj create mode 100644 src/AzureIoTHubPortal.Domain/Base/EntityBase.cs create mode 100644 src/AzureIoTHubPortal.Domain/ConfigHandler.cs rename src/{AzureIoTHub.Portal/Server => AzureIoTHubPortal.Domain}/Entities/DeviceModelProperty.cs (75%) rename src/{AzureIoTHub.Portal/Server => AzureIoTHubPortal.Domain}/Exceptions/BaseException.cs (91%) rename src/{AzureIoTHub.Portal/Server => AzureIoTHubPortal.Domain}/Exceptions/InternalServerErrorException.cs (80%) rename src/{AzureIoTHub.Portal/Server => AzureIoTHubPortal.Domain}/Exceptions/ResourceAlreadyExistsException.cs (81%) rename src/{AzureIoTHub.Portal/Server => AzureIoTHubPortal.Domain}/Exceptions/ResourceNotFoundException.cs (80%) create mode 100644 src/AzureIoTHubPortal.Domain/IRepository.cs create mode 100644 src/AzureIoTHubPortal.Domain/ITableClientFactory.cs create mode 100644 src/AzureIoTHubPortal.Domain/IUnitOfWork.cs create mode 100644 src/AzureIoTHubPortal.Domain/Repositories/IDeviceModelPropertiesRepository.cs rename src/{AzureIoTHub.Portal/Server => AzureIoTHubPortal.Domain/Shared}/Constants/ErrorTitles.cs (89%) rename src/{AzureIoTHub.Portal/Server => AzureIoTHubPortal.Domain/Shared}/Constants/MetricName.cs (94%) create mode 100644 src/docker-compose.dcproj create mode 100644 src/docker-compose.override.yml create mode 100644 src/docker-compose.yml create mode 100644 src/launchSettings.json diff --git a/.github/workflows/arm-ttk.yml b/.github/workflows/arm-ttk.yml index 7f66d19d9..eea766bcc 100644 --- a/.github/workflows/arm-ttk.yml +++ b/.github/workflows/arm-ttk.yml @@ -2,7 +2,7 @@ name: Validate ARM templates on: pull_request: - branches: [ main ] + branches: [ main, v3-dev ] paths: - 'templates/**' push: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88a01c2ec..3cb07e37e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,9 +3,9 @@ name: Build & Test # Controls when the workflow will run on: pull_request: - branches: [ main ] + branches: [ main, v3/main ] push: - branches: [ main ] + branches: [ main, v3/main ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -28,11 +28,11 @@ jobs: working-directory: src/ - name: Restore dependencies - run: dotnet restore + run: dotnet restore AzureIoTHub.Portal.sln working-directory: src/ - name: Build - run: dotnet build --no-restore -p:ClientAssetsRestoreCommand="npm ci" + run: dotnet build AzureIoTHub.Portal.sln --no-restore -p:ClientAssetsRestoreCommand="npm ci" working-directory: src/ - name: Generate Open API documentation diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3b4d043e9..26ae4fe06 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,10 +1,10 @@ name: "Code Scanning" -on: +on: pull_request: - branches: [ main ] + branches: [ main, v3/main ] push: - branches: [main] + branches: [ main, v3/main ] schedule: # ┌───────────── minute (0 - 59) # │ ┌───────────── hour (0 - 23) @@ -45,7 +45,7 @@ jobs: queries: +security-and-quality,security-extended - name: Build - run: dotnet build --configuration Release + run: dotnet build AzureIoTHub.Portal.sln --configuration Release working-directory: src/ - name: Perform CodeQL Analysis diff --git a/codecov.yml b/codecov.yml index cd336b4c6..bc179cef5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,4 +5,8 @@ coverage: target: auto # auto compares coverage to the previous base commit ignore: - "**/Startup.cs" - - "**/Program.cs" \ No newline at end of file + - "**/Program.cs" + - "**/PortalDbContext.cs" + - "**/Migrations/*.cs" + - "**/Seeds/*.cs" + - "**/PortalDbContextFactory.cs" \ No newline at end of file diff --git a/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj b/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj new file mode 100644 index 000000000..d2481872c --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/AzureIoTHub.Portal.Infrastructure.csproj @@ -0,0 +1,27 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AzureIoTHub.Portal/Server/ConfigHandler.cs b/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs similarity index 50% rename from src/AzureIoTHub.Portal/Server/ConfigHandler.cs rename to src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs index 042e9e97a..efc729b54 100644 --- a/src/AzureIoTHub.Portal/Server/ConfigHandler.cs +++ b/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerBase.cs @@ -1,14 +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.Server +namespace AzureIoTHub.Portal.Infrastructure { - using System; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.Hosting; + using AzureIoTHub.Portal.Domain; - public abstract class ConfigHandler + internal abstract class ConfigHandlerBase : ConfigHandler { internal const string PortalNameKey = "PortalName"; internal const string IoTHubConnectionStringKey = "IoTHub:ConnectionString"; @@ -16,6 +13,7 @@ public abstract class ConfigHandler internal const string DPSServiceEndpointKey = "IoTDPS:ServiceEndpoint"; internal const string DPSIDScopeKey = "IoTDPS:IDScope"; internal const string UseSecurityHeadersKey = "UseSecurityHeaders"; + internal const string PostgreSQLConnectionStringKey = "PostgreSQL:ConnectionString"; internal const string OIDCScopeKey = "OIDC:Scope"; internal const string OIDCAuthorityKey = "OIDC:Authority"; @@ -45,76 +43,5 @@ public abstract class ConfigHandler internal const string IdeasUrlKey = "Ideas:Url"; internal const string IdeasAuthenticationHeaderKey = "Ideas:Authentication:Header"; internal const string IdeasAuthenticationTokenKey = "Ideas:Authentication:Token"; - - internal static ConfigHandler Create(IWebHostEnvironment env, IConfiguration config) - { - ArgumentNullException.ThrowIfNull(env, nameof(env)); - ArgumentNullException.ThrowIfNull(config, nameof(config)); - - if (env.IsProduction()) - { - return new ProductionConfigHandler(config); - } - - return new DevelopmentConfigHandler(config); - } - - internal abstract string IoTHubConnectionString { get; } - - internal abstract string DPSConnectionString { get; } - - internal abstract string DPSEndpoint { get; } - - internal abstract string DPSScopeID { get; } - - internal abstract string StorageAccountConnectionString { get; } - - internal abstract int StorageAccountDeviceModelImageMaxAge { get; } - - internal abstract bool UseSecurityHeaders { get; } - - internal abstract string OIDCScope { get; } - - internal abstract string OIDCApiClientId { get; } - - internal abstract string OIDCClientId { get; } - - internal abstract string OIDCMetadataUrl { get; } - - internal abstract string OIDCAuthority { get; } - - internal abstract bool OIDCValidateIssuer { get; } - - internal abstract bool OIDCValidateAudience { get; } - - internal abstract bool OIDCValidateLifetime { get; } - - internal abstract bool OIDCValidateIssuerSigningKey { get; } - - internal abstract bool OIDCValidateActor { get; } - - internal abstract bool OIDCValidateTokenReplay { get; } - - internal abstract bool IsLoRaEnabled { get; } - - internal abstract string LoRaKeyManagementUrl { get; } - - internal abstract string LoRaKeyManagementCode { get; } - - internal abstract string LoRaKeyManagementApiVersion { get; } - - internal abstract string PortalName { get; } - - internal abstract int MetricExporterRefreshIntervalInSeconds { get; } - - internal abstract int MetricLoaderRefreshIntervalInMinutes { get; } - - internal abstract bool IdeasEnabled { get; } - - internal abstract string IdeasUrl { get; } - - internal abstract string IdeasAuthenticationHeader { get; } - - internal abstract string IdeasAuthenticationToken { get; } } } diff --git a/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs b/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs new file mode 100644 index 000000000..fc4c718b0 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/ConfigHandlerFactory.cs @@ -0,0 +1,26 @@ +// 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 +{ + using System; + using AzureIoTHub.Portal.Domain; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Hosting; + + public static class ConfigHandlerFactory + { + public static ConfigHandler Create(IHostEnvironment env, IConfiguration config) + { + ArgumentNullException.ThrowIfNull(env, nameof(env)); + ArgumentNullException.ThrowIfNull(config, nameof(config)); + + if (env.IsProduction()) + { + return new ProductionConfigHandler(config); + } + + return new DevelopmentConfigHandler(config); + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/DevelopmentConfigHandler.cs b/src/AzureIoTHub.Portal.Infrastructure/DevelopmentConfigHandler.cs new file mode 100644 index 000000000..1b0b52708 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/DevelopmentConfigHandler.cs @@ -0,0 +1,74 @@ +// 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 +{ + using Microsoft.Extensions.Configuration; + + internal class DevelopmentConfigHandler : ConfigHandlerBase + { + private readonly IConfiguration config; + + internal DevelopmentConfigHandler(IConfiguration config) + { + this.config = config; + } + + public override string PortalName => this.config[PortalNameKey]; + + public override int MetricExporterRefreshIntervalInSeconds => this.config.GetValue(MetricExporterRefreshIntervalKey, 30); + + public override int MetricLoaderRefreshIntervalInMinutes => this.config.GetValue(MetricLoaderRefreshIntervalKey, 10); + + public override string IoTHubConnectionString => this.config[IoTHubConnectionStringKey]; + + public override string DPSConnectionString => this.config[DPSConnectionStringKey]; + + public override string DPSEndpoint => this.config[DPSServiceEndpointKey]; + + public override string DPSScopeID => this.config[DPSIDScopeKey]; + + public override string StorageAccountConnectionString => this.config[StorageAccountConnectionStringKey]; + + public override int StorageAccountDeviceModelImageMaxAge => this.config.GetValue(StorageAccountDeviceModelImageMaxAgeKey, 86400); + + public override bool UseSecurityHeaders => this.config.GetValue(UseSecurityHeadersKey, true); + + public override string OIDCScope => this.config[OIDCScopeKey]; + + public override string OIDCAuthority => this.config[OIDCAuthorityKey]; + + public override string OIDCMetadataUrl => this.config[OIDCMetadataUrlKey]; + + public override string OIDCClientId => this.config[OIDCClientIdKey]; + + public override string OIDCApiClientId => this.config[OIDCApiClientIdKey]; + + public override bool OIDCValidateIssuer => this.config.GetValue(OIDCValidateIssuerKey, true); + + public override bool OIDCValidateAudience => this.config.GetValue(OIDCValidateAudienceKey, true); + + public override bool OIDCValidateLifetime => this.config.GetValue(OIDCValidateLifetimeKey, true); + + public override bool OIDCValidateIssuerSigningKey => this.config.GetValue(OIDCValidateIssuerSigningKeyKey, true); + + public override bool OIDCValidateActor => this.config.GetValue(OIDCValidateActorKey, false); + + public override bool OIDCValidateTokenReplay => this.config.GetValue(OIDCValidateTokenReplayKey, false); + + public override bool IsLoRaEnabled => bool.Parse(this.config[IsLoRaFeatureEnabledKey] ?? "true"); + + public override string LoRaKeyManagementUrl => this.config[LoRaKeyManagementUrlKey]; + + public override string LoRaKeyManagementCode => this.config[LoRaKeyManagementCodeKey]; + + public override string LoRaKeyManagementApiVersion => this.config[LoRaKeyManagementApiVersionKey]; + + public override bool IdeasEnabled => this.config.GetValue(IdeasEnabledKey, false); + public override string IdeasUrl => this.config.GetValue(IdeasUrlKey, string.Empty); + public override string IdeasAuthenticationHeader => this.config.GetValue(IdeasAuthenticationHeaderKey, "Ocp-Apim-Subscription-Key"); + public override string IdeasAuthenticationToken => this.config.GetValue(IdeasAuthenticationTokenKey, string.Empty); + + public override string PostgreSQLConnectionString => this.config[PostgreSQLConnectionStringKey]; + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Factories/TableClientFactory.cs b/src/AzureIoTHub.Portal.Infrastructure/Factories/TableClientFactory.cs new file mode 100644 index 000000000..741958475 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Factories/TableClientFactory.cs @@ -0,0 +1,71 @@ +// 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.Factories +{ + using System; + using Azure; + using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + + public class TableClientFactory : ITableClientFactory + { + private readonly string connectionString; + + internal const string DeviceCommandTableName = "DeviceCommands"; + internal const string DeviceTemplateTableName = "DeviceTemplates"; + internal const string EdgeDeviceTemplateTableName = "EdgeDeviceTemplates"; + internal const string DeviceTagSettingTableName = "DeviceTagSettings"; + internal const string DeviceTemplatePropertiesTableName = "DeviceTemplateProperties"; + internal const string EdgeModuleCommandsTableName = "EdgeModuleCommands"; + + public TableClientFactory(string connectionString) + { + this.connectionString = connectionString; + } + + public TableClient GetDeviceCommands() + { + return CreateClient(DeviceCommandTableName); + } + + public TableClient GetDeviceTemplates() + { + return CreateClient(DeviceTemplateTableName); + } + + public TableClient GetEdgeDeviceTemplates() + { + return CreateClient(EdgeDeviceTemplateTableName); + } + + public TableClient GetDeviceTagSettings() + { + return CreateClient(DeviceTagSettingTableName); + } + + private TableClient CreateClient(string tableName) + { + var tableClient = new TableClient(this.connectionString, tableName); + _ = tableClient.CreateIfNotExists(); + + return tableClient; + } + + public TableClient GetDeviceTemplateProperties() + { + return CreateClient(DeviceTemplatePropertiesTableName); + } + + public TableClient GetTemplatesHealthCheck() + { + return CreateClient("tableHealthCheck"); + } + + public TableClient GetEdgeModuleCommands() + { + return CreateClient(EdgeModuleCommandsTableName); + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220903171411_Initial Create.Designer.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220903171411_Initial Create.Designer.cs new file mode 100644 index 000000000..e25e391f5 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220903171411_Initial Create.Designer.cs @@ -0,0 +1,28 @@ +// +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("20220903171411_Initial Create")] + partial class InitialCreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220903171411_Initial Create.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220903171411_Initial Create.cs new file mode 100644 index 000000000..578f8d336 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220903171411_Initial Create.cs @@ -0,0 +1,22 @@ +// 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 InitialCreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220904153232_Add DeviceModelProperty support.Designer.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220904153232_Add DeviceModelProperty support.Designer.cs new file mode 100644 index 000000000..9819bdc97 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220904153232_Add DeviceModelProperty support.Designer.cs @@ -0,0 +1,56 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace AzureIoTHub.Portal.Infrastructure.Migrations +{ + [DbContext(typeof(PortalDbContext))] + [Migration("20220904153232_Add DeviceModelProperty support")] + partial class AddDeviceModelPropertysupport + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220904153232_Add DeviceModelProperty support.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220904153232_Add DeviceModelProperty support.cs new file mode 100644 index 000000000..c1871a884 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/20220904153232_Add DeviceModelProperty support.cs @@ -0,0 +1,38 @@ +// 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 AddDeviceModelPropertysupport : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.CreateTable( + name: "DeviceModelProperties", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + Name = table.Column(type: "text", nullable: false), + DisplayName = table.Column(type: "text", nullable: false), + IsWritable = table.Column(type: "boolean", nullable: false), + Order = table.Column(type: "integer", nullable: false), + PropertyType = table.Column(type: "integer", nullable: false), + ModelId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + _ = table.PrimaryKey("PK_DeviceModelProperties", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropTable( + name: "DeviceModelProperties"); + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs b/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs new file mode 100644 index 000000000..c9ba8489c --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Migrations/PortalDbContextModelSnapshot.cs @@ -0,0 +1,54 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +#nullable disable + +namespace AzureIoTHub.Portal.Infrastructure.Migrations +{ + [DbContext(typeof(PortalDbContext))] + partial class PortalDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + 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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs new file mode 100644 index 000000000..a5f8c0a26 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContext.cs @@ -0,0 +1,40 @@ +// 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 +{ + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Infrastructure.Seeds; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + + public class PortalDbContext : DbContext + { + public DbSet DeviceModelProperties { 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) +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + try + { + var config = this.Database.GetService(); + + _ = modelBuilder + .MigrateDeviceModelProperties(config); + } + catch (InvalidOperationException) + { + + } + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/PortalDbContextFactory.cs b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContextFactory.cs new file mode 100644 index 000000000..479ff60df --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/PortalDbContextFactory.cs @@ -0,0 +1,20 @@ +// 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 +{ + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Design; + + public class PortalDbContextFactory : IDesignTimeDbContextFactory + { + public PortalDbContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + + _ = optionsBuilder.UseNpgsql("Server=database;Database=cgigeiotdemo;Port=5432;User Id=postgres;Password=postgrePassword;Pooling=true;Connection Lifetime=0;Command Timeout=0;"); + + return new PortalDbContext(optionsBuilder.Options); + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/ProductionConfigHandler.cs b/src/AzureIoTHub.Portal.Infrastructure/ProductionConfigHandler.cs new file mode 100644 index 000000000..b2e423dd2 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/ProductionConfigHandler.cs @@ -0,0 +1,74 @@ +// 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 +{ + using Microsoft.Extensions.Configuration; + + internal class ProductionConfigHandler : ConfigHandlerBase + { + private readonly IConfiguration config; + + internal ProductionConfigHandler(IConfiguration config) + { + this.config = config; + } + + public override string PortalName => this.config[PortalNameKey]; + + public override int MetricExporterRefreshIntervalInSeconds => this.config.GetValue(MetricExporterRefreshIntervalKey, 30); + + public override int MetricLoaderRefreshIntervalInMinutes => this.config.GetValue(MetricLoaderRefreshIntervalKey, 10); + + public override string IoTHubConnectionString => this.config.GetConnectionString(IoTHubConnectionStringKey); + + public override string DPSConnectionString => this.config.GetConnectionString(DPSConnectionStringKey); + + public override string DPSEndpoint => this.config[DPSServiceEndpointKey]; + + public override string DPSScopeID => this.config[DPSIDScopeKey]; + + public override string StorageAccountConnectionString => this.config.GetConnectionString(StorageAccountConnectionStringKey); + + public override string PostgreSQLConnectionString => this.config.GetConnectionString(PostgreSQLConnectionStringKey); + + public override int StorageAccountDeviceModelImageMaxAge => this.config.GetValue(StorageAccountDeviceModelImageMaxAgeKey, 86400); + + public override bool UseSecurityHeaders => this.config.GetValue(UseSecurityHeadersKey, true); + + public override string OIDCScope => this.config[OIDCScopeKey]; + + public override string OIDCAuthority => this.config[OIDCAuthorityKey]; + + public override string OIDCMetadataUrl => this.config[OIDCMetadataUrlKey]; + + public override string OIDCClientId => this.config[OIDCClientIdKey]; + + public override string OIDCApiClientId => this.config[OIDCApiClientIdKey]; + + public override bool OIDCValidateIssuer => this.config.GetValue(OIDCValidateIssuerKey, true); + + public override bool OIDCValidateAudience => this.config.GetValue(OIDCValidateAudienceKey, true); + + public override bool OIDCValidateLifetime => this.config.GetValue(OIDCValidateLifetimeKey, true); + + public override bool OIDCValidateIssuerSigningKey => this.config.GetValue(OIDCValidateIssuerSigningKeyKey, true); + + public override bool OIDCValidateActor => this.config.GetValue(OIDCValidateActorKey, false); + + public override bool OIDCValidateTokenReplay => this.config.GetValue(OIDCValidateTokenReplayKey, false); + + public override bool IsLoRaEnabled => bool.Parse(this.config[IsLoRaFeatureEnabledKey] ?? "true"); + + public override string LoRaKeyManagementUrl => this.config[LoRaKeyManagementUrlKey]; + + public override string LoRaKeyManagementCode => this.config.GetConnectionString(LoRaKeyManagementCodeKey); + + public override string LoRaKeyManagementApiVersion => this.config[LoRaKeyManagementApiVersionKey]; + + public override bool IdeasEnabled => this.config.GetValue(IdeasEnabledKey, false); + public override string IdeasUrl => this.config.GetValue(IdeasUrlKey, string.Empty); + public override string IdeasAuthenticationHeader => this.config.GetValue(IdeasAuthenticationHeaderKey, "Ocp-Apim-Subscription-Key"); + public override string IdeasAuthenticationToken => this.config.GetValue(IdeasAuthenticationTokenKey, string.Empty); + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceModelPropertiesRepository.cs b/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceModelPropertiesRepository.cs new file mode 100644 index 000000000..37aca31a4 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Repositories/DeviceModelPropertiesRepository.cs @@ -0,0 +1,53 @@ +// 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.Entities; + using AzureIoTHub.Portal.Domain.Repositories; + using Microsoft.EntityFrameworkCore; + + public class DeviceModelPropertiesRepository : GenericRepository, IDeviceModelPropertiesRepository + { + public DeviceModelPropertiesRepository(PortalDbContext context) : base(context) + { + } + + public async Task> GetModelProperties(string modelId) + { + return await this.context.Set() + .Where(x => x.ModelId == modelId) + .ToArrayAsync(); + } + + public async Task SavePropertiesForModel(string modelId, IEnumerable items) + { + ArgumentNullException.ThrowIfNull(items, nameof(items)); + + var modelItems = this.context.Set().Where(c => c.ModelId == modelId); + + foreach (var item in modelItems) + { + if (items.Any(c => c.Id == item.Id)) + { + _ = this.context.Update(item); + continue; + } + + _ = this.context.Remove(item); + } + + foreach (var item in items) + { + if (modelItems.Any(c => c.Id == item.Id)) + { + continue; + } + + item.ModelId = modelId; + + _ = await this.context.AddAsync(item); + } + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Repositories/GenericRepository.cs b/src/AzureIoTHub.Portal.Infrastructure/Repositories/GenericRepository.cs new file mode 100644 index 000000000..07af67243 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Repositories/GenericRepository.cs @@ -0,0 +1,54 @@ +// 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; + using AzureIoTHub.Portal.Domain.Base; + using Microsoft.EntityFrameworkCore; + using System.Collections.Generic; + using System.Threading.Tasks; + + public class GenericRepository : IRepository where T : EntityBase + { + protected readonly PortalDbContext context; + + public GenericRepository(PortalDbContext context) + { + this.context = context; + } + + public IEnumerable GetAll() + { + return this.context.Set() + .ToList(); + } + + public async Task GetByIdAsync(object id) + { + var t = await this.context.Set().FindAsync(id); + return t; + } + + public async Task InsertAsync(T obj) + { + _ = await this.context.Set() + .AddAsync(obj); + } + + public void Update(T obj) + { + _ = this.context.Set().Attach(obj); + this.context.Entry(obj).State = EntityState.Modified; + } + + public void Delete(object id) + { + var existing = this.context + .Set() + .Find(id); + + _ = this.context.Set().Remove(existing); + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/Seeds/DeviceModelPropoertySeeder.cs b/src/AzureIoTHub.Portal.Infrastructure/Seeds/DeviceModelPropoertySeeder.cs new file mode 100644 index 000000000..3fed6b5c7 --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/Seeds/DeviceModelPropoertySeeder.cs @@ -0,0 +1,45 @@ +// 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.Seeds +{ + using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Models; + using AzureIoTHub.Portal.Infrastructure.Factories; + using Microsoft.EntityFrameworkCore; + + internal static class DeviceModelPropoertySeeder + { + public static ModelBuilder MigrateDeviceModelProperties(this ModelBuilder modelBuilder, ConfigHandler config) + { + var table = new TableClientFactory(config.StorageAccountConnectionString) + .GetDeviceTemplateProperties(); + + foreach (var item in table.Query().ToArray()) + { +#pragma warning disable CS8629 // Nullable value type may be null. + _ = modelBuilder.Entity() + .HasData(new DeviceModelProperty + { + Id = item.RowKey, + ModelId = item.PartitionKey, + Name = item.GetString(nameof(DeviceModelProperty.Name)), + DisplayName = item.GetString(nameof(DeviceModelProperty.DisplayName)), + PropertyType = Enum.Parse(item.GetString(nameof(DeviceModelProperty.PropertyType))), + Order = item.GetInt32(nameof(DeviceModelProperty.Order)) ?? 0, + IsWritable = item.GetBoolean(nameof(DeviceModelProperty.IsWritable)).Value + }); +#pragma warning restore CS8629 // Nullable value type may be null. + + if (config is ProductionConfigHandler) + { + _ = table.DeleteEntity(item.PartitionKey, item.RowKey); + } + } + + return modelBuilder; + } + } +} diff --git a/src/AzureIoTHub.Portal.Infrastructure/UnitOfWork.cs b/src/AzureIoTHub.Portal.Infrastructure/UnitOfWork.cs new file mode 100644 index 000000000..ff64d431f --- /dev/null +++ b/src/AzureIoTHub.Portal.Infrastructure/UnitOfWork.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.Infrastructure +{ + using AzureIoTHub.Portal.Domain; + using Microsoft.EntityFrameworkCore; + using System; + using System.Threading.Tasks; + + public class UnitOfWork : IUnitOfWork, IDisposable + where TContext : DbContext + { + private bool disposed; + + public UnitOfWork(TContext context) + { + this.Context = context; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public TContext Context { get; } + + public async Task SaveAsync() + { + _ = await this.Context.SaveChangesAsync(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed && disposing) + { + this.Context.Dispose(); + } + + disposed = true; + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/AzureIoTHub.Portal.Tests.Unit.csproj b/src/AzureIoTHub.Portal.Tests.Unit/AzureIoTHub.Portal.Tests.Unit.csproj index bf67079ec..baf632309 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/AzureIoTHub.Portal.Tests.Unit.csproj +++ b/src/AzureIoTHub.Portal.Tests.Unit/AzureIoTHub.Portal.Tests.Unit.csproj @@ -20,6 +20,7 @@ + @@ -47,7 +48,7 @@ - + diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ConfigHandlerFactoryTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ConfigHandlerFactoryTest.cs new file mode 100644 index 000000000..cec598f97 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ConfigHandlerFactoryTest.cs @@ -0,0 +1,57 @@ +// 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 +{ + using AzureIoTHub.Portal.Infrastructure; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Hosting; + using Moq; + using NUnit.Framework; + + [TestFixture] + public class ConfigHandlerFactoryTest + { + private MockRepository mockRepository; + private Mock mockConfiguration; + private Mock mockHostEnvironment; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + + this.mockConfiguration = this.mockRepository.Create(); + this.mockHostEnvironment = this.mockRepository.Create(); + } + + [Test] + public void WhenUsingDevEnvironmentShouldReturnDevelopmentConfigHandler() + { + // Arrange + _ = this.mockHostEnvironment.Setup(c => c.EnvironmentName) + .Returns(Environments.Development); + + // Act + var result = ConfigHandlerFactory.Create(this.mockHostEnvironment.Object, this.mockConfiguration.Object); + + // Assert + Assert.IsAssignableFrom(result); + } + + + [Test] + public void WhenUsingProdEnvironmentShouldReturnProductionConfigHandler() + { + // Arrange + _ = this.mockHostEnvironment.Setup(c => c.EnvironmentName) + .Returns(Environments.Production); + + // Act + var result = ConfigHandlerFactory.Create(this.mockHostEnvironment.Object, this.mockConfiguration.Object); + + // Assert + Assert.IsAssignableFrom(result); + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/DevelopmentConfigHandlerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/DevelopmentConfigHandlerTests.cs similarity index 52% rename from src/AzureIoTHub.Portal.Tests.Unit/Server/DevelopmentConfigHandlerTests.cs rename to src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/DevelopmentConfigHandlerTests.cs index 558127e6a..7d389c50b 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/DevelopmentConfigHandlerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/DevelopmentConfigHandlerTests.cs @@ -1,12 +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.Tests.Unit.Server +namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure { using System; using System.Globalization; - using System.Reflection; - using AzureIoTHub.Portal.Server; + using AzureIoTHub.Portal.Infrastructure; using FluentAssertions; using Microsoft.Extensions.Configuration; using Moq; @@ -32,20 +31,21 @@ private DevelopmentConfigHandler CreateDevelopmentConfigHandler() return new DevelopmentConfigHandler(this.mockConfiguration.Object); } - [TestCase(ConfigHandler.PortalNameKey, nameof(ConfigHandler.PortalName))] - [TestCase(ConfigHandler.DPSServiceEndpointKey, nameof(ConfigHandler.DPSEndpoint))] - [TestCase(ConfigHandler.DPSIDScopeKey, nameof(ConfigHandler.DPSScopeID))] - [TestCase(ConfigHandler.OIDCScopeKey, nameof(ConfigHandler.OIDCScope))] - [TestCase(ConfigHandler.OIDCAuthorityKey, nameof(ConfigHandler.OIDCAuthority))] - [TestCase(ConfigHandler.OIDCMetadataUrlKey, nameof(ConfigHandler.OIDCMetadataUrl))] - [TestCase(ConfigHandler.OIDCClientIdKey, nameof(ConfigHandler.OIDCClientId))] - [TestCase(ConfigHandler.OIDCApiClientIdKey, nameof(ConfigHandler.OIDCApiClientId))] - [TestCase(ConfigHandler.LoRaKeyManagementUrlKey, nameof(ConfigHandler.LoRaKeyManagementUrl))] - [TestCase(ConfigHandler.LoRaKeyManagementCodeKey, nameof(ConfigHandler.LoRaKeyManagementCode))] - [TestCase(ConfigHandler.LoRaKeyManagementApiVersionKey, nameof(ConfigHandler.LoRaKeyManagementApiVersion))] - [TestCase(ConfigHandler.IoTHubConnectionStringKey, nameof(ConfigHandler.IoTHubConnectionString))] - [TestCase(ConfigHandler.DPSConnectionStringKey, nameof(ConfigHandler.DPSConnectionString))] - [TestCase(ConfigHandler.StorageAccountConnectionStringKey, nameof(ConfigHandler.StorageAccountConnectionString))] + [TestCase(ConfigHandlerBase.PortalNameKey, nameof(ConfigHandlerBase.PortalName))] + [TestCase(ConfigHandlerBase.DPSServiceEndpointKey, nameof(ConfigHandlerBase.DPSEndpoint))] + [TestCase(ConfigHandlerBase.DPSIDScopeKey, nameof(ConfigHandlerBase.DPSScopeID))] + [TestCase(ConfigHandlerBase.OIDCScopeKey, nameof(ConfigHandlerBase.OIDCScope))] + [TestCase(ConfigHandlerBase.OIDCAuthorityKey, nameof(ConfigHandlerBase.OIDCAuthority))] + [TestCase(ConfigHandlerBase.OIDCMetadataUrlKey, nameof(ConfigHandlerBase.OIDCMetadataUrl))] + [TestCase(ConfigHandlerBase.OIDCClientIdKey, nameof(ConfigHandlerBase.OIDCClientId))] + [TestCase(ConfigHandlerBase.OIDCApiClientIdKey, nameof(ConfigHandlerBase.OIDCApiClientId))] + [TestCase(ConfigHandlerBase.LoRaKeyManagementUrlKey, nameof(ConfigHandlerBase.LoRaKeyManagementUrl))] + [TestCase(ConfigHandlerBase.LoRaKeyManagementCodeKey, nameof(ConfigHandlerBase.LoRaKeyManagementCode))] + [TestCase(ConfigHandlerBase.LoRaKeyManagementApiVersionKey, nameof(ConfigHandlerBase.LoRaKeyManagementApiVersion))] + [TestCase(ConfigHandlerBase.IoTHubConnectionStringKey, nameof(ConfigHandlerBase.IoTHubConnectionString))] + [TestCase(ConfigHandlerBase.DPSConnectionStringKey, nameof(ConfigHandlerBase.DPSConnectionString))] + [TestCase(ConfigHandlerBase.StorageAccountConnectionStringKey, nameof(ConfigHandlerBase.StorageAccountConnectionString))] + [TestCase(ConfigHandlerBase.PostgreSQLConnectionStringKey, nameof(ConfigHandlerBase.PostgreSQLConnectionString))] public void SettingsShouldGetValueFromAppSettings(string configKey, string configPropertyName) { // Arrange @@ -58,7 +58,7 @@ public void SettingsShouldGetValueFromAppSettings(string configKey, string confi // Act var result = configHandler .GetType() - .GetProperty(configPropertyName, BindingFlags.Instance | BindingFlags.NonPublic) + .GetProperty(configPropertyName) .GetValue(configHandler, null); // Assert @@ -66,7 +66,64 @@ public void SettingsShouldGetValueFromAppSettings(string configKey, string confi this.mockRepository.VerifyAll(); } - [TestCase(ConfigHandler.IsLoRaFeatureEnabledKey, nameof(ConfigHandler.IsLoRaEnabled))] + [TestCase(ConfigHandlerBase.OIDCValidateAudienceKey, nameof(ConfigHandlerBase.OIDCValidateAudience))] + [TestCase(ConfigHandlerBase.OIDCValidateIssuerKey, nameof(ConfigHandlerBase.OIDCValidateIssuer))] + [TestCase(ConfigHandlerBase.OIDCValidateIssuerSigningKeyKey, nameof(ConfigHandlerBase.OIDCValidateIssuerSigningKey))] + [TestCase(ConfigHandlerBase.OIDCValidateLifetimeKey, nameof(ConfigHandlerBase.OIDCValidateLifetime))] + [TestCase(ConfigHandlerBase.UseSecurityHeadersKey, nameof(ConfigHandlerBase.UseSecurityHeaders))] + public void SecuritySwitchesShouldBeEnabledByDefault(string configKey, string configPropertyName) + { + // Arrange + var configHandler = CreateDevelopmentConfigHandler(); + var mockConfigurationSection = this.mockRepository.Create(); + + _ = mockConfigurationSection.SetupGet(c => c.Value) + .Returns((string)null); + + _ = mockConfigurationSection.SetupGet(c => c.Path) + .Returns(string.Empty); + + _ = this.mockConfiguration.Setup(c => c.GetSection(configKey)) + .Returns(mockConfigurationSection.Object); + + // Act + var result = (bool)configHandler + .GetType() + .GetProperty(configPropertyName) + .GetValue(configHandler, null); + + // Assert + Assert.IsTrue(result); + } + + [TestCase(ConfigHandlerBase.OIDCValidateActorKey, nameof(ConfigHandlerBase.OIDCValidateActor))] + [TestCase(ConfigHandlerBase.OIDCValidateTokenReplayKey, nameof(ConfigHandlerBase.OIDCValidateTokenReplay))] + public void SecuritySwitchesShouldBeDisabledByDefault(string configKey, string configPropertyName) + { + // Arrange + var configHandler = CreateDevelopmentConfigHandler(); + var mockConfigurationSection = this.mockRepository.Create(); + + _ = mockConfigurationSection.SetupGet(c => c.Value) + .Returns((string)null); + + _ = mockConfigurationSection.SetupGet(c => c.Path) + .Returns(string.Empty); + + _ = this.mockConfiguration.Setup(c => c.GetSection(configKey)) + .Returns(mockConfigurationSection.Object); + + // Act + var result = (bool)configHandler + .GetType() + .GetProperty(configPropertyName) + .GetValue(configHandler, null); + + // Assert + Assert.IsFalse(result); + } + + [TestCase(ConfigHandlerBase.IsLoRaFeatureEnabledKey, nameof(ConfigHandlerBase.IsLoRaEnabled))] public void SettingsShouldGetBoolFromAppSettings(string configKey, string configPropertyName) { // Arrange @@ -79,7 +136,7 @@ public void SettingsShouldGetBoolFromAppSettings(string configKey, string config // Act var result = productionConfigHandler .GetType() - .GetProperty(configPropertyName, BindingFlags.Instance | BindingFlags.NonPublic) + .GetProperty(configPropertyName) .GetValue(productionConfigHandler, null); // Assert @@ -94,7 +151,7 @@ public void SettingsShouldGetBoolFromAppSettings(string configKey, string config // Act result = productionConfigHandler .GetType() - .GetProperty(configPropertyName, BindingFlags.Instance | BindingFlags.NonPublic) + .GetProperty(configPropertyName) .GetValue(productionConfigHandler, null); // Assert diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Factories/TableClientFactoryTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Factories/TableClientFactoryTests.cs new file mode 100644 index 000000000..ad14cc852 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Factories/TableClientFactoryTests.cs @@ -0,0 +1,160 @@ +// 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.Factories +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using Azure.Data.Tables; + using AzureIoTHub.Portal.Infrastructure.Factories; + using Docker.DotNet; + using Docker.DotNet.Models; + using NUnit.Framework; + + [TestFixture] + public class TableClientFactoryTests + { + private const string ContainerName = "azurite"; + private const string ImageName = "mcr.microsoft.com/azure-storage/azurite"; + private const string ImageTag = "latest"; + private static readonly string TestContainerName = ContainerName + "AzureIoTHub"; + + private string containerId; + + [TestCase(nameof(TableClientFactory.GetDeviceCommands), TableClientFactory.DeviceCommandTableName)] + [TestCase(nameof(TableClientFactory.GetDeviceTagSettings), TableClientFactory.DeviceTagSettingTableName)] + [TestCase(nameof(TableClientFactory.GetEdgeDeviceTemplates), TableClientFactory.EdgeDeviceTemplateTableName)] + [TestCase(nameof(TableClientFactory.GetEdgeModuleCommands), TableClientFactory.EdgeModuleCommandsTableName)] + [TestCase(nameof(TableClientFactory.GetDeviceTemplateProperties), TableClientFactory.DeviceTemplatePropertiesTableName)] + [TestCase(nameof(TableClientFactory.GetDeviceTemplates), TableClientFactory.DeviceTemplateTableName)] + public void GetTableShoudReturnExpectedTableClient(string methodName, string tableName) + { + // Arrange + var connectionString = "UseDevelopmentStorage=true"; + var tableClientFactory = new TableClientFactory(connectionString); + + // Act + var result = tableClientFactory + .GetType() + .GetMethod(methodName) + .Invoke(tableClientFactory, Array.Empty()) as TableClient; + // Assert + Assert.AreEqual(tableName, result.Name); + } + + [OneTimeSetUp] + protected void SetUp() + { + IList containers = new List(); + var dockerConnection = Environment.OSVersion.Platform.ToString().Contains("Win", StringComparison.Ordinal) ? + "npipe://./pipe/docker_engine" : + "unix:///var/run/docker.sock"; + Console.WriteLine("Starting container"); + using var conf = new DockerClientConfiguration(new Uri(dockerConnection)); // localhost + using var client = conf.CreateClient(); + + try + { + + Console.WriteLine("On Premise execution detected"); + Console.WriteLine("Starting container..."); + containers = client.Containers.ListContainersAsync(new ContainersListParameters() { All = true }).GetAwaiter().GetResult(); + Console.WriteLine("listing container..."); + + // Download image only if not found + try + { + _ = client.Images.InspectImageAsync(ImageName).GetAwaiter().GetResult(); + } + catch (DockerImageNotFoundException) + { + client.Images.CreateImageAsync(new ImagesCreateParameters() { FromImage = ImageName, Tag = ImageTag }, new AuthConfig(), new Progress()).GetAwaiter().GetResult(); + } + + // Create the container + var config = new Config() + { + Hostname = "localhost" + }; + + // Configure the ports to expose + var hostConfig = new HostConfig() + { + PortBindings = new Dictionary> + { + { + $"10000/tcp", new List { new PortBinding { HostIP = "127.0.0.1", HostPort = "10000" } } + }, + { + $"10001/tcp", new List { new PortBinding { HostIP = "127.0.0.1", HostPort = "10001" } } + }, + { + $"10002/tcp", new List { new PortBinding { HostIP = "127.0.0.1", HostPort = "10002" } } + } + } + }; + + Console.WriteLine("Creating container..."); + + // Create the container + var response = client.Containers.CreateContainerAsync(new CreateContainerParameters(config) + { + Image = ImageName + ":" + ImageTag, + Name = TestContainerName, + Tty = false, + HostConfig = hostConfig + }).GetAwaiter().GetResult(); + + this.containerId = response.ID; + + Console.WriteLine("Starting container..."); + + var started = client.Containers.StartContainerAsync(this.containerId, new ContainerStartParameters()).GetAwaiter().GetResult(); + if (!started) + { + Assert.False(true, "Cannot start the docker container"); + } + + Console.WriteLine("Finish booting sequence container..."); + } + catch (DockerApiException e) when (e.StatusCode == HttpStatusCode.Conflict) + { + var container = containers.FirstOrDefault(c => c.Names.Contains("/" + TestContainerName)); + if (container is { } c && c.State.Equals("exited", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine("Starting existing container."); + _ = client.Containers.StartContainerAsync(container.ID, new ContainerStartParameters()).GetAwaiter().GetResult(); + } + else + { + Console.WriteLine("Docker container is already running."); + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + throw; + } + } + + [OneTimeTearDown] + protected void TearDown() + { + if (!string.IsNullOrEmpty(this.containerId)) + { + // we are running locally + var dockerConnection = System.Environment.OSVersion.Platform.ToString().Contains("Win", StringComparison.Ordinal) ? + "npipe://./pipe/docker_engine" : + "unix:///var/run/docker.sock"; + using var conf = new DockerClientConfiguration(new Uri(dockerConnection)); // localhost + using var client = conf.CreateClient(); + client.Containers.RemoveContainerAsync(this.containerId, new ContainerRemoveParameters() + { + Force = true + }).GetAwaiter().GetResult(); + } + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/ProductionConfigHandlerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ProductionConfigHandlerTests.cs similarity index 56% rename from src/AzureIoTHub.Portal.Tests.Unit/Server/ProductionConfigHandlerTests.cs rename to src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ProductionConfigHandlerTests.cs index d614898f7..5b9c09e1f 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/ProductionConfigHandlerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/ProductionConfigHandlerTests.cs @@ -1,7 +1,7 @@ // 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 +namespace AzureIoTHub.Portal.Tests.Unit.Infrastructure { using AzureIoTHub.Portal.Server; using System; @@ -11,6 +11,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server using Microsoft.Extensions.Configuration; using Moq; using NUnit.Framework; + using AzureIoTHub.Portal.Infrastructure; [TestFixture] public class ProductionConfigHandlerTests @@ -32,10 +33,11 @@ private ProductionConfigHandler CreateProductionConfigHandler() return new ProductionConfigHandler(this.mockConfiguration.Object); } - [TestCase(ConfigHandler.IoTHubConnectionStringKey, nameof(ConfigHandler.IoTHubConnectionString))] - [TestCase(ConfigHandler.DPSConnectionStringKey, nameof(ConfigHandler.DPSConnectionString))] - [TestCase(ConfigHandler.StorageAccountConnectionStringKey, nameof(ConfigHandler.StorageAccountConnectionString))] - [TestCase(ConfigHandler.LoRaKeyManagementCodeKey, nameof(ConfigHandler.LoRaKeyManagementCode))] + [TestCase(ConfigHandlerBase.IoTHubConnectionStringKey, nameof(ConfigHandlerBase.IoTHubConnectionString))] + [TestCase(ConfigHandlerBase.DPSConnectionStringKey, nameof(ConfigHandlerBase.DPSConnectionString))] + [TestCase(ConfigHandlerBase.StorageAccountConnectionStringKey, nameof(ConfigHandlerBase.StorageAccountConnectionString))] + [TestCase(ConfigHandlerBase.LoRaKeyManagementCodeKey, nameof(ConfigHandlerBase.LoRaKeyManagementCode))] + [TestCase(ConfigHandlerBase.PostgreSQLConnectionStringKey, nameof(ConfigHandlerBase.PostgreSQLConnectionString))] public void SecretsShouldGetValueFromConnectionStrings(string configKey, string configPropertyName) { // Arrange @@ -52,7 +54,7 @@ public void SecretsShouldGetValueFromConnectionStrings(string configKey, string // Act var result = productionConfigHandler .GetType() - .GetProperty(configPropertyName, BindingFlags.Instance | BindingFlags.NonPublic) + .GetProperty(configPropertyName) .GetValue(productionConfigHandler, null); // Assert @@ -60,16 +62,16 @@ public void SecretsShouldGetValueFromConnectionStrings(string configKey, string this.mockRepository.VerifyAll(); } - [TestCase(ConfigHandler.PortalNameKey, nameof(ConfigHandler.PortalName))] - [TestCase(ConfigHandler.DPSServiceEndpointKey, nameof(ConfigHandler.DPSEndpoint))] - [TestCase(ConfigHandler.DPSIDScopeKey, nameof(ConfigHandler.DPSScopeID))] - [TestCase(ConfigHandler.OIDCScopeKey, nameof(ConfigHandler.OIDCScope))] - [TestCase(ConfigHandler.OIDCAuthorityKey, nameof(ConfigHandler.OIDCAuthority))] - [TestCase(ConfigHandler.OIDCMetadataUrlKey, nameof(ConfigHandler.OIDCMetadataUrl))] - [TestCase(ConfigHandler.OIDCClientIdKey, nameof(ConfigHandler.OIDCClientId))] - [TestCase(ConfigHandler.OIDCApiClientIdKey, nameof(ConfigHandler.OIDCApiClientId))] - [TestCase(ConfigHandler.LoRaKeyManagementUrlKey, nameof(ConfigHandler.LoRaKeyManagementUrl))] - [TestCase(ConfigHandler.LoRaKeyManagementApiVersionKey, nameof(ConfigHandler.LoRaKeyManagementApiVersion))] + [TestCase(ConfigHandlerBase.PortalNameKey, nameof(ConfigHandlerBase.PortalName))] + [TestCase(ConfigHandlerBase.DPSServiceEndpointKey, nameof(ConfigHandlerBase.DPSEndpoint))] + [TestCase(ConfigHandlerBase.DPSIDScopeKey, nameof(ConfigHandlerBase.DPSScopeID))] + [TestCase(ConfigHandlerBase.OIDCScopeKey, nameof(ConfigHandlerBase.OIDCScope))] + [TestCase(ConfigHandlerBase.OIDCAuthorityKey, nameof(ConfigHandlerBase.OIDCAuthority))] + [TestCase(ConfigHandlerBase.OIDCMetadataUrlKey, nameof(ConfigHandlerBase.OIDCMetadataUrl))] + [TestCase(ConfigHandlerBase.OIDCClientIdKey, nameof(ConfigHandlerBase.OIDCClientId))] + [TestCase(ConfigHandlerBase.OIDCApiClientIdKey, nameof(ConfigHandlerBase.OIDCApiClientId))] + [TestCase(ConfigHandlerBase.LoRaKeyManagementUrlKey, nameof(ConfigHandlerBase.LoRaKeyManagementUrl))] + [TestCase(ConfigHandlerBase.LoRaKeyManagementApiVersionKey, nameof(ConfigHandlerBase.LoRaKeyManagementApiVersion))] public void SettingsShouldGetValueFromAppSettings(string configKey, string configPropertyName) { // Arrange @@ -82,7 +84,7 @@ public void SettingsShouldGetValueFromAppSettings(string configKey, string confi // Act var result = productionConfigHandler .GetType() - .GetProperty(configPropertyName, BindingFlags.Instance | BindingFlags.NonPublic) + .GetProperty(configPropertyName) .GetValue(productionConfigHandler, null); // Assert @@ -90,7 +92,64 @@ public void SettingsShouldGetValueFromAppSettings(string configKey, string confi this.mockRepository.VerifyAll(); } - [TestCase(ConfigHandler.IsLoRaFeatureEnabledKey, nameof(ConfigHandler.IsLoRaEnabled))] + [TestCase(ConfigHandlerBase.OIDCValidateAudienceKey, nameof(ConfigHandlerBase.OIDCValidateAudience))] + [TestCase(ConfigHandlerBase.OIDCValidateIssuerKey, nameof(ConfigHandlerBase.OIDCValidateIssuer))] + [TestCase(ConfigHandlerBase.OIDCValidateIssuerSigningKeyKey, nameof(ConfigHandlerBase.OIDCValidateIssuerSigningKey))] + [TestCase(ConfigHandlerBase.OIDCValidateLifetimeKey, nameof(ConfigHandlerBase.OIDCValidateLifetime))] + [TestCase(ConfigHandlerBase.UseSecurityHeadersKey, nameof(ConfigHandlerBase.UseSecurityHeaders))] + public void SecuritySwitchesShouldBeEnabledByDefault(string configKey, string configPropertyName) + { + // Arrange + var configHandler = CreateProductionConfigHandler(); + var mockConfigurationSection = this.mockRepository.Create(); + + _ = mockConfigurationSection.SetupGet(c => c.Value) + .Returns((string)null); + + _ = mockConfigurationSection.SetupGet(c => c.Path) + .Returns(string.Empty); + + _ = this.mockConfiguration.Setup(c => c.GetSection(configKey)) + .Returns(mockConfigurationSection.Object); + + // Act + var result = (bool)configHandler + .GetType() + .GetProperty(configPropertyName) + .GetValue(configHandler, null); + + // Assert + Assert.IsTrue(result); + } + + [TestCase(ConfigHandlerBase.OIDCValidateActorKey, nameof(ConfigHandlerBase.OIDCValidateActor))] + [TestCase(ConfigHandlerBase.OIDCValidateTokenReplayKey, nameof(ConfigHandlerBase.OIDCValidateTokenReplay))] + public void SecuritySwitchesShouldBeDisabledByDefault(string configKey, string configPropertyName) + { + // Arrange + var configHandler = CreateProductionConfigHandler(); + var mockConfigurationSection = this.mockRepository.Create(); + + _ = mockConfigurationSection.SetupGet(c => c.Value) + .Returns((string)null); + + _ = mockConfigurationSection.SetupGet(c => c.Path) + .Returns(string.Empty); + + _ = this.mockConfiguration.Setup(c => c.GetSection(configKey)) + .Returns(mockConfigurationSection.Object); + + // Act + var result = (bool)configHandler + .GetType() + .GetProperty(configPropertyName) + .GetValue(configHandler, null); + + // Assert + Assert.IsFalse(result); + } + + [TestCase(ConfigHandlerBase.IsLoRaFeatureEnabledKey, nameof(ConfigHandlerBase.IsLoRaEnabled))] public void SettingsShouldGetBoolFromAppSettings(string configKey, string configPropertyName) { // Arrange @@ -103,7 +162,7 @@ public void SettingsShouldGetBoolFromAppSettings(string configKey, string config // Act var result = productionConfigHandler .GetType() - .GetProperty(configPropertyName, BindingFlags.Instance | BindingFlags.NonPublic) + .GetProperty(configPropertyName) .GetValue(productionConfigHandler, null); // Assert @@ -118,7 +177,7 @@ public void SettingsShouldGetBoolFromAppSettings(string configKey, string config // Act result = productionConfigHandler .GetType() - .GetProperty(configPropertyName, BindingFlags.Instance | BindingFlags.NonPublic) + .GetProperty(configPropertyName) .GetValue(productionConfigHandler, null); // Assert diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceModelPropertiesRepositoryTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceModelPropertiesRepositoryTests.cs new file mode 100644 index 000000000..25a6b8ea1 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/DeviceModelPropertiesRepositoryTests.cs @@ -0,0 +1,126 @@ +// 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.Repositories +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Infrastructure.Repositories; + using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Moq; + using NUnit.Framework; + + public class DeviceModelPropertiesRepositoryTests : RepositoryTestBase + { + private MockRepository mockRepository; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + + var context = SetupDbContext(); + + _ = context.Database.EnsureDeleted(); + _ = context.Database.EnsureCreated(); + } + + [Test] + public async Task GetModelPropertiesTests() + { + // Arrange + var context = SetupDbContext(); + var modelId = Guid.NewGuid().ToString(); + + _ = context.Add(new DeviceModelProperty() + { + Id = Guid.NewGuid().ToString(), + ModelId = modelId, + DisplayName = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString() + }); + + _ = context.Add(new DeviceModelProperty() + { + Id = Guid.NewGuid().ToString(), + ModelId = Guid.NewGuid().ToString(), + DisplayName = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString() + }); + + _ = await context.SaveChangesAsync(); + + var repository = new DeviceModelPropertiesRepository(context); + + // Act + var result = await repository.GetModelProperties(modelId); + + // Assert + Assert.AreEqual(1, result.Count()); + } + + [Test] + public async Task SavePropertiesForModel() + { + // Arrange + var context = SetupDbContext(); + var modelId = Guid.NewGuid().ToString(); + var initialPropertyId = Guid.NewGuid().ToString(); + _ = context.Add(new DeviceModelProperty() + { + Id = initialPropertyId, + ModelId = modelId, + DisplayName = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString() + }); + + _ = context.Add(new DeviceModelProperty() + { + Id = Guid.NewGuid().ToString(), + ModelId = Guid.NewGuid().ToString(), + DisplayName = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString() + }); + + _ = await context.SaveChangesAsync(); + + var repositoryContext = SetupDbContext(); + var repository = new DeviceModelPropertiesRepository(repositoryContext); + + // Act + await repository.SavePropertiesForModel(modelId, new[] + { + new DeviceModelProperty() + { + Id = initialPropertyId, + ModelId = Guid.NewGuid().ToString(), + DisplayName = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString() + }, + new DeviceModelProperty() + { + Id = Guid.NewGuid().ToString(), + ModelId = Guid.NewGuid().ToString(), + DisplayName = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString() + }, + new DeviceModelProperty() + { + Id = Guid.NewGuid().ToString(), + ModelId = Guid.NewGuid().ToString(), + DisplayName = Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString() + } + }); + + _ = await repositoryContext.SaveChangesAsync(); + + // Assert + var finalContext = SetupDbContext(); + Assert.AreEqual(4, finalContext.Set().Count()); + Assert.AreEqual(3, finalContext.Set().Where(c => c.ModelId == modelId).Count()); + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GenericRepositoryTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GenericRepositoryTests.cs new file mode 100644 index 000000000..e64dbd547 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/Repositories/GenericRepositoryTests.cs @@ -0,0 +1,154 @@ +// 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.Repositories +{ + using System; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Infrastructure.Repositories; + using AzureIoTHub.Portal.Tests.Unit.UnitTests.Bases; + using Microsoft.EntityFrameworkCore; + using Moq; + using NUnit.Framework; + + [TestFixture] + public class GenericRepositoryTests : RepositoryTestBase + { + private MockRepository mockRepository; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + + var context = SetupDbContext(); + + _ = context.Database.EnsureDeleted(); + _ = context.Database.EnsureCreated(); + } + + [Test] + public void GetAllTest() + { + // Arrange + var instance = new GenericRepository(SetupDbContext()); + + // Act + var result = instance.GetAll(); + + // Assert + Assert.IsNotNull(result); + Assert.IsEmpty(result); + } + + [Test] + public async Task GetByIdAsyncTest() + { + // Arrange + var entityId = Guid.NewGuid().ToString(); + + var context = SetupDbContext(); + var instance = new GenericRepository(context); + + _ = context.Add(new DeviceModelProperty() + { + Id = entityId + }); + + // Act + var result = await instance.GetByIdAsync(entityId); + + // Assert + Assert.IsNotNull(result); + } + + [Test] + public async Task InsertAsyncTest() + { + // Arrange + var entityId = Guid.NewGuid().ToString(); + + var context = SetupDbContext(); + var instance = new GenericRepository(context); + + // Act + await instance.InsertAsync(new DeviceModelProperty() + { + Id = entityId, + Name = string.Empty, + DisplayName = string.Empty, + ModelId = string.Empty + }); + + // Assert + var result = await context.SaveChangesAsync(); + Assert.AreEqual(1, result); + } + + [Test] + public async Task UpdateAsyncTest() + { + // Arrange + var entityId = Guid.NewGuid().ToString(); + + var firstContext = SetupDbContext(); + + _ = firstContext.Add(new DeviceModelProperty() + { + Id = entityId, + Name = "Name1", + DisplayName = string.Empty, + ModelId = string.Empty + }); + + _ = firstContext.SaveChanges(); + + var context = SetupDbContext(); + var instance = new GenericRepository(context); + + // Act + var updated = new DeviceModelProperty() + { + Id = entityId, + Name = "Name2", + DisplayName = string.Empty, + ModelId = string.Empty + }; + + instance.Update(updated); + + // Assert + Assert.AreEqual(EntityState.Modified, context.Entry(updated).State); + } + + [Test] + public async Task DeleteTest() + { + // Arrange + var entityId = Guid.NewGuid().ToString(); + + var firstContext = SetupDbContext(); + + _ = firstContext.Add(new DeviceModelProperty() + { + Id = entityId, + Name = "Name1", + DisplayName = string.Empty, + ModelId = string.Empty + }); + + _ = firstContext.SaveChanges(); + + var context = SetupDbContext(); + var instance = new GenericRepository(context); + + // Act + instance.Delete(entityId); + _ = context.SaveChanges(); + + // Assert + Assert.IsNull(context.Set().Find(entityId)); + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/UnitOfWorkTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/UnitOfWorkTests.cs new file mode 100644 index 000000000..3118f162d --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Infrastructure/UnitOfWorkTests.cs @@ -0,0 +1,58 @@ +// 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 +{ + using System.Threading; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Infrastructure; + using Microsoft.EntityFrameworkCore; + using Moq; + using NUnit.Framework; + + [TestFixture] + public class UnitOfWorkTests + { + private MockRepository mockRepository; + private Mock mockDbContext; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + this.mockDbContext = this.mockRepository.Create(); + } + + [TestCase] + public async Task SaveAsyncTest() + { + // Arrange + using var instance = new UnitOfWork(this.mockDbContext.Object); + + _ = this.mockDbContext.Setup(c => c.SaveChangesAsync(It.IsAny())) + .ReturnsAsync(0); + + _ = this.mockDbContext.Setup(c => c.Dispose()); + + // Act + await instance.SaveAsync(); + + // Assert + this.mockDbContext.Verify(c => c.SaveChangesAsync(It.IsAny()), Times.Once); + } + + [TestCase] + public void DisposeTest() + { + // Arrange + using var instance = new UnitOfWork(this.mockDbContext.Object); + _ = this.mockDbContext.Setup(c => c.Dispose()); + + // Act + instance.Dispose(); + + // Assert + this.mockDbContext.Verify(c => c.Dispose(), Times.Once); + } + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelPropertiesControllerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelPropertiesControllerTests.cs index 043e6f432..e9abef933 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelPropertiesControllerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelPropertiesControllerTests.cs @@ -5,21 +5,18 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 { using System; using System.Collections.Generic; - using System.Threading; using System.Threading.Tasks; using AutoMapper; using Azure; - using Azure.Data.Tables; - using Models.v10; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Controllers.V10; - using AzureIoTHub.Portal.Server.Entities; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Server.Services; using FluentAssertions; using Hellang.Middleware.ProblemDetails; - using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; + using Models.v10; using Moq; using NUnit.Framework; @@ -30,9 +27,7 @@ public class DeviceModelPropertiesControllerTests private Mock> mockLogger; private Mock mockMapper; - private Mock mockTableClientFactory; - private Mock mockDeviceTemplatesTableClient; - private Mock mockDeviceModelPropertiesTableClient; + private Mock mockDeviceModelPropertiesService; [SetUp] public void SetUp() @@ -41,9 +36,7 @@ public void SetUp() this.mockLogger = this.mockRepository.Create>(); this.mockMapper = this.mockRepository.Create(); - this.mockTableClientFactory = this.mockRepository.Create(); - this.mockDeviceTemplatesTableClient = this.mockRepository.Create(); - this.mockDeviceModelPropertiesTableClient = this.mockRepository.Create(); + this.mockDeviceModelPropertiesService = this.mockRepository.Create(); } [Test] @@ -51,37 +44,26 @@ public async Task GetPropertiesStateUnderTestExpectedBehavior() { // Arrange var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - var entity = SetupMockEntity(); - var mockResponse = this.mockRepository.Create(); + var modelId = Guid.NewGuid().ToString(); - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == $"PartitionKey eq '{entity.RowKey}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] - { - Page.FromValues(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(modelId)) + .ReturnsAsync(new[] { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = entity.RowKey + Id = Guid.NewGuid().ToString(), + ModelId = modelId } - }, null, mockResponse.Object) - })); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); + }); - _ = this.mockMapper.Setup(c => c.Map(It.Is(x => x.PartitionKey == entity.RowKey))) + _ = this.mockMapper.Setup(c => c.Map(It.Is(x => x.ModelId == modelId))) .Returns((DeviceModelProperty x) => new DeviceProperty { Name = x.Name, }); // Act - var response = await deviceModelPropertiesController.GetProperties(entity.RowKey); + var response = await deviceModelPropertiesController.GetProperties(modelId); // Assert Assert.IsAssignableFrom(response.Result); @@ -97,71 +79,20 @@ public async Task GetPropertiesStateUnderTestExpectedBehavior() this.mockRepository.VerifyAll(); } - [Test] - public async Task GetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccurs() - { - // Arrange - var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - var entity = SetupMockEntity(); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == $"PartitionKey eq '{entity.RowKey}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new RequestFailedException("test")); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); - - // Act - var act = () => deviceModelPropertiesController.GetProperties(entity.RowKey); - - // Assert - _ = await act.Should().ThrowAsync(); - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task GetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursWhenCheckingIfDeviceModelExists() - { - // Arrange - var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - var modelId = Guid.NewGuid().ToString(); - var entity = new TableEntity("0", modelId); - - _ = this.mockDeviceTemplatesTableClient.Setup(c => c.GetEntityAsync( - It.Is(p => p == "0"), - It.Is(k => k == modelId), - It.IsAny>(), - It.IsAny())) - .ThrowsAsync(new RequestFailedException("test")); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplates()) - .Returns(this.mockDeviceTemplatesTableClient.Object); - - _ = this.mockLogger.Setup(x => x.Log(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())); - - // Act - var act = () => deviceModelPropertiesController.GetProperties(entity.RowKey); - - // Assert - _ = await act.Should().ThrowAsync(); - this.mockRepository.VerifyAll(); - } - [Test] public async Task WhenDeviceModelNotExistsGetPropertiesShouldReturnHttp404() { // Arrange var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - SetupNotFoundEntity(); + + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(It.IsAny())) + .Throws(new ResourceNotFoundException("Not Found")); // Act var response = await deviceModelPropertiesController.GetProperties(Guid.NewGuid().ToString()); // Assert - Assert.IsAssignableFrom(response.Result); + Assert.IsAssignableFrom(response.Result); this.mockRepository.VerifyAll(); } @@ -170,7 +101,8 @@ public async Task SetPropertiesStateUnderTestExpectedBehavior() { // Arrange var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - var entity = SetupMockEntity(); + var modelId = Guid.NewGuid().ToString(); + var mockResponse = this.mockRepository.Create(); var existingProperty = Guid.NewGuid().ToString(); @@ -183,34 +115,8 @@ public async Task SetPropertiesStateUnderTestExpectedBehavior() } }; - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == $"PartitionKey eq '{entity.RowKey}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] - { - Page.FromValues(new DeviceModelProperty[] - { - new DeviceModelProperty - { - RowKey = existingProperty, - PartitionKey = entity.RowKey, - } - }, null, mockResponse.Object) - })); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.AddEntityAsync( - It.Is(x => x.PartitionKey == entity.RowKey), - It.IsAny())) - .ReturnsAsync(mockResponse.Object); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.DeleteEntityAsync( - It.Is(x => x == entity.RowKey), - It.Is(x => x == existingProperty), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(mockResponse.Object); + _ = this.mockDeviceModelPropertiesService.Setup(c => c.SavePropertiesForModel(modelId, It.IsAny>())) + .Returns(Task.CompletedTask); _ = this.mockMapper.Setup(c => c.Map( It.IsAny(), @@ -218,60 +124,22 @@ public async Task SetPropertiesStateUnderTestExpectedBehavior() .Returns((DeviceProperty x, Action> _) => new DeviceModelProperty { Name = x.Name, - PartitionKey = entity.RowKey + ModelId = modelId }); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); - // Act - var result = await deviceModelPropertiesController.SetProperties(entity.RowKey, properties); + var result = await deviceModelPropertiesController.SetProperties(modelId, properties); // Assert Assert.IsAssignableFrom(result); this.mockRepository.VerifyAll(); } - [Test] - public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursWhenGettingExistingProperties() - { - // Arrange - var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - var entity = SetupMockEntity(); - - var properties = new[] - { - new DeviceProperty - { - DisplayName =Guid.NewGuid().ToString(), - Name = Guid.NewGuid().ToString() - } - }; - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == $"PartitionKey eq '{entity.RowKey}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new RequestFailedException("test")); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); - - // Act - var act = () => deviceModelPropertiesController.SetProperties(entity.RowKey, properties); - - // Assert - _ = await act.Should().ThrowAsync(); - this.mockRepository.VerifyAll(); - } - [Test] public async Task SetPropertiesShouldThrowProblemDetailsExceptionWhenModelIsNotValid() { // Arrange var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - var entity = SetupMockEntity(); var properties = new[] { @@ -281,186 +149,35 @@ public async Task SetPropertiesShouldThrowProblemDetailsExceptionWhenModelIsNotV deviceModelPropertiesController.ModelState.AddModelError("Key", "Device model is invalid"); // Act - var act = () => deviceModelPropertiesController.SetProperties(entity.RowKey, properties); + var act = () => deviceModelPropertiesController.SetProperties(Guid.NewGuid().ToString(), properties); // Assert _ = await act.Should().ThrowAsync(); this.mockRepository.VerifyAll(); } - [Test] - public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursWhenDeletingProperty() - { - // Arrange - var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - var entity = SetupMockEntity(); - var mockResponse = this.mockRepository.Create(); - var existingProperty = Guid.NewGuid().ToString(); - - var properties = new[] - { - new DeviceProperty - { - DisplayName =Guid.NewGuid().ToString(), - Name = Guid.NewGuid().ToString() - } - }; - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == $"PartitionKey eq '{entity.RowKey}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] - { - Page.FromValues(new DeviceModelProperty[] - { - new DeviceModelProperty - { - RowKey = existingProperty, - PartitionKey = entity.RowKey, - } - }, null, mockResponse.Object) - })); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.DeleteEntityAsync( - It.Is(x => x == entity.RowKey), - It.Is(x => x == existingProperty), - It.IsAny(), - It.IsAny())) - .ThrowsAsync(new RequestFailedException("test")); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); - - // Act - var act = () => deviceModelPropertiesController.SetProperties(entity.RowKey, properties); - - // Assert - _ = await act.Should().ThrowAsync(); - this.mockRepository.VerifyAll(); - } - - [Test] - public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueOccursWhenAddingProperty() - { - // Arrange - var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - var entity = SetupMockEntity(); - var mockResponse = this.mockRepository.Create(); - var existingProperty = Guid.NewGuid().ToString(); - - var properties = new[] - { - new DeviceProperty - { - DisplayName =Guid.NewGuid().ToString(), - Name = Guid.NewGuid().ToString() - } - }; - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == $"PartitionKey eq '{entity.RowKey}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] - { - Page.FromValues(new DeviceModelProperty[] - { - new DeviceModelProperty - { - RowKey = existingProperty, - PartitionKey = entity.RowKey, - } - }, null, mockResponse.Object) - })); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.AddEntityAsync( - It.Is(x => x.PartitionKey == entity.RowKey), - It.IsAny())) - .ThrowsAsync(new RequestFailedException("test")); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.DeleteEntityAsync( - It.Is(x => x == entity.RowKey), - It.Is(x => x == existingProperty), - It.IsAny(), - It.IsAny())) - .ReturnsAsync(mockResponse.Object); - - _ = this.mockMapper.Setup(c => c.Map( - It.IsAny(), - It.IsAny>>())) - .Returns((DeviceProperty x, Action> _) => new DeviceModelProperty - { - Name = x.Name, - PartitionKey = entity.RowKey - }); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); - - // Act - var act = () => deviceModelPropertiesController.SetProperties(entity.RowKey, properties); - - // Assert - _ = await act.Should().ThrowAsync(); - this.mockRepository.VerifyAll(); - } - [Test] public async Task WhenDeviceModelNotExistsSetPropertiesShouldReturnHttp404() { // Arrange var deviceModelPropertiesController = CreateDeviceModelPropertiesController(); - SetupNotFoundEntity(); + + _ = this.mockDeviceModelPropertiesService.Setup(c => c.SavePropertiesForModel(It.IsAny(), It.IsAny>())) + .Throws(new ResourceNotFoundException("Not Found")); // Act - var result = await deviceModelPropertiesController.SetProperties(Guid.NewGuid().ToString(), null); + var result = await deviceModelPropertiesController.SetProperties(Guid.NewGuid().ToString(), Array.Empty()); // Assert - Assert.IsAssignableFrom(result); + Assert.IsAssignableFrom(result); this.mockRepository.VerifyAll(); } - private TableEntity SetupMockEntity() - { - var mockResponse = this.mockRepository.Create>(); - var modelId = Guid.NewGuid().ToString(); - var entity = new TableEntity("0", modelId); - - _ = this.mockDeviceTemplatesTableClient.Setup(c => c.GetEntityAsync( - It.Is(p => p == "0"), - It.Is(k => k == modelId), - It.IsAny>(), - It.IsAny())) - .ReturnsAsync(mockResponse.Object); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplates()) - .Returns(this.mockDeviceTemplatesTableClient.Object); - - return entity; - } - - private void SetupNotFoundEntity() - { - _ = this.mockDeviceTemplatesTableClient.Setup(c => c.GetEntityAsync( - It.Is(p => p == "0"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new RequestFailedException(StatusCodes.Status404NotFound, "Not Found")); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplates()) - .Returns(this.mockDeviceTemplatesTableClient.Object); - } - private DeviceModelPropertiesController CreateDeviceModelPropertiesController() { return new DeviceModelPropertiesController( - this.mockLogger.Object, this.mockMapper.Object, - this.mockTableClientFactory.Object); + this.mockDeviceModelPropertiesService.Object); } } } diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelsControllerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelsControllerTests.cs index cbfb95353..b25bc8bc5 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelsControllerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DeviceModelsControllerTests.cs @@ -12,11 +12,10 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 using System.Threading.Tasks; using Azure; using Azure.Data.Tables; - using Models.v10; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Controllers.V10; using AzureIoTHub.Portal.Server.Controllers.V10.LoRaWAN; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; @@ -26,6 +25,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 using Microsoft.Azure.Devices.Provisioning.Service; using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; + using Models.v10; using Moq; using NUnit.Framework; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs index 14493a4aa..c3c12b78b 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/DevicesControllerTests.cs @@ -10,10 +10,10 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 using System.Threading.Tasks; using Azure; using Azure.Data.Tables; - using Models.v10; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Repositories; using AzureIoTHub.Portal.Server.Controllers.V10; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; @@ -28,6 +28,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; + using Models.v10; using Moq; using NUnit.Framework; @@ -43,6 +44,7 @@ public class DevicesControllerTests private Mock mockDevicePropertyService; private Mock mockDeviceTagService; private Mock> mockDeviceTwinMapper; + private Mock mockDeviceModelPropertiesRepository; private Mock mockTableClientFactory; [SetUp] @@ -56,8 +58,9 @@ public void SetUp() this.mockDevicePropertyService = this.mockRepository.Create(); this.mockDeviceTagService = this.mockRepository.Create(); this.mockDeviceTwinMapper = this.mockRepository.Create>(); - this.mockTableClientFactory = this.mockRepository.Create(); this.mockUrlHelper = this.mockRepository.Create(); + this.mockDeviceModelPropertiesRepository = this.mockRepository.Create(); + this.mockTableClientFactory = this.mockRepository.Create(); } private DevicesController CreateDevicesController() @@ -68,8 +71,8 @@ private DevicesController CreateDevicesController() this.mockDeviceTagService.Object, this.mockProvisioningServiceManager.Object, this.mockDeviceTwinMapper.Object, - this.mockTableClientFactory.Object, - this.mockDevicePropertyService.Object) + this.mockDevicePropertyService.Object, + this.mockTableClientFactory.Object) { Url = this.mockUrlHelper.Object }; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeDevicesControllerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeDevicesControllerTests.cs index 09909638c..78e514279 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeDevicesControllerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/EdgeDevicesControllerTests.cs @@ -7,18 +7,18 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v10 using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - using Models.v10; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Controllers.V10; using AzureIoTHub.Portal.Server.Services; using FluentAssertions; using Microsoft.AspNetCore.Mvc; + using Microsoft.Azure.Devices; + using Microsoft.Azure.Devices.Common.Exceptions; + using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; + using Models.v10; using Moq; using NUnit.Framework; - using Microsoft.Azure.Devices.Shared; - using Microsoft.Azure.Devices; - using Microsoft.Azure.Devices.Common.Exceptions; - using AzureIoTHub.Portal.Server.Exceptions; [TestFixture] public class EdgeDevicesControllerTests 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 1b32de156..8eb83cb19 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 @@ -7,9 +7,9 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10.LoRaWAN; using AzureIoTHub.Portal.Server.Controllers.V10.LoRaWAN; - using AzureIoTHub.Portal.Server.Exceptions; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; using FluentAssertions; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsControllerTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsControllerTest.cs index b50529680..5ded11adb 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsControllerTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsControllerTest.cs @@ -12,12 +12,11 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN using System.Threading.Tasks; using Azure; using Azure.Data.Tables; - using Models.v10; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10.LoRaWAN; using AzureIoTHub.Portal.Server.Controllers.V10; using AzureIoTHub.Portal.Server.Controllers.V10.LoRaWAN; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; @@ -27,6 +26,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN using Microsoft.Azure.Devices.Provisioning.Service; using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; + using Models.v10; using Moq; using NUnit.Framework; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesControllerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesControllerTests.cs index 26025e77b..475ff6bb7 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesControllerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesControllerTests.cs @@ -6,15 +6,11 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0.LoRaWAN using System; using System.Collections.Generic; using System.Linq; - using System.Net.Http; - using System.Net; - using System.Threading; using System.Threading.Tasks; - using Azure; using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Models.v10.LoRaWAN; using AzureIoTHub.Portal.Server.Controllers.V10; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/SettingsControllerTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/SettingsControllerTest.cs index 6d3a143a0..8fe92a244 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/SettingsControllerTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Controllers/v1.0/SettingsControllerTest.cs @@ -5,15 +5,15 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Controllers.v1._0 { using System; using System.Globalization; - using Models.v10; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Server.Controllers.V10; using AzureIoTHub.Portal.Server.Identity; using FluentAssertions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; + using Models.v10; using Moq; using NUnit.Framework; - using Portal.Server; [TestFixture] public class SettingsControllerTest diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Exceptions/InternalServerErrorExceptionTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Exceptions/InternalServerErrorExceptionTests.cs index 7a0660b7f..bbdac51b2 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Exceptions/InternalServerErrorExceptionTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Exceptions/InternalServerErrorExceptionTests.cs @@ -4,8 +4,8 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Exceptions { using System; - using AzureIoTHub.Portal.Server.Constants; - using AzureIoTHub.Portal.Server.Exceptions; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Shared.Constants; using FluentAssertions; using NUnit.Framework; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Factories/TableClientFactoryTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Factories/TableClientFactoryTests.cs deleted file mode 100644 index e585356fa..000000000 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Factories/TableClientFactoryTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Tests.Unit.Server.Factories -{ - using System; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; - using FluentAssertions; - using NUnit.Framework; - - public class TableClientFactoryTests - { - [Test] - public void GetDeviceTemplatesShouldThrowInternalServerErrorExceptionWhenAnIssueOccurs() - { - // Arrange - var connectionString = Guid.NewGuid().ToString(); - var tableClientFactory = new TableClientFactory(connectionString); - - // Act - var act = () => tableClientFactory.GetDeviceTemplates(); - - // Assert - _ = act.Should().Throw(); - } - - [Test] - public void GetEdgeModuleCommandsShouldThrowInternalServerErrorExceptionWhenAnIssueOccurs() - { - // Arrange - var connectionString = Guid.NewGuid().ToString(); - var tableClientFactory = new TableClientFactory(connectionString); - - // Act - var act = () => tableClientFactory.GetEdgeModuleCommands(); - - // Assert - _ = act.Should().Throw(); - } - } -} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Filters/LoRaFeatureActiveFilterAttributeTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Filters/LoRaFeatureActiveFilterAttributeTest.cs index 9484bd553..d258a7ab9 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Filters/LoRaFeatureActiveFilterAttributeTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Filters/LoRaFeatureActiveFilterAttributeTest.cs @@ -5,6 +5,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Filters { using System; using System.Collections.Generic; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Server.Filters; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -13,7 +14,6 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Filters using Microsoft.AspNetCore.Routing; using Moq; using NUnit.Framework; - using Portal.Server; [TestFixture] public class LoRaFeatureActiveFilterAttributeTest diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceModelCommandsManagerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceModelCommandsManagerTests.cs index cb19d3af5..0cf4940de 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceModelCommandsManagerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceModelCommandsManagerTests.cs @@ -7,10 +7,10 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Managers using System.Threading; using Azure; using Azure.Data.Tables; - using Models.v10.LoRaWAN; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; + using Models.v10.LoRaWAN; using Moq; using NUnit.Framework; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceModelImageManagerTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceModelImageManagerTest.cs index 10baad8e2..94aa46fde 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceModelImageManagerTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceModelImageManagerTest.cs @@ -13,13 +13,13 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Managers using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; - using AzureIoTHub.Portal.Server.Exceptions; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Managers; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; - using Portal.Server; using UnitTests.Bases; [TestFixture] diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceProvisioningServiceManagerTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceProvisioningServiceManagerTests.cs index 5927594dd..fc5a73d3d 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceProvisioningServiceManagerTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Managers/DeviceProvisioningServiceManagerTests.cs @@ -6,6 +6,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Managers using System.Net; using System.Net.Http; using System.Threading.Tasks; + using AzureIoTHub.Portal.Infrastructure; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Wrappers; using Microsoft.Azure.Devices.Provisioning.Service; @@ -13,7 +14,6 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Managers using Microsoft.Extensions.Configuration; using Moq; using NUnit.Framework; - using Portal.Server; [TestFixture] public class DeviceProvisioningServiceManagerTests diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/DeviceTwinMapperTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/DeviceTwinMapperTests.cs index b050fda81..8461ede11 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/DeviceTwinMapperTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Mappers/DeviceTwinMapperTests.cs @@ -5,13 +5,13 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Mappers { using System; using System.Collections.Generic; - using Models.v10; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Server.Extensions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Helpers; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using Microsoft.Azure.Devices.Shared; + using Models.v10; using Moq; using NUnit.Framework; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConcentratorMetricExporterServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConcentratorMetricExporterServiceTests.cs index 8b57912c9..e5e36a00c 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConcentratorMetricExporterServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConcentratorMetricExporterServiceTests.cs @@ -3,17 +3,17 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { - using AzureIoTHub.Portal.Server; using System; using System.Threading; - using NUnit.Framework; - using Microsoft.Extensions.Logging; - using Moq; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Shared.Constants; using AzureIoTHub.Portal.Server.Services; using AzureIoTHub.Portal.Shared.Models.v1._0; using FluentAssertions; + using Microsoft.Extensions.Logging; + using Moq; + using NUnit.Framework; using Prometheus; - using Portal.Server.Constants; [TestFixture] public class ConcentratorMetricExporterServiceTests : IDisposable diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConcentratorMetricLoaderServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConcentratorMetricLoaderServiceTests.cs index e6d5fc4ed..5508d5df7 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConcentratorMetricLoaderServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConcentratorMetricLoaderServiceTests.cs @@ -3,17 +3,17 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { - using AzureIoTHub.Portal.Server; using System; using System.Threading; - using NUnit.Framework; - using Microsoft.Extensions.Logging; - using Moq; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Services; using AzureIoTHub.Portal.Shared.Models.v1._0; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; - using Portal.Server.Exceptions; + using Microsoft.Extensions.Logging; + using Moq; + using NUnit.Framework; [TestFixture] public class ConcentratorMetricLoaderServiceTests : IDisposable diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs index 8583b123d..bf2f4d0ad 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/ConfigServiceTests.cs @@ -3,20 +3,20 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Server.Exceptions; using AzureIoTHub.Portal.Server.Services; using AzureIoTHub.Portal.Shared.Models.v10.IoTEdgeModule; using FluentAssertions; using Microsoft.Azure.Devices; using Moq; - using Newtonsoft.Json.Linq; using Newtonsoft.Json; + using Newtonsoft.Json.Linq; using NUnit.Framework; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; [TestFixture] public class ConfigServiceTests diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceConfigurationsServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceConfigurationsServiceTest.cs index 43bdf3c89..6347bb66f 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceConfigurationsServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceConfigurationsServiceTest.cs @@ -6,21 +6,21 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services using System; using System.Collections.Generic; using System.Linq; + using System.Threading; using System.Threading.Tasks; + using Azure; using Azure.Data.Tables; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Services; + using FluentAssertions; + using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Azure.Devices; - using Moq; - using NUnit.Framework; - using System.Threading; - using Azure; + using Microsoft.Graph.ExternalConnectors; using Models; using Models.v10; - using AzureIoTHub.Portal.Server.Entities; - using AzureIoTHub.Portal.Server.Exceptions; - using FluentAssertions; - using Microsoft.AspNetCore.Mvc.ViewFeatures; + using Moq; + using NUnit.Framework; using Configuration = Microsoft.Azure.Devices.Configuration; [TestFixture] @@ -29,8 +29,7 @@ public class DeviceConfigurationsServiceTest private MockRepository mockRepository; private Mock mockConfigService; - private Mock mockTableClientFactory; - private Mock mockDeviceModelPropertiesTableClient; + private Mock mockDeviceModelPropertiesService; [SetUp] public void SetUp() @@ -38,13 +37,12 @@ public void SetUp() this.mockRepository = new MockRepository(MockBehavior.Strict); this.mockConfigService = this.mockRepository.Create(); - this.mockTableClientFactory = this.mockRepository.Create(); - this.mockDeviceModelPropertiesTableClient = this.mockRepository.Create(); + this.mockDeviceModelPropertiesService = this.mockRepository.Create(); } private DeviceConfigurationsService CreateService() { - return new DeviceConfigurationsService(this.mockConfigService.Object, this.mockTableClientFactory.Object); + return new DeviceConfigurationsService(this.mockConfigService.Object, this.mockDeviceModelPropertiesService.Object); } [Test] @@ -176,27 +174,15 @@ public async Task CreateConfigStateUnderTestExpectedBehavior() It.Is(x => x == 100))) .Returns(Task.CompletedTask); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.Query( - It.Is(x => x == $"PartitionKey eq '{deviceConfig.ModelId}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(Pageable.FromPages(new[] - { - Page.FromValues(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) + .ReturnsAsync(new[] { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = deviceConfig.ModelId + Id = Guid.NewGuid().ToString(), + ModelId = deviceConfig.ModelId } - }, null, mockResponse.Object) - })); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); + }); // Act await deviceConfigurationsService.CreateConfigurationAsync(deviceConfig); @@ -217,16 +203,9 @@ public async Task CreateConfigShouldThrowInternalServerErrorExceptionOnGettingDe ModelId = Guid.NewGuid().ToString() }; - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.Query( - It.Is(x => x == $"PartitionKey eq '{deviceConfig.ModelId}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) .Throws(new RequestFailedException("test")); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); - // Act var act = () => deviceConfigurationsService.CreateConfigurationAsync(deviceConfig); @@ -255,27 +234,15 @@ public async Task UpdateConfigStateUnderTestExpectedBehavior() It.Is(x => x == 100))) .Returns(Task.CompletedTask); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.Query( - It.Is(x => x == $"PartitionKey eq '{deviceConfig.ModelId}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(Pageable.FromPages(new[] - { - Page.FromValues(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) + .ReturnsAsync(new[] { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = deviceConfig.ModelId + Id = Guid.NewGuid().ToString(), + ModelId = deviceConfig.ModelId } - }, null, mockResponse.Object) - })); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); + }); // Act await deviceConfigurationsService.UpdateConfigurationAsync(deviceConfig); @@ -296,16 +263,9 @@ public async Task UpdateConfigShouldThrowInternalServerErrorExceptionOnGettingDe ModelId = Guid.NewGuid().ToString() }; - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.Query( - It.Is(x => x == $"PartitionKey eq '{deviceConfig.ModelId}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) .Throws(new RequestFailedException("test")); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); - // Act var act = () => deviceConfigurationsService.UpdateConfigurationAsync(deviceConfig); @@ -409,29 +369,17 @@ public async Task UpdateConfigShouldUpdatePropertyInValueType(DevicePropertyType .Callback((string _, Dictionary properties, string _, Dictionary _, int _) => requestedProperties = properties); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.Query( - It.Is(x => x == $"PartitionKey eq '{deviceConfig.ModelId}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(Pageable.FromPages(new[] - { - Page.FromValues(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) + .ReturnsAsync(new[] { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = deviceConfig.ModelId, + Id = Guid.NewGuid().ToString(), + ModelId = deviceConfig.ModelId, Name = propertyName, PropertyType = propertyType } - }, null, mockResponse.Object) - })); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); + }); // Act await deviceConfigurationsService.UpdateConfigurationAsync(deviceConfig); @@ -480,29 +428,17 @@ public async Task CreateConfigShouldUpdatePropertyInValueType(DevicePropertyType .Callback((string _, Dictionary properties, string _, Dictionary _, int _) => requestedProperties = properties); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.Query( - It.Is(x => x == $"PartitionKey eq '{deviceConfig.ModelId}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(Pageable.FromPages(new[] - { - Page.FromValues(new[] - { - new DeviceModelProperty + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) + .ReturnsAsync(new[] { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = deviceConfig.ModelId, - Name = propertyName, - PropertyType = propertyType - } - }, null, mockResponse.Object) - })); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); + new DeviceModelProperty + { + Id = Guid.NewGuid().ToString(), + ModelId = deviceConfig.ModelId, + Name = propertyName, + PropertyType = propertyType + } + }); // Act await deviceConfigurationsService.CreateConfigurationAsync(deviceConfig); @@ -542,29 +478,17 @@ public async Task WhenPropertyNotPresentInModelUpdateConfigShouldNotUpdateThePro .Callback((string _, Dictionary properties, string _, Dictionary _, int _) => requestedProperties = properties); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.Query( - It.Is(x => x == $"PartitionKey eq '{deviceConfig.ModelId}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(Pageable.FromPages(new[] - { - Page.FromValues(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) + .ReturnsAsync(new[] { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = deviceConfig.ModelId, + Id = Guid.NewGuid().ToString(), + ModelId = deviceConfig.ModelId, Name = propertyName, PropertyType = DevicePropertyType.String } - }, null, mockResponse.Object) - })); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); + }); // Act await deviceConfigurationsService.UpdateConfigurationAsync(deviceConfig); @@ -605,29 +529,17 @@ public async Task WhenPropertyNotPresentInModelCreateConfigShouldNotUpdateThePro .Callback((string _, Dictionary properties, string _, Dictionary _, int _) => requestedProperties = properties); - var mockResponse = this.mockRepository.Create(); - - _ = this.mockDeviceModelPropertiesTableClient.Setup(c => c.Query( - It.Is(x => x == $"PartitionKey eq '{deviceConfig.ModelId}'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(Pageable.FromPages(new[] - { - Page.FromValues(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties(deviceConfig.ModelId)) + .ReturnsAsync(new[] { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = deviceConfig.ModelId, + Id = Guid.NewGuid().ToString(), + ModelId = deviceConfig.ModelId, Name = propertyName, PropertyType = DevicePropertyType.String } - }, null, mockResponse.Object) - })); - - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(this.mockDeviceModelPropertiesTableClient.Object); + }); // Act await deviceConfigurationsService.CreateConfigurationAsync(deviceConfig); diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceMetricExporterServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceMetricExporterServiceTests.cs index e2a318fff..9572dc55d 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceMetricExporterServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceMetricExporterServiceTests.cs @@ -3,16 +3,16 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { - using AzureIoTHub.Portal.Server; using System; using System.Threading; - using NUnit.Framework; - using Microsoft.Extensions.Logging; - using Moq; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Shared.Constants; using AzureIoTHub.Portal.Server.Services; using AzureIoTHub.Portal.Shared.Models.v1._0; using FluentAssertions; - using Portal.Server.Constants; + using Microsoft.Extensions.Logging; + using Moq; + using NUnit.Framework; using Prometheus; [TestFixture] diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceMetricLoaderServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceMetricLoaderServiceTests.cs index b4162946a..764244206 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceMetricLoaderServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceMetricLoaderServiceTests.cs @@ -3,17 +3,17 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { - using AzureIoTHub.Portal.Server; using System; using System.Threading; - using NUnit.Framework; - using Microsoft.Extensions.Logging; - using Moq; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Services; using AzureIoTHub.Portal.Shared.Models.v1._0; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; - using Portal.Server.Exceptions; + using Microsoft.Extensions.Logging; + using Moq; + using NUnit.Framework; [TestFixture] public class DeviceMetricLoaderServiceTests : IDisposable diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceModelPropertiesServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceModelPropertiesServiceTests.cs new file mode 100644 index 000000000..c5a12926b --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceModelPropertiesServiceTests.cs @@ -0,0 +1,193 @@ +// 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.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Azure; + using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Server.Services; + using Microsoft.AspNetCore.Http; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Logging; + using Moq; + using NUnit.Framework; + + [TestFixture] + public class DeviceModelPropertiesServiceTests + { + private MockRepository mockRepository; + private Mock mockTableClientFactory; + private Mock mockDeviceTemplatesTableClient; + private Mock mockDeviceModelPropertiesRepository; + private Mock mockUnitOfWork; + private Mock> mockLogger; + + [SetUp] + public void SetUp() + { + this.mockRepository = new MockRepository(MockBehavior.Strict); + this.mockTableClientFactory = this.mockRepository.Create(); + this.mockDeviceTemplatesTableClient = this.mockRepository.Create(); + this.mockDeviceModelPropertiesRepository = this.mockRepository.Create(); + this.mockUnitOfWork = this.mockRepository.Create(); + this.mockLogger = this.mockRepository.Create>(); + } + + [Test] + public async Task GetPropertiesStateUnderTestExpectedBehavior() + { + // Arrange + var instance = CreateDeviceModelPropertiesService(); + var entity = SetupMockEntity(); + + _ = this.mockDeviceModelPropertiesRepository.Setup(c => c.GetModelProperties(entity.RowKey)) + .ReturnsAsync(new[] + { + new DeviceModelProperty + { + Id = Guid.NewGuid().ToString(), + ModelId = entity.RowKey + } + }); + + // Act + var result = await instance.GetModelProperties(entity.RowKey); + + // Assert + Assert.NotNull(result); + Assert.AreEqual(1, result.Count()); + + this.mockRepository.VerifyAll(); + } + + [Test] + public void WhenDeviceModelNotExistsGetPropertiesShouldReturnHttp404() + { + // Arrange + var instance = CreateDeviceModelPropertiesService(); + SetupNotFoundEntity(); + + // Act + _ = Assert.ThrowsAsync(async () => await instance.GetModelProperties(Guid.NewGuid().ToString())); + } + + [Test] + public async Task SetPropertiesStateUnderTestExpectedBehavior() + { + // Arrange + var instance = CreateDeviceModelPropertiesService(); + var entity = SetupMockEntity(); + + var properties = new[] + { + new DeviceModelProperty + { + DisplayName =Guid.NewGuid().ToString(), + Name = Guid.NewGuid().ToString() + } + }; + + _ = this.mockDeviceModelPropertiesRepository.Setup(c => c.SavePropertiesForModel(entity.RowKey, It.IsAny>())) + .Returns(Task.CompletedTask); + + _ = this.mockUnitOfWork.Setup(c => c.SaveAsync()) + .Returns(Task.CompletedTask); + + // Act + await instance.SavePropertiesForModel(entity.RowKey, properties); + + // Assert + this.mockRepository.VerifyAll(); + } + + [Test] + public void WhenExceptionOccuresSavePropertiesForModelShouldThrowInternalServerErrorException() + { + // Arrange + var instance = CreateDeviceModelPropertiesService(); + + var entity = SetupMockEntity(); + + _ = this.mockDeviceModelPropertiesRepository.Setup(c => c.SavePropertiesForModel(entity.RowKey, It.IsAny>())) + .Returns(Task.CompletedTask); + + _ = this.mockUnitOfWork.Setup(c => c.SaveAsync()) + .Throws(); + + // Act + _ = Assert.ThrowsAsync(async () => await instance.SavePropertiesForModel(entity.RowKey, Array.Empty())); + } + + [Test] + public void WhenExceptionOccuresDuringCheckingModelExistanceShouldThrowInternalServerErrorException() + { + // Arrange + var instance = CreateDeviceModelPropertiesService(); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplates()) + .Returns(this.mockDeviceTemplatesTableClient.Object); + + _ = this.mockDeviceTemplatesTableClient.Setup(c => c.GetEntityAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) + .Throws(new RequestFailedException("failed")); + + _ = this.mockLogger.Setup(c => c.Log(LogLevel.Error, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())); + + // Act + _ = Assert.ThrowsAsync(async () => await instance.GetModelProperties(Guid.NewGuid().ToString())); + _ = Assert.ThrowsAsync(async () => await instance.SavePropertiesForModel(Guid.NewGuid().ToString(), Array.Empty())); + + this.mockRepository.VerifyAll(); + } + + private TableEntity SetupMockEntity() + { + var mockResponse = this.mockRepository.Create>(); + var modelId = Guid.NewGuid().ToString(); + var entity = new TableEntity("0", modelId); + + _ = this.mockDeviceTemplatesTableClient.Setup(c => c.GetEntityAsync( + It.Is(p => p == "0"), + It.Is(k => k == modelId), + It.IsAny>(), + It.IsAny())) + .ReturnsAsync(mockResponse.Object); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplates()) + .Returns(this.mockDeviceTemplatesTableClient.Object); + + return entity; + } + + private void SetupNotFoundEntity() + { + _ = this.mockDeviceTemplatesTableClient.Setup(c => c.GetEntityAsync( + It.Is(p => p == "0"), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Throws(new RequestFailedException(StatusCodes.Status404NotFound, "Not Found")); + + _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplates()) + .Returns(this.mockDeviceTemplatesTableClient.Object); + } + + private DeviceModelPropertiesService CreateDeviceModelPropertiesService() + { + return new DeviceModelPropertiesService( + this.mockLogger.Object, + this.mockUnitOfWork.Object, + this.mockTableClientFactory.Object, + this.mockDeviceModelPropertiesRepository.Object); + } + + } +} diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DevicePropertyServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DevicePropertyServiceTests.cs index e738b5e94..88bde5055 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DevicePropertyServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DevicePropertyServiceTests.cs @@ -4,31 +4,26 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { using System; - using System.Collections.Generic; using System.Linq; - using System.Threading; using System.Threading.Tasks; using Azure; - using Azure.Data.Tables; - using UnitTests.Bases; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Server.Services; using FluentAssertions; + using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.DependencyInjection; + using Models.v10; using Moq; using NUnit.Framework; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Services; - using Microsoft.Azure.Devices.Shared; - using Models.v10; - using Portal.Server.Entities; - using Portal.Server.Factories; using Portal.Server.Helpers; + using UnitTests.Bases; [TestFixture] public class DevicePropertyServiceTests : BackendUnitTest { private Mock mockDeviceService; - private Mock mockTableClientFactory; - + private Mock mockDeviceModelPropertiesService; private IDevicePropertyService devicePropertyService; public override void Setup() @@ -37,12 +32,11 @@ public override void Setup() this.mockDeviceService = MockRepository.Create(); - this.mockTableClientFactory = MockRepository.Create(); + this.mockDeviceModelPropertiesService = MockRepository.Create(); _ = ServiceCollection.AddSingleton(this.mockDeviceService.Object); - _ = ServiceCollection.AddSingleton(this.mockTableClientFactory.Object); + _ = ServiceCollection.AddSingleton(this.mockDeviceModelPropertiesService.Object); _ = ServiceCollection.AddSingleton(); - Services = ServiceCollection.BuildServiceProvider(); this.devicePropertyService = Services.GetRequiredService(); @@ -57,21 +51,11 @@ public async Task GetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueO DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); DeviceHelper.SetDesiredProperty(twin, "writable", "ccc"); - var mockTableClient = MockRepository.Create(); - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) .ReturnsAsync(twin); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Throws(new RequestFailedException("test")); - + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties("bbb")) + .Throws(new RequestFailedException("test")); // Act var act = () => this.devicePropertyService.GetProperties("aaa"); @@ -121,40 +105,27 @@ public async Task GetPropertiesShouldReturnDeviceProperties() DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); DeviceHelper.SetDesiredProperty(twin, "writable", "ccc"); - var mockTableClient = MockRepository.Create(); - var mockResponse = MockRepository.Create(); - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) .ReturnsAsync(twin); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties("bbb")) + .ReturnsAsync(new[] { - Page.FromValues(new[] - { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", + Id = Guid.NewGuid().ToString(), + ModelId = "bbb", IsWritable = true, Name = "writable", }, new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", + Id = Guid.NewGuid().ToString(), + ModelId = "bbb", IsWritable = false, Name = "notwritable", } - }, null, mockResponse.Object) - })); + }); // Act var devicePropertyValues = await this.devicePropertyService.GetProperties("aaa"); @@ -207,9 +178,6 @@ public async Task SetPropertiesShouldUpdateDesiredProperties() DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - var mockTableClient = MockRepository.Create(); - var mockResponse = MockRepository.Create(); - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) .ReturnsAsync(twin); @@ -217,28 +185,17 @@ public async Task SetPropertiesShouldUpdateDesiredProperties() It.Is(c => c == twin))) .ReturnsAsync(twin); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties("bbb")) + .ReturnsAsync(new[] { - Page.FromValues(new[] - { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", + Id = Guid.NewGuid().ToString(), + ModelId = "bbb", IsWritable = true, Name = "writable", } - }, null, mockResponse.Object) - })); - + }); // Act await this.devicePropertyService.SetProperties("aaa", new[] { @@ -263,21 +220,11 @@ public async Task SetPropertiesShouldThrowInternalServerErrorExceptionWhenIssueO DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - var mockTableClient = MockRepository.Create(); - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) .ReturnsAsync(twin); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties("bbb")) .Throws(new RequestFailedException("test")); - // Act var act = () => this.devicePropertyService.SetProperties("aaa", new[] { @@ -301,9 +248,6 @@ public async Task WhenPropertyNotWrittableSetPropertiesShouldNotUpdateDesiredPro DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - var mockTableClient = MockRepository.Create(); - var mockResponse = MockRepository.Create(); - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) .ReturnsAsync(twin); @@ -311,28 +255,17 @@ public async Task WhenPropertyNotWrittableSetPropertiesShouldNotUpdateDesiredPro It.Is(c => c == twin))) .ReturnsAsync(twin); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties("bbb")) + .ReturnsAsync(new[] { - Page.FromValues(new[] - { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", + Id = Guid.NewGuid().ToString(), + ModelId = "bbb", IsWritable = false, Name = "notwritable", } - }, null, mockResponse.Object) - })); - + }); // Act await this.devicePropertyService.SetProperties("aaa", new[] { @@ -357,9 +290,6 @@ public async Task WhenPropertyNotInModelSetPropertiesShouldNotUpdateDesiredPrope DeviceHelper.SetTagValue(twin, "ModelId", "bbb"); - var mockTableClient = MockRepository.Create(); - var mockResponse = MockRepository.Create(); - _ = this.mockDeviceService.Setup(c => c.GetDeviceTwin("aaa")) .ReturnsAsync(twin); @@ -367,27 +297,17 @@ public async Task WhenPropertyNotInModelSetPropertiesShouldNotUpdateDesiredPrope It.Is(c => c == twin))) .ReturnsAsync(twin); - _ = this.mockTableClientFactory.Setup(c => c.GetDeviceTemplateProperties()) - .Returns(mockTableClient.Object); - - _ = mockTableClient.Setup(c => c.QueryAsync( - It.Is(x => x == "PartitionKey eq 'bbb'"), - It.IsAny(), - It.IsAny>(), - It.IsAny())) - .Returns(AsyncPageable.FromPages(new[] + _ = this.mockDeviceModelPropertiesService.Setup(c => c.GetModelProperties("bbb")) + .ReturnsAsync(new[] { - Page.FromValues(new[] - { new DeviceModelProperty { - RowKey = Guid.NewGuid().ToString(), - PartitionKey = "bbb", + Id = Guid.NewGuid().ToString(), + ModelId = "bbb", IsWritable = true, Name = "writable", } - }, null, mockResponse.Object) - })); + }); // Act await this.devicePropertyService.SetProperties("aaa", new[] diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceServiceTests.cs index b9f984aea..b93110da3 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceServiceTests.cs @@ -7,17 +7,17 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services using System.Linq; using System.Threading; using System.Threading.Tasks; - using Models.v10; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Services; - using Shared.Constants; using FluentAssertions; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; + using Models.v10; using Moq; using Newtonsoft.Json; using NUnit.Framework; - using Portal.Server.Exceptions; + using Shared.Constants; [TestFixture] public class DeviceServiceTests diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceTagServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceTagServiceTests.cs index 9aaf2a562..352e7fd2b 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceTagServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/DeviceTagServiceTests.cs @@ -10,14 +10,14 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services using System.Threading.Tasks; using Azure; using Azure.Data.Tables; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; - using Models.v10; using FluentAssertions; + using Models.v10; using Moq; using NUnit.Framework; - using Portal.Server.Exceptions; [TestFixture] public class DeviceTagServiceTests diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceMetricExporterServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceMetricExporterServiceTests.cs index c2d67c44c..a7c000758 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceMetricExporterServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceMetricExporterServiceTests.cs @@ -3,16 +3,16 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { - using AzureIoTHub.Portal.Server; using System; using System.Threading; - using NUnit.Framework; - using Microsoft.Extensions.Logging; - using Moq; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Shared.Constants; using AzureIoTHub.Portal.Server.Services; using AzureIoTHub.Portal.Shared.Models.v1._0; using FluentAssertions; - using Portal.Server.Constants; + using Microsoft.Extensions.Logging; + using Moq; + using NUnit.Framework; using Prometheus; [TestFixture] diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceMetricLoaderServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceMetricLoaderServiceTests.cs index a2af8036f..ded1f5a87 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceMetricLoaderServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceMetricLoaderServiceTests.cs @@ -3,17 +3,17 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { - using AzureIoTHub.Portal.Server; using System; using System.Threading; - using NUnit.Framework; - using Microsoft.Extensions.Logging; - using Moq; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Services; using AzureIoTHub.Portal.Shared.Models.v1._0; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; - using AzureIoTHub.Portal.Server.Exceptions; + using Microsoft.Extensions.Logging; + using Moq; + using NUnit.Framework; [TestFixture] public class EdgeDeviceMetricLoaderServiceTests : IDisposable diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceServiceTest.cs index f08f2de9f..e82619638 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeDeviceServiceTest.cs @@ -3,22 +3,22 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { - using System.Linq; using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; + using FluentAssertions; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Shared; using Moq; using NUnit.Framework; - using Microsoft.AspNetCore.Mvc; - using Microsoft.AspNetCore.Mvc.Routing; - using AzureIoTHub.Portal.Models.v10; - using System.Threading.Tasks; - using System.Collections.Generic; - using AzureIoTHub.Portal.Server.Exceptions; - using FluentAssertions; [TestFixture] public class EdgeDeviceServiceTest diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs index f6ef8358a..20bf993e1 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/EdgeModelServiceTest.cs @@ -12,11 +12,11 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services using System.Threading.Tasks; using Azure; using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Server.Controllers.V10.LoRaWAN; using AzureIoTHub.Portal.Server.Entities; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/IdeaServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/IdeaServiceTests.cs index b30e11ede..e8a436372 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/IdeaServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/IdeaServiceTests.cs @@ -3,21 +3,21 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { - using AzureIoTHub.Portal.Server; using System.Net; using System.Net.Http; using System.Threading.Tasks; using AutoFixture; - using UnitTests.Bases; - using UnitTests.Helpers; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Server.Services; + using AzureIoTHub.Portal.Shared.Models.v1._0; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Moq; using NUnit.Framework; using RichardSzalay.MockHttp; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Services; - using AzureIoTHub.Portal.Shared.Models.v1._0; + using UnitTests.Bases; + using UnitTests.Helpers; [TestFixture] public class IdeaServiceTests : BackendUnitTest diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANCommandServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANCommandServiceTests.cs index b122f4de8..b08630fe1 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANCommandServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANCommandServiceTests.cs @@ -1,7 +1,7 @@ // 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.Services +namespace AzureIoTHub.Portal.Tests.Unit.Domain.Services { using System; using System.Collections.Generic; @@ -9,15 +9,15 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services using System.Threading.Tasks; using Azure; using Azure.Data.Tables; - using Models.v10.LoRaWAN; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Controllers.V10.LoRaWAN; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; using FluentAssertions; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; + using Models.v10.LoRaWAN; using Moq; using NUnit.Framework; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs index 117d793c4..5fa5bc922 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANConcentratorServiceTests.cs @@ -4,11 +4,10 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { using System; - using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10.LoRaWAN; - using AzureIoTHub.Portal.Server.Exceptions; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANDeviceServiceTests.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANDeviceServiceTests.cs index bd0419c8e..65b4fe321 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANDeviceServiceTests.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/Services/LoRaWANDeviceServiceTests.cs @@ -5,28 +5,24 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.Services { using System; using System.Collections.Generic; - using System.Linq; - using System.Text; + using System.Net; + using System.Net.Http; + using System.Threading; using System.Threading.Tasks; + using Azure; using Azure.Data.Tables; - using AzureIoTHub.Portal.Models.v10.LoRaWAN; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Server.Controllers.V10; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Models.v10.LoRaWAN; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; - using Microsoft.AspNetCore.Mvc; + using FluentAssertions; + using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; - using Azure; - using System.Threading; - using System.Net.Http; - using System.Net; - using Microsoft.Azure.Devices.Shared; - using AzureIoTHub.Portal.Server.Exceptions; - using FluentAssertions; [TestFixture] public class LoRaWANDeviceServiceTests diff --git a/src/AzureIoTHub.Portal.Tests.Unit/Server/ServicesHealthCheck/TableStorageHealthCheckTest.cs b/src/AzureIoTHub.Portal.Tests.Unit/Server/ServicesHealthCheck/TableStorageHealthCheckTest.cs index d5d868a6e..6b6ccbaaa 100644 --- a/src/AzureIoTHub.Portal.Tests.Unit/Server/ServicesHealthCheck/TableStorageHealthCheckTest.cs +++ b/src/AzureIoTHub.Portal.Tests.Unit/Server/ServicesHealthCheck/TableStorageHealthCheckTest.cs @@ -1,7 +1,7 @@ // 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.ServicesHealthCheck +namespace AzureIoTHub.Portal.Tests.Unit.Domain.ServicesHealthCheck { using System; using System.Collections.Generic; @@ -10,7 +10,7 @@ namespace AzureIoTHub.Portal.Tests.Unit.Server.ServicesHealthCheck using Azure; using Azure.Data.Tables; using Azure.Data.Tables.Models; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Server.ServicesHealthCheck; using Microsoft.Extensions.Diagnostics.HealthChecks; using Moq; diff --git a/src/AzureIoTHub.Portal.Tests.Unit/UnitTests/Bases/RepositoryTestBase.cs b/src/AzureIoTHub.Portal.Tests.Unit/UnitTests/Bases/RepositoryTestBase.cs new file mode 100644 index 000000000..ce8f65db7 --- /dev/null +++ b/src/AzureIoTHub.Portal.Tests.Unit/UnitTests/Bases/RepositoryTestBase.cs @@ -0,0 +1,22 @@ +// 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.UnitTests.Bases +{ + using AzureIoTHub.Portal.Infrastructure; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Diagnostics; + + public abstract class RepositoryTestBase + { + protected static PortalDbContext SetupDbContext() + { + var contextOptions = new DbContextOptionsBuilder() + .UseInMemoryDatabase("TestContext") + .ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning)) + .Options; + + return new PortalDbContext(contextOptions); + } + } +} diff --git a/src/AzureIoTHub.Portal.sln b/src/AzureIoTHub.Portal.sln index 550f890ca..91ef91ea5 100644 --- a/src/AzureIoTHub.Portal.sln +++ b/src/AzureIoTHub.Portal.sln @@ -52,6 +52,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATES", "ISSUE_TE ..\.github\ISSUE_TEMPLATE\user_story.md = ..\.github\ISSUE_TEMPLATE\user_story.md EndProjectSection EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{B9D2DE01-84DE-461F-998C-20B57E4AA021}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureIoTHub.Portal.Domain", "AzureIoTHubPortal.Domain\AzureIoTHub.Portal.Domain.csproj", "{BC1E7AEF-393D-4771-8AD2-C0BE7E5C405F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureIoTHub.Portal.Infrastructure", "AzureIoTHub.Portal.Infrastructure\AzureIoTHub.Portal.Infrastructure.csproj", "{C0E587A8-607D-4746-8536-44AB34074522}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -74,6 +80,18 @@ Global {51FD5B90-B422-47BF-83F2-516520CFB124}.Debug|Any CPU.Build.0 = Debug|Any CPU {51FD5B90-B422-47BF-83F2-516520CFB124}.Release|Any CPU.ActiveCfg = Release|Any CPU {51FD5B90-B422-47BF-83F2-516520CFB124}.Release|Any CPU.Build.0 = Release|Any CPU + {B9D2DE01-84DE-461F-998C-20B57E4AA021}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9D2DE01-84DE-461F-998C-20B57E4AA021}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9D2DE01-84DE-461F-998C-20B57E4AA021}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9D2DE01-84DE-461F-998C-20B57E4AA021}.Release|Any CPU.Build.0 = Release|Any CPU + {BC1E7AEF-393D-4771-8AD2-C0BE7E5C405F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC1E7AEF-393D-4771-8AD2-C0BE7E5C405F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BC1E7AEF-393D-4771-8AD2-C0BE7E5C405F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BC1E7AEF-393D-4771-8AD2-C0BE7E5C405F}.Release|Any CPU.Build.0 = Release|Any CPU + {C0E587A8-607D-4746-8536-44AB34074522}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0E587A8-607D-4746-8536-44AB34074522}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0E587A8-607D-4746-8536-44AB34074522}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0E587A8-607D-4746-8536-44AB34074522}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/AzureIoTHub.Portal/Client/Program.cs b/src/AzureIoTHub.Portal/Client/Program.cs index 2c60c91b1..82ce7d292 100644 --- a/src/AzureIoTHub.Portal/Client/Program.cs +++ b/src/AzureIoTHub.Portal/Client/Program.cs @@ -113,5 +113,6 @@ private static async Task ConfigurePortalSettings(WebAssemblyHostBuilder builder _ = builder.Services.AddSingleton(settings); } + } } diff --git a/src/AzureIoTHub.Portal/Client/assets/package-lock.json b/src/AzureIoTHub.Portal/Client/assets/package-lock.json index 9db4e0b17..c44364d2e 100644 --- a/src/AzureIoTHub.Portal/Client/assets/package-lock.json +++ b/src/AzureIoTHub.Portal/Client/assets/package-lock.json @@ -1,3216 +1,6 @@ { - "name": "assets", - "lockfileVersion": 2, "requires": true, - "packages": { - "": { - "dependencies": { - "oidc-client": "1.11.5" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "^4.29.3", - "@typescript-eslint/parser": "^4.29.3", - "acorn": "^8.8.0", - "eslint": "^7.32.0", - "inspectpack": "^4.7.1", - "scss-tokenizer": ">=0.4.3", - "ts-loader": "^9.2.5", - "typescript": "^4.4.2", - "webpack": "5.74.0", - "webpack-cli": "^4.10.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@types/eslint": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", - "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.6.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz", - "integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "dev": true, - "dependencies": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", - "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.7", - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", - "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", - "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", - "dev": true, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", - "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/visitor-keys": "4.33.0", - "debug": "^4.3.1", - "globby": "^11.0.3", - "is-glob": "^4.0.1", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", - "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "4.33.0", - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", - "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x", - "webpack-cli": "4.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", - "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", - "dev": true, - "dependencies": { - "envinfo": "^7.7.3" - }, - "peerDependencies": { - "webpack-cli": "4.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", - "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true, - "peerDependencies": { - "webpack-cli": "4.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001375", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001375.tgz", - "integrity": "sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/chalk/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/chalk/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", - "dev": true - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/core-js": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz", - "integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.213", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.213.tgz", - "integrity": "sha512-+3DbGHGOCHTVB/Ms63bGqbyC1b8y7Fk86+7ltssB8NQrZtSCvZG6eooSl9U2Q0yw++fL2DpHKOdTU0NVEkFObg==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "node_modules/fp-ts": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.12.2.tgz", - "integrity": "sha512-v8J7ud+nTkP5Zz17GhpCsY19wiRbB9miuj61nBcCJyDpu52zs9Z4O7OLDfYoKFQMJ9EsSZA7W1vRgC1d3jy5qw==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/inspectpack": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/inspectpack/-/inspectpack-4.7.1.tgz", - "integrity": "sha512-XoDJbKSM9I2KA+8+OLFJHm8m4NM2pMEgsDD2hze6swVfynEed9ngCx36mRR+otzOsskwnxIZWXjI23FTW1uHqA==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "fp-ts": "^2.6.1", - "io-ts": "^2.2.13", - "io-ts-reporters": "^1.2.2", - "pify": "^5.0.0", - "semver-compare": "^1.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "inspectpack": "bin/inspectpack.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/io-ts": { - "version": "2.2.17", - "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.17.tgz", - "integrity": "sha512-RkQY06h6rRyADVEI46OCAUYTP2p18Vdtz9Movi19Mmj7SJ1NhN/yGyW7CxlcBVxh95WKg2YSbTmcUPqqeLuhXw==", - "dev": true, - "peerDependencies": { - "fp-ts": "^2.5.0" - } - }, - "node_modules/io-ts-reporters": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/io-ts-reporters/-/io-ts-reporters-1.2.2.tgz", - "integrity": "sha512-igASwWWkDY757OutNcM6zTtdJf/eTZYkoe2ymsX2qpm5bKZLo74FJYjsCtMQOEdY7dRHLLEulCyFQwdN69GBCg==", - "dev": true, - "peerDependencies": { - "fp-ts": "^2.0.2", - "io-ts": "^2.0.0" - } - }, - "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "node_modules/oidc-client": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/oidc-client/-/oidc-client-1.11.5.tgz", - "integrity": "sha512-LcKrKC8Av0m/KD/4EFmo9Sg8fSQ+WFJWBrmtWd+tZkNn3WT/sQG3REmPANE9tzzhbjW6VkTNy4xhAXCfPApAOg==", - "dependencies": { - "acorn": "^7.4.1", - "base64-js": "^1.5.1", - "core-js": "^3.8.3", - "crypto-js": "^4.0.0", - "serialize-javascript": "^4.0.0" - } - }, - "node_modules/oidc-client/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/oidc-client/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rechoir": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", - "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", - "dev": true, - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/scss-tokenizer": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", - "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", - "dev": true, - "dependencies": { - "js-base64": "^2.4.9", - "source-map": "^0.7.3" - } - }, - "node_modules/scss-tokenizer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/table": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", - "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", - "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.7", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.7.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser/node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-loader": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.3.1.tgz", - "integrity": "sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", - "integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", - "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", - "dev": true, - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.2.0", - "@webpack-cli/info": "^1.5.0", - "@webpack-cli/serve": "^1.7.0", - "colorette": "^2.0.14", - "commander": "^7.0.0", - "cross-spawn": "^7.0.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^2.2.0", - "rechoir": "^0.7.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "4.x.x || 5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "@webpack-cli/migrate": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", - "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - } - }, + "lockfileVersion": 1, "dependencies": { "@babel/code-frame": { "version": "7.12.11", @@ -3651,8 +441,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true, - "requires": {} + "dev": true }, "@webpack-cli/info": { "version": "1.5.0", @@ -3667,8 +456,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true, - "requires": {} + "dev": true }, "@xtuc/ieee754": { "version": "1.2.0", @@ -3692,15 +480,13 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "ajv": { "version": "6.12.6", @@ -3718,8 +504,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} + "dev": true }, "ansi-colors": { "version": "4.1.3", @@ -4491,15 +1276,13 @@ "version": "2.2.17", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.17.tgz", "integrity": "sha512-RkQY06h6rRyADVEI46OCAUYTP2p18Vdtz9Movi19Mmj7SJ1NhN/yGyW7CxlcBVxh95WKg2YSbTmcUPqqeLuhXw==", - "dev": true, - "requires": {} + "dev": true }, "io-ts-reporters": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/io-ts-reporters/-/io-ts-reporters-1.2.2.tgz", "integrity": "sha512-igASwWWkDY757OutNcM6zTtdJf/eTZYkoe2ymsX2qpm5bKZLo74FJYjsCtMQOEdY7dRHLLEulCyFQwdN69GBCg==", - "dev": true, - "requires": {} + "dev": true }, "is-core-module": { "version": "2.10.0", diff --git a/src/AzureIoTHub.Portal/Server/AzureIoTHub.Portal.Server.csproj b/src/AzureIoTHub.Portal/Server/AzureIoTHub.Portal.Server.csproj index a23469853..e94116851 100644 --- a/src/AzureIoTHub.Portal/Server/AzureIoTHub.Portal.Server.csproj +++ b/src/AzureIoTHub.Portal/Server/AzureIoTHub.Portal.Server.csproj @@ -4,24 +4,24 @@ net6.0 AzureIoTHub.Portal.Server-DDED92B6-DADB-4B27-88BD-450D1F21395C 0 + true + $(NoWarn); + Linux + ..\.. + ..\..\docker-compose.dcproj + + + + + + - - - - - - - - - - - - - - - + + + + @@ -31,41 +31,47 @@ + + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + + + - - + + + + + + + + + + + + - - - - - - @@ -80,11 +86,5 @@ - - - - - true - $(NoWarn); - + diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelControllerBase.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelControllerBase.cs index 0a08b9e18..7543fecff 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelControllerBase.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelControllerBase.cs @@ -9,16 +9,16 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 using System.Threading.Tasks; using Azure; using Azure.Data.Tables; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Server.Helpers; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Services; + using AzureIoTHub.Portal.Shared.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Shared.Models; public abstract class DeviceModelsControllerBase : ControllerBase where TListItemModel : class, IDeviceModel diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelPropertiesController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelPropertiesController.cs index 78cee996b..59d8ffbc6 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelPropertiesController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelPropertiesController.cs @@ -6,12 +6,11 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Server.Services; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; - using Microsoft.Extensions.Logging; - using Microsoft.AspNetCore.Authorization; [Authorize] [ApiController] @@ -23,14 +22,12 @@ public class DeviceModelPropertiesController : DeviceModelPropertiesControllerBa /// /// Initializes a new instance of the Device model properties controller class. /// - /// The logger. /// The mapper. - /// the table client factory. + /// The device model properties service. public DeviceModelPropertiesController( - ILogger log, IMapper mapper, - ITableClientFactory tableClientFactory) - : base(log, mapper, tableClientFactory) + IDeviceModelPropertiesService deviceModelPropertiesService) + : base(mapper, deviceModelPropertiesService) { } diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelPropertiesControllerBase.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelPropertiesControllerBase.cs index e58737761..5100e07b6 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelPropertiesControllerBase.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelPropertiesControllerBase.cs @@ -3,53 +3,42 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 { - using System; using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; using AutoMapper; - using Azure; - using Azure.Data.Tables; - using AzureIoTHub.Portal.Server.Entities; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; - using Exceptions; + using AzureIoTHub.Portal.Server.Services; using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; - using Microsoft.Extensions.Logging; public abstract class DeviceModelPropertiesControllerBase : ControllerBase { /// - /// The table client factory. - /// - private readonly ITableClientFactory tableClientFactory; - - /// - /// The logger. + /// The mapper. /// - private readonly ILogger log; + private readonly IMapper mapper; /// - /// The mapper. + /// The device model properties services. /// - private readonly IMapper mapper; + private readonly IDeviceModelPropertiesService deviceModelPropertiesService; /// /// Initializes a new instance of the Device model properties controller base class. /// /// The logger. /// The mapper. - /// the table client factory. + /// The device model properties services.. protected DeviceModelPropertiesControllerBase( - ILogger log, IMapper mapper, - ITableClientFactory tableClientFactory) + IDeviceModelPropertiesService deviceModelPropertiesService) { - this.log = log; - this.mapper = mapper; - this.tableClientFactory = tableClientFactory; + this.deviceModelPropertiesService = deviceModelPropertiesService; } /// @@ -58,32 +47,21 @@ protected DeviceModelPropertiesControllerBase( /// The device model properties public virtual async Task>> GetProperties(string id) { - if (!await DeviceModelExists(id)) - { - return NotFound(); - } - - AsyncPageable items; + var result = new List(); try { - items = this.tableClientFactory - .GetDeviceTemplateProperties() - .QueryAsync($"PartitionKey eq '{id}'"); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to get existing device model properties for device with id {id}", e); - } - - var result = new List(); + foreach (var item in await this.deviceModelPropertiesService.GetModelProperties(id)) + { + result.Add(this.mapper.Map(item)); + } - await foreach (var item in items) + return Ok(result); + } + catch (ResourceNotFoundException e) { - result.Add(this.mapper.Map(item)); + return this.NotFound(e.Message); } - - return Ok(result); } /// @@ -93,11 +71,6 @@ public virtual async Task>> GetProperti /// The model properties public virtual async Task SetProperties(string id, IEnumerable properties) { - if (!(await DeviceModelExists(id))) - { - return NotFound(); - } - if (!ModelState.IsValid) { var validation = new ValidationProblemDetails(ModelState) @@ -108,72 +81,18 @@ public virtual async Task SetProperties(string id, IEnumerable items; - try { - items = table - .QueryAsync($"PartitionKey eq '{id}'"); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to get existing device model properties for device with id {id}", e); - } - - await foreach (var item in items) - { - try - { - _ = await table.DeleteEntityAsync(id, item.RowKey); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to delete the property {item.RowKey} for device model with id {id}", e); - } - } + var entities = properties.Select(item => this.mapper.Map(item, opts => opts.Items[nameof(DeviceModelProperty.ModelId)] = id)) + .ToArray(); - foreach (var item in properties) - { - var entity = this.mapper.Map(item, opts => opts.Items[nameof(DeviceModelProperty.PartitionKey)] = id); + await this.deviceModelPropertiesService.SavePropertiesForModel(id, entities); - try - { - _ = await table.AddEntityAsync(entity); - } - catch (RequestFailedException e) - { - throw new InternalServerErrorException($"Unable to add the property {item.Name} for device model with id {id}", e); - } + return Ok(); } - - return Ok(); - } - - private async Task DeviceModelExists(string id) - { - try + catch (ResourceNotFoundException e) { - _ = await this.tableClientFactory - .GetDeviceTemplates() - .GetEntityAsync("0", id); - - return true; - } - catch (RequestFailedException e) - { - if (e.Status == StatusCodes.Status404NotFound) - { - return false; - } - - this.log.LogError(e.Message, e); - - throw new InternalServerErrorException($"Unable to check if device model with id {id} exist", e); + return this.NotFound(e.Message); } } } diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelsController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelsController.cs index 3d8201476..a7a700002 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelsController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DeviceModelsController.cs @@ -5,14 +5,14 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 { using System.Collections.Generic; using System.Threading.Tasks; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Services; - using AzureIoTHub.Portal.Models.v10; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; - using Microsoft.AspNetCore.Authorization; [Authorize] [ApiController] diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesController.cs index 4c795af21..99c9d170f 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesController.cs @@ -5,14 +5,14 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 { using System.Collections.Generic; using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Models.v10; - using Factories; using Managers; using Mappers; - using Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; + using Services; [Authorize] [ApiController] @@ -29,7 +29,8 @@ public DevicesController( IDeviceTagService deviceTagService, IDeviceProvisioningServiceManager deviceProvisioningServiceManager, IDeviceTwinMapper deviceTwinMapper, - ITableClientFactory tableClientFactory, IDevicePropertyService devicePropertyService) + IDevicePropertyService devicePropertyService, + ITableClientFactory tableClientFactory) : base(logger, devicesService, deviceTagService, deviceTwinMapper, deviceProvisioningServiceManager, tableClientFactory) { this.devicePropertyService = devicePropertyService; diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesControllerBase.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesControllerBase.cs index 2760b2df3..e21fb7714 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesControllerBase.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/DevicesControllerBase.cs @@ -11,13 +11,13 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 using System.Threading.Tasks; using Azure; using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using AzureIoTHub.Portal.Server.Services; using AzureIoTHub.Portal.Shared.Models; - using Exceptions; using Hellang.Middleware.ProblemDetails; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -33,8 +33,8 @@ public abstract class DevicesControllerBase : ControllerBase private readonly IDeviceService devicesService; private readonly IDeviceTagService deviceTagService; private readonly IDeviceTwinMapper deviceTwinMapper; - private readonly ITableClientFactory tableClientFactory; private readonly IDeviceProvisioningServiceManager deviceProvisioningServiceManager; + private readonly ITableClientFactory tableClientFactory; protected ILogger Logger { get; } diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/EdgeDevicesController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/EdgeDevicesController.cs index 682b4f486..991a7413a 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/EdgeDevicesController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/EdgeDevicesController.cs @@ -6,8 +6,8 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 using System; using System.Collections.Generic; using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Server.Exceptions; using AzureIoTHub.Portal.Server.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/EdgeModelsController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/EdgeModelsController.cs index 6e5f36b23..46f24a114 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/EdgeModelsController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/EdgeModelsController.cs @@ -5,8 +5,8 @@ namespace AzureIoTHub.Portal.Server.Controllers.v10 { using System.Collections.Generic; using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Server.Exceptions; using AzureIoTHub.Portal.Server.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsController.cs index fa76a5f7e..f3ca5398f 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANDeviceModelsController.cs @@ -5,16 +5,16 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10.LoRaWAN { using System.Collections.Generic; using System.Threading.Tasks; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Models.v10.LoRaWAN; using AzureIoTHub.Portal.Server.Filters; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Services; - using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Models.v10.LoRaWAN; + using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; - using Microsoft.AspNetCore.Authorization; [Authorize] [ApiController] diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesController.cs index c487cea1b..6ba1479ad 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/LoRaWAN/LoRaWANDevicesController.cs @@ -4,9 +4,9 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 { using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Models.v10.LoRaWAN; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Filters; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; diff --git a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/SettingsController.cs b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/SettingsController.cs index 9e7df54b2..20f3e2c0d 100644 --- a/src/AzureIoTHub.Portal/Server/Controllers/v1.0/SettingsController.cs +++ b/src/AzureIoTHub.Portal/Server/Controllers/v1.0/SettingsController.cs @@ -12,6 +12,7 @@ namespace AzureIoTHub.Portal.Server.Controllers.V10 using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; + using AzureIoTHub.Portal.Domain; [ApiController] [AllowAnonymous] diff --git a/src/AzureIoTHub.Portal/Server/DevelopmentConfigHandler.cs b/src/AzureIoTHub.Portal/Server/DevelopmentConfigHandler.cs deleted file mode 100644 index 1c259dbd5..000000000 --- a/src/AzureIoTHub.Portal/Server/DevelopmentConfigHandler.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Server -{ - using Microsoft.Extensions.Configuration; - - internal class DevelopmentConfigHandler : ConfigHandler - { - private readonly IConfiguration config; - - internal DevelopmentConfigHandler(IConfiguration config) - { - this.config = config; - } - - internal override string PortalName => this.config[PortalNameKey]; - - internal override int MetricExporterRefreshIntervalInSeconds => this.config.GetValue(MetricExporterRefreshIntervalKey, 30); - - internal override int MetricLoaderRefreshIntervalInMinutes => this.config.GetValue(MetricLoaderRefreshIntervalKey, 10); - - internal override string IoTHubConnectionString => this.config[IoTHubConnectionStringKey]; - - internal override string DPSConnectionString => this.config[DPSConnectionStringKey]; - - internal override string DPSEndpoint => this.config[DPSServiceEndpointKey]; - - internal override string DPSScopeID => this.config[DPSIDScopeKey]; - - internal override string StorageAccountConnectionString => this.config[StorageAccountConnectionStringKey]; - - internal override int StorageAccountDeviceModelImageMaxAge => this.config.GetValue(StorageAccountDeviceModelImageMaxAgeKey, 86400); - - internal override bool UseSecurityHeaders => this.config.GetValue(UseSecurityHeadersKey, true); - - internal override string OIDCScope => this.config[OIDCScopeKey]; - - internal override string OIDCAuthority => this.config[OIDCAuthorityKey]; - - internal override string OIDCMetadataUrl => this.config[OIDCMetadataUrlKey]; - - internal override string OIDCClientId => this.config[OIDCClientIdKey]; - - internal override string OIDCApiClientId => this.config[OIDCApiClientIdKey]; - - internal override bool OIDCValidateIssuer => this.config.GetValue(OIDCValidateIssuerKey, true); - - internal override bool OIDCValidateAudience => this.config.GetValue(OIDCValidateAudienceKey, true); - - internal override bool OIDCValidateLifetime => this.config.GetValue(OIDCValidateLifetimeKey, true); - - internal override bool OIDCValidateIssuerSigningKey => this.config.GetValue(OIDCValidateIssuerSigningKeyKey, true); - - internal override bool OIDCValidateActor => this.config.GetValue(OIDCValidateActorKey, false); - - internal override bool OIDCValidateTokenReplay => this.config.GetValue(OIDCValidateTokenReplayKey, false); - - internal override bool IsLoRaEnabled => bool.Parse(this.config[IsLoRaFeatureEnabledKey] ?? "true"); - - internal override string LoRaKeyManagementUrl => this.config[LoRaKeyManagementUrlKey]; - - internal override string LoRaKeyManagementCode => this.config[LoRaKeyManagementCodeKey]; - - internal override string LoRaKeyManagementApiVersion => this.config[LoRaKeyManagementApiVersionKey]; - - internal override bool IdeasEnabled => this.config.GetValue(IdeasEnabledKey, false); - internal override string IdeasUrl => this.config.GetValue(IdeasUrlKey, string.Empty); - internal override string IdeasAuthenticationHeader => this.config.GetValue(IdeasAuthenticationHeaderKey, "Ocp-Apim-Subscription-Key"); - internal override string IdeasAuthenticationToken => this.config.GetValue(IdeasAuthenticationTokenKey, string.Empty); - } -} diff --git a/src/AzureIoTHub.Portal/Server/Entities/EntityBase.cs b/src/AzureIoTHub.Portal/Server/Entities/EntityBase.cs index dcfa2d870..4c05aee3c 100644 --- a/src/AzureIoTHub.Portal/Server/Entities/EntityBase.cs +++ b/src/AzureIoTHub.Portal/Server/Entities/EntityBase.cs @@ -1,11 +1,11 @@ -// Copyright (c) CGI France. All rights reserved. +// 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.Entities { - using System; - using Azure; using Azure.Data.Tables; + using Azure; + using System; public abstract class EntityBase : ITableEntity { diff --git a/src/AzureIoTHub.Portal/Server/Factories/ITableClientFactory.cs b/src/AzureIoTHub.Portal/Server/Factories/ITableClientFactory.cs deleted file mode 100644 index 9c493e148..000000000 --- a/src/AzureIoTHub.Portal/Server/Factories/ITableClientFactory.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Server.Factories -{ - using Azure.Data.Tables; - - public interface ITableClientFactory - { - const string DeviceCommandTableName = "DeviceCommands"; - const string DeviceTemplateTableName = "DeviceTemplates"; - const string EdgeDeviceTemplateTableName = "EdgeDeviceTemplates"; - const string DeviceTagSettingTableName = "DeviceTagSettings"; - const string DeviceTemplatePropertiesTableName = "DeviceTemplateProperties"; - const string EdgeModuleCommandsTableName = "EdgeModuleCommands"; - - TableClient GetDeviceCommands(); - - TableClient GetDeviceTemplates(); - - TableClient GetEdgeDeviceTemplates(); - - TableClient GetDeviceTemplateProperties(); - - TableClient GetDeviceTagSettings(); - - public TableClient GetTemplatesHealthCheck(); - - TableClient GetEdgeModuleCommands(); - } -} diff --git a/src/AzureIoTHub.Portal/Server/Factories/TableClientFactory.cs b/src/AzureIoTHub.Portal/Server/Factories/TableClientFactory.cs deleted file mode 100644 index 48a4e99da..000000000 --- a/src/AzureIoTHub.Portal/Server/Factories/TableClientFactory.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Server.Factories -{ - using System; - using Azure; - using Azure.Data.Tables; - using Exceptions; - - public class TableClientFactory : ITableClientFactory - { - private readonly string connectionString; - - public TableClientFactory(string connectionString) - { - this.connectionString = connectionString; - } - - public TableClient GetDeviceCommands() - { - return CreateClient(ITableClientFactory.DeviceCommandTableName); - } - - public TableClient GetDeviceTemplates() - { - return CreateClient(ITableClientFactory.DeviceTemplateTableName); - } - - public TableClient GetEdgeDeviceTemplates() - { - return CreateClient(ITableClientFactory.EdgeDeviceTemplateTableName); - } - - public TableClient GetDeviceTagSettings() - { - return CreateClient(ITableClientFactory.DeviceTagSettingTableName); - } - - private TableClient CreateClient(string tableName) - { - try - { - var tableClient = new TableClient(this.connectionString, tableName); - - _ = tableClient.CreateIfNotExists(); - - return tableClient; - } - catch (Exception e) when (e is ArgumentNullException or - InvalidOperationException or - RequestFailedException) - { - throw new InternalServerErrorException($"Unable to create table client with table name {tableName}", e); - } - } - - public TableClient GetDeviceTemplateProperties() - { - return CreateClient(ITableClientFactory.DeviceTemplatePropertiesTableName); - } - - public TableClient GetTemplatesHealthCheck() - { - return CreateClient("tableHealthCheck"); - } - - public TableClient GetEdgeModuleCommands() - { - return CreateClient(ITableClientFactory.EdgeModuleCommandsTableName); - } - } -} diff --git a/src/AzureIoTHub.Portal/Server/Filters/LoRaFeatureActiveFilterAttribute.cs b/src/AzureIoTHub.Portal/Server/Filters/LoRaFeatureActiveFilterAttribute.cs index 1a8fea4cc..0584321af 100644 --- a/src/AzureIoTHub.Portal/Server/Filters/LoRaFeatureActiveFilterAttribute.cs +++ b/src/AzureIoTHub.Portal/Server/Filters/LoRaFeatureActiveFilterAttribute.cs @@ -4,6 +4,7 @@ namespace AzureIoTHub.Portal.Server.Filters { using System; + using AzureIoTHub.Portal.Domain; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; diff --git a/src/AzureIoTHub.Portal/Server/Managers/DeviceModelCommandsManager.cs b/src/AzureIoTHub.Portal/Server/Managers/DeviceModelCommandsManager.cs index 98803f1eb..4c2c65e72 100644 --- a/src/AzureIoTHub.Portal/Server/Managers/DeviceModelCommandsManager.cs +++ b/src/AzureIoTHub.Portal/Server/Managers/DeviceModelCommandsManager.cs @@ -7,9 +7,9 @@ namespace AzureIoTHub.Portal.Server.Managers using System.Collections.ObjectModel; using System.Linq; using Azure.Data.Tables; - using AzureIoTHub.Portal.Server.Factories; - using AzureIoTHub.Portal.Server.Mappers; + using AzureIoTHub.Portal.Domain; using AzureIoTHub.Portal.Models.v10.LoRaWAN; + using AzureIoTHub.Portal.Server.Mappers; public class DeviceModelCommandsManager : IDeviceModelCommandsManager { diff --git a/src/AzureIoTHub.Portal/Server/Managers/DeviceModelImageManager.cs b/src/AzureIoTHub.Portal/Server/Managers/DeviceModelImageManager.cs index a1be69618..5bc4e59ca 100644 --- a/src/AzureIoTHub.Portal/Server/Managers/DeviceModelImageManager.cs +++ b/src/AzureIoTHub.Portal/Server/Managers/DeviceModelImageManager.cs @@ -11,7 +11,8 @@ namespace AzureIoTHub.Portal.Server.Managers using Azure; using Azure.Storage.Blobs; using Azure.Storage.Blobs.Models; - using AzureIoTHub.Portal.Server.Exceptions; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using Microsoft.Extensions.Logging; public class DeviceModelImageManager : IDeviceModelImageManager diff --git a/src/AzureIoTHub.Portal/Server/Managers/DeviceProvisioningServiceManager.cs b/src/AzureIoTHub.Portal/Server/Managers/DeviceProvisioningServiceManager.cs index 113bd4d4f..b8b0660ec 100644 --- a/src/AzureIoTHub.Portal/Server/Managers/DeviceProvisioningServiceManager.cs +++ b/src/AzureIoTHub.Portal/Server/Managers/DeviceProvisioningServiceManager.cs @@ -13,6 +13,7 @@ namespace AzureIoTHub.Portal.Server.Managers using Microsoft.Azure.Devices.Provisioning.Service; using Microsoft.Azure.Devices.Shared; using System.Threading; + using AzureIoTHub.Portal.Domain; public class DeviceProvisioningServiceManager : IDeviceProvisioningServiceManager { diff --git a/src/AzureIoTHub.Portal/Server/Mappers/DevicePropertyProfile.cs b/src/AzureIoTHub.Portal/Server/Mappers/DevicePropertyProfile.cs index ab23d7093..e0c7c1082 100644 --- a/src/AzureIoTHub.Portal/Server/Mappers/DevicePropertyProfile.cs +++ b/src/AzureIoTHub.Portal/Server/Mappers/DevicePropertyProfile.cs @@ -4,7 +4,7 @@ namespace AzureIoTHub.Portal.Server.Mappers { using AutoMapper; - using AzureIoTHub.Portal.Server.Entities; + using AzureIoTHub.Portal.Domain.Entities; using AzureIoTHub.Portal.Models.v10; public class DevicePropertyProfile : Profile @@ -14,8 +14,7 @@ public DevicePropertyProfile() _ = CreateMap(); _ = CreateMap() - .ForMember(c => c.RowKey, opts => opts.MapFrom(c => c.Name)) - .ForMember(c => c.PartitionKey, opts => opts.MapFrom((_, _, _, context) => context.Items[nameof(DeviceModelProperty.PartitionKey)])); + .ForMember(c => c.ModelId, opts => opts.MapFrom((_, _, _, context) => context.Items[nameof(DeviceModelProperty.ModelId)])); } } } diff --git a/src/AzureIoTHub.Portal/Server/ProductionConfigHandler.cs b/src/AzureIoTHub.Portal/Server/ProductionConfigHandler.cs deleted file mode 100644 index d9f43c3d6..000000000 --- a/src/AzureIoTHub.Portal/Server/ProductionConfigHandler.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) CGI France. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace AzureIoTHub.Portal.Server -{ - using Microsoft.Extensions.Configuration; - - internal class ProductionConfigHandler : ConfigHandler - { - private readonly IConfiguration config; - - internal ProductionConfigHandler(IConfiguration config) - { - this.config = config; - } - - internal override string PortalName => this.config[PortalNameKey]; - - internal override int MetricExporterRefreshIntervalInSeconds => this.config.GetValue(MetricExporterRefreshIntervalKey, 30); - - internal override int MetricLoaderRefreshIntervalInMinutes => this.config.GetValue(MetricLoaderRefreshIntervalKey, 10); - - internal override string IoTHubConnectionString => this.config.GetConnectionString(IoTHubConnectionStringKey); - - internal override string DPSConnectionString => this.config.GetConnectionString(DPSConnectionStringKey); - - internal override string DPSEndpoint => this.config[DPSServiceEndpointKey]; - - internal override string DPSScopeID => this.config[DPSIDScopeKey]; - - internal override string StorageAccountConnectionString => this.config.GetConnectionString(StorageAccountConnectionStringKey); - - internal override int StorageAccountDeviceModelImageMaxAge => this.config.GetValue(StorageAccountDeviceModelImageMaxAgeKey, 86400); - - internal override bool UseSecurityHeaders => this.config.GetValue(UseSecurityHeadersKey, true); - - internal override string OIDCScope => this.config[OIDCScopeKey]; - - internal override string OIDCAuthority => this.config[OIDCAuthorityKey]; - - internal override string OIDCMetadataUrl => this.config[OIDCMetadataUrlKey]; - - internal override string OIDCClientId => this.config[OIDCClientIdKey]; - - internal override string OIDCApiClientId => this.config[OIDCApiClientIdKey]; - - internal override bool OIDCValidateIssuer => this.config.GetValue(OIDCValidateIssuerKey, true); - - internal override bool OIDCValidateAudience => this.config.GetValue(OIDCValidateAudienceKey, true); - - internal override bool OIDCValidateLifetime => this.config.GetValue(OIDCValidateLifetimeKey, true); - - internal override bool OIDCValidateIssuerSigningKey => this.config.GetValue(OIDCValidateIssuerSigningKeyKey, true); - - internal override bool OIDCValidateActor => this.config.GetValue(OIDCValidateActorKey, false); - - internal override bool OIDCValidateTokenReplay => this.config.GetValue(OIDCValidateTokenReplayKey, false); - - internal override bool IsLoRaEnabled => bool.Parse(this.config[IsLoRaFeatureEnabledKey] ?? "true"); - - internal override string LoRaKeyManagementUrl => this.config[LoRaKeyManagementUrlKey]; - - internal override string LoRaKeyManagementCode => this.config.GetConnectionString(LoRaKeyManagementCodeKey); - - internal override string LoRaKeyManagementApiVersion => this.config[LoRaKeyManagementApiVersionKey]; - - internal override bool IdeasEnabled => this.config.GetValue(IdeasEnabledKey, false); - internal override string IdeasUrl => this.config.GetValue(IdeasUrlKey, string.Empty); - internal override string IdeasAuthenticationHeader => this.config.GetValue(IdeasAuthenticationHeaderKey, "Ocp-Apim-Subscription-Key"); - internal override string IdeasAuthenticationToken => this.config.GetValue(IdeasAuthenticationTokenKey, string.Empty); - } -} diff --git a/src/AzureIoTHub.Portal/Server/Services/ConcentratorMetricExporterService.cs b/src/AzureIoTHub.Portal/Server/Services/ConcentratorMetricExporterService.cs index b162deec9..ed6cd7761 100644 --- a/src/AzureIoTHub.Portal/Server/Services/ConcentratorMetricExporterService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/ConcentratorMetricExporterService.cs @@ -6,7 +6,8 @@ namespace AzureIoTHub.Portal.Server.Services using System; using System.Threading; using System.Threading.Tasks; - using Constants; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Shared.Constants; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Prometheus; diff --git a/src/AzureIoTHub.Portal/Server/Services/ConcentratorMetricLoaderService.cs b/src/AzureIoTHub.Portal/Server/Services/ConcentratorMetricLoaderService.cs index 8b96cc877..720372d90 100644 --- a/src/AzureIoTHub.Portal/Server/Services/ConcentratorMetricLoaderService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/ConcentratorMetricLoaderService.cs @@ -6,7 +6,8 @@ namespace AzureIoTHub.Portal.Server.Services using System; using System.Threading; using System.Threading.Tasks; - using Exceptions; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/src/AzureIoTHub.Portal/Server/Services/ConfigService.cs b/src/AzureIoTHub.Portal/Server/Services/ConfigService.cs index 73abbc3aa..c79520e6d 100644 --- a/src/AzureIoTHub.Portal/Server/Services/ConfigService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/ConfigService.cs @@ -9,8 +9,8 @@ namespace AzureIoTHub.Portal.Server.Services using System.Linq; using System.Text; using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Server.Exceptions; using AzureIoTHub.Portal.Server.Helpers; using Extensions; using Microsoft.Azure.Devices; diff --git a/src/AzureIoTHub.Portal/Server/Services/DeviceConfigurationsService.cs b/src/AzureIoTHub.Portal/Server/Services/DeviceConfigurationsService.cs index 11f7d101e..0f053bf76 100644 --- a/src/AzureIoTHub.Portal/Server/Services/DeviceConfigurationsService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/DeviceConfigurationsService.cs @@ -8,11 +8,10 @@ namespace AzureIoTHub.Portal.Server.Services using System.Linq; using System.Threading.Tasks; using Azure; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models; using AzureIoTHub.Portal.Models.v10; - using AzureIoTHub.Portal.Server.Entities; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Helpers; using AzureIoTHub.Portal.Shared.Models.v10; @@ -23,15 +22,12 @@ public class DeviceConfigurationsService : IDeviceConfigurationsService /// private readonly IConfigService configService; - /// - /// The table client factory. - /// - private readonly ITableClientFactory tableClientFactory; + private readonly IDeviceModelPropertiesService deviceModelPropertiesService; - public DeviceConfigurationsService(IConfigService configService, ITableClientFactory tableClientFactory) + public DeviceConfigurationsService(IConfigService configService, IDeviceModelPropertiesService deviceModelPropertiesService) { this.configService = configService; - this.tableClientFactory = tableClientFactory; + this.deviceModelPropertiesService = deviceModelPropertiesService; } public async Task> GetDeviceConfigurationListAsync() @@ -86,13 +82,10 @@ public async Task DeleteConfigurationAsync(string configurationId) private async Task CreateOrUpdateConfiguration(DeviceConfig deviceConfig) { - DeviceModelProperty[] items; + IEnumerable items; try { - items = this.tableClientFactory - .GetDeviceTemplateProperties() - .Query($"PartitionKey eq '{deviceConfig.ModelId}'") - .ToArray(); + items = await this.deviceModelPropertiesService.GetModelProperties(deviceConfig.ModelId); } catch (RequestFailedException e) { diff --git a/src/AzureIoTHub.Portal/Server/Services/DeviceMetricExporterService.cs b/src/AzureIoTHub.Portal/Server/Services/DeviceMetricExporterService.cs index a1cbc6917..e3fdc3343 100644 --- a/src/AzureIoTHub.Portal/Server/Services/DeviceMetricExporterService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/DeviceMetricExporterService.cs @@ -6,7 +6,8 @@ namespace AzureIoTHub.Portal.Server.Services using System; using System.Threading; using System.Threading.Tasks; - using Constants; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Shared.Constants; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Prometheus; diff --git a/src/AzureIoTHub.Portal/Server/Services/DeviceMetricLoaderService.cs b/src/AzureIoTHub.Portal/Server/Services/DeviceMetricLoaderService.cs index 4fb70f66b..732e8b1c3 100644 --- a/src/AzureIoTHub.Portal/Server/Services/DeviceMetricLoaderService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/DeviceMetricLoaderService.cs @@ -6,7 +6,8 @@ namespace AzureIoTHub.Portal.Server.Services using System; using System.Threading; using System.Threading.Tasks; - using Exceptions; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/src/AzureIoTHub.Portal/Server/Services/DeviceModelPropertiesService.cs b/src/AzureIoTHub.Portal/Server/Services/DeviceModelPropertiesService.cs new file mode 100644 index 000000000..1ae52e62c --- /dev/null +++ b/src/AzureIoTHub.Portal/Server/Services/DeviceModelPropertiesService.cs @@ -0,0 +1,95 @@ +// 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.Services +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Azure; + using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Repositories; + using Microsoft.AspNetCore.Http; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Logging; + + public class DeviceModelPropertiesService : IDeviceModelPropertiesService + { + /// + /// The unit of work. + /// + private readonly IUnitOfWork unitOfWork; + + /// + /// The table client factory. + /// + private readonly ITableClientFactory tableClientFactory; + + /// + /// The device model properties repository. + /// + private readonly IDeviceModelPropertiesRepository deviceModelPropertiesRepository; + + /// + /// The logger. + /// + private readonly ILogger log; + + public DeviceModelPropertiesService( + ILogger log, IUnitOfWork unitOfWork, ITableClientFactory tableClientFactory, IDeviceModelPropertiesRepository deviceModelPropertiesRepository) + { + this.log = log; + this.unitOfWork = unitOfWork; + this.tableClientFactory = tableClientFactory; + this.deviceModelPropertiesRepository = deviceModelPropertiesRepository; + } + + public async Task> GetModelProperties(string modelId) + { + _ = await AssertModelExists(modelId); + + return await this.deviceModelPropertiesRepository.GetModelProperties(modelId); + } + + public async Task SavePropertiesForModel(string modelId, IEnumerable items) + { + _ = await AssertModelExists(modelId); + + try + { + await this.deviceModelPropertiesRepository.SavePropertiesForModel(modelId, items); + + await this.unitOfWork.SaveAsync(); + } + catch (DbUpdateException e) + { + throw new InternalServerErrorException($"Unable to set properties for model {modelId}", e); + } + } + + private async Task AssertModelExists(string id) + { + try + { + _ = await this.tableClientFactory + .GetDeviceTemplates() + .GetEntityAsync("0", id); + + return true; + } + catch (RequestFailedException e) + { + if (e.Status == StatusCodes.Status404NotFound) + { + throw new ResourceNotFoundException($"The model {id} doesn't exist."); + } + + this.log.LogError(e.Message, e); + + throw new InternalServerErrorException($"Unable to check if device model with id {id} exist", e); + } + } + } +} diff --git a/src/AzureIoTHub.Portal/Server/Services/DevicePropertyService.cs b/src/AzureIoTHub.Portal/Server/Services/DevicePropertyService.cs index 676e3adb2..5319507ad 100644 --- a/src/AzureIoTHub.Portal/Server/Services/DevicePropertyService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/DevicePropertyService.cs @@ -7,9 +7,8 @@ namespace AzureIoTHub.Portal.Server.Services using System.Linq; using System.Threading.Tasks; using Azure; - using Entities; - using Exceptions; - using Factories; + using AzureIoTHub.Portal.Domain.Entities; + using AzureIoTHub.Portal.Domain.Exceptions; using Helpers; using Models.v10; using Newtonsoft.Json; @@ -18,15 +17,14 @@ namespace AzureIoTHub.Portal.Server.Services public class DevicePropertyService : IDevicePropertyService { private readonly IDeviceService devicesService; - private readonly ITableClientFactory tableClientFactory; + private readonly IDeviceModelPropertiesService deviceModelPropertiesService; - public DevicePropertyService(IDeviceService devicesService, ITableClientFactory tableClientFactory) + public DevicePropertyService(IDeviceService devicesService, IDeviceModelPropertiesService deviceModelPropertiesService) { this.devicesService = devicesService; - this.tableClientFactory = tableClientFactory; + this.deviceModelPropertiesService = deviceModelPropertiesService; } - public async Task> GetProperties(string deviceId) { var device = await this.devicesService.GetDeviceTwin(deviceId); @@ -43,13 +41,11 @@ public async Task> GetProperties(string deviceI throw new ResourceNotFoundException($"Device {deviceId} has no modelId tag value"); } - AsyncPageable items; + IEnumerable items; try { - items = this.tableClientFactory - .GetDeviceTemplateProperties() - .QueryAsync($"PartitionKey eq '{modelId}'"); + items = await this.deviceModelPropertiesService.GetModelProperties(modelId); } catch (RequestFailedException e) { @@ -78,7 +74,7 @@ public async Task> GetProperties(string deviceI throw new InternalServerErrorException($"Unable to read reported properties for device with id {deviceId}", e); } - await foreach (var item in items) + foreach (var item in items) { var value = item.IsWritable ? desiredPropertiesAsJson.SelectToken(item.Name)?.Value() : reportedPropertiesAsJson.SelectToken(item.Name)?.Value(); @@ -112,13 +108,12 @@ public async Task SetProperties(string deviceId, IEnumerable items; + IEnumerable items; try { - items = this.tableClientFactory - .GetDeviceTemplateProperties() - .QueryAsync($"PartitionKey eq '{modelId}'"); + items = await this.deviceModelPropertiesService.GetModelProperties(modelId); + } catch (RequestFailedException e) { @@ -127,7 +122,7 @@ public async Task SetProperties(string deviceId, IEnumerable(); - await foreach (var item in items) + foreach (var item in items) { if (!item.IsWritable) { diff --git a/src/AzureIoTHub.Portal/Server/Services/DeviceService.cs b/src/AzureIoTHub.Portal/Server/Services/DeviceService.cs index f48bb7e7e..b43b716a7 100644 --- a/src/AzureIoTHub.Portal/Server/Services/DeviceService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/DeviceService.cs @@ -10,9 +10,9 @@ namespace AzureIoTHub.Portal.Server.Services using System.Linq; using System.Text; using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Shared.Constants; - using Exceptions; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Shared; using Microsoft.Extensions.Logging; diff --git a/src/AzureIoTHub.Portal/Server/Services/DeviceTagService.cs b/src/AzureIoTHub.Portal/Server/Services/DeviceTagService.cs index 8c2877d3a..ff14324c8 100644 --- a/src/AzureIoTHub.Portal/Server/Services/DeviceTagService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/DeviceTagService.cs @@ -3,16 +3,16 @@ namespace AzureIoTHub.Portal.Server.Services { - using Azure.Data.Tables; - using AzureIoTHub.Portal.Server.Factories; - using AzureIoTHub.Portal.Server.Mappers; - using AzureIoTHub.Portal.Models.v10; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Azure; - using Exceptions; + using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Server.Mappers; public class DeviceTagService : IDeviceTagService { diff --git a/src/AzureIoTHub.Portal/Server/Services/EdgeDeviceMetricExporterService.cs b/src/AzureIoTHub.Portal/Server/Services/EdgeDeviceMetricExporterService.cs index 6e8d8ffb0..528484b63 100644 --- a/src/AzureIoTHub.Portal/Server/Services/EdgeDeviceMetricExporterService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/EdgeDeviceMetricExporterService.cs @@ -6,7 +6,8 @@ namespace AzureIoTHub.Portal.Server.Services using System; using System.Threading; using System.Threading.Tasks; - using Constants; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Shared.Constants; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Prometheus; diff --git a/src/AzureIoTHub.Portal/Server/Services/EdgeDeviceMetricLoaderService.cs b/src/AzureIoTHub.Portal/Server/Services/EdgeDeviceMetricLoaderService.cs index 20ae89e0b..d5d1cf3d3 100644 --- a/src/AzureIoTHub.Portal/Server/Services/EdgeDeviceMetricLoaderService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/EdgeDeviceMetricLoaderService.cs @@ -6,7 +6,8 @@ namespace AzureIoTHub.Portal.Server.Services using System; using System.Threading; using System.Threading.Tasks; - using Exceptions; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/src/AzureIoTHub.Portal/Server/Services/EdgeDevicesService.cs b/src/AzureIoTHub.Portal/Server/Services/EdgeDevicesService.cs index 03754a792..b29650154 100644 --- a/src/AzureIoTHub.Portal/Server/Services/EdgeDevicesService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/EdgeDevicesService.cs @@ -3,21 +3,21 @@ namespace AzureIoTHub.Portal.Server.Services { - using AzureIoTHub.Portal.Models.v10; - using System.Threading.Tasks; - using AzureIoTHub.Portal.Server.Managers; - using Microsoft.Azure.Devices; + using System; using System.Collections.Generic; - using AzureIoTHub.Portal.Server.Mappers; - using Microsoft.Azure.Devices.Shared; - using Newtonsoft.Json.Linq; using System.Linq; - using System; - using Newtonsoft.Json; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Server.Helpers; - using AzureIoTHub.Portal.Server.Exceptions; + using AzureIoTHub.Portal.Server.Managers; + using AzureIoTHub.Portal.Server.Mappers; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; + using Microsoft.Azure.Devices; + using Microsoft.Azure.Devices.Shared; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; public class EdgeDevicesService : IEdgeDevicesService { diff --git a/src/AzureIoTHub.Portal/Server/Services/EdgeModelService.cs b/src/AzureIoTHub.Portal/Server/Services/EdgeModelService.cs index 4bb4644f5..3b69109ed 100644 --- a/src/AzureIoTHub.Portal/Server/Services/EdgeModelService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/EdgeModelService.cs @@ -3,19 +3,19 @@ namespace AzureIoTHub.Portal.Server.Services { - using Azure.Data.Tables; - using Azure; - using AzureIoTHub.Portal.Models.v10; + using System; using System.Collections.Generic; using System.Linq; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; + using System.Threading.Tasks; + using Azure; + using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Models.v10; + using AzureIoTHub.Portal.Server.Entities; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using Microsoft.AspNetCore.Http; - using System.Threading.Tasks; - using System; - using AzureIoTHub.Portal.Server.Entities; public class EdgeModelService : IEdgeModelService { diff --git a/src/AzureIoTHub.Portal/Server/Services/IDeviceModelPropertiesService.cs b/src/AzureIoTHub.Portal/Server/Services/IDeviceModelPropertiesService.cs new file mode 100644 index 000000000..78aee5675 --- /dev/null +++ b/src/AzureIoTHub.Portal/Server/Services/IDeviceModelPropertiesService.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.Server.Services +{ + using AzureIoTHub.Portal.Domain.Entities; + using System.Collections.Generic; + using System.Threading.Tasks; + + public interface IDeviceModelPropertiesService + { + Task> GetModelProperties(string modelId); + Task SavePropertiesForModel(string modelId, IEnumerable items); + } +} diff --git a/src/AzureIoTHub.Portal/Server/Services/IdeaService.cs b/src/AzureIoTHub.Portal/Server/Services/IdeaService.cs index c4ed0480d..f73d28aaa 100644 --- a/src/AzureIoTHub.Portal/Server/Services/IdeaService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/IdeaService.cs @@ -7,7 +7,8 @@ namespace AzureIoTHub.Portal.Server.Services using System.Net.Http.Json; using System.Text; using System.Threading.Tasks; - using Exceptions; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Shared.Models.v1._0; diff --git a/src/AzureIoTHub.Portal/Server/Services/LoRaWANCommandService.cs b/src/AzureIoTHub.Portal/Server/Services/LoRaWANCommandService.cs index 7ac948ae7..160ae896b 100644 --- a/src/AzureIoTHub.Portal/Server/Services/LoRaWANCommandService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/LoRaWANCommandService.cs @@ -7,10 +7,10 @@ namespace AzureIoTHub.Portal.Server.Services using System.Threading.Tasks; using Azure; using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10.LoRaWAN; using AzureIoTHub.Portal.Server.Controllers.V10.LoRaWAN; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Mappers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; diff --git a/src/AzureIoTHub.Portal/Server/Services/LoRaWANDeviceService.cs b/src/AzureIoTHub.Portal/Server/Services/LoRaWANDeviceService.cs index bed3038cf..b53ebc0d8 100644 --- a/src/AzureIoTHub.Portal/Server/Services/LoRaWANDeviceService.cs +++ b/src/AzureIoTHub.Portal/Server/Services/LoRaWANDeviceService.cs @@ -7,10 +7,10 @@ namespace AzureIoTHub.Portal.Server.Services using System.Threading.Tasks; using Azure; using Azure.Data.Tables; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; using AzureIoTHub.Portal.Models.v10; using AzureIoTHub.Portal.Models.v10.LoRaWAN; - using AzureIoTHub.Portal.Server.Exceptions; - using AzureIoTHub.Portal.Server.Factories; using AzureIoTHub.Portal.Server.Managers; using AzureIoTHub.Portal.Server.Mappers; using Microsoft.Extensions.Logging; diff --git a/src/AzureIoTHub.Portal/Server/ServicesHealthCheck/TableStorageHealthCheck.cs b/src/AzureIoTHub.Portal/Server/ServicesHealthCheck/TableStorageHealthCheck.cs index b2cef597f..8d0ef06dd 100644 --- a/src/AzureIoTHub.Portal/Server/ServicesHealthCheck/TableStorageHealthCheck.cs +++ b/src/AzureIoTHub.Portal/Server/ServicesHealthCheck/TableStorageHealthCheck.cs @@ -7,7 +7,7 @@ namespace AzureIoTHub.Portal.Server.ServicesHealthCheck using System.Threading; using System.Threading.Tasks; using Azure.Data.Tables; - using AzureIoTHub.Portal.Server.Factories; + using AzureIoTHub.Portal.Domain; using Microsoft.Extensions.Diagnostics.HealthChecks; public class TableStorageHealthCheck : IHealthCheck diff --git a/src/AzureIoTHub.Portal/Server/Startup.cs b/src/AzureIoTHub.Portal/Server/Startup.cs index 0dc68a7b0..534e35614 100644 --- a/src/AzureIoTHub.Portal/Server/Startup.cs +++ b/src/AzureIoTHub.Portal/Server/Startup.cs @@ -11,9 +11,13 @@ namespace AzureIoTHub.Portal.Server using AutoMapper; using Azure; using Azure.Storage.Blobs; - using Exceptions; + using AzureIoTHub.Portal.Domain; + using AzureIoTHub.Portal.Domain.Exceptions; + using AzureIoTHub.Portal.Domain.Repositories; + using AzureIoTHub.Portal.Infrastructure; + using AzureIoTHub.Portal.Infrastructure.Factories; + using AzureIoTHub.Portal.Infrastructure.Repositories; using Extensions; - using Factories; using Hellang.Middleware.ProblemDetails; using Hellang.Middleware.ProblemDetails.Mvc; using Identity; @@ -28,6 +32,7 @@ namespace AzureIoTHub.Portal.Server using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Provisioning.Service; + using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -39,6 +44,7 @@ namespace AzureIoTHub.Portal.Server using Polly; using Polly.Extensions.Http; using Prometheus; + using Quartz; using Services; using ServicesHealthCheck; using Shared.Models.v1._0; @@ -65,7 +71,7 @@ public void ConfigureServices(IServiceCollection services) { ArgumentNullException.ThrowIfNull(services, nameof(services)); - var configuration = ConfigHandler.Create(HostEnvironment, Configuration); + var configuration = ConfigHandlerFactory.Create(HostEnvironment, Configuration); _ = services.Configure(opts => { @@ -95,6 +101,11 @@ public void ConfigureServices(IServiceCollection services) opts.TokenValidationParameters.ValidateTokenReplay = configuration.OIDCValidateTokenReplay; }); + _ = services + .AddDbContextPool(opts => opts.UseNpgsql(configuration.PostgreSQLConnectionString)); + + _ = services.AddScoped>(); + _ = services.AddSingleton(configuration); _ = services.AddSingleton(new PortalMetric()); @@ -134,6 +145,9 @@ public void ConfigureServices(IServiceCollection services) _ = services.AddTransient(); _ = services.AddTransient(); _ = services.AddTransient(); + _ = services.AddTransient(); + + _ = services.AddScoped(); _ = services.AddMudServices(); @@ -261,6 +275,7 @@ Specify the authorization token got from your IDP as a header. _ = services.AddSingleton(mapper); _ = services.AddHealthChecks() + .AddDbContextCheck() .AddCheck("iothubHealth") .AddCheck("storageAccountHealth") .AddCheck("tableStorageHealth") @@ -276,6 +291,24 @@ Specify the authorization token got from your IDP as a header. _ = services.AddHostedService(); _ = services.AddHostedService(); _ = services.AddHostedService(); + + // Add the required Quartz.NET services + _ = services.AddQuartz(q => + { + q.UseMicrosoftDependencyInjectionJobFactory(); + q.UsePersistentStore(opts => + { + // JSON is recommended persistent format to store data in database for greenfield projects. + // You should also strongly consider setting useProperties to true to restrict key - values to be strings. + opts.UseJsonSerializer(); + opts.UseProperties = true; + + opts.UsePostgres(configuration.StorageAccountConnectionString); + }); + }); + + // Add the Quartz.NET hosted service + _ = services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true); } private static void ConfigureIdeasFeature(IServiceCollection services, ConfigHandler configuration) @@ -367,11 +400,12 @@ public async void Configure(IApplicationBuilder app, IWebHostEnvironment env) }); }); - var deviceModelImageManager = app.ApplicationServices.GetService(); await deviceModelImageManager?.InitializeDefaultImageBlob()!; await deviceModelImageManager?.SyncImagesCacheControl()!; + + await EnsureDatabaseCreatedAndUpToDate(app, env)!; } private static void UseApiExceptionMiddleware(IApplicationBuilder app) @@ -394,5 +428,20 @@ private Task HandleApiFallback(HttpContext context) context.Response.StatusCode = StatusCodes.Status404NotFound; return Task.CompletedTask; } + + private static async Task EnsureDatabaseCreatedAndUpToDate(IApplicationBuilder app, IWebHostEnvironment env) + { + using var scope = app.ApplicationServices.CreateScope(); + + using var context = scope.ServiceProvider.GetRequiredService(); + + if (env.IsDevelopment()) + { + _ = await context.Database.EnsureDeletedAsync(); + } + + // Create the database if not exists and migrate it using the database bigration scripts. + _ = await context.Database.EnsureCreatedAsync(); + } } } diff --git a/src/AzureIoTHubPortal.Domain/AzureIoTHub.Portal.Domain.csproj b/src/AzureIoTHubPortal.Domain/AzureIoTHub.Portal.Domain.csproj new file mode 100644 index 000000000..f0bb58b36 --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/AzureIoTHub.Portal.Domain.csproj @@ -0,0 +1,17 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/AzureIoTHubPortal.Domain/Base/EntityBase.cs b/src/AzureIoTHubPortal.Domain/Base/EntityBase.cs new file mode 100644 index 000000000..ebf8e146e --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/Base/EntityBase.cs @@ -0,0 +1,13 @@ +// 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.Base +{ + using System.ComponentModel.DataAnnotations; + + public abstract class EntityBase + { + [Key] + public string Id { get; set; } + } +} diff --git a/src/AzureIoTHubPortal.Domain/ConfigHandler.cs b/src/AzureIoTHubPortal.Domain/ConfigHandler.cs new file mode 100644 index 000000000..b4cc474bc --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/ConfigHandler.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.Domain +{ + public abstract class ConfigHandler + { + public abstract string IoTHubConnectionString { get; } + + public abstract string DPSConnectionString { get; } + + public abstract string DPSEndpoint { get; } + + public abstract string DPSScopeID { get; } + + public abstract string StorageAccountConnectionString { get; } + + public abstract int StorageAccountDeviceModelImageMaxAge { get; } + + public abstract bool UseSecurityHeaders { get; } + + public abstract string OIDCScope { get; } + + public abstract string OIDCApiClientId { get; } + + public abstract string OIDCClientId { get; } + + public abstract string OIDCMetadataUrl { get; } + + public abstract string OIDCAuthority { get; } + + public abstract bool OIDCValidateIssuer { get; } + + public abstract bool OIDCValidateAudience { get; } + + public abstract bool OIDCValidateLifetime { get; } + + public abstract bool OIDCValidateIssuerSigningKey { get; } + + public abstract bool OIDCValidateActor { get; } + + public abstract bool OIDCValidateTokenReplay { get; } + + public abstract bool IsLoRaEnabled { get; } + + public abstract string LoRaKeyManagementUrl { get; } + + public abstract string LoRaKeyManagementCode { get; } + + public abstract string LoRaKeyManagementApiVersion { get; } + + public abstract string PortalName { get; } + + public abstract int MetricExporterRefreshIntervalInSeconds { get; } + + public abstract int MetricLoaderRefreshIntervalInMinutes { get; } + + public abstract bool IdeasEnabled { get; } + + public abstract string IdeasUrl { get; } + + public abstract string IdeasAuthenticationHeader { get; } + + public abstract string IdeasAuthenticationToken { get; } + + public abstract string PostgreSQLConnectionString { get; } + } +} diff --git a/src/AzureIoTHub.Portal/Server/Entities/DeviceModelProperty.cs b/src/AzureIoTHubPortal.Domain/Entities/DeviceModelProperty.cs similarity index 75% rename from src/AzureIoTHub.Portal/Server/Entities/DeviceModelProperty.cs rename to src/AzureIoTHubPortal.Domain/Entities/DeviceModelProperty.cs index 8082ae9e1..b9de2ce28 100644 --- a/src/AzureIoTHub.Portal/Server/Entities/DeviceModelProperty.cs +++ b/src/AzureIoTHubPortal.Domain/Entities/DeviceModelProperty.cs @@ -1,8 +1,10 @@ // 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.Entities +namespace AzureIoTHub.Portal.Domain.Entities { + using System.ComponentModel.DataAnnotations; + using AzureIoTHub.Portal.Domain.Base; using AzureIoTHub.Portal.Models; public class DeviceModelProperty : EntityBase @@ -10,6 +12,7 @@ public class DeviceModelProperty : EntityBase /// /// The property name /// + [Required] public string Name { get; set; } /// @@ -22,16 +25,25 @@ public class DeviceModelProperty : EntityBase /// > Note: if writable, the property is set to the desired properties of the device twin /// > otherwise, the property is read from the reported properties. /// + [Required] public bool IsWritable { get; set; } /// /// The property display order. /// + [Required] public int Order { get; set; } /// /// The device property type /// + [Required] public DevicePropertyType PropertyType { get; set; } + + /// + /// The model identifier. + /// + [Required] + public string ModelId { get; set; } } } diff --git a/src/AzureIoTHub.Portal/Server/Exceptions/BaseException.cs b/src/AzureIoTHubPortal.Domain/Exceptions/BaseException.cs similarity index 91% rename from src/AzureIoTHub.Portal/Server/Exceptions/BaseException.cs rename to src/AzureIoTHubPortal.Domain/Exceptions/BaseException.cs index 306a0f95e..ffd811878 100644 --- a/src/AzureIoTHub.Portal/Server/Exceptions/BaseException.cs +++ b/src/AzureIoTHubPortal.Domain/Exceptions/BaseException.cs @@ -1,7 +1,7 @@ // 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.Exceptions +namespace AzureIoTHub.Portal.Domain.Exceptions { using System; diff --git a/src/AzureIoTHub.Portal/Server/Exceptions/InternalServerErrorException.cs b/src/AzureIoTHubPortal.Domain/Exceptions/InternalServerErrorException.cs similarity index 80% rename from src/AzureIoTHub.Portal/Server/Exceptions/InternalServerErrorException.cs rename to src/AzureIoTHubPortal.Domain/Exceptions/InternalServerErrorException.cs index 953f2ea35..01f5992e5 100644 --- a/src/AzureIoTHub.Portal/Server/Exceptions/InternalServerErrorException.cs +++ b/src/AzureIoTHubPortal.Domain/Exceptions/InternalServerErrorException.cs @@ -1,10 +1,10 @@ // 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.Exceptions +namespace AzureIoTHub.Portal.Domain.Exceptions { using System; - using AzureIoTHub.Portal.Server.Constants; + using AzureIoTHub.Portal.Domain.Shared.Constants; public class InternalServerErrorException : BaseException { diff --git a/src/AzureIoTHub.Portal/Server/Exceptions/ResourceAlreadyExistsException.cs b/src/AzureIoTHubPortal.Domain/Exceptions/ResourceAlreadyExistsException.cs similarity index 81% rename from src/AzureIoTHub.Portal/Server/Exceptions/ResourceAlreadyExistsException.cs rename to src/AzureIoTHubPortal.Domain/Exceptions/ResourceAlreadyExistsException.cs index 474eeefcf..5d0d583c0 100644 --- a/src/AzureIoTHub.Portal/Server/Exceptions/ResourceAlreadyExistsException.cs +++ b/src/AzureIoTHubPortal.Domain/Exceptions/ResourceAlreadyExistsException.cs @@ -1,10 +1,10 @@ // 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.Exceptions +namespace AzureIoTHub.Portal.Domain.Exceptions { using System; - using Constants; + using AzureIoTHub.Portal.Domain.Shared.Constants; public class ResourceAlreadyExistsException : BaseException { diff --git a/src/AzureIoTHub.Portal/Server/Exceptions/ResourceNotFoundException.cs b/src/AzureIoTHubPortal.Domain/Exceptions/ResourceNotFoundException.cs similarity index 80% rename from src/AzureIoTHub.Portal/Server/Exceptions/ResourceNotFoundException.cs rename to src/AzureIoTHubPortal.Domain/Exceptions/ResourceNotFoundException.cs index 52015c321..892a49ab8 100644 --- a/src/AzureIoTHub.Portal/Server/Exceptions/ResourceNotFoundException.cs +++ b/src/AzureIoTHubPortal.Domain/Exceptions/ResourceNotFoundException.cs @@ -1,10 +1,10 @@ // 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.Exceptions +namespace AzureIoTHub.Portal.Domain.Exceptions { using System; - using Constants; + using AzureIoTHub.Portal.Domain.Shared.Constants; public class ResourceNotFoundException : BaseException { diff --git a/src/AzureIoTHubPortal.Domain/IRepository.cs b/src/AzureIoTHubPortal.Domain/IRepository.cs new file mode 100644 index 000000000..6ebdd1032 --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/IRepository.cs @@ -0,0 +1,21 @@ +// 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 +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + public interface IRepository where T : class + { + IEnumerable GetAll(); + + Task GetByIdAsync(object id); + + Task InsertAsync(T obj); + + void Update(T obj); + + void Delete(object id); + } +} diff --git a/src/AzureIoTHubPortal.Domain/ITableClientFactory.cs b/src/AzureIoTHubPortal.Domain/ITableClientFactory.cs new file mode 100644 index 000000000..6d1706328 --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/ITableClientFactory.cs @@ -0,0 +1,24 @@ +// 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 +{ + using Azure.Data.Tables; + + public interface ITableClientFactory + { + TableClient GetDeviceCommands(); + + TableClient GetDeviceTemplates(); + + TableClient GetEdgeDeviceTemplates(); + + TableClient GetDeviceTemplateProperties(); + + TableClient GetDeviceTagSettings(); + + TableClient GetTemplatesHealthCheck(); + + TableClient GetEdgeModuleCommands(); + } +} diff --git a/src/AzureIoTHubPortal.Domain/IUnitOfWork.cs b/src/AzureIoTHubPortal.Domain/IUnitOfWork.cs new file mode 100644 index 000000000..2dd831d2a --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/IUnitOfWork.cs @@ -0,0 +1,12 @@ +// 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 +{ + using System.Threading.Tasks; + + public interface IUnitOfWork + { + Task SaveAsync(); + } +} diff --git a/src/AzureIoTHubPortal.Domain/Repositories/IDeviceModelPropertiesRepository.cs b/src/AzureIoTHubPortal.Domain/Repositories/IDeviceModelPropertiesRepository.cs new file mode 100644 index 000000000..eb18f4c6f --- /dev/null +++ b/src/AzureIoTHubPortal.Domain/Repositories/IDeviceModelPropertiesRepository.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.Domain.Repositories +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using AzureIoTHub.Portal.Domain.Entities; + + public interface IDeviceModelPropertiesRepository : IRepository + { + Task> GetModelProperties(string modelId); + Task SavePropertiesForModel(string modelId, IEnumerable items); + } +} diff --git a/src/AzureIoTHub.Portal/Server/Constants/ErrorTitles.cs b/src/AzureIoTHubPortal.Domain/Shared/Constants/ErrorTitles.cs similarity index 89% rename from src/AzureIoTHub.Portal/Server/Constants/ErrorTitles.cs rename to src/AzureIoTHubPortal.Domain/Shared/Constants/ErrorTitles.cs index d405108b3..15a35be17 100644 --- a/src/AzureIoTHub.Portal/Server/Constants/ErrorTitles.cs +++ b/src/AzureIoTHubPortal.Domain/Shared/Constants/ErrorTitles.cs @@ -1,7 +1,7 @@ // 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.Constants +namespace AzureIoTHub.Portal.Domain.Shared.Constants { public static class ErrorTitles { diff --git a/src/AzureIoTHub.Portal/Server/Constants/MetricName.cs b/src/AzureIoTHubPortal.Domain/Shared/Constants/MetricName.cs similarity index 94% rename from src/AzureIoTHub.Portal/Server/Constants/MetricName.cs rename to src/AzureIoTHubPortal.Domain/Shared/Constants/MetricName.cs index 00e535b58..bfaf54d3e 100644 --- a/src/AzureIoTHub.Portal/Server/Constants/MetricName.cs +++ b/src/AzureIoTHubPortal.Domain/Shared/Constants/MetricName.cs @@ -1,7 +1,7 @@ // 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.Constants +namespace AzureIoTHub.Portal.Domain.Shared.Constants { public static class MetricName { diff --git a/src/docker-compose.dcproj b/src/docker-compose.dcproj new file mode 100644 index 000000000..9cdcce1f7 --- /dev/null +++ b/src/docker-compose.dcproj @@ -0,0 +1,18 @@ + + + + 2.1 + Linux + b9d2de01-84de-461f-998c-20b57e4aa021 + LaunchBrowser + {Scheme}://localhost:{ServicePort}/{Scheme}://{ServiceHost}:{ServicePort} + azureiothub.portal.server + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml new file mode 100644 index 000000000..c92e398e0 --- /dev/null +++ b/src/docker-compose.override.yml @@ -0,0 +1,13 @@ +version: '3.4' + +services: + azureiothub.portal.server: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=https://+:443;http://+:80 + ports: + - "80" + - "8001:443" + volumes: + - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro + - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro diff --git a/src/docker-compose.yml b/src/docker-compose.yml new file mode 100644 index 000000000..12934beb6 --- /dev/null +++ b/src/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.4' + +services: + database: + image: postgres + restart: always + environment: + POSTGRES_PASSWORD: postgrePassword + ports: + - 5432:5432 + azureiothub.portal.server: + image: ${DOCKER_REGISTRY-}azureiothubportalserver + build: + context: . + dockerfile: AzureIoTHub.Portal/Server/Dockerfile + depends_on: + - "database" diff --git a/src/launchSettings.json b/src/launchSettings.json new file mode 100644 index 000000000..be7d71c2f --- /dev/null +++ b/src/launchSettings.json @@ -0,0 +1,15 @@ +{ + "profiles": { + "Docker Compose": { + "commandName": "DockerCompose", + "commandVersion": "1.0", + "composeLaunchAction": "LaunchBrowser", + "composeLaunchServiceName": "azureiothub.portal.server", + "composeLaunchUrl": "https://localhost:8001", + "serviceActions": { + "azureiothub.portal.server": "StartDebugging", + "database": "StartWithoutDebugging" + } + } + } +} diff --git a/templates/azuredeploy.json b/templates/azuredeploy.json index 311272b8d..c312c27fb 100644 --- a/templates/azuredeploy.json +++ b/templates/azuredeploy.json @@ -8,6 +8,18 @@ "description": "Prefix used for resource names. Should be unique as this will also be used for domain names." } }, + "pgsqlAdminLogin": { + "type": "string", + "metadata": { + "description": "PostgreSQL user" + } + }, + "pgsqlAdminPassword": { + "type": "securestring", + "metadata": { + "description": "PostgreSQL password" + } + }, "openIdAuthority": { "type": "string", "metadata": { @@ -185,6 +197,12 @@ "uniqueSolutionPrefix": { "value": "[parameters('uniqueSolutionPrefix')]" }, + "pgsqlAdminLogin": { + "value": "[parameters('pgsqlAdminLogin')]" + }, + "pgsqlAdminPassword": { + "value": "[parameters('pgsqlAdminPassword')]" + }, "openIdAuthority": { "value": "[parameters('openIdAuthority')]" }, @@ -230,6 +248,12 @@ "uniqueSolutionPrefix": { "value": "[parameters('uniqueSolutionPrefix')]" }, + "pgsqlAdminLogin": { + "value": "[parameters('pgsqlAdminLogin')]" + }, + "pgsqlAdminPassword": { + "value": "[parameters('pgsqlAdminPassword')]" + }, "openIdAuthority": { "value": "[parameters('openIdAuthority')]" }, diff --git a/templates/azuredeployUI.json b/templates/azuredeployUI.json index d5415846d..c3fc6b57b 100644 --- a/templates/azuredeployUI.json +++ b/templates/azuredeployUI.json @@ -40,6 +40,37 @@ }, "visible": true }, + { + "name": "databaseSection", + "type": "Microsoft.Common.Section", + "label": "Database", + "elements": [ + { + "name": "pgsqlAdminLogin", + "type": "Microsoft.Common.TextBox", + "label": "PostgreSQL user", + "toolTip": "PostgreSQL user", + "constraints": { + "required": true + } + }, + { + "name": "pgsqlAdminPassword", + "type": "Microsoft.Common.PasswordBox", + "label": { + "password": "PostgreSQL password", + "confirmPassword": "Confirm PostgreSQL password" + }, + "toolTip": "PostgreSQL password", + "constraints": { + "required": true, + "regex": "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{12,}$", + "validationMessage": "The password must be alphanumeric, contain at least 12 characters, and have at least 1 letter and 1 number" + } + } + ], + "visible": true + }, { "name": "identityProviderSection", "type": "Microsoft.Common.Section", @@ -303,6 +334,10 @@ "outputs": { "parameters": { "uniqueSolutionPrefix": "[steps('commonStep').uniqueSolutionPrefix]", + + "pgsqlAdminLogin": "[steps('commonStep').databaseSection.pgsqlAdminLogin]", + "pgsqlAdminPassword": "[steps('commonStep').databaseSection.pgsqlAdminPassword]", + "openIdAuthority": "[steps('commonStep').identityProviderSection.openIdAuthority]", "openIdMetadataURL": "[steps('commonStep').identityProviderSection.openIdMetadataURL]", "clientId": "[steps('commonStep').identityProviderSection.clientId]", diff --git a/templates/portalDeployWithLoRa.json b/templates/portalDeployWithLoRa.json index ed91ec2c0..45cf5a8e9 100644 --- a/templates/portalDeployWithLoRa.json +++ b/templates/portalDeployWithLoRa.json @@ -14,6 +14,20 @@ "description": "Prefix used for resource names. Should be unique as this will also be used for domain names." } }, + "pgsqlAdminLogin": { + "type": "string", + "defaultValue": "[concat(uniqueString(resourceGroup().id, newGuid()))]", + "metadata": { + "description": "PostgreSQL user" + } + }, + "pgsqlAdminPassword": { + "type": "securestring", + "defaultValue": "[concat(uniqueString(resourceGroup().id, newGuid()), 'x', '!')]", + "metadata": { + "description": "PostgreSQL password" + } + }, "openIdAuthority": { "type": "string", "metadata": { @@ -68,6 +82,7 @@ } }, "variables": { + "pgsqlServerName": "[concat(parameters('uniqueSolutionPrefix'), 'pgsql')]", "iotHubName": "[concat(parameters('uniqueSolutionPrefix'), 'hub')]", "dpsName": "[concat(parameters('uniqueSolutionPrefix'), 'dps')]", "siteName": "[concat(parameters('uniqueSolutionPrefix'), 'portal')]", @@ -109,6 +124,30 @@ "allocationPolicy": "Hashed" } }, + { + "type": "Microsoft.DBforPostgreSQL/servers", + "apiVersion": "2017-12-01", + "name": "[variables('pgsqlServerName')]", + "location": "[parameters('location')]", + "sku": { + "name": "B_Gen5_2", + "tier": "Basic", + "capacity": 2, + "size": "5120", + "family": "Gen5" + }, + "properties": { + "createMode": "Default", + "version": "11", + "administratorLogin": "[parameters('pgsqlAdminLogin')]", + "administratorLoginPassword": "[parameters('pgsqlAdminPassword')]", + "storageProfile": { + "storageMB": 5120, + "backupRetentionDays": 7, + "geoRedundantBackup": "Disabled" + } + } + }, { "type": "Microsoft.Web/serverfarms", "apiVersion": "2021-02-01", @@ -184,6 +223,11 @@ "name": "LoRaKeyManagement__Code", "type": "Custom", "connectionString": "[listkeys(variables('functionAppDefaultHost'),'2021-02-01').masterKey]" + }, + { + "name": "PostgreSQL__ConnectionString", + "type": "Custom", + "connectionString": "[concat('User ID=', parameters('pgsqlAdminLogin'), ';Password=', parameters('pgsqlAdminPassword'), ';Host=', concat(variables('pgsqlServerName'), '.postgres.database.azure.com') ,';Port=5432;Database=', variables('siteName') ,';Pooling=true;Min Pool Size=0;Max Pool Size=100;Connection Lifetime=0;" } ], "appSettings": [ diff --git a/templates/portalDeployWithoutLoRa.json b/templates/portalDeployWithoutLoRa.json index 405f00d5d..9f991e71f 100644 --- a/templates/portalDeployWithoutLoRa.json +++ b/templates/portalDeployWithoutLoRa.json @@ -14,6 +14,20 @@ "description": "Prefix used for resource names. Should be unique as this will also be used for domain names." } }, + "pgsqlAdminLogin": { + "type": "string", + "defaultValue": "[concat(uniqueString(resourceGroup().id, newGuid()))]", + "metadata": { + "description": "PostgreSQL user" + } + }, + "pgsqlAdminPassword": { + "type": "securestring", + "defaultValue": "[concat(uniqueString(resourceGroup().id, newGuid()), 'x', '!')]", + "metadata": { + "description": "PostgreSQL password" + } + }, "openIdAuthority": { "type": "string", "metadata": { @@ -68,6 +82,7 @@ } }, "variables": { + "pgsqlServerName": "[concat(parameters('uniqueSolutionPrefix'), 'pgsql')]", "iotHubName": "[concat(parameters('uniqueSolutionPrefix'), 'hub')]", "dpsName": "[concat(parameters('uniqueSolutionPrefix'), 'dps')]", "siteName": "[concat(parameters('uniqueSolutionPrefix'), 'portal')]", @@ -83,17 +98,17 @@ }, "resources": [ { - "type": "Microsoft.Devices/IotHubs", - "apiVersion": "2021-07-02", - "sku": { - "name": "S1", - "tier": "Standard", - "capacity": 1 - }, - "name": "[variables('iotHubName')]", - "location": "[parameters('location')]", - "properties": {}, - "dependsOn": [] + "type": "Microsoft.Devices/IotHubs", + "apiVersion": "2021-07-02", + "sku": { + "name": "S1", + "tier": "Standard", + "capacity": 1 + }, + "name": "[variables('iotHubName')]", + "location": "[parameters('location')]", + "properties": {}, + "dependsOn": [] }, { "type": "Microsoft.Devices/provisioningServices", @@ -120,38 +135,62 @@ ] }, { - "type": "Microsoft.Storage/storageAccounts", - "name": "[variables('storageAccountName')]", - "apiVersion": "2021-09-01", - "location": "[parameters('location')]", - "sku": { - "name": "Standard_LRS", - "tier": "Standard" - }, - "properties": { - "accountType": "[variables('storageAccountType')]" - }, - "dependsOn": [] + "type": "Microsoft.Storage/storageAccounts", + "name": "[variables('storageAccountName')]", + "apiVersion": "2021-09-01", + "location": "[parameters('location')]", + "sku": { + "name": "Standard_LRS", + "tier": "Standard" + }, + "properties": { + "accountType": "[variables('storageAccountType')]" + }, + "dependsOn": [] }, { "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2021-09-01", "name": "[format('{0}/default/{1}', variables('storageAccountName'), variables('deviceImageContainerName'))]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" - ] + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" + ] }, { - "type": "Microsoft.Insights/components", - "kind": "web", - "name": "[variables('appInsightName')]", - "apiVersion": "2020-02-02", - "location": "[parameters('location')]", - "scale": null, - "properties": { - "Application_Type": "web" - }, - "dependsOn": [] + "type": "Microsoft.DBforPostgreSQL/servers", + "apiVersion": "2017-12-01", + "name": "[variables('pgsqlServerName')]", + "location": "[parameters('location')]", + "sku": { + "name": "B_Gen5_2", + "tier": "Basic", + "capacity": 2, + "size": "5120", + "family": "Gen5" + }, + "properties": { + "createMode": "Default", + "version": "11", + "administratorLogin": "[parameters('pgsqlAdminLogin')]", + "administratorLoginPassword": "[parameters('pgsqlAdminPassword')]", + "storageProfile": { + "storageMB": 5120, + "backupRetentionDays": 7, + "geoRedundantBackup": "Disabled" + } + } + }, + { + "type": "Microsoft.Insights/components", + "kind": "web", + "name": "[variables('appInsightName')]", + "apiVersion": "2020-02-02", + "location": "[parameters('location')]", + "scale": null, + "properties": { + "Application_Type": "web" + }, + "dependsOn": [] }, { "type": "Microsoft.Web/serverfarms", @@ -225,6 +264,11 @@ "name": "StorageAccount__ConnectionString", "type": "Custom", "connectionString": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountId'),'2015-05-01-preview').key1)]" + }, + { + "name": "PostgreSQL__ConnectionString", + "type": "Custom", + "connectionString": "[concat('User ID=', parameters('pgsqlAdminLogin'), ';Password=', parameters('pgsqlAdminPassword'), ';Host=', concat(variables('pgsqlServerName'), '.postgres.database.azure.com') ,';Port=5432;Database=', variables('siteName') ,';Pooling=true;Min Pool Size=0;Max Pool Size=100;Connection Lifetime=0;" } ], "appSettings": [