using Akka.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Interfaces;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.Configuration;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests;
///
/// Verifies WithOtOpcUaRuntimeActors spawns DriverHostActor + DbHealthProbeActor
/// on the host's ActorSystem and registers both under their marker keys. This is the
/// driver-role mirror of the admin-role WithOtOpcUaControlPlaneSingletons bootstrap.
///
public sealed class ServiceCollectionExtensionsTests
{
[Fact]
public async Task WithOtOpcUaRuntimeActors_spawns_driver_host_and_db_health_probe()
{
using var host = Host.CreateDefaultBuilder()
.ConfigureServices((_, services) =>
{
services.AddSingleton>(
new InMemoryConfigDbFactory(Guid.NewGuid().ToString("N")));
services.AddSingleton(new FakeClusterRoleInfo());
services.AddAkka("otopcua-test", (ab, _) =>
{
ab.AddHocon(@"
akka.actor.provider = ""Akka.Cluster.ClusterActorRefProvider, Akka.Cluster""
akka.remote.dot-netty.tcp.hostname = ""127.0.0.1""
akka.remote.dot-netty.tcp.port = 0
akka.cluster.seed-nodes = []
akka.cluster.roles = [""driver""]
", HoconAddMode.Prepend);
ab.WithOtOpcUaRuntimeActors();
});
})
.Build();
await host.StartAsync();
try
{
var driverHost = host.Services.GetRequiredService>();
var dbHealth = host.Services.GetRequiredService>();
var historian = host.Services.GetRequiredService>();
var mux = host.Services.GetRequiredService>();
var publish = host.Services.GetRequiredService>();
driverHost.ActorRef.ShouldNotBeNull();
dbHealth.ActorRef.ShouldNotBeNull();
historian.ActorRef.ShouldNotBeNull();
mux.ActorRef.ShouldNotBeNull();
publish.ActorRef.ShouldNotBeNull();
driverHost.ActorRef.Path.Name.ShouldBe(ServiceCollectionExtensions.DriverHostActorName);
dbHealth.ActorRef.Path.Name.ShouldBe(ServiceCollectionExtensions.DbHealthProbeActorName);
historian.ActorRef.Path.Name.ShouldBe(ServiceCollectionExtensions.HistorianAdapterActorName);
mux.ActorRef.Path.Name.ShouldBe(ServiceCollectionExtensions.DependencyMuxActorName);
publish.ActorRef.Path.Name.ShouldBe(ServiceCollectionExtensions.OpcUaPublishActorName);
}
finally
{
await host.StopAsync();
}
}
private sealed class InMemoryConfigDbFactory(string dbName) : IDbContextFactory
{
public OtOpcUaConfigDbContext CreateDbContext()
{
var opts = new DbContextOptionsBuilder()
.UseInMemoryDatabase(dbName)
.Options;
return new OtOpcUaConfigDbContext(opts);
}
}
private sealed class FakeClusterRoleInfo : IClusterRoleInfo
{
public NodeId LocalNode { get; } = NodeId.Parse("test-node");
public IReadOnlySet LocalRoles { get; } = new HashSet(["driver"]);
public bool HasRole(string role) => LocalRoles.Contains(role);
public IReadOnlyList MembersWithRole(string role) => Array.Empty();
public NodeId? RoleLeader(string role) => null;
public event EventHandler? RoleLeaderChanged
{
add { _ = value; }
remove { _ = value; }
}
}
}