docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
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.
This commit is contained in:
@@ -8,7 +8,7 @@ using ZB.MOM.WW.ScadaBridge.CLI.Commands;
|
||||
namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the <c>scadabridge audit export</c> subcommand (Audit Log #23 M8-T3):
|
||||
/// Tests for the <c>scadabridge audit export</c> subcommand (Audit Log):
|
||||
/// required-flag enforcement, query-string construction, streaming the response body
|
||||
/// to the output file, and the parquet-not-implemented (501) path.
|
||||
/// </summary>
|
||||
@@ -17,6 +17,7 @@ public class AuditExportCommandTests
|
||||
{
|
||||
// ---- CLI parsing: required flags --------------------------------------
|
||||
|
||||
/// <summary>Verifies that omitting a required flag produces a parse error and a non-zero exit code.</summary>
|
||||
[Fact]
|
||||
public void Export_MissingRequiredFlag_ProducesParseErrorAndNonZeroExit()
|
||||
{
|
||||
@@ -28,6 +29,7 @@ public class AuditExportCommandTests
|
||||
Assert.Contains("--output", err);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the command parses without error when all required flags are present.</summary>
|
||||
[Fact]
|
||||
public void Export_AllRequiredFlagsPresent_ParsesWithoutError()
|
||||
{
|
||||
@@ -40,6 +42,7 @@ public class AuditExportCommandTests
|
||||
Assert.Empty(parse.Errors);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an invalid format value is not rejected at the parse layer.</summary>
|
||||
[Fact]
|
||||
public void Export_InvalidFormat_IsNotRejectedAtTheParseLayer()
|
||||
{
|
||||
@@ -56,6 +59,8 @@ public class AuditExportCommandTests
|
||||
Assert.Empty(parse.Errors);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the format value is accepted in any letter case.</summary>
|
||||
/// <param name="format">The format value to parse, in varying case.</param>
|
||||
[Theory]
|
||||
[InlineData("CSV")]
|
||||
[InlineData("Csv")]
|
||||
@@ -73,6 +78,7 @@ public class AuditExportCommandTests
|
||||
|
||||
// ---- Query string -----------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that the query string includes the time window, the format and the filters.</summary>
|
||||
[Fact]
|
||||
public void BuildQueryString_IncludesWindowFormatAndFilters()
|
||||
{
|
||||
@@ -96,6 +102,7 @@ public class AuditExportCommandTests
|
||||
Assert.Equal("2026-05-20T12:00:00.0000000+00:00", parsed["toUtc"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that multi-value filters emit one query key per value.</summary>
|
||||
[Fact]
|
||||
public void BuildQueryString_MultiValueFilters_EmitOneKeyPerValue()
|
||||
{
|
||||
@@ -120,6 +127,7 @@ public class AuditExportCommandTests
|
||||
Assert.Equal(new[] { "site-1", "site-2" }, parsed.GetValues("sourceSiteId"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that multi-value filters that were not set are omitted from the query string.</summary>
|
||||
[Fact]
|
||||
public void BuildQueryString_OmitsUnsetMultiValueFilters()
|
||||
{
|
||||
@@ -140,6 +148,7 @@ public class AuditExportCommandTests
|
||||
Assert.Null(parsed["sourceSiteId"]);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that multiple channel values supplied in a single token are accepted.</summary>
|
||||
[Fact]
|
||||
public void Export_MultipleChannelValues_SingleToken_AreAccepted()
|
||||
{
|
||||
@@ -154,6 +163,7 @@ public class AuditExportCommandTests
|
||||
Assert.Empty(parse.Errors);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that multiple channel values supplied through a repeated flag are accepted.</summary>
|
||||
[Fact]
|
||||
public void Export_MultipleChannelValues_RepeatedFlag_AreAccepted()
|
||||
{
|
||||
@@ -167,6 +177,7 @@ public class AuditExportCommandTests
|
||||
Assert.Empty(parse.Errors);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a multi-value channel option still parses when one name is unrecognised.</summary>
|
||||
[Fact]
|
||||
public void Export_MultiValueChannel_WithOneUnrecognisedName_StillParses()
|
||||
{
|
||||
@@ -188,14 +199,19 @@ public class AuditExportCommandTests
|
||||
{
|
||||
private readonly HttpStatusCode _status;
|
||||
private readonly Func<HttpContent> _content;
|
||||
/// <summary>Gets the path and query string of the last request the handler received.</summary>
|
||||
public string? RequestPathAndQuery { get; private set; }
|
||||
|
||||
/// <summary>Initializes a handler that answers every request with the given status and content.</summary>
|
||||
/// <param name="status">The HTTP status code to return.</param>
|
||||
/// <param name="content">A factory that creates the response content.</param>
|
||||
public BodyHandler(HttpStatusCode status, Func<HttpContent> content)
|
||||
{
|
||||
_status = status;
|
||||
_content = content;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -204,6 +220,8 @@ public class AuditExportCommandTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a successful export streams the response body to the output file.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_Success_StreamsResponseToOutputFile()
|
||||
{
|
||||
@@ -234,11 +252,13 @@ public class AuditExportCommandTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an HTTP 403 response returns exit code 2.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_Http403_ReturnsExitCode2()
|
||||
{
|
||||
// CLI-018: an HTTP 403 on /api/audit/export must produce exit code 2 per the
|
||||
// documented CLI contract — the legacy bare-1 return masked auth failures
|
||||
// An HTTP 403 on /api/audit/export must produce exit code 2 per the
|
||||
// documented CLI contract — the legacy plain exit code 1 masked auth failures
|
||||
// as generic command failures.
|
||||
var path = Path.Combine(Path.GetTempPath(), $"audit-export-403-{Guid.NewGuid():N}.csv");
|
||||
try
|
||||
@@ -262,6 +282,8 @@ public class AuditExportCommandTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unauthorized error code on a response whose status is not 403 also returns exit code 2.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_UnauthorizedCodeOnNon403_ReturnsExitCode2()
|
||||
{
|
||||
@@ -286,6 +308,8 @@ public class AuditExportCommandTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a parquet 501 response prints the server message and returns a non-zero exit code.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_Parquet501_PrintsServerMessageAndReturnsNonZero()
|
||||
{
|
||||
@@ -312,6 +336,8 @@ public class AuditExportCommandTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a large response body is streamed to the file rather than fully buffered.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_LargeBody_IsStreamedNotFullyBuffered()
|
||||
{
|
||||
@@ -351,18 +377,27 @@ public class AuditExportCommandTests
|
||||
private readonly byte _value;
|
||||
private long _remaining;
|
||||
|
||||
/// <summary>Initializes a stream that yields the given byte repeatedly.</summary>
|
||||
/// <param name="value">The byte value to repeat.</param>
|
||||
/// <param name="length">The total number of bytes the stream yields.</param>
|
||||
public RepeatingStream(byte value, long length)
|
||||
{
|
||||
_value = value;
|
||||
_remaining = length;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead => true;
|
||||
/// <inheritdoc />
|
||||
public override bool CanSeek => false;
|
||||
/// <inheritdoc />
|
||||
public override bool CanWrite => false;
|
||||
/// <inheritdoc />
|
||||
public override long Length => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_remaining <= 0) return 0;
|
||||
@@ -372,14 +407,18 @@ public class AuditExportCommandTests
|
||||
return n;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush() { }
|
||||
/// <inheritdoc />
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Deadline + Ctrl-C cancellation (arch-review 2026-08-19 P6-8: audit export
|
||||
// Deadline + Ctrl-C cancellation (audit export
|
||||
// was the one CLI HTTP path with NO deadline — a hung server hung the CLI
|
||||
// forever). Every test below bounds its own wait well under what the old,
|
||||
// deadline-less implementation would have done (indefinite hang) — a
|
||||
@@ -389,8 +428,10 @@ public class AuditExportCommandTests
|
||||
/// <summary>A handler that never completes the request until its <see cref="CancellationToken"/> fires — simulates a server that accepted the connection but never answers.</summary>
|
||||
private sealed class HangingHandler : HttpMessageHandler
|
||||
{
|
||||
/// <summary>Gets the number of requests the handler has received.</summary>
|
||||
public int CallCount { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -410,17 +451,26 @@ public class AuditExportCommandTests
|
||||
private readonly byte[] _firstChunk;
|
||||
private bool _servedFirstChunk;
|
||||
|
||||
/// <summary>Initializes a stream that serves the given first chunk and then stalls.</summary>
|
||||
/// <param name="firstChunk">The bytes served before the stream goes silent.</param>
|
||||
public StallingStream(byte[] firstChunk) => _firstChunk = firstChunk;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead => true;
|
||||
/// <inheritdoc />
|
||||
public override bool CanSeek => false;
|
||||
/// <inheritdoc />
|
||||
public override bool CanWrite => false;
|
||||
/// <inheritdoc />
|
||||
public override long Length => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Read(byte[] buffer, int offset, int count) =>
|
||||
ReadAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_servedFirstChunk)
|
||||
@@ -435,9 +485,13 @@ public class AuditExportCommandTests
|
||||
return 0; // unreachable — Task.Delay(Infinite) only returns via cancellation
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush() { }
|
||||
/// <inheritdoc />
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -447,17 +501,26 @@ public class AuditExportCommandTests
|
||||
private readonly byte[] _firstChunk;
|
||||
private bool _servedFirstChunk;
|
||||
|
||||
/// <summary>Initializes a stream that serves the given first chunk and then throws.</summary>
|
||||
/// <param name="firstChunk">The bytes served before the stream faults.</param>
|
||||
public FaultingStream(byte[] firstChunk) => _firstChunk = firstChunk;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead => true;
|
||||
/// <inheritdoc />
|
||||
public override bool CanSeek => false;
|
||||
/// <inheritdoc />
|
||||
public override bool CanWrite => false;
|
||||
/// <inheritdoc />
|
||||
public override long Length => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Read(byte[] buffer, int offset, int count) =>
|
||||
ReadAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_servedFirstChunk)
|
||||
@@ -471,9 +534,13 @@ public class AuditExportCommandTests
|
||||
throw new IOException("connection reset by peer (simulated)");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush() { }
|
||||
/// <inheritdoc />
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
/// <inheritdoc />
|
||||
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -494,6 +561,8 @@ public class AuditExportCommandTests
|
||||
return capture.ToString();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a server that never sends response headers causes a timeout with a friendly error message.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_ServerNeverSendsHeaders_TimesOutWithFriendlyError()
|
||||
{
|
||||
@@ -521,6 +590,8 @@ public class AuditExportCommandTests
|
||||
Assert.False(File.Exists(path));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the inactivity timeout fires when the stream stalls mid-transfer.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_StreamStallsMidTransfer_InactivityTimeoutFires()
|
||||
{
|
||||
@@ -562,6 +633,8 @@ public class AuditExportCommandTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an I/O exception during streaming produces a clean message rather than a stack trace.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_MidStreamIOException_ProducesCleanMessageNotStackTrace()
|
||||
{
|
||||
@@ -596,6 +669,8 @@ public class AuditExportCommandTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a Ctrl-C cancellation token cancels the export promptly with a clean message.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RunExport_CtrlCToken_CancelsPromptlyWithCleanMessage()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user