From 5b77d6a061b6d1a9e87fd9ec36d0551fddd0591f Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Fri, 2 Oct 2026 09:42:24 +0200 Subject: [PATCH] test(domain): cover clamd timeout, caller cancellation, null content and defaults (refs #192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Domain mutation score 89.87% → 91.98%, back above the 90% break. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Big.Tests/ClamdDocumentScannerTests.cs | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/services/domain/Big.Tests/ClamdDocumentScannerTests.cs b/services/domain/Big.Tests/ClamdDocumentScannerTests.cs index 8ee42a7..8739226 100644 --- a/services/domain/Big.Tests/ClamdDocumentScannerTests.cs +++ b/services/domain/Big.Tests/ClamdDocumentScannerTests.cs @@ -17,13 +17,14 @@ public class ClamdDocumentScannerTests public Task Received { get; } public int Port => ((IPEndPoint)_listener.LocalEndpoint).Port; - public FakeClamd(string reply) + /// The answer, or null to accept the request and never answer (a hung clamd). + public FakeClamd(string? reply) { _listener.Start(); Received = Serve(reply); } - private async Task Serve(string reply) + private async Task Serve(string? reply) { using var client = await _listener.AcceptTcpClientAsync(); var stream = client.GetStream(); @@ -35,7 +36,10 @@ public class ClamdDocumentScannerTests if (n == 0) break; received.Write(buffer, 0, n); } - await stream.WriteAsync(System.Text.Encoding.ASCII.GetBytes(reply + "\0")); + if (reply is null) + await Task.Delay(TimeSpan.FromSeconds(10)); // long past any test timeout + else + await stream.WriteAsync(System.Text.Encoding.ASCII.GetBytes(reply + "\0")); return received.ToArray(); } @@ -82,4 +86,32 @@ public class ClamdDocumentScannerTests Assert.Equal(ScanVerdict.Unavailable, await ScannerFor(port).ScanAsync([1, 2, 3])); } + + [Fact] + public async Task A_clamd_that_never_answers_is_unavailable_after_the_timeout() + { + using var clamd = new FakeClamd(reply: null); + var scanner = new ClamdDocumentScanner( + new ClamAvOptions { Host = "127.0.0.1", Port = clamd.Port, Timeout = TimeSpan.FromMilliseconds(300) }); + + Assert.Equal(ScanVerdict.Unavailable, await scanner.ScanAsync([1, 2, 3])); + } + + [Fact] + public async Task A_cancelled_request_is_cancelled_not_reported_unavailable() + { + // The caller giving up is not a scanner outage: it propagates instead of becoming a 503. + using var clamd = new FakeClamd(reply: null); + using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(300)); + + await Assert.ThrowsAnyAsync(() => ScannerFor(clamd.Port).ScanAsync([1, 2, 3], cts.Token)); + } + + [Fact] + public async Task Rejects_null_content() + => await Assert.ThrowsAsync(() => ScannerFor(1).ScanAsync(null!)); + + [Fact] + public void Defaults_to_the_clamav_service_on_the_clamd_port() + => Assert.Equal(("clamav", 3310), (new ClamAvOptions().Host, new ClamAvOptions().Port)); }