fix(cli): historyupdate exits non-zero unless every item is Good, and never prints a blank status

A Good service result with missing or too few per-item results, a missing
result entry, or an Uncertain item now fails the command; a status code
the SDK has no name for prints as hex.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 14:21:32 -04:00
parent 8d51380ff1
commit 6e10e45bb9
2 changed files with 131 additions and 13 deletions
@@ -130,4 +130,79 @@ public class HistoryUpdateCommandTests
await Should.ThrowAsync<CommandException>(async () => await cmd.ExecuteAsync(console));
TestConsoleHelper.GetOutput(console).ShouldContain("BadEntryExists");
}
private static readonly string[] TwoTimes = ["2026-01-15T08:00:00Z", "2026-01-15T09:00:00Z"];
/// <summary>A Good service result that carries NO per-item results must not exit 0.</summary>
[Fact]
public async Task Execute_GoodServiceResult_WithMissingItemResults_Throws()
{
var svc = new FakeOpcUaClientService
{
HistoryUpdateResult = new HistoryUpdateOutcome(StatusCodes.Good, [StatusCodes.Good], [new List<StatusCode>()])
};
using var console = TestConsoleHelper.CreateConsole();
await Should.ThrowAsync<CommandException>(async () =>
await Make(svc, values: ["1", "2"], times: TwoTimes).ExecuteAsync(console));
TestConsoleHelper.GetOutput(console).ShouldContain("(no result returned)");
}
/// <summary>Fewer per-item results than items sent must not exit 0, even when every returned one is Good.</summary>
[Fact]
public async Task Execute_TooFewItemResults_Throws()
{
var svc = new FakeOpcUaClientService
{
HistoryUpdateResult = new HistoryUpdateOutcome(StatusCodes.Good, [StatusCodes.Good],
[new List<StatusCode> { StatusCodes.GoodEntryInserted }])
};
using var console = TestConsoleHelper.CreateConsole();
await Should.ThrowAsync<CommandException>(async () =>
await Make(svc, values: ["1", "2"], times: TwoTimes).ExecuteAsync(console));
}
/// <summary>A Good service result with no history-update result at all must not exit 0.</summary>
[Fact]
public async Task Execute_NoResultEntry_Throws()
{
var svc = new FakeOpcUaClientService
{
HistoryUpdateResult = new HistoryUpdateOutcome(StatusCodes.Good, [], [])
};
using var console = TestConsoleHelper.CreateConsole();
await Should.ThrowAsync<CommandException>(async () =>
await Make(svc, mode: "delete-raw", start: TwoTimes[0], end: TwoTimes[1]).ExecuteAsync(console));
}
/// <summary>An Uncertain item result is not Good and fails the command.</summary>
[Fact]
public async Task Execute_UncertainItem_Throws()
{
var svc = new FakeOpcUaClientService
{
HistoryUpdateResult = new HistoryUpdateOutcome(StatusCodes.Good, [StatusCodes.Good],
[new List<StatusCode> { StatusCodes.UncertainDataSubNormal }])
};
using var console = TestConsoleHelper.CreateConsole();
await Should.ThrowAsync<CommandException>(async () =>
await Make(svc, values: ["1"], times: ["2026-01-15T08:00:00Z"]).ExecuteAsync(console));
TestConsoleHelper.GetOutput(console).ShouldContain("UncertainDataSubNormal");
}
/// <summary>A status the SDK has no name for prints as hex, never blank.</summary>
[Fact]
public async Task Execute_UnnamedStatus_PrintsHex()
{
const uint unnamed = 0x8FFF0000u;
StatusCodes.GetBrowseName(unnamed).ShouldBeNullOrEmpty(); // precondition: genuinely unnamed
var svc = new FakeOpcUaClientService
{
HistoryUpdateResult = new HistoryUpdateOutcome(StatusCodes.Good, [StatusCodes.Good],
[new List<StatusCode> { new(unnamed) }])
};
using var console = TestConsoleHelper.CreateConsole();
await Should.ThrowAsync<CommandException>(async () =>
await Make(svc, values: ["1"], times: ["2026-01-15T08:00:00Z"]).ExecuteAsync(console));
TestConsoleHelper.GetOutput(console).ShouldContain("0x8FFF0000");
}
}