015c556ced
AddZbSecretsSqlServerStore / AddZbSecretsSqlServerReplication (and the Akka extension, same defect class) TryAdd-ed their ISecretStore, ISecretsStoreMigrator and ISecretReplicator ahead of their internal AddZbSecrets call. An application that had already called AddZbSecrets therefore silently kept the SQLite store (shared-store mode) or NoOpSecretReplicator (hub/Akka modes). They now call AddZbSecrets first and RemoveAll + AddSingleton their own registrations; a later AddZbSecrets only TryAdds, so either order yields the same graph with exactly one ISecretStore registration. - tests: RegistrationOrderTests (SqlServer + Akka) build and resolve the real container in both orders; core-first fails without the fix - Directory.Build.props 0.6.5 -> 0.6.6 (NOT packed or published) - code-reviews: Secrets-001 Resolved; README regenerated Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
87 lines
3.5 KiB
C#
87 lines
3.5 KiB
C#
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using ZB.MOM.WW.Secrets.Abstractions;
|
|
using ZB.MOM.WW.Secrets.DependencyInjection;
|
|
using ZB.MOM.WW.Secrets.Replication;
|
|
using ZB.MOM.WW.Secrets.Replicator.SqlServer.DependencyInjection;
|
|
using ZB.MOM.WW.Secrets.Sqlite;
|
|
|
|
namespace ZB.MOM.WW.Secrets.Replicator.SqlServer.Tests.DependencyInjection;
|
|
|
|
/// <summary>
|
|
/// scadaproj#30 (Secrets-001): the SQL-Server extensions must produce the same graph whether the
|
|
/// application calls <c>AddZbSecrets</c> before or after them.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Before the fix both modes put their registrations in with <c>TryAdd</c> ahead of their own
|
|
/// internal <c>AddZbSecrets</c> call, so an application that had ALREADY called <c>AddZbSecrets</c>
|
|
/// — the order the README teaches, and the only way to pass <c>contentRootPath</c> — silently kept
|
|
/// the SQLite store (shared-store mode) or the no-op replicator (hub mode). Every test here builds the
|
|
/// real container and resolves, in both orders.
|
|
/// </remarks>
|
|
public sealed class RegistrationOrderTests
|
|
{
|
|
public enum Order
|
|
{
|
|
CoreFirst,
|
|
ExtensionFirst,
|
|
}
|
|
|
|
private static IConfiguration Config() =>
|
|
new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["Secrets:SqlitePath"] = Path.Combine(Path.GetTempPath(), $"zb-order-{Guid.NewGuid():N}.db"),
|
|
["Secrets:RunMigrationsOnStartup"] = "false",
|
|
["Secrets:MasterKey:Source"] = "Environment",
|
|
["Secrets:MasterKey:EnvVarName"] = "ZB_SECRETS_TEST_KEY",
|
|
["Secrets:SqlServer:ConnectionString"] = "Server=unused;Database=x;",
|
|
}).Build();
|
|
|
|
private static ServiceProvider Build(Order order, Action<IServiceCollection, IConfiguration> extension)
|
|
{
|
|
IConfiguration config = Config();
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
|
|
if (order == Order.CoreFirst)
|
|
{
|
|
services.AddZbSecrets(config, "Secrets");
|
|
extension(services, config);
|
|
}
|
|
else
|
|
{
|
|
extension(services, config);
|
|
services.AddZbSecrets(config, "Secrets");
|
|
}
|
|
|
|
return services.BuildServiceProvider();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(Order.CoreFirst)]
|
|
[InlineData(Order.ExtensionFirst)]
|
|
public void SharedStore_mode_wins_in_either_order(Order order)
|
|
{
|
|
using ServiceProvider provider = Build(order, (s, c) => s.AddZbSecretsSqlServerStore(c));
|
|
|
|
Assert.IsType<SqlServerSecretStore>(provider.GetRequiredService<ISecretStore>());
|
|
Assert.IsType<SqlServerSecretsStoreMigrator>(provider.GetRequiredService<ISecretsStoreMigrator>());
|
|
|
|
// Exactly one store registration is visible, so nothing enumerating the seam can pick up the
|
|
// displaced SQLite store either.
|
|
Assert.IsType<SqlServerSecretStore>(Assert.Single(provider.GetServices<ISecretStore>()));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(Order.CoreFirst)]
|
|
[InlineData(Order.ExtensionFirst)]
|
|
public void HubReplication_mode_publishes_through_the_hub_in_either_order(Order order)
|
|
{
|
|
using ServiceProvider provider = Build(order, (s, c) => s.AddZbSecretsSqlServerReplication(c));
|
|
|
|
Assert.IsType<SqlServerSecretReplicator>(provider.GetRequiredService<ISecretReplicator>());
|
|
Assert.IsType<ReplicatingSecretStore>(Assert.Single(provider.GetServices<ISecretStore>()));
|
|
Assert.IsType<SqliteSecretsStoreMigrator>(provider.GetRequiredService<ISecretsStoreMigrator>());
|
|
}
|
|
}
|