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>(); driverHost.ActorRef.ShouldNotBeNull(); dbHealth.ActorRef.ShouldNotBeNull(); historian.ActorRef.ShouldNotBeNull(); driverHost.ActorRef.Path.Name.ShouldBe(ServiceCollectionExtensions.DriverHostActorName); dbHealth.ActorRef.Path.Name.ShouldBe(ServiceCollectionExtensions.DbHealthProbeActorName); historian.ActorRef.Path.Name.ShouldBe(ServiceCollectionExtensions.HistorianAdapterActorName); } 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; } } } }