using Akka.Actor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ZB.MOM.WW.Secrets.Abstractions;
using ZB.MOM.WW.Secrets.DependencyInjection;
using ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.DependencyInjection;
namespace ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.Tests.DependencyInjection;
///
/// scadaproj#33 (Secrets-010): the replication actor must exist once the host has started, without
/// anything resolving . Before the fix the actor was created only through
/// the store → replicator chain, so a node that expanded its references pre-host and never touched
/// the store at runtime had no actor and silently missed every rotation.
///
///
/// Nothing in these tests may resolve , or
/// before the assertion — that would create the actor the old way and
/// make the test vacuous. could not catch the defect for
/// exactly that reason.
///
public sealed class EagerActorStartTests(SingleNodeClusterFixture cluster)
: IClassFixture
{
public enum Order
{
CoreFirst,
ExtensionFirst,
}
private static Dictionary Settings(string actorName) => new()
{
["Secrets:SqlitePath"] = Path.Combine(Path.GetTempPath(), $"zb-eager-{Guid.NewGuid():N}.db"),
["Secrets:RunMigrationsOnStartup"] = "true",
["Secrets:Replication:AnnounceInterval"] = "00:00:30",
["Secrets:Replication:ActorName"] = actorName,
};
private async Task ActorExistsAsync(string actorName)
{
try
{
await cluster.System.ActorSelection($"/user/{actorName}").ResolveOne(TimeSpan.FromSeconds(3));
return true;
}
catch (ActorNotFoundException)
{
return false;
}
}
[Theory]
[InlineData(Order.CoreFirst)]
[InlineData(Order.ExtensionFirst)]
public async Task A_started_generic_host_creates_the_actor_without_resolving_the_store(Order order)
{
string actorName = $"zb-eager-host-{order}";
HostApplicationBuilder builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings
{
Args = [],
DisableDefaults = true,
});
builder.Configuration.AddInMemoryCollection(Settings(actorName));
builder.Services.AddSingleton(cluster.System);
if (order == Order.CoreFirst)
{
builder.Services.AddZbSecrets(builder.Configuration, "Secrets");
builder.Services.AddZbSecretsAkkaReplication(builder.Configuration);
}
else
{
builder.Services.AddZbSecretsAkkaReplication(builder.Configuration);
builder.Services.AddZbSecrets(builder.Configuration, "Secrets");
}
using IHost host = builder.Build();
Assert.False(await ActorExistsAsync(actorName), "positive control: no actor before the host starts");
await host.StartAsync();
try
{
Assert.True(await ActorExistsAsync(actorName), "the replication actor was not created at startup");
}
finally
{
await host.StopAsync();
}
}
[Fact]
public async Task Without_a_host_lifetime_the_starter_creates_the_actor_in_StartAsync()
{
string actorName = "zb-eager-bare";
IConfigurationRoot config = new ConfigurationBuilder().AddInMemoryCollection(Settings(actorName)).Build();
var services = new ServiceCollection();
services.AddLogging();
services.AddSingleton(cluster.System);
services.AddZbSecretsAkkaReplication(config);
await using ServiceProvider provider = services.BuildServiceProvider();
foreach (IHostedService hosted in provider.GetServices())
{
await hosted.StartAsync(CancellationToken.None);
}
Assert.True(await ActorExistsAsync(actorName), "the replication actor was not created at startup");
}
}