131 lines
6.1 KiB
C#
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"));
|
|
}
|
|
}
|