Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.ConfigDiff.Tests/ScannerConfigDiffTests.cs
StellaOps Bot 37e11918e0 save progress
2026-01-06 09:42:20 +02:00

267 lines
9.5 KiB
C#

// <copyright file="ScannerConfigDiffTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// </copyright>
// Sprint: SPRINT_20260105_002_005_TEST_cross_cutting
// Task: CCUT-022
using System.Collections.Immutable;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.TestKit;
using StellaOps.Testing.ConfigDiff;
using Xunit;
namespace StellaOps.Scanner.ConfigDiff.Tests;
/// <summary>
/// Config-diff tests for the Scanner module.
/// Verifies that configuration changes produce only expected behavioral deltas.
/// </summary>
[Trait("Category", TestCategories.ConfigDiff)]
[Trait("Category", TestCategories.Integration)]
[Trait("BlastRadius", TestCategories.BlastRadius.Scanning)]
public class ScannerConfigDiffTests : ConfigDiffTestBase
{
/// <summary>
/// Initializes a new instance of the <see cref="ScannerConfigDiffTests"/> class.
/// </summary>
public ScannerConfigDiffTests()
: base(
new ConfigDiffTestConfig(StrictMode: true),
NullLogger.Instance)
{
}
/// <summary>
/// Verifies that changing scan depth only affects traversal behavior.
/// </summary>
[Fact]
public async Task ChangingScanDepth_OnlyAffectsTraversal()
{
// Arrange
var baselineConfig = new ScannerTestConfig
{
MaxScanDepth = 10,
EnableReachabilityAnalysis = true,
MaxConcurrentAnalyzers = 4
};
var changedConfig = baselineConfig with
{
MaxScanDepth = 20
};
// Act
var result = await TestConfigIsolationAsync(
baselineConfig,
changedConfig,
changedSetting: "MaxScanDepth",
unrelatedBehaviors:
[
async config => await GetReachabilityBehaviorAsync(config),
async config => await GetConcurrencyBehaviorAsync(config),
async config => await GetOutputFormatBehaviorAsync(config)
]);
// Assert
result.IsSuccess.Should().BeTrue(
because: "changing scan depth should not affect reachability or concurrency");
}
/// <summary>
/// Verifies that enabling reachability analysis produces expected delta.
/// </summary>
[Fact]
public async Task EnablingReachability_ProducesExpectedDelta()
{
// Arrange
var baselineConfig = new ScannerTestConfig { EnableReachabilityAnalysis = false };
var changedConfig = new ScannerTestConfig { EnableReachabilityAnalysis = true };
var expectedDelta = new ConfigDelta(
ChangedBehaviors: ["ReachabilityMode", "ScanDuration", "OutputDetail"],
BehaviorDeltas:
[
new BehaviorDelta("ReachabilityMode", "disabled", "enabled", null),
new BehaviorDelta("ScanDuration", "increase", null,
"Reachability analysis adds processing time"),
new BehaviorDelta("OutputDetail", "basic", "enhanced",
"Reachability data added to findings")
]);
// Act
var result = await TestConfigBehavioralDeltaAsync(
baselineConfig,
changedConfig,
getBehavior: async config => await CaptureReachabilityBehaviorAsync(config),
computeDelta: ComputeBehaviorSnapshotDelta,
expectedDelta: expectedDelta);
// Assert
result.IsSuccess.Should().BeTrue(
because: "enabling reachability should produce expected behavioral delta");
}
/// <summary>
/// Verifies that changing SBOM format only affects output.
/// </summary>
[Fact]
public async Task ChangingSbomFormat_OnlyAffectsOutput()
{
// Arrange
var baselineConfig = new ScannerTestConfig { SbomFormat = "spdx-3.0" };
var changedConfig = new ScannerTestConfig { SbomFormat = "cyclonedx-1.7" };
// Act
var result = await TestConfigIsolationAsync(
baselineConfig,
changedConfig,
changedSetting: "SbomFormat",
unrelatedBehaviors:
[
async config => await GetScanningBehaviorAsync(config),
async config => await GetVulnMatchingBehaviorAsync(config),
async config => await GetReachabilityBehaviorAsync(config)
]);
// Assert
result.IsSuccess.Should().BeTrue(
because: "SBOM format should only affect output serialization");
}
/// <summary>
/// Verifies that changing concurrency produces expected delta.
/// </summary>
[Fact]
public async Task ChangingConcurrency_ProducesExpectedDelta()
{
// Arrange
var baselineConfig = new ScannerTestConfig { MaxConcurrentAnalyzers = 2 };
var changedConfig = new ScannerTestConfig { MaxConcurrentAnalyzers = 8 };
var expectedDelta = new ConfigDelta(
ChangedBehaviors: ["ParallelismLevel", "ResourceUsage"],
BehaviorDeltas:
[
new BehaviorDelta("ParallelismLevel", "2", "8", null),
new BehaviorDelta("ResourceUsage", "increase", null,
"More concurrent analyzers use more resources")
]);
// Act
var result = await TestConfigBehavioralDeltaAsync(
baselineConfig,
changedConfig,
getBehavior: async config => await CaptureConcurrencyBehaviorAsync(config),
computeDelta: ComputeBehaviorSnapshotDelta,
expectedDelta: expectedDelta);
// Assert
result.IsSuccess.Should().BeTrue();
}
/// <summary>
/// Verifies that changing vulnerability threshold only affects filtering.
/// </summary>
[Fact]
public async Task ChangingVulnThreshold_OnlyAffectsFiltering()
{
// Arrange
var baselineConfig = new ScannerTestConfig { MinimumSeverity = "medium" };
var changedConfig = new ScannerTestConfig { MinimumSeverity = "critical" };
// Act
var result = await TestConfigIsolationAsync(
baselineConfig,
changedConfig,
changedSetting: "MinimumSeverity",
unrelatedBehaviors:
[
async config => await GetScanningBehaviorAsync(config),
async config => await GetSbomBehaviorAsync(config)
]);
// Assert
result.IsSuccess.Should().BeTrue(
because: "severity threshold should only affect output filtering");
}
// Helper methods
private static Task<object> GetReachabilityBehaviorAsync(ScannerTestConfig config)
{
return Task.FromResult<object>(new { Enabled = config.EnableReachabilityAnalysis });
}
private static Task<object> GetConcurrencyBehaviorAsync(ScannerTestConfig config)
{
return Task.FromResult<object>(new { MaxAnalyzers = config.MaxConcurrentAnalyzers });
}
private static Task<object> GetOutputFormatBehaviorAsync(ScannerTestConfig config)
{
return Task.FromResult<object>(new { Format = config.SbomFormat });
}
private static Task<object> GetScanningBehaviorAsync(ScannerTestConfig config)
{
return Task.FromResult<object>(new { Depth = config.MaxScanDepth });
}
private static Task<object> GetVulnMatchingBehaviorAsync(ScannerTestConfig config)
{
return Task.FromResult<object>(new { MatchingMode = "standard" });
}
private static Task<object> GetSbomBehaviorAsync(ScannerTestConfig config)
{
return Task.FromResult<object>(new { Format = config.SbomFormat });
}
private static Task<BehaviorSnapshot> CaptureReachabilityBehaviorAsync(ScannerTestConfig config)
{
var snapshot = new BehaviorSnapshot(
ConfigurationId: $"reachability-{config.EnableReachabilityAnalysis}",
Behaviors:
[
new CapturedBehavior("ReachabilityMode",
config.EnableReachabilityAnalysis ? "enabled" : "disabled", DateTimeOffset.UtcNow),
new CapturedBehavior("ScanDuration",
config.EnableReachabilityAnalysis ? "increase" : "standard", DateTimeOffset.UtcNow),
new CapturedBehavior("OutputDetail",
config.EnableReachabilityAnalysis ? "enhanced" : "basic", DateTimeOffset.UtcNow)
],
CapturedAt: DateTimeOffset.UtcNow);
return Task.FromResult(snapshot);
}
private static Task<BehaviorSnapshot> CaptureConcurrencyBehaviorAsync(ScannerTestConfig config)
{
var snapshot = new BehaviorSnapshot(
ConfigurationId: $"concurrency-{config.MaxConcurrentAnalyzers}",
Behaviors:
[
new CapturedBehavior("ParallelismLevel", config.MaxConcurrentAnalyzers.ToString(), DateTimeOffset.UtcNow),
new CapturedBehavior("ResourceUsage",
config.MaxConcurrentAnalyzers > 4 ? "increase" : "standard", DateTimeOffset.UtcNow)
],
CapturedAt: DateTimeOffset.UtcNow);
return Task.FromResult(snapshot);
}
}
/// <summary>
/// Test configuration for Scanner module.
/// </summary>
public sealed record ScannerTestConfig
{
public int MaxScanDepth { get; init; } = 10;
public bool EnableReachabilityAnalysis { get; init; } = true;
public int MaxConcurrentAnalyzers { get; init; } = 4;
public string SbomFormat { get; init; } = "spdx-3.0";
public string MinimumSeverity { get; init; } = "medium";
public bool IncludeDevDependencies { get; init; } = false;
}