Files
git.stella-ops.org/src/PacksRegistry/StellaOps.PacksRegistry/StellaOps.PacksRegistry.Tests/PacksApiTests.cs

131 lines
6.1 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.IO.Compression;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.PacksRegistry.Core.Contracts;
using StellaOps.PacksRegistry.Core.Services;
using StellaOps.PacksRegistry.Infrastructure.InMemory;
using StellaOps.PacksRegistry.WebService.Contracts;
using StellaOps.TestKit;
namespace StellaOps.PacksRegistry.Tests;
public sealed class PacksApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public PacksApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
services.RemoveAll<IPackRepository>();
services.RemoveAll<IParityRepository>();
services.RemoveAll<ILifecycleRepository>();
services.RemoveAll<IAuditRepository>();
services.AddSingleton<IPackRepository, InMemoryPackRepository>();
services.AddSingleton<IParityRepository, InMemoryParityRepository>();
services.AddSingleton<ILifecycleRepository, InMemoryLifecycleRepository>();
services.AddSingleton<IAuditRepository, InMemoryAuditRepository>();
services.AddSingleton(TimeProvider.System);
services.RemoveAll<PackService>();
services.RemoveAll<ParityService>();
services.RemoveAll<LifecycleService>();
services.RemoveAll<ExportService>();
services.AddSingleton<PackService>();
services.AddSingleton<ParityService>();
services.AddSingleton<LifecycleService>();
services.AddSingleton<ExportService>();
});
});
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Upload_and_download_round_trip()
{
var ct = TestContext.Current.CancellationToken;
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", "t1");
var auth = _factory.Services.GetRequiredService<StellaOps.PacksRegistry.WebService.Options.AuthOptions>();
if (!string.IsNullOrWhiteSpace(auth.ApiKey))
{
client.DefaultRequestHeaders.Add("X-API-Key", auth.ApiKey);
}
var payload = new PackUploadRequest
{
Name = "demo",
Version = "1.0.0",
TenantId = "t1",
Content = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("hello")),
ProvenanceUri = "https://example/provenance.json",
ProvenanceContent = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("{\"provenance\":true}"))
};
var message = new HttpRequestMessage(HttpMethod.Post, "/api/v1/packs")
{
Content = JsonContent.Create(payload)
};
var response = await client.SendAsync(message, ct);
if (response.StatusCode != HttpStatusCode.Created)
{
var body = await response.Content.ReadAsStringAsync(ct);
throw new InvalidOperationException($"Upload failed with {response.StatusCode}: {body}");
}
var created = await response.Content.ReadFromJsonAsync<PackResponse>(cancellationToken: ct);
Assert.NotNull(created);
Assert.Equal("demo", created!.Name);
Assert.Equal("1.0.0", created.Version);
var get = await client.GetAsync($"/api/v1/packs/{created.PackId}", ct);
Assert.Equal(HttpStatusCode.OK, get.StatusCode);
var content = await client.GetAsync($"/api/v1/packs/{created.PackId}/content", ct);
Assert.Equal(HttpStatusCode.OK, content.StatusCode);
var bytes = await content.Content.ReadAsByteArrayAsync(ct);
Assert.Equal("hello", System.Text.Encoding.UTF8.GetString(bytes));
Assert.True(content.Headers.Contains("X-Content-Digest"));
var prov = await client.GetAsync($"/api/v1/packs/{created.PackId}/provenance", ct);
Assert.Equal(HttpStatusCode.OK, prov.StatusCode);
var provBytes = await prov.Content.ReadAsByteArrayAsync(ct);
Assert.Contains("provenance", System.Text.Encoding.UTF8.GetString(provBytes));
Assert.True(prov.Headers.Contains("X-Provenance-Digest"));
var manifest = await client.GetFromJsonAsync<PackManifestResponse>($"/api/v1/packs/{created.PackId}/manifest", ct);
Assert.NotNull(manifest);
Assert.Equal(created.PackId, manifest!.PackId);
Assert.True(manifest.ContentLength > 0);
Assert.True(manifest.ProvenanceLength > 0);
// parity status
var parityResponse = await client.PostAsJsonAsync($"/api/v1/packs/{created.PackId}/parity", new ParityRequest { Status = "ready", Notes = "tests" }, ct);
Assert.Equal(HttpStatusCode.OK, parityResponse.StatusCode);
var parity = await client.GetFromJsonAsync<ParityResponse>($"/api/v1/packs/{created.PackId}/parity", ct);
Assert.NotNull(parity);
Assert.Equal("ready", parity!.Status);
var newSignature = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(created.Digest));
var rotationResponse = await client.PostAsJsonAsync($"/api/v1/packs/{created.PackId}/signature", new RotateSignatureRequest { Signature = newSignature }, ct);
Assert.Equal(HttpStatusCode.OK, rotationResponse.StatusCode);
var rotated = await rotationResponse.Content.ReadFromJsonAsync<PackResponse>(cancellationToken: ct);
Assert.Equal(newSignature, rotated!.Signature);
var offlineSeed = await client.PostAsJsonAsync("/api/v1/export/offline-seed", new OfflineSeedRequest { TenantId = "t1", IncludeContent = true, IncludeProvenance = true }, ct);
Assert.Equal(HttpStatusCode.OK, offlineSeed.StatusCode);
var bytesZip = await offlineSeed.Content.ReadAsByteArrayAsync(ct);
using var archive = new ZipArchive(new MemoryStream(bytesZip));
Assert.NotNull(archive.GetEntry("packs.ndjson"));
Assert.NotNull(archive.GetEntry($"content/{created.PackId}.bin"));
}
}