Refactor SurfaceCacheValidator to simplify oldest entry calculation

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
This commit is contained in:
master
2025-12-16 10:44:00 +02:00
parent b1f40945b7
commit 4391f35d8a
107 changed files with 10844 additions and 287 deletions

View File

@@ -0,0 +1,155 @@
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
}
}
}