Files
mxaccessgw/clients/dotnet/MxGateway.Client.Tests/MxGatewayClientCliTests.cs
Joseph Doherty 56949c967b Add stream-alarms and acknowledge-alarm to the .NET CLI
stream-alarms attaches to the gateway's central alarm feed (mirrors
stream-events: --max-events cap, --json/--jsonl, --filter-prefix);
acknowledge-alarm is a unary session-less ack (--reference required,
--comment, --operator). Both wired through IMxGatewayCliClient and the
adapter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:01:58 -04:00

885 lines
33 KiB
C#

using Google.Protobuf.WellKnownTypes;
using MxGateway.Client.Cli;
using MxGateway.Contracts.Proto;
using MxGateway.Contracts.Proto.Galaxy;
namespace MxGateway.Client.Tests;
/// <summary>Tests for the CLI command interface.</summary>
public sealed class MxGatewayClientCliTests
{
/// <summary>Verifies that the version command prints compiled protocol versions.</summary>
[Fact]
public void Run_Version_PrintsCompiledProtocolVersions()
{
using var output = new StringWriter();
using var error = new StringWriter();
var exitCode = MxGatewayClientCli.Run(["version"], output, error);
Assert.Equal(0, exitCode);
Assert.Contains("gateway-protocol=3", output.ToString());
Assert.Contains("worker-protocol=1", output.ToString());
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that the version command with --json flag prints JSON protocol versions.</summary>
[Fact]
public async Task RunAsync_VersionJson_PrintsJsonProtocolVersions()
{
using var output = new StringWriter();
using var error = new StringWriter();
int exitCode = await MxGatewayClientCli.RunAsync(["version", "--json"], output, error);
Assert.Equal(0, exitCode);
Assert.Contains("\"gatewayProtocolVersion\":3", output.ToString());
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that the write command builds a write request and prints JSON reply.</summary>
[Fact]
public async Task RunAsync_Write_BuildsWriteCommandAndPrintsJsonReply()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
fakeClient.InvokeReplies.Enqueue(new MxCommandReply
{
SessionId = "session-fixture",
Kind = MxCommandKind.Write,
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
});
int exitCode = await MxGatewayClientCli.RunAsync(
[
"write",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--session-id",
"session-fixture",
"--server-handle",
"12",
"--item-handle",
"34",
"--type",
"int32",
"--value",
"123",
"--json",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
MxCommandRequest request = Assert.Single(fakeClient.InvokeRequests);
Assert.Equal(MxCommandKind.Write, request.Command.Kind);
Assert.Equal(123, request.Command.Write.Value.Int32Value);
Assert.Contains("MX_COMMAND_KIND_WRITE", output.ToString());
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that error output redacts sensitive API key values.</summary>
[Fact]
public async Task RunAsync_ErrorOutput_RedactsApiKey()
{
using var output = new StringWriter();
using var error = new StringWriter();
int exitCode = await MxGatewayClientCli.RunAsync(
[
"open-session",
"--endpoint",
"http://localhost:5000",
"--api-key",
"secret-api-key",
],
output,
error,
_ => throw new InvalidOperationException("boom secret-api-key"));
Assert.Equal(1, exitCode);
Assert.DoesNotContain("secret-api-key", error.ToString());
Assert.Contains("[redacted]", error.ToString());
}
/// <summary>
/// Verifies that error output redacts the API key even when it was sourced from
/// the <c>--api-key-env</c> environment variable rather than passed via
/// <c>--api-key</c> — the documented default credential path.
/// </summary>
[Fact]
public async Task RunAsync_ErrorOutput_RedactsApiKey_WhenSourcedFromEnvironmentVariable()
{
const string environmentVariableName = "MXGATEWAY_TEST_API_KEY_REDACT";
using var output = new StringWriter();
using var error = new StringWriter();
Environment.SetEnvironmentVariable(environmentVariableName, "env-secret-api-key");
try
{
int exitCode = await MxGatewayClientCli.RunAsync(
[
"open-session",
"--endpoint",
"http://localhost:5000",
"--api-key-env",
environmentVariableName,
],
output,
error,
_ => throw new InvalidOperationException("boom env-secret-api-key"));
Assert.Equal(1, exitCode);
Assert.DoesNotContain("env-secret-api-key", error.ToString());
Assert.Contains("[redacted]", error.ToString());
}
finally
{
Environment.SetEnvironmentVariable(environmentVariableName, null);
}
}
/// <summary>Verifies that stream-events with max-events limit stops output in non-JSON format.</summary>
[Fact]
public async Task RunAsync_StreamEvents_WithMaxEventsStopsNonJsonOutput()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
fakeClient.Events.Add(new MxEvent
{
SessionId = "session-fixture",
Family = MxEventFamily.OnDataChange,
WorkerSequence = 1,
});
fakeClient.Events.Add(new MxEvent
{
SessionId = "session-fixture",
Family = MxEventFamily.OnWriteComplete,
WorkerSequence = 2,
});
int exitCode = await MxGatewayClientCli.RunAsync(
[
"stream-events",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--session-id",
"session-fixture",
"--max-events",
"1",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
Assert.Contains("workerSequence", output.ToString());
Assert.DoesNotContain("ON_WRITE_COMPLETE", output.ToString());
}
/// <summary>
/// Client.Dotnet-017 regression: a finite-window event collector
/// (<c>stream-events --timeout</c>) must exit 0 and emit the events
/// that arrived before the timeout fired, instead of propagating the
/// timeout-driven <see cref="OperationCanceledException"/> as an
/// unhandled exception (exit code -532462766). The fix wraps the
/// <c>await foreach</c> in a token-aware catch so the cancellation
/// ends the foreach gracefully; the aggregated JSON output still runs.
/// </summary>
[Fact]
public async Task RunAsync_StreamEvents_WhenTimeoutFiresAfterEvents_EmitsCollectedEventsAndExitsZero()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
fakeClient.Events.Add(new MxEvent
{
SessionId = "session-fixture",
Family = MxEventFamily.OnDataChange,
WorkerSequence = 1,
});
fakeClient.Events.Add(new MxEvent
{
SessionId = "session-fixture",
Family = MxEventFamily.OnDataChange,
WorkerSequence = 2,
});
// Park forever after yielding the configured events so the CLI's
// --timeout drives the cancellation path.
fakeClient.StreamHangAfterEvents = async token =>
{
await Task.Delay(Timeout.InfiniteTimeSpan, token).ConfigureAwait(false);
};
int exitCode = await MxGatewayClientCli.RunAsync(
[
"stream-events",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--session-id",
"session-fixture",
"--json",
"--max-events",
"200",
"--timeout",
"1s",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
string json = output.ToString();
// Aggregate JSON output must run even though the foreach exited via
// cancellation, and it must contain both events that arrived first.
Assert.Contains("\"events\"", json);
Assert.Contains("\"workerSequence\":\"1\"", json);
Assert.Contains("\"workerSequence\":\"2\"", json);
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that stream-alarms with --max-events stops output and distinguishes payload cases.</summary>
[Fact]
public async Task RunAsync_StreamAlarms_WithMaxEventsStopsAndDistinguishesPayloadCases()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
fakeClient.AlarmFeedMessages.Add(new AlarmFeedMessage
{
ActiveAlarm = new ActiveAlarmSnapshot { AlarmFullReference = "Tank01.Level.HiHi" },
});
fakeClient.AlarmFeedMessages.Add(new AlarmFeedMessage { SnapshotComplete = true });
int exitCode = await MxGatewayClientCli.RunAsync(
[
"stream-alarms",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--filter-prefix",
"Tank01",
"--max-events",
"1",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
StreamAlarmsRequest request = Assert.Single(fakeClient.StreamAlarmsRequests);
Assert.Equal("Tank01", request.AlarmFilterPrefix);
string text = output.ToString();
Assert.Contains("active-alarm", text);
Assert.Contains("Tank01.Level.HiHi", text);
Assert.DoesNotContain("snapshot-complete", text);
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that acknowledge-alarm builds a request and prints the JSON reply.</summary>
[Fact]
public async Task RunAsync_AcknowledgeAlarm_BuildsRequestAndPrintsJsonReply()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
fakeClient.AcknowledgeAlarmReplies.Enqueue(new AcknowledgeAlarmReply
{
CorrelationId = "ack-fixture",
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
Hresult = 0,
});
int exitCode = await MxGatewayClientCli.RunAsync(
[
"acknowledge-alarm",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--reference",
"Tank01.Level.HiHi",
"--comment",
"ack from cli",
"--operator",
"operator1",
"--json",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
AcknowledgeAlarmRequest request = Assert.Single(fakeClient.AcknowledgeAlarmRequests);
Assert.Equal("Tank01.Level.HiHi", request.AlarmFullReference);
Assert.Equal("ack from cli", request.Comment);
Assert.Equal("operator1", request.OperatorUser);
Assert.Contains("ack-fixture", output.ToString());
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that smoke command closes opened session when a command fails.</summary>
[Fact]
public async Task RunAsync_Smoke_WhenCommandFails_ClosesOpenedSession()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new()
{
InvokeFailure = new InvalidOperationException("register failed"),
};
int exitCode = await MxGatewayClientCli.RunAsync(
[
"smoke",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--item",
"Area001.Pump001.Speed",
"--json",
],
output,
error,
_ => fakeClient);
Assert.Equal(1, exitCode);
CloseSessionRequest closeRequest = Assert.Single(fakeClient.CloseSessionRequests);
Assert.Equal("session-fixture", closeRequest.SessionId);
}
/// <summary>Verifies that galaxy-test-connection command prints JSON reply.</summary>
[Fact]
public async Task RunAsync_GalaxyTestConnection_PrintsJsonReply()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new()
{
GalaxyTestConnectionReply = new TestConnectionReply { Ok = true },
};
int exitCode = await MxGatewayClientCli.RunAsync(
[
"galaxy-test-connection",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--json",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
Assert.Single(fakeClient.GalaxyTestConnectionRequests);
Assert.Contains("\"ok\": true", output.ToString());
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that galaxy-discover command prints hierarchy summary.</summary>
[Fact]
public async Task RunAsync_GalaxyDiscover_PrintsHierarchySummary()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
fakeClient.GalaxyDiscoverHierarchyReplies.Enqueue(new DiscoverHierarchyReply
{
NextPageToken = "7:1",
TotalObjectCount = 2,
Objects =
{
new GalaxyObject
{
GobjectId = 7,
TagName = "DelmiaReceiver_001",
ContainedName = "DelmiaReceiver",
ParentGobjectId = 1,
Attributes =
{
new GalaxyAttribute
{
AttributeName = "DownloadPath",
FullTagReference = "DelmiaReceiver_001.DownloadPath",
},
},
},
},
});
fakeClient.GalaxyDiscoverHierarchyReplies.Enqueue(new DiscoverHierarchyReply
{
TotalObjectCount = 2,
Objects =
{
new GalaxyObject
{
GobjectId = 8,
TagName = "DelmiaReceiver_002",
ContainedName = "DelmiaReceiver",
ParentGobjectId = 1,
},
},
});
int exitCode = await MxGatewayClientCli.RunAsync(
[
"galaxy-discover",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
Assert.Equal(2, fakeClient.GalaxyDiscoverHierarchyRequests.Count);
Assert.Equal(5000, fakeClient.GalaxyDiscoverHierarchyRequests[0].PageSize);
Assert.Equal("", fakeClient.GalaxyDiscoverHierarchyRequests[0].PageToken);
Assert.Equal("7:1", fakeClient.GalaxyDiscoverHierarchyRequests[1].PageToken);
string text = output.ToString();
Assert.Contains("objects=2", text);
Assert.Contains("DelmiaReceiver_001", text);
Assert.Contains("DelmiaReceiver_002", text);
Assert.Contains("attributes=1", text);
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that galaxy-watch command prints text output for deploy events.</summary>
[Fact]
public async Task RunAsync_GalaxyWatch_PrintsTextOutputForEvents()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
DateTime deploy = new(2026, 4, 28, 14, 30, 0, DateTimeKind.Utc);
fakeClient.GalaxyDeployEvents.Add(new DeployEvent
{
Sequence = 1,
ObservedAt = Timestamp.FromDateTime(deploy),
TimeOfLastDeploy = Timestamp.FromDateTime(deploy),
TimeOfLastDeployPresent = true,
ObjectCount = 5,
AttributeCount = 17,
});
fakeClient.GalaxyDeployEvents.Add(new DeployEvent
{
Sequence = 2,
ObservedAt = Timestamp.FromDateTime(deploy.AddSeconds(30)),
TimeOfLastDeploy = Timestamp.FromDateTime(deploy.AddSeconds(30)),
TimeOfLastDeployPresent = true,
ObjectCount = 6,
AttributeCount = 18,
});
int exitCode = await MxGatewayClientCli.RunAsync(
[
"galaxy-watch",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--last-seen-deploy-time",
"2026-04-28T14:00:00Z",
"--max-events",
"2",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
WatchDeployEventsRequest request = Assert.Single(fakeClient.GalaxyWatchDeployEventsRequests);
Assert.NotNull(request.LastSeenDeployTime);
string text = output.ToString();
Assert.Contains("sequence=1", text);
Assert.Contains("sequence=2", text);
Assert.Contains("objects=5", text);
Assert.Contains("attributes=18", text);
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that galaxy-watch with --json emits one JSON object per event.</summary>
[Fact]
public async Task RunAsync_GalaxyWatch_JsonEmitsOneObjectPerEvent()
{
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
fakeClient.GalaxyDeployEvents.Add(new DeployEvent
{
Sequence = 42,
ObjectCount = 99,
AttributeCount = 1024,
});
int exitCode = await MxGatewayClientCli.RunAsync(
[
"galaxy-watch",
"--endpoint",
"http://localhost:5000",
"--api-key",
"test-api-key",
"--max-events",
"1",
"--json",
],
output,
error,
_ => fakeClient);
Assert.Equal(0, exitCode);
string text = output.ToString();
Assert.Contains("\"sequence\": \"42\"", text);
Assert.Contains("\"objectCount\": 99", text);
}
/// <summary>Verifies that batch mode executes a single no-gateway command and writes the EOR sentinel.</summary>
[Fact]
public async Task RunAsync_Batch_SingleVersionCommand_WritesOutputAndEorSentinel()
{
using var output = new StringWriter();
using var error = new StringWriter();
using var stdin = new StringReader("version --json\n");
int exitCode = await MxGatewayClientCli.RunAsync(
["batch"],
output,
error,
clientFactory: null,
standardInput: stdin);
Assert.Equal(0, exitCode);
string text = output.ToString();
Assert.Contains("\"gatewayProtocolVersion\"", text);
Assert.Contains("__MXGW_BATCH_EOR__", text);
// Sentinel must appear after the output, not before.
int outputIdx = text.IndexOf("gatewayProtocolVersion", StringComparison.Ordinal);
int eorIdx = text.IndexOf("__MXGW_BATCH_EOR__", StringComparison.Ordinal);
Assert.True(outputIdx < eorIdx, "EOR sentinel must follow command output.");
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that batch mode processes two commands sequentially and writes two EOR sentinels.</summary>
[Fact]
public async Task RunAsync_Batch_TwoVersionCommands_WritesTwoEorSentinels()
{
using var output = new StringWriter();
using var error = new StringWriter();
// Two commands followed by EOF (end of string).
using var stdin = new StringReader("version\nversion --json\n");
int exitCode = await MxGatewayClientCli.RunAsync(
["batch"],
output,
error,
clientFactory: null,
standardInput: stdin);
Assert.Equal(0, exitCode);
string text = output.ToString();
int firstEor = text.IndexOf("__MXGW_BATCH_EOR__", StringComparison.Ordinal);
int secondEor = text.IndexOf("__MXGW_BATCH_EOR__", firstEor + 1, StringComparison.Ordinal);
Assert.True(firstEor >= 0, "First EOR sentinel must be present.");
Assert.True(secondEor > firstEor, "Second EOR sentinel must follow first.");
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Verifies that batch mode on EOF (empty stdin) exits 0 immediately without writing any sentinel.</summary>
[Fact]
public async Task RunAsync_Batch_EmptyStdin_ExitsZeroWithNoOutput()
{
using var output = new StringWriter();
using var error = new StringWriter();
using var stdin = new StringReader(string.Empty);
int exitCode = await MxGatewayClientCli.RunAsync(
["batch"],
output,
error,
clientFactory: null,
standardInput: stdin);
Assert.Equal(0, exitCode);
Assert.Equal(string.Empty, output.ToString());
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>
/// Verifies that batch mode continues after a command failure and writes the error JSON
/// to stdout (not stderr), followed by the EOR sentinel.
/// </summary>
[Fact]
public async Task RunAsync_Batch_CommandFailure_WritesErrorJsonToStdoutAndContinues()
{
using var output = new StringWriter();
using var error = new StringWriter();
// First line: a gateway command with no API key (will fail).
// Second line: version (will succeed).
using var stdin = new StringReader("open-session --endpoint http://localhost:5000\nversion --json\n");
int exitCode = await MxGatewayClientCli.RunAsync(
["batch"],
output,
error,
clientFactory: _ => throw new InvalidOperationException("injected failure"),
standardInput: stdin);
Assert.Equal(0, exitCode);
string text = output.ToString();
// Error record: the error JSON must be on stdout, not stderr.
Assert.Contains("\"error\"", text);
Assert.Equal(string.Empty, error.ToString());
// Both records must be present.
int firstEor = text.IndexOf("__MXGW_BATCH_EOR__", StringComparison.Ordinal);
int secondEor = text.IndexOf("__MXGW_BATCH_EOR__", firstEor + 1, StringComparison.Ordinal);
Assert.True(firstEor >= 0, "EOR after failed command must be present.");
Assert.True(secondEor > firstEor, "EOR after successful command must follow first EOR.");
// Second record must contain the version output.
string afterFirstEor = text[(firstEor + "__MXGW_BATCH_EOR__".Length)..];
Assert.Contains("\"gatewayProtocolVersion\"", afterFirstEor);
}
/// <summary>Verifies that batch mode treats an empty (blank) line as EOF and exits 0.</summary>
[Fact]
public async Task RunAsync_Batch_EmptyLine_ExitsZeroAfterPreviousCommands()
{
using var output = new StringWriter();
using var error = new StringWriter();
// One command, then an empty line (stop signal), then another command that must NOT run.
using var stdin = new StringReader("version --json\n\nversion --json\n");
int exitCode = await MxGatewayClientCli.RunAsync(
["batch"],
output,
error,
clientFactory: null,
standardInput: stdin);
Assert.Equal(0, exitCode);
string text = output.ToString();
// Only one EOR sentinel — the second command after the empty line must not execute.
int firstEor = text.IndexOf("__MXGW_BATCH_EOR__", StringComparison.Ordinal);
int secondEor = text.IndexOf("__MXGW_BATCH_EOR__", firstEor + 1, StringComparison.Ordinal);
Assert.True(firstEor >= 0, "One EOR sentinel must be present.");
Assert.Equal(-1, secondEor);
Assert.Equal(string.Empty, error.ToString());
}
/// <summary>Fake CLI client for testing.</summary>
private sealed class FakeCliClient : IMxGatewayCliClient
{
/// <summary>Queue of invoke replies to return.</summary>
public Queue<MxCommandReply> InvokeReplies { get; } = new();
/// <summary>List of received invoke requests.</summary>
public List<MxCommandRequest> InvokeRequests { get; } = [];
/// <summary>List of received close session requests.</summary>
public List<CloseSessionRequest> CloseSessionRequests { get; } = [];
/// <summary>List of events to yield when streaming.</summary>
public List<MxEvent> Events { get; } = [];
/// <summary>Exception to throw on invoke, if any.</summary>
public Exception? InvokeFailure { get; init; }
/// <summary>
/// When set, after yielding all <see cref="Events"/> the stream
/// awaits the provided handle and then throws
/// <see cref="OperationCanceledException"/> — used to simulate the
/// CLI timeout / Ctrl+C cancellation path (Client.Dotnet-017).
/// </summary>
public Func<CancellationToken, Task>? StreamHangAfterEvents { get; set; }
/// <inheritdoc />
public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
}
/// <inheritdoc />
public Task<OpenSessionReply> OpenSessionAsync(
OpenSessionRequest request,
CancellationToken cancellationToken)
{
return Task.FromResult(new OpenSessionReply
{
SessionId = "session-fixture",
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
GatewayProtocolVersion = 1,
WorkerProtocolVersion = 1,
});
}
/// <inheritdoc />
public Task<CloseSessionReply> CloseSessionAsync(
CloseSessionRequest request,
CancellationToken cancellationToken)
{
CloseSessionRequests.Add(request);
return Task.FromResult(new CloseSessionReply
{
SessionId = request.SessionId,
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
FinalState = SessionState.Closed,
});
}
/// <inheritdoc />
public Task<MxCommandReply> InvokeAsync(
MxCommandRequest request,
CancellationToken cancellationToken)
{
InvokeRequests.Add(request);
if (InvokeFailure is not null)
{
throw InvokeFailure;
}
return Task.FromResult(InvokeReplies.Dequeue());
}
/// <inheritdoc />
public async IAsyncEnumerable<MxEvent> StreamEventsAsync(
StreamEventsRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
foreach (MxEvent gatewayEvent in Events)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Yield();
yield return gatewayEvent;
}
if (StreamHangAfterEvents is not null)
{
await StreamHangAfterEvents(cancellationToken).ConfigureAwait(false);
}
}
/// <summary>Queue of acknowledge-alarm replies to return.</summary>
public Queue<AcknowledgeAlarmReply> AcknowledgeAlarmReplies { get; } = new();
/// <summary>List of received acknowledge-alarm requests.</summary>
public List<AcknowledgeAlarmRequest> AcknowledgeAlarmRequests { get; } = [];
/// <summary>List of received stream-alarms requests.</summary>
public List<StreamAlarmsRequest> StreamAlarmsRequests { get; } = [];
/// <summary>List of alarm feed messages to yield when streaming alarms.</summary>
public List<AlarmFeedMessage> AlarmFeedMessages { get; } = [];
/// <inheritdoc />
public Task<AcknowledgeAlarmReply> AcknowledgeAlarmAsync(
AcknowledgeAlarmRequest request,
CancellationToken cancellationToken)
{
AcknowledgeAlarmRequests.Add(request);
return Task.FromResult(AcknowledgeAlarmReplies.Dequeue());
}
/// <inheritdoc />
public async IAsyncEnumerable<AlarmFeedMessage> StreamAlarmsAsync(
StreamAlarmsRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
StreamAlarmsRequests.Add(request);
foreach (AlarmFeedMessage feedMessage in AlarmFeedMessages)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Yield();
yield return feedMessage;
}
}
/// <summary>Galaxy test connection reply to return.</summary>
public TestConnectionReply GalaxyTestConnectionReply { get; set; } = new() { Ok = true };
/// <summary>Galaxy get last deploy time reply to return.</summary>
public GetLastDeployTimeReply GalaxyGetLastDeployTimeReply { get; set; } = new() { Present = false };
/// <summary>Galaxy discover hierarchy reply to return.</summary>
public DiscoverHierarchyReply GalaxyDiscoverHierarchyReply { get; set; } = new();
public Queue<DiscoverHierarchyReply> GalaxyDiscoverHierarchyReplies { get; } = new();
/// <summary>List of received galaxy test connection requests.</summary>
public List<TestConnectionRequest> GalaxyTestConnectionRequests { get; } = [];
/// <summary>List of received galaxy get last deploy time requests.</summary>
public List<GetLastDeployTimeRequest> GalaxyGetLastDeployTimeRequests { get; } = [];
/// <summary>List of received galaxy discover hierarchy requests.</summary>
public List<DiscoverHierarchyRequest> GalaxyDiscoverHierarchyRequests { get; } = [];
/// <inheritdoc />
public Task<TestConnectionReply> GalaxyTestConnectionAsync(
TestConnectionRequest request,
CancellationToken cancellationToken)
{
GalaxyTestConnectionRequests.Add(request);
return Task.FromResult(GalaxyTestConnectionReply);
}
/// <inheritdoc />
public Task<GetLastDeployTimeReply> GalaxyGetLastDeployTimeAsync(
GetLastDeployTimeRequest request,
CancellationToken cancellationToken)
{
GalaxyGetLastDeployTimeRequests.Add(request);
return Task.FromResult(GalaxyGetLastDeployTimeReply);
}
/// <inheritdoc />
public Task<DiscoverHierarchyReply> GalaxyDiscoverHierarchyAsync(
DiscoverHierarchyRequest request,
CancellationToken cancellationToken)
{
GalaxyDiscoverHierarchyRequests.Add(request);
return Task.FromResult(
GalaxyDiscoverHierarchyReplies.TryDequeue(out DiscoverHierarchyReply? reply)
? reply
: GalaxyDiscoverHierarchyReply);
}
/// <summary>List of received galaxy watch deploy events requests.</summary>
public List<WatchDeployEventsRequest> GalaxyWatchDeployEventsRequests { get; } = [];
/// <summary>List of deploy events to yield when watching.</summary>
public List<DeployEvent> GalaxyDeployEvents { get; } = [];
/// <inheritdoc />
public async IAsyncEnumerable<DeployEvent> GalaxyWatchDeployEventsAsync(
WatchDeployEventsRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
GalaxyWatchDeployEventsRequests.Add(request);
foreach (DeployEvent deployEvent in GalaxyDeployEvents)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Yield();
yield return deployEvent;
}
}
}
}