using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using StellaOps.BinaryIndex.GroundTruth.SecDb.Configuration; using StellaOps.BinaryIndex.GroundTruth.SecDb.Tests.Fixtures; using Xunit; namespace StellaOps.BinaryIndex.GroundTruth.SecDb.Tests; /// /// Integration tests for SecDb connector. /// These tests require network access to gitlab.alpinelinux.org. /// Skip in CI by setting SKIP_INTEGRATION_TESTS=true. /// [Trait("Category", "Integration")] public class SecDbConnectorIntegrationTests : IAsyncLifetime { private ServiceProvider? _services; private readonly bool _skipTests; public SecDbConnectorIntegrationTests() { _skipTests = Environment.GetEnvironmentVariable("SKIP_INTEGRATION_TESTS")?.ToLowerInvariant() == "true" || Environment.GetEnvironmentVariable("CI")?.ToLowerInvariant() == "true"; } public ValueTask InitializeAsync() { if (_skipTests) return ValueTask.CompletedTask; var services = new ServiceCollection(); services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); services.AddSecDbConnector(opts => { opts.Branches = ["v3.19"]; opts.Repositories = ["main"]; opts.TimeoutSeconds = 120; opts.FetchAports = false; // Don't fetch aports for integration tests }); _services = services.BuildServiceProvider(); return ValueTask.CompletedTask; } public ValueTask DisposeAsync() { _services?.Dispose(); return ValueTask.CompletedTask; } [Fact] [Trait("Category", "NetworkIntegration")] public async Task SecDbConnector_CanTestConnectivity() { Skip.If(_skipTests, "Integration tests skipped"); // Arrange var connector = _services!.GetRequiredService(); // Act try { var result = await connector.TestConnectivityAsync(); // Assert - only if network is available result.IsConnected.Should().BeTrue("Should be able to connect to Alpine GitLab"); result.Latency.Should().BeLessThan(TimeSpan.FromSeconds(30)); } catch (HttpRequestException) { // Network unavailable - skip test Skip.If(true, "Network unavailable"); } } [Fact] public async Task SecDbConnector_CanGetMetadata() { Skip.If(_skipTests, "Integration tests skipped"); // Arrange var connector = _services!.GetRequiredService(); // Act var metadata = await connector.GetMetadataAsync(); // Assert metadata.SourceId.Should().Be("secdb-alpine"); metadata.DisplayName.Should().Contain("Alpine"); metadata.BaseUrl.Should().Contain("gitlab.alpinelinux.org"); } [Fact] public void SecDbConnector_HasCorrectProperties() { Skip.If(_skipTests, "Integration tests skipped"); // Arrange var connector = _services!.GetRequiredService(); // Assert connector.SourceId.Should().Be("secdb-alpine"); connector.DisplayName.Should().Contain("SecDB"); connector.SupportedDistros.Should().Contain("alpine"); } [Fact] [Trait("Category", "NetworkIntegration")] public async Task SecDbConnector_FetchAndGetVulnerabilities_ReturnsData() { Skip.If(_skipTests, "Integration tests skipped"); // Arrange var connector = _services!.GetRequiredService(); try { // First fetch the data await connector.FetchAsync(_services!, CancellationToken.None); // Act - get vulnerabilities for a well-known package var vulnerabilities = await connector.GetVulnerabilitiesForPackageAsync("curl"); // Assert vulnerabilities.Should().NotBeEmpty("curl should have known vulnerabilities"); vulnerabilities.Should().OnlyContain(v => v.CveId.StartsWith("CVE-")); } catch (HttpRequestException) { // Network unavailable - skip test Skip.If(true, "Network unavailable"); } } } /// /// Provides Skip functionality for xUnit when condition is true. /// public static class Skip { public static void If(bool condition, string reason) { if (condition) { throw new SkipException(reason); } } } /// /// Exception to skip a test. /// public class SkipException : Exception { public SkipException(string reason) : base(reason) { } } /// /// Test meter factory for diagnostics. /// internal sealed class TestMeterFactory : System.Diagnostics.Metrics.IMeterFactory { public System.Diagnostics.Metrics.Meter Create(System.Diagnostics.Metrics.MeterOptions options) => new(options.Name, options.Version); public void Dispose() { } }