Files
tiku-backend.net/Tiku.UnitTests/Assets/ClamAvAssetSecurityScannerTests.cs
xiong c497a3ca8d
Some checks failed
ci / release-gate (push) Has been cancelled
清理代码
2026-08-03 12:31:39 +08:00

114 lines
4.2 KiB
C#

using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Microsoft.Extensions.Options;
using Tiku.Application.Assets;
using Tiku.Infrastructure.Assets;
namespace Tiku.UnitTests.Assets;
public sealed class ClamAvAssetSecurityScannerTests
{
[Theory]
[InlineData("stream: OK", AssetSecurityScanVerdict.Clean, null)]
[InlineData("stream: Eicar-Signature FOUND", AssetSecurityScanVerdict.Infected, "Eicar-Signature")]
public async Task Instream_protocol_parses_clean_and_infected_responses(
string response,
AssetSecurityScanVerdict expectedVerdict,
string? expectedSignature)
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
var server = ServeScanAsync(listener, response);
var scanner = CreateScanner(port);
var payload = Encoding.UTF8.GetBytes("X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*");
var result = await scanner.ScanAsync(new MemoryStream(payload), payload.Length);
await server;
Assert.Equal(expectedVerdict, result.Verdict);
Assert.Equal(expectedSignature, result.Signature);
Assert.Equal(payload.Length, result.BytesScanned);
}
[Fact]
public async Task Ping_requires_pong()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
var server = Task.Run(async () =>
{
using var client = await listener.AcceptTcpClientAsync();
await using var stream = client.GetStream();
Assert.Equal("zPING\0", Encoding.ASCII.GetString(await ReadExactAsync(stream, 6)));
await stream.WriteAsync("PONG\0"u8.ToArray());
listener.Stop();
});
Assert.True(await CreateScanner(port).CheckHealthAsync());
await server;
}
[Fact]
public async Task Oversized_stream_is_rejected_before_connecting()
{
var scanner = CreateScanner(1, 4);
var exception = await Assert.ThrowsAsync<AssetSecurityScannerException>(() =>
scanner.ScanAsync(new MemoryStream(new byte[5]), 5));
Assert.Equal("clamav_stream_too_large", exception.Code);
}
[Fact]
public async Task Unavailable_daemon_is_reported_as_transient_scanner_failure()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
var exception = await Assert.ThrowsAsync<AssetSecurityScannerException>(() =>
CreateScanner(port).ScanAsync(new MemoryStream([1]), 1));
Assert.Equal("clamav_unavailable", exception.Code);
}
private static ClamAvAssetSecurityScanner CreateScanner(int port, long streamMaxLength = 1024 * 1024)
{
return new ClamAvAssetSecurityScanner(Options.Create(new ClamAvOptions
{
Host = IPAddress.Loopback.ToString(),
Port = port,
TimeoutSeconds = 2,
ChunkBytes = 1024,
StreamMaxLength = streamMaxLength
}));
}
private static Task ServeScanAsync(TcpListener listener, string response)
{
return Task.Run(async () =>
{
using var client = await listener.AcceptTcpClientAsync();
await using var stream = client.GetStream();
Assert.Equal("zINSTREAM\0", Encoding.ASCII.GetString(await ReadExactAsync(stream, 10)));
while (true)
{
var lengthBytes = await ReadExactAsync(stream, 4);
var length = BinaryPrimitives.ReadUInt32BigEndian(lengthBytes);
if (length == 0) break;
_ = await ReadExactAsync(stream, checked((int)length));
}
await stream.WriteAsync(Encoding.UTF8.GetBytes(response + "\0"));
listener.Stop();
});
}
private static async Task<byte[]> ReadExactAsync(Stream stream, int length)
{
var buffer = new byte[length];
await stream.ReadExactlyAsync(buffer);
return buffer;
}
}