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.AkkaDotNet.DependencyInjection; using ZB.MOM.WW.Secrets.Sqlite; namespace ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.Tests.DependencyInjection; /// /// Registration tests for AddZbSecretsAkkaReplication. /// /// These exist because 0.2.0 shipped with Akka replication silently inert and every other test in /// this assembly still passed. The bug was pure registration ORDER: the extension called /// AddZbSecrets first, which does /// TryAddSingleton<ISecretReplicator, NoOpSecretReplicator>(), so the package's own /// TryAddSingleton<ISecretReplicator> found a descriptor already present and was /// silently discarded. Writes then published into a no-op sink and no replication actor was ever /// spawned — with no exception and no log line anywhere. /// /// /// The equivalent SQL-Server package always had a test asserting its replicator type, and always /// had the correct order. That asymmetry is exactly why the defect landed in one package and not /// the other, so the gap is closed here. /// /// /// These assert against the because the 0.2.0 defect was entirely /// about which descriptor wins, and the descriptor is where it is visible. Resolution through a /// BUILT provider — where the scadaproj#1 singleton-cycle deadlock lived — is covered by /// , and end-to-end actor behaviour by /// TwoNodeClusterReplicationTests. (An earlier revision of this comment claimed a /// provider-based test was impossible because DistributedPubSub.Get needs a joined /// cluster; the hang it was avoiding was actually that DI deadlock, and a single self-joined /// node is all the cluster the actor needs.) /// /// public sealed class AddZbSecretsAkkaReplicationTests { private static IServiceCollection Register() { IConfigurationRoot config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Secrets:SqlitePath"] = Path.Combine(Path.GetTempPath(), $"zb-akkadi-{Guid.NewGuid():N}.db"), ["Secrets:Replication:AnnounceInterval"] = "00:00:30", }) .Build(); var services = new ServiceCollection(); services.AddLogging(); services.AddZbSecretsAkkaReplication(config, "Secrets", "Secrets:Replication"); return services; } private static ServiceDescriptor Descriptor(IServiceCollection services) => services.Single(d => d.ServiceType == typeof(T)); [Fact] public void ISecretReplicator_is_not_bound_to_the_no_op_sink() { // THE REGRESSION TEST. Before the fix this descriptor was NoOpSecretReplicator, so every // replicated write went nowhere. A no-op secret replicator is the worst failure mode // available: the cluster reports healthy and silently never converges. ServiceDescriptor descriptor = Descriptor(Register()); Assert.NotEqual(typeof(NoOpSecretReplicator), descriptor.ImplementationType); } [Fact] public void ISecretReplicator_is_registered_by_this_package_via_a_factory() { // The package registers through a factory (it needs the lazily-created actor ref), whereas // the core no-op is registered by concrete type. A factory registration is therefore proof // that this package's descriptor won the TryAdd race rather than core's. ServiceDescriptor descriptor = Descriptor(Register()); Assert.NotNull(descriptor.ImplementationFactory); Assert.Null(descriptor.ImplementationType); } [Fact] public void ISecretStore_is_decorated_so_writes_actually_publish() { // AddSingleton (not TryAdd) means the decorator is appended, and the LAST registration for // a service type is what the container resolves. If this regressed to the bare SQLite // store, writes would never publish. ServiceDescriptor last = Register() .Last(d => d.ServiceType == typeof(ISecretStore)); Assert.NotNull(last.ImplementationFactory); } [Fact] public void The_undecorated_concrete_store_is_registered() { // ReplicatingSecretStore resolves SqliteSecretStore by concrete type. Core registering it // only behind ISecretStore was the OTHER defect this library shipped, so pin it here too. IServiceCollection services = Register(); Assert.Contains(services, d => d.ServiceType == typeof(SqliteSecretStore)); } [Fact] public void The_store_migrator_seam_is_registered() { IServiceCollection services = Register(); Assert.Contains(services, d => d.ServiceType == typeof(ISecretsStoreMigrator)); } }