sprints work
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
|
||||
// Sprint: SPRINT_20260110_012_002_BINDEX
|
||||
// Task: GSA-009 - Integration Tests for Golden Set Authoring Flow
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.BinaryIndex.GoldenSet.Tests.Integration.Authoring;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for the golden set authoring workflow.
|
||||
/// Tests the end-to-end flow from extraction to review.
|
||||
/// </summary>
|
||||
[Trait("Category", "Integration")]
|
||||
public sealed class GoldenSetAuthoringIntegrationTests
|
||||
{
|
||||
private readonly FakeTimeProvider _timeProvider;
|
||||
private readonly GoldenSetValidator _validator;
|
||||
private readonly SinkRegistry _sinkRegistry;
|
||||
private readonly GoldenSetEnrichmentService _enrichmentService;
|
||||
private readonly GoldenSetReviewService _reviewService;
|
||||
|
||||
public GoldenSetAuthoringIntegrationTests()
|
||||
{
|
||||
_timeProvider = new FakeTimeProvider(DateTimeOffset.UtcNow);
|
||||
_validator = new GoldenSetValidator(new CveValidator());
|
||||
_sinkRegistry = new SinkRegistry(
|
||||
Options.Create(new SinkRegistryOptions()),
|
||||
NullLogger<SinkRegistry>.Instance);
|
||||
_enrichmentService = new GoldenSetEnrichmentService(
|
||||
_sinkRegistry,
|
||||
NullLogger<GoldenSetEnrichmentService>.Instance);
|
||||
_reviewService = new GoldenSetReviewService(
|
||||
_validator,
|
||||
_timeProvider,
|
||||
NullLogger<GoldenSetReviewService>.Instance);
|
||||
}
|
||||
|
||||
#region Full Authoring Workflow Tests
|
||||
|
||||
[Fact]
|
||||
public async Task FullAuthoringWorkflow_ValidCve_CompletesSuccessfully()
|
||||
{
|
||||
// Step 1: Create initial definition
|
||||
var definition = new GoldenSetDefinition
|
||||
{
|
||||
Id = "CVE-2024-TEST-001",
|
||||
Component = "openssl",
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "PKCS7_verify",
|
||||
Sinks = ["memcpy"],
|
||||
TaintInvariant = "Attacker-controlled PKCS7 data flows to unbounded memcpy"
|
||||
}
|
||||
],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "author@test.com",
|
||||
CreatedAt = _timeProvider.GetUtcNow(),
|
||||
SourceRef = "https://nvd.nist.gov/vuln/detail/CVE-2024-TEST-001",
|
||||
Tags = ["memory-corruption"]
|
||||
}
|
||||
};
|
||||
|
||||
// Step 2: Validate initial definition
|
||||
var validationResult = await _validator.ValidateAsync(definition);
|
||||
validationResult.IsValid.Should().BeTrue("initial definition should be valid");
|
||||
|
||||
// Step 3: Enrich with sink context
|
||||
var enriched = await _enrichmentService.EnrichAsync(definition);
|
||||
enriched.Should().NotBeNull();
|
||||
|
||||
// Step 4: Validate enriched definition
|
||||
validationResult = await _validator.ValidateAsync(enriched);
|
||||
validationResult.IsValid.Should().BeTrue("enriched definition should be valid");
|
||||
|
||||
// Step 5: Submit for review
|
||||
var reviewSubmission = await _reviewService.SubmitForReviewAsync(
|
||||
definition,
|
||||
"author@test.com",
|
||||
"Initial submission for review");
|
||||
reviewSubmission.Should().NotBeNull();
|
||||
reviewSubmission.ReviewId.Should().NotBeEmpty();
|
||||
|
||||
// Step 6: Approve review
|
||||
var approval = await _reviewService.ApproveAsync(
|
||||
reviewSubmission.ReviewId,
|
||||
"reviewer@test.com",
|
||||
"LGTM - verified against patch diff");
|
||||
approval.Should().NotBeNull();
|
||||
approval.Approved.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReviewWorkflow_RejectionAndResubmit_CompletesSuccessfully()
|
||||
{
|
||||
// Create initial incomplete definition
|
||||
var definition = new GoldenSetDefinition
|
||||
{
|
||||
Id = "CVE-2024-TEST-002",
|
||||
Component = "glibc",
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "strcpy", // Missing proper context
|
||||
Sinks = ImmutableArray<string>.Empty // Empty sinks
|
||||
}
|
||||
],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "author@test.com",
|
||||
CreatedAt = _timeProvider.GetUtcNow(),
|
||||
SourceRef = "https://nvd.nist.gov/vuln/detail/CVE-2024-TEST-002"
|
||||
}
|
||||
};
|
||||
|
||||
// Submit for review
|
||||
var reviewSubmission = await _reviewService.SubmitForReviewAsync(
|
||||
definition,
|
||||
"author@test.com",
|
||||
"First attempt");
|
||||
|
||||
// Reject with feedback
|
||||
var rejection = await _reviewService.RejectAsync(
|
||||
reviewSubmission.ReviewId,
|
||||
"reviewer@test.com",
|
||||
"Missing sink definitions. Please add vulnerable sinks.");
|
||||
rejection.Should().NotBeNull();
|
||||
rejection.Rejected.Should().BeTrue();
|
||||
|
||||
// Fix the issues and resubmit
|
||||
var fixedDefinition = definition with
|
||||
{
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "strcpy",
|
||||
Sinks = ["gets", "strcpy"],
|
||||
TaintInvariant = "User input flows to strcpy without length check",
|
||||
SourceFile = "glibc/string/strcpy.c"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Resubmit
|
||||
var resubmission = await _reviewService.SubmitForReviewAsync(
|
||||
fixedDefinition,
|
||||
"author@test.com",
|
||||
"Fixed: Added sinks and taint invariant");
|
||||
resubmission.ReviewId.Should().NotBe(reviewSubmission.ReviewId);
|
||||
|
||||
// Now approve
|
||||
var approval = await _reviewService.ApproveAsync(
|
||||
resubmission.ReviewId,
|
||||
"reviewer@test.com",
|
||||
"Approved after fixes");
|
||||
approval.Approved.Should().BeTrue();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enrichment Tests
|
||||
|
||||
[Fact]
|
||||
public async Task EnrichAsync_WithKnownSinks_AddsContext()
|
||||
{
|
||||
var definition = new GoldenSetDefinition
|
||||
{
|
||||
Id = "CVE-2024-TEST-003",
|
||||
Component = "openssl",
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "X509_NAME_oneline",
|
||||
Sinks = ["memcpy"]
|
||||
}
|
||||
],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "author@test.com",
|
||||
CreatedAt = _timeProvider.GetUtcNow(),
|
||||
SourceRef = "https://example.com"
|
||||
}
|
||||
};
|
||||
|
||||
var enriched = await _enrichmentService.EnrichAsync(definition);
|
||||
|
||||
enriched.Should().NotBeNull();
|
||||
// Enrichment should preserve original data
|
||||
enriched.Id.Should().Be(definition.Id);
|
||||
enriched.Targets.Should().HaveCount(1);
|
||||
enriched.Targets[0].Sinks.Should().Contain("memcpy");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnrichAsync_EmptyTargets_ReturnsOriginal()
|
||||
{
|
||||
var definition = new GoldenSetDefinition
|
||||
{
|
||||
Id = "CVE-2024-TEST-004",
|
||||
Component = "unknown-component",
|
||||
Targets = [],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "author@test.com",
|
||||
CreatedAt = _timeProvider.GetUtcNow(),
|
||||
SourceRef = "https://example.com"
|
||||
}
|
||||
};
|
||||
|
||||
var enriched = await _enrichmentService.EnrichAsync(definition);
|
||||
|
||||
enriched.Should().NotBeNull();
|
||||
enriched.Targets.Should().BeEmpty();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Validation Integration Tests
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_InvalidCveId_ReturnsErrors()
|
||||
{
|
||||
var definition = new GoldenSetDefinition
|
||||
{
|
||||
Id = "INVALID-CVE-FORMAT",
|
||||
Component = "openssl",
|
||||
Targets = [],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "author@test.com",
|
||||
CreatedAt = _timeProvider.GetUtcNow(),
|
||||
SourceRef = "https://example.com"
|
||||
}
|
||||
};
|
||||
|
||||
var result = await _validator.ValidateAsync(definition);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_ContentDigest_IsDeterministic()
|
||||
{
|
||||
var definition = new GoldenSetDefinition
|
||||
{
|
||||
Id = "CVE-2024-TEST-005",
|
||||
Component = "openssl",
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "SSL_read",
|
||||
Sinks = ["recv"]
|
||||
}
|
||||
],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "author@test.com",
|
||||
CreatedAt = new DateTimeOffset(2024, 1, 15, 10, 30, 0, TimeSpan.Zero),
|
||||
SourceRef = "https://example.com"
|
||||
}
|
||||
};
|
||||
|
||||
var result1 = await _validator.ValidateAsync(definition);
|
||||
var result2 = await _validator.ValidateAsync(definition);
|
||||
|
||||
result1.ContentDigest.Should().Be(result2.ContentDigest);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sink Registry Integration Tests
|
||||
|
||||
[Fact]
|
||||
public void SinkRegistry_LookupKnownSink_ReturnsContext()
|
||||
{
|
||||
var context = _sinkRegistry.GetSinkContext("memcpy");
|
||||
|
||||
context.Should().NotBeNull();
|
||||
context!.Category.Should().Be("memory");
|
||||
context.CweIds.Should().Contain("CWE-120");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SinkRegistry_LookupUnknownSink_ReturnsNull()
|
||||
{
|
||||
var context = _sinkRegistry.GetSinkContext("unknown_function_xyz");
|
||||
|
||||
context.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SinkRegistry_GetSinksByCategory_ReturnsMatching()
|
||||
{
|
||||
var memorySinks = _sinkRegistry.GetSinksByCategory("memory");
|
||||
|
||||
memorySinks.Should().NotBeEmpty();
|
||||
memorySinks.Should().Contain("memcpy");
|
||||
memorySinks.Should().Contain("strcpy");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Fact]
|
||||
public async Task FullWorkflow_WithGhsaId_CompletesSuccessfully()
|
||||
{
|
||||
var definition = new GoldenSetDefinition
|
||||
{
|
||||
Id = "GHSA-abcd-1234-efgh",
|
||||
Component = "nodejs-package",
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "parseInput",
|
||||
Sinks = ["eval"]
|
||||
}
|
||||
],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "author@test.com",
|
||||
CreatedAt = _timeProvider.GetUtcNow(),
|
||||
SourceRef = "https://github.com/advisories/GHSA-abcd-1234-efgh"
|
||||
}
|
||||
};
|
||||
|
||||
var validationResult = await _validator.ValidateAsync(definition);
|
||||
validationResult.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FullWorkflow_WithMultipleTargets_CompletesSuccessfully()
|
||||
{
|
||||
var definition = new GoldenSetDefinition
|
||||
{
|
||||
Id = "CVE-2024-TEST-006",
|
||||
Component = "libxml2",
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "xmlParseEntity",
|
||||
Sinks = ["memcpy"],
|
||||
TaintInvariant = "XML entity expansion leads to buffer overflow"
|
||||
},
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "xmlStringGetNodeList",
|
||||
Sinks = ["realloc"],
|
||||
TaintInvariant = "Malformed entity reference causes realloc with wrong size"
|
||||
},
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "xmlNodeAddContent",
|
||||
Sinks = ["strcpy"],
|
||||
TaintInvariant = "Entity content copied without bounds check"
|
||||
}
|
||||
],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "security-team@test.com",
|
||||
CreatedAt = _timeProvider.GetUtcNow(),
|
||||
SourceRef = "https://nvd.nist.gov/vuln/detail/CVE-2024-TEST-006",
|
||||
Tags = ["xxe", "xml-entity-expansion", "memory-corruption"]
|
||||
}
|
||||
};
|
||||
|
||||
var validationResult = await _validator.ValidateAsync(definition);
|
||||
validationResult.IsValid.Should().BeTrue();
|
||||
validationResult.ContentDigest.Should().NotBeNullOrEmpty();
|
||||
|
||||
var enriched = await _enrichmentService.EnrichAsync(definition);
|
||||
enriched.Targets.Should().HaveCount(3);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
|
||||
// Sprint: SPRINT_20260110_012_001_BINDEX
|
||||
// Task: GSF-010 - PostgreSQL Integration Tests for Golden Set Store
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using Npgsql;
|
||||
using Testcontainers.PostgreSql;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.BinaryIndex.GoldenSet.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for <see cref="PostgresGoldenSetStore"/> using Testcontainers.
|
||||
/// </summary>
|
||||
[Trait("Category", "Integration")]
|
||||
public sealed class PostgresGoldenSetStoreTests : IAsyncLifetime
|
||||
{
|
||||
private PostgreSqlContainer _postgres = null!;
|
||||
private NpgsqlDataSource _dataSource = null!;
|
||||
private PostgresGoldenSetStore _store = null!;
|
||||
private FakeTimeProvider _timeProvider = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_postgres = new PostgreSqlBuilder()
|
||||
.WithImage("postgres:16-alpine")
|
||||
.WithDatabase("goldensets_test")
|
||||
.WithUsername("test")
|
||||
.WithPassword("test")
|
||||
.Build();
|
||||
|
||||
await _postgres.StartAsync();
|
||||
|
||||
var connectionString = _postgres.GetConnectionString();
|
||||
_dataSource = NpgsqlDataSource.Create(connectionString);
|
||||
|
||||
// Run migration
|
||||
await RunMigrationAsync();
|
||||
|
||||
_timeProvider = new FakeTimeProvider(DateTimeOffset.UtcNow);
|
||||
var validator = new GoldenSetValidator(new CveValidator());
|
||||
var options = Options.Create(new GoldenSetOptions());
|
||||
var logger = NullLogger<PostgresGoldenSetStore>.Instance;
|
||||
|
||||
_store = new PostgresGoldenSetStore(
|
||||
_dataSource,
|
||||
validator,
|
||||
_timeProvider,
|
||||
options,
|
||||
logger);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
await _dataSource.DisposeAsync();
|
||||
await _postgres.DisposeAsync();
|
||||
}
|
||||
|
||||
private async Task RunMigrationAsync()
|
||||
{
|
||||
var migrationSql = await File.ReadAllTextAsync(GetMigrationPath());
|
||||
|
||||
await using var conn = await _dataSource.OpenConnectionAsync();
|
||||
await using var cmd = new NpgsqlCommand(migrationSql, conn);
|
||||
await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
private static string GetMigrationPath()
|
||||
{
|
||||
// Navigate from bin/Debug/net10.0 to the Migrations folder
|
||||
var baseDir = AppContext.BaseDirectory;
|
||||
var projectDir = Path.GetFullPath(Path.Combine(baseDir, "..", "..", "..", "..", ".."));
|
||||
return Path.Combine(projectDir, "__Libraries", "StellaOps.BinaryIndex.GoldenSet", "Migrations", "V1_0_0__initial_schema.sql");
|
||||
}
|
||||
|
||||
#region Store Tests
|
||||
|
||||
[Fact]
|
||||
public async Task StoreAsync_ValidDefinition_ReturnsSuccessWithDigest()
|
||||
{
|
||||
// Arrange
|
||||
var definition = CreateTestDefinition("CVE-2024-0001");
|
||||
|
||||
// Act
|
||||
var result = await _store.StoreAsync(definition);
|
||||
|
||||
// Assert
|
||||
result.Success.Should().BeTrue();
|
||||
result.ContentDigest.Should().NotBeNullOrEmpty();
|
||||
result.WasUpdated.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StoreAsync_DuplicateId_UpdatesAndReturnsWasUpdated()
|
||||
{
|
||||
// Arrange
|
||||
var definition1 = CreateTestDefinition("CVE-2024-0002");
|
||||
await _store.StoreAsync(definition1);
|
||||
|
||||
var definition2 = definition1 with
|
||||
{
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "different_function",
|
||||
Sinks = ["strcat"]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _store.StoreAsync(definition2);
|
||||
|
||||
// Assert
|
||||
result.Success.Should().BeTrue();
|
||||
result.WasUpdated.Should().BeTrue();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetById Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ExistingId_ReturnsDefinition()
|
||||
{
|
||||
// Arrange
|
||||
var definition = CreateTestDefinition("CVE-2024-0003");
|
||||
await _store.StoreAsync(definition);
|
||||
|
||||
// Act
|
||||
var retrieved = await _store.GetByIdAsync("CVE-2024-0003");
|
||||
|
||||
// Assert
|
||||
retrieved.Should().NotBeNull();
|
||||
retrieved!.Id.Should().Be("CVE-2024-0003");
|
||||
retrieved.Component.Should().Be(definition.Component);
|
||||
retrieved.Targets.Should().HaveCount(definition.Targets.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_NonExistingId_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
var retrieved = await _store.GetByIdAsync("CVE-NONEXISTENT");
|
||||
|
||||
// Assert
|
||||
retrieved.Should().BeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetByDigest Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetByDigestAsync_ExistingDigest_ReturnsDefinition()
|
||||
{
|
||||
// Arrange
|
||||
var definition = CreateTestDefinition("CVE-2024-0004");
|
||||
var storeResult = await _store.StoreAsync(definition);
|
||||
|
||||
// Act
|
||||
var retrieved = await _store.GetByDigestAsync(storeResult.ContentDigest);
|
||||
|
||||
// Assert
|
||||
retrieved.Should().NotBeNull();
|
||||
retrieved!.Id.Should().Be("CVE-2024-0004");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region List Tests
|
||||
|
||||
[Fact]
|
||||
public async Task ListAsync_WithComponentFilter_ReturnsMatching()
|
||||
{
|
||||
// Arrange
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0010", "openssl"));
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0011", "glibc"));
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0012", "openssl"));
|
||||
|
||||
var query = new GoldenSetListQuery { ComponentFilter = "openssl" };
|
||||
|
||||
// Act
|
||||
var results = await _store.ListAsync(query);
|
||||
|
||||
// Assert
|
||||
results.Should().HaveCount(2);
|
||||
results.Should().AllSatisfy(r => r.Component.Should().Be("openssl"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAsync_WithStatusFilter_ReturnsMatching()
|
||||
{
|
||||
// Arrange
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0020"), GoldenSetStatus.Draft);
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0021"), GoldenSetStatus.Approved);
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0022"), GoldenSetStatus.Draft);
|
||||
|
||||
var query = new GoldenSetListQuery { StatusFilter = GoldenSetStatus.Draft };
|
||||
|
||||
// Act
|
||||
var results = await _store.ListAsync(query);
|
||||
|
||||
// Assert
|
||||
results.Should().HaveCountGreaterThanOrEqualTo(2);
|
||||
results.Should().AllSatisfy(r => r.Status.Should().Be(GoldenSetStatus.Draft));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAsync_WithPagination_ReturnsCorrectPage()
|
||||
{
|
||||
// Arrange
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
await _store.StoreAsync(CreateTestDefinition($"CVE-2024-003{i}"));
|
||||
}
|
||||
|
||||
var query = new GoldenSetListQuery
|
||||
{
|
||||
Limit = 2,
|
||||
Offset = 2,
|
||||
OrderBy = GoldenSetOrderBy.IdAsc
|
||||
};
|
||||
|
||||
// Act
|
||||
var results = await _store.ListAsync(query);
|
||||
|
||||
// Assert
|
||||
results.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateStatus Tests
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateStatusAsync_ValidTransition_UpdatesStatus()
|
||||
{
|
||||
// Arrange
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0040"), GoldenSetStatus.Draft);
|
||||
|
||||
// Act
|
||||
var result = await _store.UpdateStatusAsync(
|
||||
"CVE-2024-0040",
|
||||
GoldenSetStatus.InReview,
|
||||
"reviewer@test.com",
|
||||
"Submitting for review");
|
||||
|
||||
// Assert
|
||||
result.Success.Should().BeTrue();
|
||||
|
||||
var stored = await _store.GetAsync("CVE-2024-0040");
|
||||
stored.Should().NotBeNull();
|
||||
stored!.Status.Should().Be(GoldenSetStatus.InReview);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateStatusAsync_NonExistingId_ReturnsFailure()
|
||||
{
|
||||
// Act
|
||||
var result = await _store.UpdateStatusAsync(
|
||||
"CVE-NONEXISTENT",
|
||||
GoldenSetStatus.Approved,
|
||||
"reviewer@test.com",
|
||||
"Approving");
|
||||
|
||||
// Assert
|
||||
result.Success.Should().BeFalse();
|
||||
result.Error.Should().Contain("not found");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetByComponent Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetByComponentAsync_ReturnsMatchingDefinitions()
|
||||
{
|
||||
// Arrange
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0050", "libcurl"), GoldenSetStatus.Approved);
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0051", "libcurl"), GoldenSetStatus.Approved);
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0052", "zlib"), GoldenSetStatus.Approved);
|
||||
|
||||
// Act
|
||||
var results = await _store.GetByComponentAsync("libcurl", GoldenSetStatus.Approved);
|
||||
|
||||
// Assert
|
||||
results.Should().HaveCount(2);
|
||||
results.Should().AllSatisfy(d => d.Component.Should().Be("libcurl"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delete Tests
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_ExistingId_ArchivesAndReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0060"));
|
||||
|
||||
// Act
|
||||
var deleted = await _store.DeleteAsync("CVE-2024-0060");
|
||||
|
||||
// Assert
|
||||
deleted.Should().BeTrue();
|
||||
|
||||
var stored = await _store.GetAsync("CVE-2024-0060");
|
||||
stored.Should().NotBeNull();
|
||||
stored!.Status.Should().Be(GoldenSetStatus.Archived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAsync_NonExistingId_ReturnsFalse()
|
||||
{
|
||||
// Act
|
||||
var deleted = await _store.DeleteAsync("CVE-NONEXISTENT");
|
||||
|
||||
// Assert
|
||||
deleted.Should().BeFalse();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AuditLog Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetAuditLogAsync_ReturnsAuditEntries()
|
||||
{
|
||||
// Arrange
|
||||
await _store.StoreAsync(CreateTestDefinition("CVE-2024-0070"));
|
||||
await _store.UpdateStatusAsync(
|
||||
"CVE-2024-0070",
|
||||
GoldenSetStatus.InReview,
|
||||
"reviewer1@test.com",
|
||||
"First review");
|
||||
await _store.UpdateStatusAsync(
|
||||
"CVE-2024-0070",
|
||||
GoldenSetStatus.Approved,
|
||||
"reviewer2@test.com",
|
||||
"Approved after review");
|
||||
|
||||
// Act
|
||||
var auditLog = await _store.GetAuditLogAsync("CVE-2024-0070");
|
||||
|
||||
// Assert
|
||||
auditLog.Should().HaveCountGreaterThanOrEqualTo(3); // created + 2 status changes
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Content Addressability Tests
|
||||
|
||||
[Fact]
|
||||
public async Task ContentDigest_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
var definition = CreateTestDefinition("CVE-2024-0080");
|
||||
|
||||
// Act
|
||||
var result1 = await _store.StoreAsync(definition);
|
||||
var retrieved = await _store.GetByIdAsync("CVE-2024-0080");
|
||||
|
||||
// Delete and re-store the same definition
|
||||
await _store.DeleteAsync("CVE-2024-0080");
|
||||
|
||||
// Need to store again with same content - digest should match
|
||||
var definition2 = retrieved!;
|
||||
var result2 = await _store.StoreAsync(definition2);
|
||||
|
||||
// Assert
|
||||
result1.ContentDigest.Should().Be(result2.ContentDigest);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private static GoldenSetDefinition CreateTestDefinition(string id, string component = "openssl")
|
||||
{
|
||||
return new GoldenSetDefinition
|
||||
{
|
||||
Id = id,
|
||||
Component = component,
|
||||
Targets =
|
||||
[
|
||||
new VulnerableTarget
|
||||
{
|
||||
FunctionName = "vulnerable_function",
|
||||
Sinks = ["memcpy", "strcpy"],
|
||||
Edges =
|
||||
[
|
||||
new BasicBlockEdge { From = "bb0", To = "bb1" },
|
||||
new BasicBlockEdge { From = "bb1", To = "bb2" }
|
||||
],
|
||||
TaintInvariant = "attacker-controlled input reaches sink without bounds check"
|
||||
}
|
||||
],
|
||||
Metadata = new GoldenSetMetadata
|
||||
{
|
||||
AuthorId = "test@example.com",
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
SourceRef = $"https://nvd.nist.gov/vuln/detail/{id}",
|
||||
Tags = ["memory-corruption", "heap-overflow"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
|
||||
<PackageReference Include="Npgsql" />
|
||||
<PackageReference Include="NSubstitute" />
|
||||
<PackageReference Include="Testcontainers.PostgreSql" />
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user