using System.Diagnostics;
using Akka.Actor;
using Akka.TestKit.Xunit2;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
///
/// WP1.6: a send to a site with no configured gRPC channel must fail the caller's Ask immediately
/// ( wrapping ) instead of
/// warning-and-dropping the message and leaving the Ask to expire at its full timeout.
///
public class GrpcSiteTransportFailFastTests : TestKit
{
private static readonly CommunicationOptions Opts = new()
{
DeploymentTimeout = TimeSpan.FromSeconds(120),
LifecycleTimeout = TimeSpan.FromSeconds(30),
ArtifactDeploymentTimeout = TimeSpan.FromSeconds(60),
QueryTimeout = TimeSpan.FromSeconds(30),
IntegrationTimeout = TimeSpan.FromSeconds(30),
DebugViewTimeout = TimeSpan.FromSeconds(10)
};
private static GrpcSiteTransport BuildTransport() => new(
new SitePairChannelProvider(
new NoKeyProvider(), Options.Create(Opts), NullLogger.Instance),
Opts,
NullLogger.Instance);
[Fact]
public void Send_ToUnconfiguredSite_FaultsTheAskFastInsteadOfTimingOut()
{
// No ReconcileSites/UpdateSite call was ever made for this site — exactly the "unknown
// site" shape SitePairChannelProvider throws SiteChannelUnavailableException for.
var transport = BuildTransport();
var probe = CreateTestProbe();
var envelope = new SiteEnvelope(
"unconfigured-site",
new EnableInstanceCommand("cmd-1", "Site1.Pump1", DateTimeOffset.UtcNow));
var stopwatch = Stopwatch.StartNew();
transport.Send(envelope, probe.Ref);
// The old warn-and-drop behavior would leave the caller silent until its own Ask timeout
// (30s in production). Well under 1s here proves the fail-fast path, not a lucky race.
var failure = probe.ExpectMsg(TimeSpan.FromSeconds(1));
stopwatch.Stop();
Assert.True(stopwatch.Elapsed < TimeSpan.FromSeconds(1),
$"expected the Ask to fault well under 1s; took {stopwatch.Elapsed}");
var cause = Assert.IsType(failure.Cause);
Assert.Equal("unconfigured-site", cause.SiteId);
Assert.Contains("unconfigured-site", cause.Message);
}
[Fact]
public async Task Ask_AgainstUnconfiguredSite_CompletesFaultedFastViaTellStatusFailure()
{
// Mirrors how CommunicationService actually calls this seam: Ask a temp actor, transport
// Tells it back. Proves the fail-fast reply satisfies a real Akka Ask, not just a TestProbe.
var transport = BuildTransport();
var askActor = Sys.ActorOf(Props.Create(() => new EchoingAskTarget(transport)));
var stopwatch = Stopwatch.StartNew();
var ex = await Assert.ThrowsAsync(async () =>
await askActor.Ask