feat(cli): table output formatter for audit events (#23 M8)
This commit is contained in:
117
tests/ScadaLink.CLI.Tests/Commands/AuditTableFormatterTests.cs
Normal file
117
tests/ScadaLink.CLI.Tests/Commands/AuditTableFormatterTests.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Text.Json;
|
||||
using ScadaLink.CLI.Commands;
|
||||
|
||||
namespace ScadaLink.CLI.Tests.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the <c>table</c> output formatter of the <c>scadalink audit query</c>
|
||||
/// subcommand (Audit Log #23 M8-T6): header rendering, long-field truncation, the
|
||||
/// empty-result-set case, and null-actor handling.
|
||||
/// </summary>
|
||||
public class AuditTableFormatterTests
|
||||
{
|
||||
private static IReadOnlyList<JsonElement> Events(string json)
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return doc.RootElement.EnumerateArray()
|
||||
.Select(e => e.Clone())
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Table_RendersHeaderRow_WithExpectedColumns()
|
||||
{
|
||||
var formatter = new TableAuditFormatter();
|
||||
var output = new StringWriter();
|
||||
|
||||
formatter.WritePage(Events("[]"), output);
|
||||
|
||||
var firstLine = output.ToString()
|
||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries)[0];
|
||||
foreach (var col in new[]
|
||||
{
|
||||
"OccurredAtUtc", "Channel", "Kind", "Status",
|
||||
"Target", "Actor", "DurationMs", "HttpStatus",
|
||||
})
|
||||
{
|
||||
Assert.Contains(col, firstLine);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Table_TruncatesLongTarget_WithEllipsis()
|
||||
{
|
||||
var formatter = new TableAuditFormatter();
|
||||
var output = new StringWriter();
|
||||
|
||||
var longTarget = new string('x', 200);
|
||||
formatter.WritePage(
|
||||
Events($"[{{\"occurredAtUtc\":\"2026-05-20T12:00:00Z\",\"channel\":\"OutboundApi\"," +
|
||||
$"\"kind\":\"SyncCall\",\"status\":\"Delivered\",\"target\":\"{longTarget}\"," +
|
||||
$"\"actor\":\"multi-role\"}}]"),
|
||||
output);
|
||||
|
||||
var text = output.ToString();
|
||||
Assert.Contains("…", text);
|
||||
// The full untruncated target must not appear verbatim.
|
||||
Assert.DoesNotContain(longTarget, text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Table_EmptyResultSet_RendersHeaderOnly_OrNoRowsMessage()
|
||||
{
|
||||
var formatter = new TableAuditFormatter();
|
||||
var output = new StringWriter();
|
||||
|
||||
formatter.WritePage(Events("[]"), output);
|
||||
|
||||
var lines = output.ToString()
|
||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
// Header only — no data rows. (A header line is always emitted so the
|
||||
// column shape is visible even with zero results.)
|
||||
Assert.Single(lines);
|
||||
Assert.Contains("OccurredAtUtc", lines[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Table_NullActor_RendersBlank()
|
||||
{
|
||||
var formatter = new TableAuditFormatter();
|
||||
var output = new StringWriter();
|
||||
|
||||
formatter.WritePage(
|
||||
Events("[{\"occurredAtUtc\":\"2026-05-20T12:00:00Z\",\"channel\":\"InboundApi\"," +
|
||||
"\"kind\":\"ApiCall\",\"status\":\"Delivered\",\"target\":\"key-1\"," +
|
||||
"\"actor\":null}]"),
|
||||
output);
|
||||
|
||||
var lines = output.ToString()
|
||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.Equal(2, lines.Length);
|
||||
// The data row must not contain the literal "null" for the actor column.
|
||||
Assert.DoesNotContain("null", lines[1]);
|
||||
Assert.Contains("InboundApi", lines[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Table_HeaderEmittedOncePerPage_DataRowsAligned()
|
||||
{
|
||||
var formatter = new TableAuditFormatter();
|
||||
var output = new StringWriter();
|
||||
|
||||
formatter.WritePage(
|
||||
Events("[{\"occurredAtUtc\":\"2026-05-20T12:00:00Z\",\"channel\":\"OutboundApi\"," +
|
||||
"\"kind\":\"SyncCall\",\"status\":\"Delivered\",\"target\":\"weather-api\"," +
|
||||
"\"actor\":\"multi-role\",\"durationMs\":42,\"httpStatus\":200}," +
|
||||
"{\"occurredAtUtc\":\"2026-05-20T12:01:00Z\",\"channel\":\"Notification\"," +
|
||||
"\"kind\":\"Send\",\"status\":\"Failed\",\"target\":\"ops-list\"," +
|
||||
"\"actor\":\"scheduler\",\"durationMs\":7}]"),
|
||||
output);
|
||||
|
||||
var lines = output.ToString()
|
||||
.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.Equal(3, lines.Length);
|
||||
Assert.Contains("weather-api", lines[1]);
|
||||
Assert.Contains("ops-list", lines[2]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user