199 lines
8.0 KiB
C#
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);
|
|
}
|
|
}
|