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.Contracts" Version="0.2.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.Ldap" Version="0.2.1" />
|
||||
<PackageVersion Include="ZB.MOM.WW.Auth.ApiKeys" Version="0.2.1" />
|
||||
<PackageVersion Include="ZB.MOM.WW.Auth.AspNetCore" Version="0.2.1" />
|
||||
<PackageVersion Include="ZB.MOM.WW.Auth.Abstractions" Version="0.3.0" />
|
||||
<PackageVersion Include="ZB.MOM.WW.Auth.Ldap" Version="0.3.0" />
|
||||
<PackageVersion Include="ZB.MOM.WW.Auth.ApiKeys" Version="0.3.0" />
|
||||
<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.Theme" Version="0.4.1" />
|
||||
<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.Akka" />
|
||||
<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.Serilog" />
|
||||
<!-- 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