Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -0,0 +1,407 @@
// -----------------------------------------------------------------------------
// ResolutionControllerIntegrationTests.cs
// Sprint: SPRINT_1227_0001_0002_BE_resolution_api
// Task: T9 — Integration tests for resolution API
// -----------------------------------------------------------------------------
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.BinaryIndex.Contracts.Resolution;
using Xunit;
namespace StellaOps.BinaryIndex.WebService.Tests;
/// <summary>
/// Integration tests for the Resolution API endpoints.
/// </summary>
[Trait("Category", "Integration")]
[Trait("Category", "BinaryIndex")]
public class ResolutionControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public ResolutionControllerIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Add test-specific services if needed
});
});
_client = _factory.CreateClient();
}
#region Single Resolution Tests
[Fact(DisplayName = "POST /api/v1/resolve/vuln returns 200 for valid request")]
public async Task ResolveVuln_ValidRequest_Returns200()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:deb/debian/openssl@3.0.7",
BuildId = "abc123def456789",
DistroRelease = "debian:bookworm"
};
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<VulnResolutionResponse>();
result.Should().NotBeNull();
result!.Package.Should().Be("pkg:deb/debian/openssl@3.0.7");
result.Status.Should().BeOneOf(ResolutionStatus.Fixed, ResolutionStatus.Vulnerable,
ResolutionStatus.NotAffected, ResolutionStatus.Unknown);
result.ResolvedAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5));
}
[Fact(DisplayName = "POST /api/v1/resolve/vuln returns 400 for missing package")]
public async Task ResolveVuln_MissingPackage_Returns400()
{
// Arrange
var request = new { BuildId = "abc123" }; // Missing required Package field
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact(DisplayName = "POST /api/v1/resolve/vuln with CVE returns targeted resolution")]
public async Task ResolveVuln_WithCveId_ReturnsTargetedResolution()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:deb/debian/openssl@3.0.7",
CveId = "CVE-2024-0001",
BuildId = "abc123def456789"
};
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
[Fact(DisplayName = "Resolution includes cache headers")]
public async Task ResolveVuln_IncludesCacheHeaders()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:deb/debian/openssl@3.0.7"
};
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
// Assert
response.Headers.Should().ContainKey("X-RateLimit-Limit");
response.Headers.Should().ContainKey("X-RateLimit-Remaining");
}
#endregion
#region Batch Resolution Tests
[Fact(DisplayName = "POST /api/v1/resolve/vuln/batch handles multiple items")]
public async Task ResolveBatch_MultipleItems_ReturnsAllResults()
{
// Arrange
var request = new BatchVulnResolutionRequest
{
Items = new[]
{
new VulnResolutionRequest { Package = "pkg:deb/debian/openssl@3.0.7" },
new VulnResolutionRequest { Package = "pkg:deb/debian/libcurl@7.88.1" },
new VulnResolutionRequest { Package = "pkg:deb/debian/zlib@1.2.13" }
}
};
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln/batch", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<BatchVulnResolutionResponse>();
result.Should().NotBeNull();
result!.Results.Should().HaveCount(3);
}
[Fact(DisplayName = "Batch resolution respects size limit")]
public async Task ResolveBatch_ExceedsSizeLimit_Returns400()
{
// Arrange - Create 501 items (assuming 500 is the limit)
var items = Enumerable.Range(0, 501)
.Select(i => new VulnResolutionRequest { Package = $"pkg:npm/package{i}@1.0.0" })
.ToArray();
var request = new BatchVulnResolutionRequest { Items = items };
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln/batch", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact(DisplayName = "Batch resolution performance under 500ms for 100 cached items")]
public async Task ResolveBatch_CachedItems_PerformanceAcceptable()
{
// Arrange
var items = Enumerable.Range(0, 100)
.Select(i => new VulnResolutionRequest
{
Package = $"pkg:deb/debian/test-package{i}@1.0.0",
BuildId = $"build-{i}"
})
.ToArray();
var request = new BatchVulnResolutionRequest { Items = items };
// Warm up cache with first request
await _client.PostAsJsonAsync("/api/v1/resolve/vuln/batch", request);
// Act
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln/batch", request);
stopwatch.Stop();
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
stopwatch.ElapsedMilliseconds.Should().BeLessThan(500,
"Cached batch resolution should complete in under 500ms");
}
#endregion
#region Cache Tests
[Fact(DisplayName = "Second request returns cached result")]
public async Task ResolveVuln_SecondRequest_ReturnsCachedResult()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:deb/debian/openssl@3.0.7",
BuildId = "cache-test-build-id"
};
// Act
var response1 = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
var result1 = await response1.Content.ReadFromJsonAsync<VulnResolutionResponse>();
var response2 = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
var result2 = await response2.Content.ReadFromJsonAsync<VulnResolutionResponse>();
// Assert
result1.Should().NotBeNull();
result2.Should().NotBeNull();
result2!.FromCache.Should().BeTrue();
result1!.Status.Should().Be(result2.Status);
}
[Fact(DisplayName = "Bypass cache option works")]
public async Task ResolveVuln_BypassCache_FreshResult()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:deb/debian/openssl@3.0.7",
BuildId = "bypass-cache-test"
};
// First request to populate cache
await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
// Second request with bypass
_client.DefaultRequestHeaders.Add("X-Bypass-Cache", "true");
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
var result = await response.Content.ReadFromJsonAsync<VulnResolutionResponse>();
// Assert
result.Should().NotBeNull();
result!.FromCache.Should().BeFalse();
// Cleanup
_client.DefaultRequestHeaders.Remove("X-Bypass-Cache");
}
#endregion
#region DSSE Attestation Tests
[Fact(DisplayName = "Response includes DSSE attestation when requested")]
public async Task ResolveVuln_WithDsseRequest_IncludesAttestation()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:deb/debian/openssl@3.0.7",
BuildId = "dsse-test-build"
};
_client.DefaultRequestHeaders.Add("X-Include-Attestation", "true");
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
var result = await response.Content.ReadFromJsonAsync<VulnResolutionResponse>();
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
// Note: Attestation may be null if signing is not configured
// Cleanup
_client.DefaultRequestHeaders.Remove("X-Include-Attestation");
}
[Fact(DisplayName = "DSSE attestation is valid base64")]
public async Task ResolveVuln_DsseAttestation_IsValidBase64()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:deb/debian/openssl@3.0.7",
BuildId = "dsse-validation-test"
};
_client.DefaultRequestHeaders.Add("X-Include-Attestation", "true");
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
var result = await response.Content.ReadFromJsonAsync<VulnResolutionResponse>();
// Assert
if (!string.IsNullOrEmpty(result?.AttestationDsse))
{
// Should not throw
var bytes = Convert.FromBase64String(result.AttestationDsse);
bytes.Should().NotBeEmpty();
// Should be valid JSON
var json = System.Text.Encoding.UTF8.GetString(bytes);
var doc = JsonDocument.Parse(json);
doc.RootElement.TryGetProperty("payload", out _).Should().BeTrue();
doc.RootElement.TryGetProperty("payloadType", out _).Should().BeTrue();
}
// Cleanup
_client.DefaultRequestHeaders.Remove("X-Include-Attestation");
}
#endregion
#region Rate Limiting Tests
[Fact(DisplayName = "Rate limiting returns 429 when exceeded")]
public async Task ResolveVuln_RateLimitExceeded_Returns429()
{
// Arrange - This test depends on rate limit configuration
// Create a client with test tenant that has low rate limit
var request = new VulnResolutionRequest
{
Package = "pkg:npm/rate-limit-test@1.0.0"
};
_client.DefaultRequestHeaders.Add("X-Tenant-Id", "rate-limit-test-tenant");
// Act - Send many requests quickly
var tasks = Enumerable.Range(0, 150)
.Select(_ => _client.PostAsJsonAsync("/api/v1/resolve/vuln", request));
var responses = await Task.WhenAll(tasks);
// Assert - At least some should be rate limited
var rateLimited = responses.Where(r => r.StatusCode == HttpStatusCode.TooManyRequests);
// Note: This may pass or fail depending on actual rate limit config
// Cleanup
_client.DefaultRequestHeaders.Remove("X-Tenant-Id");
}
[Fact(DisplayName = "Rate limit headers are present")]
public async Task ResolveVuln_RateLimitHeaders_Present()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:npm/headers-test@1.0.0"
};
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
// Assert
response.Headers.Contains("X-RateLimit-Limit").Should().BeTrue();
response.Headers.Contains("X-RateLimit-Remaining").Should().BeTrue();
response.Headers.Contains("X-RateLimit-Reset").Should().BeTrue();
}
#endregion
#region Evidence Tests
[Fact(DisplayName = "Fixed resolution includes evidence")]
public async Task ResolveVuln_FixedStatus_IncludesEvidence()
{
// Arrange
var request = new VulnResolutionRequest
{
Package = "pkg:deb/debian/openssl@3.0.7-1+deb12u1",
BuildId = "fixed-binary-build-id",
DistroRelease = "debian:bookworm"
};
// Act
var response = await _client.PostAsJsonAsync("/api/v1/resolve/vuln", request);
var result = await response.Content.ReadFromJsonAsync<VulnResolutionResponse>();
// Assert
if (result?.Status == ResolutionStatus.Fixed)
{
result.Evidence.Should().NotBeNull();
result.Evidence!.MatchType.Should().NotBeNullOrEmpty();
result.Evidence.Confidence.Should().BeGreaterThan(0);
}
}
#endregion
}
/// <summary>
/// Placeholder for batch request if not in Contracts.
/// </summary>
public record BatchVulnResolutionRequest
{
public VulnResolutionRequest[] Items { get; init; } = Array.Empty<VulnResolutionRequest>();
public ResolutionOptions? Options { get; init; }
}
public record BatchVulnResolutionResponse
{
public VulnResolutionResponse[] Results { get; init; } = Array.Empty<VulnResolutionResponse>();
public int TotalCount { get; init; }
public int SuccessCount { get; init; }
public int ErrorCount { get; init; }
}
public record ResolutionOptions
{
public bool BypassCache { get; init; }
public bool IncludeDsseAttestation { get; init; }
}