Add global using for Xunit in test project Enhance ImportValidatorTests with async validation and quarantine checks Implement FileSystemQuarantineServiceTests for quarantine functionality Add integration tests for ImportValidator to check monotonicity Create BundleVersionTests to validate version parsing and comparison logic Implement VersionMonotonicityCheckerTests for monotonicity checks and activation logic
156 lines
6.0 KiB
C#
156 lines
6.0 KiB
C#
using System.Text.Json;
|
|
using FluentAssertions;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.AirGap.Importer.Quarantine;
|
|
|
|
namespace StellaOps.AirGap.Importer.Tests.Quarantine;
|
|
|
|
public sealed class FileSystemQuarantineServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task QuarantineAsync_ShouldCreateExpectedFiles_AndListAsyncShouldReturnEntry()
|
|
{
|
|
var root = CreateTempDirectory();
|
|
try
|
|
{
|
|
var bundlePath = Path.Combine(root, "bundle.tar.zst");
|
|
await File.WriteAllTextAsync(bundlePath, "bundle-bytes");
|
|
|
|
var options = Options.Create(new QuarantineOptions
|
|
{
|
|
QuarantineRoot = Path.Combine(root, "quarantine"),
|
|
RetentionPeriod = TimeSpan.FromDays(30),
|
|
MaxQuarantineSizeBytes = 1024 * 1024,
|
|
EnableAutomaticCleanup = true
|
|
});
|
|
|
|
var svc = new FileSystemQuarantineService(
|
|
options,
|
|
NullLogger<FileSystemQuarantineService>.Instance,
|
|
TimeProvider.System);
|
|
|
|
var result = await svc.QuarantineAsync(new QuarantineRequest(
|
|
TenantId: "tenant-a",
|
|
BundlePath: bundlePath,
|
|
ManifestJson: "{\"version\":\"1.0.0\"}",
|
|
ReasonCode: "dsse:invalid",
|
|
ReasonMessage: "dsse:invalid",
|
|
VerificationLog: new[] { "tuf:ok", "dsse:invalid" },
|
|
Metadata: new Dictionary<string, string> { ["k"] = "v" }));
|
|
|
|
result.Success.Should().BeTrue();
|
|
Directory.Exists(result.QuarantinePath).Should().BeTrue();
|
|
|
|
File.Exists(Path.Combine(result.QuarantinePath, "bundle.tar.zst")).Should().BeTrue();
|
|
File.Exists(Path.Combine(result.QuarantinePath, "manifest.json")).Should().BeTrue();
|
|
File.Exists(Path.Combine(result.QuarantinePath, "verification.log")).Should().BeTrue();
|
|
File.Exists(Path.Combine(result.QuarantinePath, "failure-reason.txt")).Should().BeTrue();
|
|
File.Exists(Path.Combine(result.QuarantinePath, "quarantine.json")).Should().BeTrue();
|
|
|
|
var listed = await svc.ListAsync("tenant-a");
|
|
listed.Should().ContainSingle(e => e.QuarantineId == result.QuarantineId);
|
|
}
|
|
finally
|
|
{
|
|
SafeDeleteDirectory(root);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RemoveAsync_ShouldMoveToRemovedFolder()
|
|
{
|
|
var root = CreateTempDirectory();
|
|
try
|
|
{
|
|
var bundlePath = Path.Combine(root, "bundle.tar.zst");
|
|
await File.WriteAllTextAsync(bundlePath, "bundle-bytes");
|
|
|
|
var quarantineRoot = Path.Combine(root, "quarantine");
|
|
var options = Options.Create(new QuarantineOptions { QuarantineRoot = quarantineRoot, MaxQuarantineSizeBytes = 1024 * 1024 });
|
|
var svc = new FileSystemQuarantineService(options, NullLogger<FileSystemQuarantineService>.Instance, TimeProvider.System);
|
|
|
|
var result = await svc.QuarantineAsync(new QuarantineRequest(
|
|
TenantId: "tenant-a",
|
|
BundlePath: bundlePath,
|
|
ManifestJson: null,
|
|
ReasonCode: "tuf:invalid",
|
|
ReasonMessage: "tuf:invalid",
|
|
VerificationLog: new[] { "tuf:invalid" }));
|
|
|
|
var removed = await svc.RemoveAsync("tenant-a", result.QuarantineId, "investigated");
|
|
removed.Should().BeTrue();
|
|
|
|
Directory.Exists(result.QuarantinePath).Should().BeFalse();
|
|
Directory.Exists(Path.Combine(quarantineRoot, "tenant-a", ".removed", result.QuarantineId)).Should().BeTrue();
|
|
}
|
|
finally
|
|
{
|
|
SafeDeleteDirectory(root);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CleanupExpiredAsync_ShouldDeleteOldEntries()
|
|
{
|
|
var root = CreateTempDirectory();
|
|
try
|
|
{
|
|
var bundlePath = Path.Combine(root, "bundle.tar.zst");
|
|
await File.WriteAllTextAsync(bundlePath, "bundle-bytes");
|
|
|
|
var quarantineRoot = Path.Combine(root, "quarantine");
|
|
var options = Options.Create(new QuarantineOptions { QuarantineRoot = quarantineRoot, MaxQuarantineSizeBytes = 1024 * 1024 });
|
|
var svc = new FileSystemQuarantineService(options, NullLogger<FileSystemQuarantineService>.Instance, TimeProvider.System);
|
|
|
|
var result = await svc.QuarantineAsync(new QuarantineRequest(
|
|
TenantId: "tenant-a",
|
|
BundlePath: bundlePath,
|
|
ManifestJson: null,
|
|
ReasonCode: "tuf:invalid",
|
|
ReasonMessage: "tuf:invalid",
|
|
VerificationLog: new[] { "tuf:invalid" }));
|
|
|
|
var jsonPath = Path.Combine(result.QuarantinePath, "quarantine.json");
|
|
var json = await File.ReadAllTextAsync(jsonPath);
|
|
var jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web) { WriteIndented = true };
|
|
var entry = JsonSerializer.Deserialize<QuarantineEntry>(json, jsonOptions);
|
|
entry.Should().NotBeNull();
|
|
|
|
var oldEntry = entry! with { QuarantinedAt = DateTimeOffset.Parse("1900-01-01T00:00:00Z") };
|
|
await File.WriteAllTextAsync(jsonPath, JsonSerializer.Serialize(oldEntry, jsonOptions));
|
|
|
|
var removed = await svc.CleanupExpiredAsync(TimeSpan.FromDays(30));
|
|
removed.Should().BeGreaterThanOrEqualTo(1);
|
|
Directory.Exists(result.QuarantinePath).Should().BeFalse();
|
|
}
|
|
finally
|
|
{
|
|
SafeDeleteDirectory(root);
|
|
}
|
|
}
|
|
|
|
private static string CreateTempDirectory()
|
|
{
|
|
var dir = Path.Combine(Path.GetTempPath(), "stellaops-airgap-tests", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(dir);
|
|
return dir;
|
|
}
|
|
|
|
private static void SafeDeleteDirectory(string path)
|
|
{
|
|
try
|
|
{
|
|
if (Directory.Exists(path))
|
|
{
|
|
Directory.Delete(path, recursive: true);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// best-effort cleanup
|
|
}
|
|
}
|
|
}
|
|
|