chore(host): pin ZB.MOM.WW.Auth 0.3.0 + ApiKeys.SqlServer; InboundApiKeyStoreOptions (Provider Sqlite|SqlServer, ReadProvider fails the boot on an unknown value)
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
This commit is contained in:
@@ -98,10 +98,11 @@
|
|||||||
<PackageVersion Include="ZB.MOM.WW.MxGateway.Client" Version="0.2.0" />
|
<PackageVersion Include="ZB.MOM.WW.MxGateway.Client" Version="0.2.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.MxGateway.Contracts" Version="0.2.0" />
|
<PackageVersion Include="ZB.MOM.WW.MxGateway.Contracts" Version="0.2.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Configuration" Version="0.1.0" />
|
<PackageVersion Include="ZB.MOM.WW.Configuration" Version="0.1.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Auth.Abstractions" Version="0.2.1" />
|
<PackageVersion Include="ZB.MOM.WW.Auth.Abstractions" Version="0.3.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Auth.Ldap" Version="0.2.1" />
|
<PackageVersion Include="ZB.MOM.WW.Auth.Ldap" Version="0.3.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Auth.ApiKeys" Version="0.2.1" />
|
<PackageVersion Include="ZB.MOM.WW.Auth.ApiKeys" Version="0.3.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Auth.AspNetCore" Version="0.2.1" />
|
<PackageVersion Include="ZB.MOM.WW.Auth.ApiKeys.SqlServer" Version="0.3.0" />
|
||||||
|
<PackageVersion Include="ZB.MOM.WW.Auth.AspNetCore" Version="0.3.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Audit" Version="0.1.0" />
|
<PackageVersion Include="ZB.MOM.WW.Audit" Version="0.1.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Theme" Version="0.4.1" />
|
<PackageVersion Include="ZB.MOM.WW.Theme" Version="0.4.1" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Secrets" Version="0.6.2" />
|
<PackageVersion Include="ZB.MOM.WW.Secrets" Version="0.6.2" />
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.ScadaBridge.Host;
|
||||||
|
|
||||||
|
/// <summary>Which backend holds inbound API keys. Bound from <c>ScadaBridge:InboundApi:ApiKeyStore</c> alongside the library's own <c>ApiKeyOptions</c>.</summary>
|
||||||
|
public enum InboundApiKeyStoreProvider { Sqlite, SqlServer }
|
||||||
|
|
||||||
|
/// <summary>Host-side selector for the inbound API key store. <c>Sqlite</c> is per-node (single-node installs); <c>SqlServer</c> shares one row set across a central pair (design 2026-09-02).</summary>
|
||||||
|
public sealed class InboundApiKeyStoreOptions
|
||||||
|
{
|
||||||
|
public const string SectionPath = "ScadaBridge:InboundApi:ApiKeyStore";
|
||||||
|
|
||||||
|
public InboundApiKeyStoreProvider Provider { get; set; } = InboundApiKeyStoreProvider.Sqlite;
|
||||||
|
|
||||||
|
/// <summary>SQLite file to import once when <see cref="Provider"/> is SqlServer; defaults to the library's <c>SqlitePath</c>. Renamed <c>*.imported</c> afterwards.</summary>
|
||||||
|
public string? ImportFromSqlitePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Reads <c>Provider</c> from configuration (case-insensitive). Throws <see cref="InvalidOperationException"/> naming the legal values on an unknown one, so a typo fails the boot with a readable message instead of silently falling back to SQLite.</summary>
|
||||||
|
public static InboundApiKeyStoreProvider ReadProvider(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
var value = configuration[$"{SectionPath}:Provider"];
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return InboundApiKeyStoreProvider.Sqlite;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Enum.TryParse<InboundApiKeyStoreProvider>(value, ignoreCase: true, out var parsed)
|
||||||
|
&& Enum.IsDefined(parsed))
|
||||||
|
{
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"{SectionPath}:Provider '{value}' is not recognised; use 'Sqlite' or 'SqlServer'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
<PackageReference Include="ZB.MOM.WW.Health" />
|
<PackageReference Include="ZB.MOM.WW.Health" />
|
||||||
<PackageReference Include="ZB.MOM.WW.Health.Akka" />
|
<PackageReference Include="ZB.MOM.WW.Health.Akka" />
|
||||||
<PackageReference Include="ZB.MOM.WW.Health.EntityFrameworkCore" />
|
<PackageReference Include="ZB.MOM.WW.Health.EntityFrameworkCore" />
|
||||||
|
<PackageReference Include="ZB.MOM.WW.Auth.ApiKeys.SqlServer" />
|
||||||
<PackageReference Include="ZB.MOM.WW.Telemetry" />
|
<PackageReference Include="ZB.MOM.WW.Telemetry" />
|
||||||
<PackageReference Include="ZB.MOM.WW.Telemetry.Serilog" />
|
<PackageReference Include="ZB.MOM.WW.Telemetry.Serilog" />
|
||||||
<!-- Host/App.razor uses the kit's <ThemeHead/>/<ThemeScripts/> directly; declare the
|
<!-- Host/App.razor uses the kit's <ThemeHead/>/<ThemeScripts/> directly; declare the
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.ScadaBridge.Host.Tests;
|
||||||
|
|
||||||
|
public class InboundApiKeyStoreOptionsTests
|
||||||
|
{
|
||||||
|
private static IConfiguration BuildConfig(Dictionary<string, string?> values)
|
||||||
|
{
|
||||||
|
return new ConfigurationBuilder()
|
||||||
|
.AddInMemoryCollection(values)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Provider_DefaultsToSqlite()
|
||||||
|
{
|
||||||
|
Assert.Equal(InboundApiKeyStoreProvider.Sqlite, new InboundApiKeyStoreOptions().Provider);
|
||||||
|
|
||||||
|
var configuration = new ConfigurationBuilder().Build();
|
||||||
|
|
||||||
|
Assert.Equal(InboundApiKeyStoreProvider.Sqlite, InboundApiKeyStoreOptions.ReadProvider(configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("sqlserver", InboundApiKeyStoreProvider.SqlServer)]
|
||||||
|
[InlineData("SQLSERVER", InboundApiKeyStoreProvider.SqlServer)]
|
||||||
|
[InlineData("SqlServer", InboundApiKeyStoreProvider.SqlServer)]
|
||||||
|
[InlineData("sqlite", InboundApiKeyStoreProvider.Sqlite)]
|
||||||
|
public void Provider_ParsesCaseInsensitively(string value, InboundApiKeyStoreProvider expected)
|
||||||
|
{
|
||||||
|
var configuration = BuildConfig(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
[$"{InboundApiKeyStoreOptions.SectionPath}:Provider"] = value,
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.Equal(expected, InboundApiKeyStoreOptions.ReadProvider(configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Postgres")]
|
||||||
|
[InlineData("7")]
|
||||||
|
public void Provider_RejectsUnknownValue(string value)
|
||||||
|
{
|
||||||
|
var configuration = BuildConfig(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
[$"{InboundApiKeyStoreOptions.SectionPath}:Provider"] = value,
|
||||||
|
});
|
||||||
|
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => InboundApiKeyStoreOptions.ReadProvider(configuration));
|
||||||
|
|
||||||
|
Assert.Contains("Sqlite", ex.Message);
|
||||||
|
Assert.Contains("SqlServer", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Binds_ImportFromSqlitePath()
|
||||||
|
{
|
||||||
|
var configuration = BuildConfig(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
[$"{InboundApiKeyStoreOptions.SectionPath}:Provider"] = "SqlServer",
|
||||||
|
[$"{InboundApiKeyStoreOptions.SectionPath}:ImportFromSqlitePath"] = "/app/data/inbound-api-keys.db",
|
||||||
|
});
|
||||||
|
|
||||||
|
var options = configuration.GetSection(InboundApiKeyStoreOptions.SectionPath).Get<InboundApiKeyStoreOptions>();
|
||||||
|
|
||||||
|
Assert.NotNull(options);
|
||||||
|
Assert.Equal(InboundApiKeyStoreProvider.SqlServer, options!.Provider);
|
||||||
|
Assert.Equal("/app/data/inbound-api-keys.db", options.ImportFromSqlitePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user