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