test(domain): cover clamd timeout, caller cancellation, null content and defaults (refs #192)
CI / lint (pull_request) Skipped
CI / build (pull_request) Skipped
CI / unit (pull_request) Skipped
CI / frontend (pull_request) Skipped
CI / mutation (pull_request) Skipped
CI / lint (pull_request) Skipped
CI / build (pull_request) Skipped
CI / unit (pull_request) Skipped
CI / frontend (pull_request) Skipped
CI / mutation (pull_request) Skipped
Domain mutation score 89.87% → 91.98%, back above the 90% break. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,13 +17,14 @@ public class ClamdDocumentScannerTests
|
|||||||
public Task<byte[]> Received { get; }
|
public Task<byte[]> Received { get; }
|
||||||
public int Port => ((IPEndPoint)_listener.LocalEndpoint).Port;
|
public int Port => ((IPEndPoint)_listener.LocalEndpoint).Port;
|
||||||
|
|
||||||
public FakeClamd(string reply)
|
/// <param name="reply">The answer, or null to accept the request and never answer (a hung clamd).</param>
|
||||||
|
public FakeClamd(string? reply)
|
||||||
{
|
{
|
||||||
_listener.Start();
|
_listener.Start();
|
||||||
Received = Serve(reply);
|
Received = Serve(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<byte[]> Serve(string reply)
|
private async Task<byte[]> Serve(string? reply)
|
||||||
{
|
{
|
||||||
using var client = await _listener.AcceptTcpClientAsync();
|
using var client = await _listener.AcceptTcpClientAsync();
|
||||||
var stream = client.GetStream();
|
var stream = client.GetStream();
|
||||||
@@ -35,7 +36,10 @@ public class ClamdDocumentScannerTests
|
|||||||
if (n == 0) break;
|
if (n == 0) break;
|
||||||
received.Write(buffer, 0, n);
|
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();
|
return received.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,4 +86,32 @@ public class ClamdDocumentScannerTests
|
|||||||
|
|
||||||
Assert.Equal(ScanVerdict.Unavailable, await ScannerFor(port).ScanAsync([1, 2, 3]));
|
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<OperationCanceledException>(() => ScannerFor(clamd.Port).ScanAsync([1, 2, 3], cts.Token));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Rejects_null_content()
|
||||||
|
=> await Assert.ThrowsAsync<ArgumentNullException>(() => 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));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user