using System.Net; using System.Net.Http.Json; using System.Reflection; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using StellaOps.SbomService.Repositories; using Xunit; namespace StellaOps.SbomService.Tests; public class ProjectionEndpointTests : IClassFixture> { private readonly WebApplicationFactory _factory; public ProjectionEndpointTests(WebApplicationFactory factory) { var contentRoot = ResolveContentRoot(); _factory = factory.WithWebHostBuilder(builder => { var fixturePath = GetProjectionFixturePath(); if (!File.Exists(fixturePath)) { throw new InvalidOperationException($"Projection fixture missing at {fixturePath}"); } builder.ConfigureAppConfiguration((_, config) => { config.AddInMemoryCollection(new Dictionary { ["SbomService:ProjectionsPath"] = fixturePath }); }); builder.ConfigureServices(services => { // Avoid MongoDB dependency in tests; use seeded in-memory repo. services.RemoveAll(); services.AddSingleton(); }); builder.UseSetting(WebHostDefaults.ContentRootKey, contentRoot); }); } [Fact] public async Task Projection_requires_tenant() { var client = _factory.CreateClient(); var response = await client.GetAsync("/sboms/snap-001/projection"); if (response.StatusCode != HttpStatusCode.BadRequest) { var body = await response.Content.ReadAsStringAsync(); throw new Xunit.Sdk.XunitException($"Expected 400 but got {(int)response.StatusCode}: {response.StatusCode}. Body: {body}"); } } [Fact] public async Task Projection_returns_payload_and_hash() { var client = _factory.CreateClient(); var response = await client.GetAsync("/sboms/snap-001/projection?tenant=tenant-a"); response.EnsureSuccessStatusCode(); var json = await response.Content.ReadFromJsonAsync(); json.Should().NotBeNull(); json!.snapshotId.Should().Be("snap-001"); json.tenantId.Should().Be("tenant-a"); json.hash.Should().NotBeNullOrEmpty(); json.projection.GetProperty("purl").GetString().Should().Be("pkg:npm/lodash@4.17.21"); } private sealed record ProjectionResponse(string snapshotId, string tenantId, string schemaVersion, string hash, System.Text.Json.JsonElement projection); private static string GetProjectionFixturePath() { // Resolve docs/modules/sbomservice/fixtures/lnm-v1/projections.json relative to test bin directory. var baseDir = ResolveContentRoot(); return Path.Combine(baseDir, "docs", "modules", "sbomservice", "fixtures", "lnm-v1", "projections.json"); } private static string ResolveContentRoot() { // Walk up from bin folder to repo root (containing docs/). var dir = AppContext.BaseDirectory; for (var i = 0; i < 6; i++) { var candidate = Path.GetFullPath(Path.Combine(dir, "..")); if (Directory.Exists(Path.Combine(candidate, "docs")) && Directory.Exists(Path.Combine(candidate, "src"))) { return candidate; } dir = candidate; } return AppContext.BaseDirectory; } }