partly or unimplemented features - now implemented

This commit is contained in:
master
2026-02-09 08:53:51 +02:00
parent 1bf6bbf395
commit 4bdc298ec1
674 changed files with 90194 additions and 2271 deletions

View File

@@ -0,0 +1,520 @@
// <copyright file="AstraConnectorIntegrationTests.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using StellaOps.Concelier.Connector.Astra.Configuration;
using StellaOps.Concelier.Connector.Astra.Internal;
using StellaOps.Concelier.Connector.Common;
using StellaOps.Concelier.Connector.Common.Fetch;
using StellaOps.Concelier.Documents;
using StellaOps.Concelier.Models;
using StellaOps.Concelier.Storage;
using StellaOps.Concelier.Storage.Advisories;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Concelier.Connector.Astra.Tests;
/// <summary>
/// Integration tests for Astra Linux connector with OVAL parsing.
/// Sprint: SPRINT_20260208_034_Concelier_astra_linux_oval_feed_connector
/// </summary>
public sealed class AstraConnectorIntegrationTests
{
[Trait("Category", TestCategories.Integration)]
[Fact]
public void OvalParser_IntegratedWithConnector_ParsesCompleteOval()
{
// Arrange
var parser = new OvalParser(NullLogger<OvalParser>.Instance);
var ovalXml = CreateCompleteAstraOvalFeed();
// Act
var definitions = parser.Parse(ovalXml);
// Assert
definitions.Should().HaveCount(3);
definitions[0].DefinitionId.Should().Be("oval:ru.astra:def:20240001");
definitions[0].Title.Should().Be("OpenSSL vulnerability in Astra Linux");
definitions[0].CveIds.Should().Contain("CVE-2024-0727");
definitions[0].Severity.Should().Be("High");
definitions[0].AffectedPackages.Should().HaveCount(1);
definitions[0].AffectedPackages[0].PackageName.Should().Be("openssl");
definitions[0].AffectedPackages[0].FixedVersion.Should().Be("1.1.1w-0+deb11u1+astra3");
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public void MapToAdvisory_ViaReflection_ProducesValidAdvisory()
{
// Arrange - Use reflection to call private MapToAdvisory method
var connector = CreateConnector();
var definition = CreateTestDefinition();
var recordedAt = DateTimeOffset.Parse("2024-06-15T12:00:00Z");
// Use reflection to access private method
var mapMethod = typeof(AstraConnector)
.GetMethod("MapToAdvisory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
mapMethod.Should().NotBeNull("MapToAdvisory method should exist");
// Act
var advisory = (Advisory)mapMethod!.Invoke(connector, new object[] { definition, recordedAt })!;
// Assert
advisory.Should().NotBeNull();
advisory.AdvisoryKey.Should().Be("CVE-2024-12345");
advisory.Title.Should().Be("Test Vulnerability");
advisory.Description.Should().Be("A test vulnerability description");
advisory.Severity.Should().NotBeNull();
advisory.Language.Should().Be("ru");
advisory.Published.Should().NotBeNull();
advisory.AffectedPackages.Should().HaveCount(1);
advisory.Provenance.Should().HaveCount(1);
advisory.Provenance[0].Source.Should().Be("distro-astra");
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public void MapToAdvisory_WithMultipleCves_FirstCveIsKey()
{
// Arrange
var connector = CreateConnector();
var definition = new AstraVulnerabilityDefinitionBuilder()
.WithDefinitionId("oval:ru.astra:def:20240099")
.WithCves("CVE-2024-11111", "CVE-2024-22222", "CVE-2024-33333")
.Build();
var recordedAt = DateTimeOffset.UtcNow;
var mapMethod = typeof(AstraConnector)
.GetMethod("MapToAdvisory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
// Act
var advisory = (Advisory)mapMethod.Invoke(connector, new object[] { definition, recordedAt })!;
// Assert
advisory.AdvisoryKey.Should().Be("CVE-2024-11111");
advisory.Aliases.Should().HaveCount(2);
advisory.Aliases.Should().Contain("CVE-2024-22222");
advisory.Aliases.Should().Contain("CVE-2024-33333");
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public void MapToAdvisory_WithNoCves_UsesDefinitionId()
{
// Arrange
var connector = CreateConnector();
var definition = new AstraVulnerabilityDefinitionBuilder()
.WithDefinitionId("oval:ru.astra:def:20240100")
.WithTitle("No CVE Advisory")
.WithCves() // Empty CVE list
.Build();
var recordedAt = DateTimeOffset.UtcNow;
var mapMethod = typeof(AstraConnector)
.GetMethod("MapToAdvisory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
// Act
var advisory = (Advisory)mapMethod.Invoke(connector, new object[] { definition, recordedAt })!;
// Assert
advisory.AdvisoryKey.Should().Be("oval:ru.astra:def:20240100");
advisory.Aliases.Should().BeEmpty();
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public void MapToAdvisory_AffectedPackages_UseDebPackageType()
{
// Arrange
var connector = CreateConnector();
var definition = new AstraVulnerabilityDefinitionBuilder()
.WithPackage("openssl", fixedVersion: "1.1.1w-0+deb11u1+astra3")
.WithPackage("curl", fixedVersion: "7.74.0-1.3+deb11u8")
.Build();
var recordedAt = DateTimeOffset.UtcNow;
var mapMethod = typeof(AstraConnector)
.GetMethod("MapToAdvisory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
// Act
var advisory = (Advisory)mapMethod.Invoke(connector, new object[] { definition, recordedAt })!;
// Assert
advisory.AffectedPackages.Should().HaveCount(2);
foreach (var pkg in advisory.AffectedPackages)
{
pkg.Type.Should().Be(AffectedPackageTypes.Deb);
pkg.Platform.Should().Be("astra-linux");
pkg.VersionRanges.Should().HaveCount(1);
pkg.VersionRanges[0].RangeKind.Should().Be("evr");
}
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public void MapToAdvisory_VersionRange_CorrectExpression()
{
// Arrange
var connector = CreateConnector();
var definition = new AstraVulnerabilityDefinitionBuilder()
.WithPackage("test-pkg", minVersion: "1.0.0", maxVersion: "1.0.5", fixedVersion: "1.0.6")
.Build();
var recordedAt = DateTimeOffset.UtcNow;
var mapMethod = typeof(AstraConnector)
.GetMethod("MapToAdvisory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
// Act
var advisory = (Advisory)mapMethod.Invoke(connector, new object[] { definition, recordedAt })!;
// Assert
advisory.AffectedPackages.Should().HaveCount(1);
var range = advisory.AffectedPackages[0].VersionRanges[0];
range.IntroducedVersion.Should().Be("1.0.0");
range.FixedVersion.Should().Be("1.0.6");
range.LastAffectedVersion.Should().Be("1.0.5");
range.RangeExpression.Should().Be(">=1.0.0, <1.0.6");
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public void EndToEnd_ParseAndMap_ProducesConsistentAdvisories()
{
// Arrange
var parser = new OvalParser(NullLogger<OvalParser>.Instance);
var connector = CreateConnector();
var ovalXml = CreateSingleDefinitionOval();
var recordedAt = DateTimeOffset.Parse("2024-06-15T12:00:00Z");
var mapMethod = typeof(AstraConnector)
.GetMethod("MapToAdvisory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
// Act
var definitions = parser.Parse(ovalXml);
var advisories = definitions
.Select(d => (Advisory)mapMethod.Invoke(connector, new object[] { d, recordedAt })!)
.ToList();
// Assert
advisories.Should().HaveCount(1);
var advisory = advisories[0];
advisory.AdvisoryKey.Should().Be("CVE-2024-12345");
advisory.Title.Should().Be("Test OpenSSL Vulnerability");
advisory.AffectedPackages.Should().HaveCount(1);
advisory.AffectedPackages[0].Identifier.Should().Be("openssl");
}
[Trait("Category", TestCategories.Integration)]
[Fact]
public void EndToEnd_DeterministicOutput_SameInputProducesSameResult()
{
// Arrange
var parser = new OvalParser(NullLogger<OvalParser>.Instance);
var connector = CreateConnector();
var ovalXml = CreateSingleDefinitionOval();
var recordedAt = DateTimeOffset.Parse("2024-06-15T12:00:00Z");
var mapMethod = typeof(AstraConnector)
.GetMethod("MapToAdvisory", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
// Act - Run twice
var definitions1 = parser.Parse(ovalXml);
var advisory1 = (Advisory)mapMethod.Invoke(connector, new object[] { definitions1[0], recordedAt })!;
var definitions2 = parser.Parse(ovalXml);
var advisory2 = (Advisory)mapMethod.Invoke(connector, new object[] { definitions2[0], recordedAt })!;
// Assert - Results should be identical
advisory1.AdvisoryKey.Should().Be(advisory2.AdvisoryKey);
advisory1.Title.Should().Be(advisory2.Title);
advisory1.Description.Should().Be(advisory2.Description);
advisory1.AffectedPackages.Should().HaveCount(advisory2.AffectedPackages.Length);
advisory1.AffectedPackages[0].Identifier.Should().Be(advisory2.AffectedPackages[0].Identifier);
}
#region Test Fixtures
private static AstraConnector CreateConnector()
{
var options = new AstraOptions
{
BulletinBaseUri = new Uri("https://astra.ru/en/support/security-bulletins/"),
OvalRepositoryUri = new Uri("https://download.astralinux.ru/astra/stable/oval/"),
RequestTimeout = TimeSpan.FromSeconds(120),
RequestDelay = TimeSpan.FromMilliseconds(500),
FailureBackoff = TimeSpan.FromMinutes(15),
MaxDefinitionsPerFetch = 100,
InitialBackfill = TimeSpan.FromDays(365),
ResumeOverlap = TimeSpan.FromDays(7),
UserAgent = "StellaOps.Concelier.Astra/0.1 (+https://stella-ops.org)"
};
var documentStore = new Mock<IDocumentStore>(MockBehavior.Strict).Object;
var dtoStore = new Mock<IDtoStore>(MockBehavior.Strict).Object;
var advisoryStore = new Mock<IAdvisoryStore>(MockBehavior.Strict).Object;
var stateRepository = new Mock<ISourceStateRepository>(MockBehavior.Strict).Object;
return new AstraConnector(
null!,
null!,
documentStore,
dtoStore,
advisoryStore,
stateRepository,
Options.Create(options),
TimeProvider.System,
NullLogger<AstraConnector>.Instance);
}
private static AstraVulnerabilityDefinition CreateTestDefinition()
{
return new AstraVulnerabilityDefinitionBuilder()
.WithDefinitionId("oval:ru.astra:def:20240001")
.WithTitle("Test Vulnerability")
.WithDescription("A test vulnerability description")
.WithCves("CVE-2024-12345")
.WithSeverity("High")
.WithPublishedDate(DateTimeOffset.Parse("2024-01-15T00:00:00Z"))
.WithPackage("openssl", fixedVersion: "1.1.1w-0+deb11u1+astra3")
.Build();
}
private static string CreateCompleteAstraOvalFeed()
{
return @"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5""
xmlns:linux=""http://oval.mitre.org/XMLSchema/oval-definitions-5#linux"">
<definitions>
<definition id=""oval:ru.astra:def:20240001"" class=""vulnerability"">
<metadata>
<title>OpenSSL vulnerability in Astra Linux</title>
<description>A buffer overflow in OpenSSL affects Astra Linux.</description>
<reference ref_id=""CVE-2024-0727"" source=""CVE""/>
<advisory>
<severity>High</severity>
<issued date=""2024-01-20""/>
</advisory>
</metadata>
<criteria>
<criterion test_ref=""test:openssl:1""/>
</criteria>
</definition>
<definition id=""oval:ru.astra:def:20240002"" class=""vulnerability"">
<metadata>
<title>Curl vulnerability in Astra Linux</title>
<description>A heap-based buffer overflow in curl.</description>
<reference ref_id=""CVE-2024-2398"" source=""CVE""/>
<advisory>
<severity>Medium</severity>
<issued date=""2024-02-15""/>
</advisory>
</metadata>
<criteria>
<criterion test_ref=""test:curl:1""/>
</criteria>
</definition>
<definition id=""oval:ru.astra:def:20240003"" class=""vulnerability"">
<metadata>
<title>Kernel vulnerability in Astra Linux</title>
<description>A privilege escalation in the Linux kernel.</description>
<reference ref_id=""CVE-2024-1086"" source=""CVE""/>
<advisory>
<severity>Critical</severity>
<issued date=""2024-03-01""/>
</advisory>
</metadata>
<criteria>
<criterion test_ref=""test:kernel:1""/>
</criteria>
</definition>
</definitions>
<tests>
<linux:dpkginfo_test id=""test:openssl:1"" check=""at least one"">
<linux:object object_ref=""obj:openssl:1""/>
<linux:state state_ref=""state:openssl:1""/>
</linux:dpkginfo_test>
<linux:dpkginfo_test id=""test:curl:1"" check=""at least one"">
<linux:object object_ref=""obj:curl:1""/>
<linux:state state_ref=""state:curl:1""/>
</linux:dpkginfo_test>
<linux:dpkginfo_test id=""test:kernel:1"" check=""at least one"">
<linux:object object_ref=""obj:kernel:1""/>
<linux:state state_ref=""state:kernel:1""/>
</linux:dpkginfo_test>
</tests>
<objects>
<linux:dpkginfo_object id=""obj:openssl:1"">
<linux:name>openssl</linux:name>
</linux:dpkginfo_object>
<linux:dpkginfo_object id=""obj:curl:1"">
<linux:name>curl</linux:name>
</linux:dpkginfo_object>
<linux:dpkginfo_object id=""obj:kernel:1"">
<linux:name>linux-image-astra</linux:name>
</linux:dpkginfo_object>
</objects>
<states>
<linux:dpkginfo_state id=""state:openssl:1"">
<linux:evr datatype=""evr_string"" operation=""less than"">1.1.1w-0+deb11u1+astra3</linux:evr>
</linux:dpkginfo_state>
<linux:dpkginfo_state id=""state:curl:1"">
<linux:evr datatype=""evr_string"" operation=""less than"">7.74.0-1.3+deb11u8</linux:evr>
</linux:dpkginfo_state>
<linux:dpkginfo_state id=""state:kernel:1"">
<linux:evr datatype=""evr_string"" operation=""less than"">5.10.0-28+astra1</linux:evr>
</linux:dpkginfo_state>
</states>
</oval_definitions>";
}
private static string CreateSingleDefinitionOval()
{
return @"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5""
xmlns:linux=""http://oval.mitre.org/XMLSchema/oval-definitions-5#linux"">
<definitions>
<definition id=""oval:ru.astra:def:20240050"" class=""vulnerability"">
<metadata>
<title>Test OpenSSL Vulnerability</title>
<description>Test vulnerability for integration testing.</description>
<reference ref_id=""CVE-2024-12345"" source=""CVE""/>
<advisory>
<severity>High</severity>
<issued date=""2024-06-01""/>
</advisory>
</metadata>
<criteria>
<criterion test_ref=""test:1""/>
</criteria>
</definition>
</definitions>
<tests>
<linux:dpkginfo_test id=""test:1"" check=""at least one"">
<linux:object object_ref=""obj:1""/>
<linux:state state_ref=""state:1""/>
</linux:dpkginfo_test>
</tests>
<objects>
<linux:dpkginfo_object id=""obj:1"">
<linux:name>openssl</linux:name>
</linux:dpkginfo_object>
</objects>
<states>
<linux:dpkginfo_state id=""state:1"">
<linux:evr datatype=""evr_string"" operation=""less than"">1.1.1w-0+deb11u1</linux:evr>
</linux:dpkginfo_state>
</states>
</oval_definitions>";
}
#endregion
#region Test Builder
/// <summary>
/// Builder for creating test AstraVulnerabilityDefinition instances.
/// </summary>
private sealed class AstraVulnerabilityDefinitionBuilder
{
private string _definitionId = "oval:ru.astra:def:20240001";
private string _title = "Test Vulnerability";
private string? _description;
private string[] _cveIds = new[] { "CVE-2024-12345" };
private string? _severity;
private DateTimeOffset? _publishedDate;
private readonly List<AstraAffectedPackage> _packages = new();
public AstraVulnerabilityDefinitionBuilder WithDefinitionId(string id)
{
_definitionId = id;
return this;
}
public AstraVulnerabilityDefinitionBuilder WithTitle(string title)
{
_title = title;
return this;
}
public AstraVulnerabilityDefinitionBuilder WithDescription(string description)
{
_description = description;
return this;
}
public AstraVulnerabilityDefinitionBuilder WithCves(params string[] cves)
{
_cveIds = cves;
return this;
}
public AstraVulnerabilityDefinitionBuilder WithSeverity(string severity)
{
_severity = severity;
return this;
}
public AstraVulnerabilityDefinitionBuilder WithPublishedDate(DateTimeOffset date)
{
_publishedDate = date;
return this;
}
public AstraVulnerabilityDefinitionBuilder WithPackage(
string packageName,
string? minVersion = null,
string? maxVersion = null,
string? fixedVersion = null)
{
_packages.Add(new AstraAffectedPackage
{
PackageName = packageName,
MinVersion = minVersion,
MaxVersion = maxVersion,
FixedVersion = fixedVersion
});
return this;
}
public AstraVulnerabilityDefinition Build()
{
return new AstraVulnerabilityDefinition
{
DefinitionId = _definitionId,
Title = _title,
Description = _description,
CveIds = _cveIds,
Severity = _severity,
PublishedDate = _publishedDate,
AffectedPackages = _packages.Count > 0 ? _packages.ToArray() : Array.Empty<AstraAffectedPackage>()
};
}
}
#endregion
}
// Make internal types accessible for testing
internal sealed record AstraVulnerabilityDefinition
{
public required string DefinitionId { get; init; }
public required string Title { get; init; }
public string? Description { get; init; }
public required string[] CveIds { get; init; }
public string? Severity { get; init; }
public DateTimeOffset? PublishedDate { get; init; }
public required AstraAffectedPackage[] AffectedPackages { get; init; }
}
internal sealed record AstraAffectedPackage
{
public required string PackageName { get; init; }
public string? MinVersion { get; init; }
public string? MaxVersion { get; init; }
public string? FixedVersion { get; init; }
}

View File

@@ -0,0 +1,340 @@
// <copyright file="OvalParserTests.cs" company="Stella Operations">
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
// </copyright>
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Concelier.Connector.Astra.Internal;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Concelier.Connector.Astra.Tests.Internal;
/// <summary>
/// Unit tests for OVAL XML parser.
/// Sprint: SPRINT_20260208_034_Concelier_astra_linux_oval_feed_connector
/// </summary>
public sealed class OvalParserTests
{
private readonly OvalParser _parser;
public OvalParserTests()
{
_parser = new OvalParser(NullLogger<OvalParser>.Instance);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_EmptyDocument_ReturnsEmptyList()
{
var xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5"">
<definitions/>
</oval_definitions>";
var result = _parser.Parse(xml);
result.Should().BeEmpty();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_SingleDefinition_ExtractsCorrectly()
{
var xml = CreateSingleDefinitionOval(
definitionId: "oval:ru.astra:def:20240001",
title: "Test Vulnerability",
description: "A test vulnerability description",
cveId: "CVE-2024-12345",
severity: "High",
publishedDate: "2024-01-15");
var result = _parser.Parse(xml);
result.Should().HaveCount(1);
var definition = result[0];
definition.DefinitionId.Should().Be("oval:ru.astra:def:20240001");
definition.Title.Should().Be("Test Vulnerability");
definition.Description.Should().Be("A test vulnerability description");
definition.CveIds.Should().ContainSingle().Which.Should().Be("CVE-2024-12345");
definition.Severity.Should().Be("High");
definition.PublishedDate.Should().NotBeNull();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_MultipleCveIds_ExtractsAll()
{
var xml = CreateMultipleCveOval(
definitionId: "oval:ru.astra:def:20240002",
cveIds: new[] { "CVE-2024-11111", "CVE-2024-22222", "CVE-2024-33333" });
var result = _parser.Parse(xml);
result.Should().HaveCount(1);
result[0].CveIds.Should().HaveCount(3);
result[0].CveIds.Should().Contain("CVE-2024-11111");
result[0].CveIds.Should().Contain("CVE-2024-22222");
result[0].CveIds.Should().Contain("CVE-2024-33333");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_WithAffectedPackage_ExtractsPackageInfo()
{
var xml = CreateOvalWithPackage(
definitionId: "oval:ru.astra:def:20240003",
packageName: "openssl",
fixedVersion: "1.1.1k-1+deb11u1+astra3");
var result = _parser.Parse(xml);
result.Should().HaveCount(1);
result[0].AffectedPackages.Should().HaveCount(1);
var pkg = result[0].AffectedPackages[0];
pkg.PackageName.Should().Be("openssl");
pkg.FixedVersion.Should().Be("1.1.1k-1+deb11u1+astra3");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_MultipleDefinitions_ParsesAll()
{
var xml = CreateMultipleDefinitionsOval(3);
var result = _parser.Parse(xml);
result.Should().HaveCount(3);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_InvalidXml_ThrowsOvalParseException()
{
var xml = "not valid xml";
var act = () => _parser.Parse(xml);
act.Should().Throw<OvalParseException>()
.WithMessage("*Failed to parse OVAL XML*");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_MissingRootElement_ThrowsOvalParseException()
{
var xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<wrong_root xmlns=""http://wrong.namespace"">
</wrong_root>";
var act = () => _parser.Parse(xml);
act.Should().Throw<OvalParseException>()
.WithMessage("*Invalid OVAL document*");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_DefinitionWithoutId_SkipsDefinition()
{
var xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5"">
<definitions>
<definition class=""vulnerability"">
<metadata>
<title>No ID Definition</title>
</metadata>
</definition>
</definitions>
</oval_definitions>";
var result = _parser.Parse(xml);
result.Should().BeEmpty();
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_WithVersionRange_ExtractsMinAndMax()
{
var xml = CreateOvalWithVersionRange(
packageName: "curl",
minVersion: "7.74.0",
maxVersion: "7.74.0-1.3+deb11u7");
var result = _parser.Parse(xml);
result.Should().HaveCount(1);
result[0].AffectedPackages.Should().HaveCount(1);
var pkg = result[0].AffectedPackages[0];
pkg.PackageName.Should().Be("curl");
pkg.MinVersion.Should().Be("7.74.0");
pkg.MaxVersion.Should().Be("7.74.0-1.3+deb11u7");
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_Deterministic_SameInputProducesSameOutput()
{
var xml = CreateSingleDefinitionOval(
definitionId: "oval:ru.astra:def:20240100",
title: "Determinism Test",
description: "Testing deterministic parsing",
cveId: "CVE-2024-99999",
severity: "Medium",
publishedDate: "2024-06-15");
var result1 = _parser.Parse(xml);
var result2 = _parser.Parse(xml);
result1.Should().HaveCount(1);
result2.Should().HaveCount(1);
result1[0].DefinitionId.Should().Be(result2[0].DefinitionId);
result1[0].Title.Should().Be(result2[0].Title);
result1[0].CveIds.Should().BeEquivalentTo(result2[0].CveIds);
}
#region Test Fixtures
private static string CreateSingleDefinitionOval(
string definitionId,
string title,
string description,
string cveId,
string severity,
string publishedDate)
{
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5""
xmlns:linux=""http://oval.mitre.org/XMLSchema/oval-definitions-5#linux"">
<definitions>
<definition id=""{definitionId}"" class=""vulnerability"">
<metadata>
<title>{title}</title>
<description>{description}</description>
<reference ref_id=""{cveId}"" source=""CVE"" ref_url=""https://nvd.nist.gov/vuln/detail/{cveId}""/>
<advisory>
<severity>{severity}</severity>
<issued date=""{publishedDate}""/>
</advisory>
</metadata>
</definition>
</definitions>
</oval_definitions>";
}
private static string CreateMultipleCveOval(string definitionId, string[] cveIds)
{
var references = string.Join("\n ",
cveIds.Select(cve => $@"<reference ref_id=""{cve}"" source=""CVE""/>"));
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5"">
<definitions>
<definition id=""{definitionId}"" class=""vulnerability"">
<metadata>
<title>Multiple CVE Test</title>
{references}
</metadata>
</definition>
</definitions>
</oval_definitions>";
}
private static string CreateOvalWithPackage(
string definitionId,
string packageName,
string fixedVersion)
{
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5""
xmlns:linux=""http://oval.mitre.org/XMLSchema/oval-definitions-5#linux"">
<definitions>
<definition id=""{definitionId}"" class=""vulnerability"">
<metadata>
<title>Package Test</title>
<reference ref_id=""CVE-2024-00001"" source=""CVE""/>
</metadata>
<criteria>
<criterion test_ref=""test:1""/>
</criteria>
</definition>
</definitions>
<tests>
<linux:dpkginfo_test id=""test:1"" check=""at least one"">
<linux:object object_ref=""obj:1""/>
<linux:state state_ref=""state:1""/>
</linux:dpkginfo_test>
</tests>
<objects>
<linux:dpkginfo_object id=""obj:1"">
<linux:name>{packageName}</linux:name>
</linux:dpkginfo_object>
</objects>
<states>
<linux:dpkginfo_state id=""state:1"">
<linux:evr datatype=""evr_string"" operation=""less than"">{fixedVersion}</linux:evr>
</linux:dpkginfo_state>
</states>
</oval_definitions>";
}
private static string CreateOvalWithVersionRange(
string packageName,
string minVersion,
string maxVersion)
{
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5""
xmlns:linux=""http://oval.mitre.org/XMLSchema/oval-definitions-5#linux"">
<definitions>
<definition id=""oval:ru.astra:def:20240010"" class=""vulnerability"">
<metadata>
<title>Version Range Test</title>
<reference ref_id=""CVE-2024-00002"" source=""CVE""/>
</metadata>
<criteria>
<criterion test_ref=""test:range:1""/>
</criteria>
</definition>
</definitions>
<tests>
<linux:dpkginfo_test id=""test:range:1"" check=""at least one"">
<linux:object object_ref=""obj:range:1""/>
<linux:state state_ref=""state:range:1""/>
</linux:dpkginfo_test>
</tests>
<objects>
<linux:dpkginfo_object id=""obj:range:1"">
<linux:name>{packageName}</linux:name>
</linux:dpkginfo_object>
</objects>
<states>
<linux:dpkginfo_state id=""state:range:1"">
<linux:evr datatype=""evr_string"" operation=""greater than or equal"">{minVersion}</linux:evr>
<linux:evr datatype=""evr_string"" operation=""less than or equal"">{maxVersion}</linux:evr>
</linux:dpkginfo_state>
</states>
</oval_definitions>";
}
private static string CreateMultipleDefinitionsOval(int count)
{
var definitions = string.Join("\n ",
Enumerable.Range(1, count).Select(i => $@"<definition id=""oval:ru.astra:def:2024000{i}"" class=""vulnerability"">
<metadata>
<title>Definition {i}</title>
<reference ref_id=""CVE-2024-1000{i}"" source=""CVE""/>
</metadata>
</definition>"));
return $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<oval_definitions xmlns=""http://oval.mitre.org/XMLSchema/oval-definitions-5"">
<definitions>
{definitions}
</definitions>
</oval_definitions>";
}
#endregion
}