513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
103 lines
4.7 KiB
C#
103 lines
4.7 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// A send to a site with no configured gRPC channel must fail the caller's Ask immediately
|
|
/// (<see cref="Status.Failure"/> wrapping <see cref="SiteChannelUnavailableException"/>) instead of
|
|
/// warning-and-dropping the message and leaving the Ask to expire at its full timeout.
|
|
/// </summary>
|
|
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<SitePairChannelProvider>.Instance),
|
|
Opts,
|
|
NullLogger<GrpcSiteTransport>.Instance);
|
|
|
|
/// <summary>Verifies that a send to an unconfigured site faults the Ask quickly instead of timing out.</summary>
|
|
[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<Status.Failure>(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<SiteChannelUnavailableException>(failure.Cause);
|
|
Assert.Equal("unconfigured-site", cause.SiteId);
|
|
Assert.Contains("unconfigured-site", cause.Message);
|
|
}
|
|
|
|
/// <summary>Verifies that an Ask against an unconfigured site completes faulted and fast via a Tell of a status failure.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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<SiteChannelUnavailableException>(async () =>
|
|
await askActor.Ask<object>(
|
|
new SiteEnvelope("unconfigured-site", new EnableInstanceCommand(
|
|
"cmd-1", "Site1.Pump1", DateTimeOffset.UtcNow)),
|
|
TimeSpan.FromSeconds(30)));
|
|
stopwatch.Stop();
|
|
|
|
Assert.Equal("unconfigured-site", ex.SiteId);
|
|
Assert.True(stopwatch.Elapsed < TimeSpan.FromSeconds(1),
|
|
$"expected the Ask to fault well under the 30s timeout; took {stopwatch.Elapsed}");
|
|
}
|
|
|
|
/// <summary>Relays an Ask into the transport, exactly as <c>CentralCommunicationActor</c> does.</summary>
|
|
private sealed class EchoingAskTarget : ReceiveActor
|
|
{
|
|
/// <summary>Initializes the actor to relay every site envelope into the given transport.</summary>
|
|
/// <param name="transport">The transport that receives each relayed envelope.</param>
|
|
public EchoingAskTarget(GrpcSiteTransport transport)
|
|
{
|
|
Receive<SiteEnvelope>(env => transport.Send(env, Sender));
|
|
}
|
|
}
|
|
|
|
private sealed class NoKeyProvider : ISitePskProvider
|
|
{
|
|
/// <inheritdoc />
|
|
public ValueTask<string> GetAsync(string siteId, CancellationToken ct) => new("k");
|
|
/// <inheritdoc />
|
|
public void Invalidate(string siteId) { }
|
|
}
|
|
}
|