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)); }