Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,140 @@
using System.Text;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Scanner.Cache;
using StellaOps.Scanner.Cache.Abstractions;
using StellaOps.Scanner.Cache.FileCas;
using StellaOps.Scanner.Cache.LayerCache;
using Xunit;
namespace StellaOps.Scanner.Cache.Tests;
public sealed class LayerCacheRoundTripTests : IAsyncLifetime
{
private readonly string _rootPath;
private readonly FakeTimeProvider _timeProvider;
private readonly IOptions<ScannerCacheOptions> _options;
private readonly LayerCacheStore _layerCache;
private readonly FileContentAddressableStore _fileCas;
public LayerCacheRoundTripTests()
{
_rootPath = Path.Combine(Path.GetTempPath(), "stellaops-cache-tests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_rootPath);
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 10, 19, 12, 0, 0, TimeSpan.Zero));
var optionsValue = new ScannerCacheOptions
{
RootPath = _rootPath,
LayerTtl = TimeSpan.FromHours(1),
FileTtl = TimeSpan.FromHours(2),
MaxBytes = 512 * 1024, // 512 KiB
WarmBytesThreshold = 256 * 1024,
ColdBytesThreshold = 400 * 1024,
MaintenanceInterval = TimeSpan.FromMinutes(5)
};
_options = Options.Create(optionsValue);
_layerCache = new LayerCacheStore(_options, NullLogger<LayerCacheStore>.Instance, _timeProvider);
_fileCas = new FileContentAddressableStore(_options, NullLogger<FileContentAddressableStore>.Instance, _timeProvider);
}
[Fact]
public async Task RoundTrip_Succeeds_And_Respects_Ttl_And_ImportExport()
{
var layerDigest = "sha256:abcd1234";
var metadata = new Dictionary<string, string>
{
["image"] = "ghcr.io/stella/sample:1",
["schema"] = "1.0"
};
using var inventoryStream = CreateStream("inventory" + Environment.NewLine + "component:libfoo" + Environment.NewLine);
using var usageStream = CreateStream("usage" + Environment.NewLine + "component:bin" + Environment.NewLine);
var request = new LayerCachePutRequest(
layerDigest,
architecture: "linux/amd64",
mediaType: "application/vnd.oci.image.layer.v1.tar",
metadata,
new List<LayerCacheArtifactContent>
{
new("inventory.cdx.json", inventoryStream, "application/json"),
new("usage.cdx.json", usageStream, "application/json")
});
var stored = await _layerCache.PutAsync(request, CancellationToken.None);
stored.LayerDigest.Should().Be(layerDigest);
stored.Artifacts.Should().ContainKey("inventory.cdx.json");
stored.TotalSizeBytes.Should().BeGreaterThan(0);
var cached = await _layerCache.TryGetAsync(layerDigest, CancellationToken.None);
cached.Should().NotBeNull();
cached!.Metadata.Should().ContainKey("image");
await using (var artifact = await _layerCache.OpenArtifactAsync(layerDigest, "inventory.cdx.json", CancellationToken.None))
{
artifact.Should().NotBeNull();
using var reader = new StreamReader(artifact!, Encoding.UTF8);
var content = await reader.ReadToEndAsync();
content.Should().Contain("component:libfoo");
}
// Store file CAS entry and validate export/import lifecycle.
var casHash = "sha256:" + new string('f', 64);
using var casStream = CreateStream("some-cas-content");
await _fileCas.PutAsync(new FileCasPutRequest(casHash, casStream), CancellationToken.None);
var exportPath = Path.Combine(_rootPath, "export");
var exportCount = await _fileCas.ExportAsync(exportPath, CancellationToken.None);
exportCount.Should().Be(1);
await _fileCas.RemoveAsync(casHash, CancellationToken.None);
(await _fileCas.TryGetAsync(casHash, CancellationToken.None)).Should().BeNull();
var importCount = await _fileCas.ImportAsync(exportPath, CancellationToken.None);
importCount.Should().Be(1);
var imported = await _fileCas.TryGetAsync(casHash, CancellationToken.None);
imported.Should().NotBeNull();
imported!.RelativePath.Should().EndWith("content.bin");
// TTL eviction
_timeProvider.Advance(TimeSpan.FromHours(2));
await _layerCache.EvictExpiredAsync(CancellationToken.None);
(await _layerCache.TryGetAsync(layerDigest, CancellationToken.None)).Should().BeNull();
// Compaction removes CAS entry once over threshold.
// Force compaction by writing a large entry.
using var largeStream = CreateStream(new string('x', 400_000));
var largeHash = "sha256:" + new string('e', 64);
await _fileCas.PutAsync(new FileCasPutRequest(largeHash, largeStream), CancellationToken.None);
_timeProvider.Advance(TimeSpan.FromMinutes(1));
await _fileCas.CompactAsync(CancellationToken.None);
(await _fileCas.TryGetAsync(casHash, CancellationToken.None)).Should().BeNull();
}
public Task InitializeAsync() => Task.CompletedTask;
public Task DisposeAsync()
{
try
{
if (Directory.Exists(_rootPath))
{
Directory.Delete(_rootPath, recursive: true);
}
}
catch
{
// Ignored best effort cleanup.
}
return Task.CompletedTask;
}
private static MemoryStream CreateStream(string content)
=> new(Encoding.UTF8.GetBytes(content));
}

View File

@@ -0,0 +1,26 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
<Compile Remove="..\StellaOps.Concelier.Tests.Shared\AssemblyInfo.cs" />
<Compile Remove="..\StellaOps.Concelier.Tests.Shared\MongoFixtureCollection.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
</ItemGroup>
<ItemGroup>
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Cache/StellaOps.Scanner.Cache.csproj" />
</ItemGroup>
</Project>