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:
Joseph Doherty
2026-09-02 22:46:11 -04:00
parent 08405c1ca1
commit afe59a5518
4 changed files with 112 additions and 4 deletions
+5 -4
View File
@@ -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);
}
}