using Akka.Actor;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin;
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;
///
/// lmxopcua#815: Test Connect with a credential reference outside the driver's namespace is refused BEFORE any
/// probe runs — the probe is the seam that would resolve the reference (store / environment) and send the value
/// to the author-chosen endpoint, so "the probe was never invoked" is "nothing was read".
///
public sealed class AdminOperationsActorCredentialScopeTests : ControlPlaneActorTestBase
{
[Theory]
[InlineData(DriverTypeNames.Galaxy, """{"Gateway":{"Endpoint":"http://attacker:5001","ApiKeySecretRef":"env:ConnectionStrings__ConfigDb"}}""")]
[InlineData(DriverTypeNames.Galaxy, """{"Gateway":{"Endpoint":"http://attacker:5001","ApiKeySecretRef":"env:MX_API_KEY"}}""")]
[InlineData(DriverTypeNames.Galaxy, """{"Gateway":{"Endpoint":"http://attacker:5001","ApiKeySecretRef":"env:ServerHistorian__ApiKey"}}""")]
[InlineData(DriverTypeNames.Galaxy, """{"Gateway":{"Endpoint":"http://attacker:5001","ApiKeySecretRef":"file:appsettings.json"}}""")]
[InlineData(DriverTypeNames.OpcUaClient, """{"EndpointUrl":"opc.tcp://attacker:4840","Password":"secret:otopcua/historian/api-key"}""")]
[InlineData(DriverTypeNames.Heidenhain, """{"Access":{"InspectPassword":"secret:galaxy/main/apikey"},"Devices":[{"Host":"attacker"}]}""")]
public void Out_of_namespace_reference_never_reaches_the_probe(string driverType, string config)
{
var probe = new RecordingProbe(driverType);
var actor = Sys.ActorOf(AdminOperationsActor.Props(NewInMemoryDbFactory(), CreateTestProbe("coord").Ref, new IDriverProbe[] { probe }));
actor.Tell(new TestDriverConnect(driverType, config, 5, Guid.NewGuid()));
var reply = ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
reply.Ok.ShouldBeFalse();
reply.Message.ShouldNotBeNull().ShouldContain("Test Connect refused");
reply.Message.ShouldContain("lmxopcua#815");
probe.Calls.ShouldBe(0);
}
/// Positive control: a namespaced reference does reach the probe (the refusal is not blanket).
[Fact]
public void A_namespaced_reference_reaches_the_probe()
{
var probe = new RecordingProbe(DriverTypeNames.Galaxy);
var actor = Sys.ActorOf(AdminOperationsActor.Props(NewInMemoryDbFactory(), CreateTestProbe("coord").Ref, new IDriverProbe[] { probe }));
actor.Tell(new TestDriverConnect(
DriverTypeNames.Galaxy, """{"Gateway":{"Endpoint":"http://gw:5001","ApiKeySecretRef":"secret:galaxy/main/apikey"}}""", 5, Guid.NewGuid()));
ExpectMsg(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Ok.ShouldBeTrue();
probe.Calls.ShouldBe(1);
}
private sealed class RecordingProbe(string driverType) : IDriverProbe
{
private int _calls;
public int Calls => Volatile.Read(ref _calls);
public string DriverType => driverType;
public Task ProbeAsync(string configJson, TimeSpan timeout, CancellationToken ct)
{
Interlocked.Increment(ref _calls);
return Task.FromResult(new DriverProbeResult(true, null, TimeSpan.Zero));
}
}
}