using Akka.Actor; using Microsoft.EntityFrameworkCore; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin; using ZB.MOM.WW.OtOpcUa.Commons.Types; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations; using ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests; /// /// R03:4.1 — admin operations echoed ex.Message to the UI, so a SqlException's connection-string /// fragment reached any ConfigEditor browser session. The reply is now a fixed sentence plus the request's /// correlation id; the detail stays in the central log. /// public sealed class AdminOperationsActorServerFaultMessageTests : ControlPlaneActorTestBase { private const string Secret = "Server=sql-prod-01;Password=hunter2"; [Fact] public void StartDeployment_server_fault_reply_carries_the_correlation_id_and_not_the_exception_text() { var coordinator = CreateTestProbe("coord"); var actor = Sys.ActorOf(AdminOperationsActor.Props(new ThrowingDbFactory(), coordinator.Ref, Enumerable.Empty())); var correlationId = CorrelationId.NewId(); actor.Tell(new StartDeployment("joe", correlationId)); var reply = ExpectMsg(PresenceBudget); reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected); reply.Message.ShouldNotBeNull(); reply.Message.ShouldNotContain("hunter2"); reply.Message.ShouldNotContain("sql-prod-01"); reply.Message.ShouldContain(correlationId.ToString()); // the operator can find the log line } [Fact] public void TestDriverConnect_probe_throw_reply_carries_the_correlation_id_and_not_the_exception_text() { var coordinator = CreateTestProbe("coord"); var actor = Sys.ActorOf(AdminOperationsActor.Props(NewInMemoryDbFactory(), coordinator.Ref, new IDriverProbe[] { new ThrowingProbe() })); var correlationId = Guid.NewGuid(); actor.Tell(new TestDriverConnect("Throwing", "{}", 5, correlationId)); var reply = ExpectMsg(PresenceBudget); reply.Ok.ShouldBeFalse(); reply.Message.ShouldNotBeNull(); reply.Message.ShouldNotContain("hunter2"); reply.Message.ShouldContain(correlationId.ToString()); } private sealed class ThrowingDbFactory : IDbContextFactory { public OtOpcUaConfigDbContext CreateDbContext() => throw new InvalidOperationException("Cannot open database: " + Secret); } private sealed class ThrowingProbe : IDriverProbe { public string DriverType => "Throwing"; public Task ProbeAsync(string configJson, TimeSpan timeout, CancellationToken ct) => throw new InvalidOperationException("probe blew up: " + Secret); } }