Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.WebService.Tests/FindingsEvidenceControllerTests.cs
2026-01-22 19:08:46 +02:00

199 lines
8.0 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Moq;
using StellaOps.Scanner.Triage;
using StellaOps.Scanner.Triage.Entities;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class FindingsEvidenceControllerTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsNotFound_WhenFindingMissing()
{
using var secrets = new TestSurfaceSecretsScope();
var mockTriageService = new Mock<ITriageQueryService>();
mockTriageService.Setup(s => s.GetFindingAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((TriageFinding?)null);
await using var factory = new ScannerApplicationFactory().WithOverrides(
configuration => { configuration["scanner:authority:enabled"] = "false"; },
configureServices: services =>
{
services.RemoveAll<ITriageQueryService>();
services.AddSingleton(mockTriageService.Object);
});
await factory.InitializeAsync();
using var client = factory.CreateClient();
var response = await client.GetAsync($"/api/v1/findings/{Guid.NewGuid()}/evidence");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsForbidden_WhenRawScopeMissing()
{
using var secrets = new TestSurfaceSecretsScope();
await using var factory = new ScannerApplicationFactory().WithOverrides(
configuration => { configuration["scanner:authority:enabled"] = "false"; });
await factory.InitializeAsync();
using var client = factory.CreateClient();
var response = await client.GetAsync($"/api/v1/findings/{Guid.NewGuid()}/evidence?includeRaw=true");
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsEvidence_WhenFindingExists()
{
using var secrets = new TestSurfaceSecretsScope();
var findingId = Guid.NewGuid();
var now = DateTimeOffset.UtcNow;
var finding = new TriageFinding
{
Id = findingId,
AssetId = Guid.NewGuid(),
AssetLabel = "prod/api-gateway:1.2.3",
Purl = "pkg:npm/lodash@4.17.20",
CveId = "CVE-2024-12345",
FirstSeenAt = now,
LastSeenAt = now,
UpdatedAt = now
};
var mockTriageService = new Mock<ITriageQueryService>();
mockTriageService.Setup(s => s.GetFindingAsync(findingId.ToString(), It.IsAny<CancellationToken>()))
.ReturnsAsync(finding);
var mockEvidenceService = new Mock<IEvidenceCompositionService>();
mockEvidenceService.Setup(s => s.ComposeAsync(It.IsAny<TriageFinding>(), false, It.IsAny<CancellationToken>()))
.ReturnsAsync(new FindingEvidenceResponse
{
FindingId = findingId.ToString(),
Cve = "CVE-2024-12345",
Component = new ComponentInfo { Name = "lodash", Version = "4.17.20", Purl = "pkg:npm/lodash@4.17.20" },
LastSeen = now
});
await using var factory = new ScannerApplicationFactory().WithOverrides(
configuration => { configuration["scanner:authority:enabled"] = "false"; },
configureServices: services =>
{
services.RemoveAll<ITriageQueryService>();
services.AddSingleton(mockTriageService.Object);
services.RemoveAll<IEvidenceCompositionService>();
services.AddSingleton(mockEvidenceService.Object);
});
await factory.InitializeAsync();
using var client = factory.CreateClient();
var response = await client.GetAsync($"/api/v1/findings/{findingId}/evidence");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<FindingEvidenceResponse>(SerializerOptions);
Assert.NotNull(result);
Assert.Equal(findingId.ToString(), result!.FindingId);
Assert.Equal("CVE-2024-12345", result.Cve);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BatchEvidence_ReturnsBadRequest_WhenTooMany()
{
using var secrets = new TestSurfaceSecretsScope();
await using var factory = new ScannerApplicationFactory().WithOverrides(
configuration => { configuration["scanner:authority:enabled"] = "false"; });
await factory.InitializeAsync();
using var client = factory.CreateClient();
var request = new BatchEvidenceRequest
{
FindingIds = Enumerable.Range(0, 101).Select(_ => Guid.NewGuid().ToString()).ToList()
};
var response = await client.PostAsJsonAsync("/api/v1/findings/evidence/batch", request);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BatchEvidence_ReturnsResults_ForExistingFindings()
{
using var secrets = new TestSurfaceSecretsScope();
var findingId = Guid.NewGuid();
var now = DateTimeOffset.UtcNow;
var finding = new TriageFinding
{
Id = findingId,
AssetId = Guid.NewGuid(),
AssetLabel = "prod/api-gateway:1.2.3",
Purl = "pkg:npm/lodash@4.17.20",
CveId = "CVE-2024-12345",
FirstSeenAt = now,
LastSeenAt = now,
UpdatedAt = now
};
var mockTriageService = new Mock<ITriageQueryService>();
mockTriageService.Setup(s => s.GetFindingAsync(findingId.ToString(), It.IsAny<CancellationToken>()))
.ReturnsAsync(finding);
mockTriageService.Setup(s => s.GetFindingAsync(It.Is<string>(id => id != findingId.ToString()), It.IsAny<CancellationToken>()))
.ReturnsAsync((TriageFinding?)null);
var mockEvidenceService = new Mock<IEvidenceCompositionService>();
mockEvidenceService.Setup(s => s.ComposeAsync(It.IsAny<TriageFinding>(), false, It.IsAny<CancellationToken>()))
.ReturnsAsync(new FindingEvidenceResponse
{
FindingId = findingId.ToString(),
Cve = "CVE-2024-12345",
Component = new ComponentInfo { Name = "lodash", Version = "4.17.20", Purl = "pkg:npm/lodash@4.17.20" },
LastSeen = now
});
await using var factory = new ScannerApplicationFactory().WithOverrides(
configuration => { configuration["scanner:authority:enabled"] = "false"; },
configureServices: services =>
{
services.RemoveAll<ITriageQueryService>();
services.AddSingleton(mockTriageService.Object);
services.RemoveAll<IEvidenceCompositionService>();
services.AddSingleton(mockEvidenceService.Object);
});
await factory.InitializeAsync();
using var client = factory.CreateClient();
var request = new BatchEvidenceRequest
{
FindingIds = new[] { findingId.ToString(), Guid.NewGuid().ToString() }
};
var response = await client.PostAsJsonAsync("/api/v1/findings/evidence/batch", request);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<BatchEvidenceResponse>(SerializerOptions);
Assert.NotNull(result);
Assert.Single(result!.Findings);
Assert.Equal(findingId.ToString(), result.Findings[0].FindingId);
}
}