using System.Net; using System.Net.Http.Json; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using StellaOps.SbomService.Models; using Xunit; namespace StellaOps.SbomService.Tests; public class SbomEndpointsTests : IClassFixture> { private readonly WebApplicationFactory _factory; public SbomEndpointsTests(WebApplicationFactory factory) { _factory = factory.WithWebHostBuilder(_ => { }); } [Fact] public async Task Paths_requires_purl() { var client = _factory.CreateClient(); var response = await client.GetAsync("/sbom/paths"); var body = await response.Content.ReadAsStringAsync(); response.StatusCode.Should().Be(HttpStatusCode.BadRequest, body); } [Fact] public async Task Paths_returns_seeded_paths_with_cursor() { var client = _factory.CreateClient(); var response = await client.GetAsync("/sbom/paths?purl=pkg:npm/lodash@4.17.21&limit=1"); response.EnsureSuccessStatusCode(); var payload = await response.Content.ReadFromJsonAsync(); payload.Should().NotBeNull(); payload!.Paths.Should().HaveCount(1); payload.Purl.Should().Be("pkg:npm/lodash@4.17.21"); payload.NextCursor.Should().Be("1"); } [Fact] public async Task Versions_returns_descending_timeline() { var client = _factory.CreateClient(); var response = await client.GetAsync("/sbom/versions?artifact=ghcr.io/stellaops/sample-api"); response.StatusCode.Should().Be(HttpStatusCode.OK, await response.Content.ReadAsStringAsync()); var payload = await response.Content.ReadFromJsonAsync(); payload.Should().NotBeNull(); payload!.Versions.Should().HaveCountGreaterThan(0); payload.Versions.Should().BeInDescendingOrder(v => v.CreatedAt); } [Fact] public async Task Console_sboms_supports_filters_and_cursor() { var client = _factory.CreateClient(); var response = await client.GetAsync("/console/sboms?artifact=sample-api&limit=1"); response.EnsureSuccessStatusCode(); var payload = await response.Content.ReadFromJsonAsync(); payload.Should().NotBeNull(); payload!.Items.Should().HaveCount(1); payload.Items[0].Artifact.Should().Contain("sample-api"); payload.NextCursor.Should().Be("1"); } [Fact] public async Task Console_sboms_filters_by_license_and_asset_tag() { var client = _factory.CreateClient(); var response = await client.GetAsync("/console/sboms?license=MIT&assetTag=owner&limit=5"); response.EnsureSuccessStatusCode(); var payload = await response.Content.ReadFromJsonAsync(); payload.Should().NotBeNull(); payload!.Items.Should().OnlyContain(i => i.License == "MIT" && i.AssetTags.ContainsKey("owner")); payload.NextCursor.Should().BeNull(); } [Fact] public async Task Console_sboms_paginates_with_cursor_offset() { var client = _factory.CreateClient(); var first = await client.GetAsync("/console/sboms?artifact=sample&limit=1"); first.EnsureSuccessStatusCode(); var firstPage = await first.Content.ReadFromJsonAsync(); firstPage!.Items.Should().HaveCount(1); firstPage.NextCursor.Should().Be("1"); var second = await client.GetAsync("/console/sboms?artifact=sample&limit=2&cursor=1"); second.EnsureSuccessStatusCode(); var secondPage = await second.Content.ReadFromJsonAsync(); secondPage!.Items.Should().HaveCount(2); secondPage.Items.Should().OnlyContain(i => i.Artifact.Contains("sample")); secondPage.NextCursor.Should().BeNull(); } [Fact] public async Task Components_lookup_requires_purl_and_paginates() { var client = _factory.CreateClient(); var bad = await client.GetAsync("/components/lookup"); bad.StatusCode.Should().Be(HttpStatusCode.BadRequest); var response = await client.GetAsync("/components/lookup?purl=pkg:npm/lodash@4.17.21&limit=1"); response.EnsureSuccessStatusCode(); var payload = await response.Content.ReadFromJsonAsync(); payload.Should().NotBeNull(); payload!.Neighbors.Should().HaveCount(1); payload.Neighbors[0].Purl.Should().Contain("express"); payload.NextCursor.Should().Be("1"); var second = await client.GetAsync("/components/lookup?purl=pkg:npm/lodash@4.17.21&limit=2&cursor=1"); second.EnsureSuccessStatusCode(); var secondPage = await second.Content.ReadFromJsonAsync(); secondPage.Should().NotBeNull(); secondPage!.Neighbors.Should().HaveCount(2); secondPage.Neighbors.Should().OnlyContain(n => n.Purl.StartsWith("pkg:npm/", StringComparison.OrdinalIgnoreCase)); secondPage.NextCursor.Should().BeNull(); } }