Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -36,6 +36,7 @@ public static class BinaryIndexServiceExtensions
|
||||
services.AddScoped<IBinaryVulnerabilityService, BinaryVulnerabilityService>();
|
||||
services.AddScoped<IBinaryFeatureExtractor, ElfFeatureExtractor>();
|
||||
services.AddScoped<BinaryVulnerabilityAnalyzer>();
|
||||
services.AddScoped<Processing.BinaryFindingMapper>();
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -87,4 +88,40 @@ internal sealed class NullBinaryVulnerabilityService : IBinaryVulnerabilityServi
|
||||
{
|
||||
return Task.FromResult(System.Collections.Immutable.ImmutableDictionary<string, System.Collections.Immutable.ImmutableArray<BinaryVulnMatch>>.Empty);
|
||||
}
|
||||
|
||||
public Task<StellaOps.BinaryIndex.FixIndex.Models.FixStatusResult?> GetFixStatusAsync(
|
||||
string distro,
|
||||
string release,
|
||||
string sourcePkg,
|
||||
string cveId,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.FromResult<StellaOps.BinaryIndex.FixIndex.Models.FixStatusResult?>(null);
|
||||
}
|
||||
|
||||
public Task<System.Collections.Immutable.ImmutableDictionary<string, StellaOps.BinaryIndex.FixIndex.Models.FixStatusResult>> GetFixStatusBatchAsync(
|
||||
string distro,
|
||||
string release,
|
||||
string sourcePkg,
|
||||
IEnumerable<string> cveIds,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.FromResult(System.Collections.Immutable.ImmutableDictionary<string, StellaOps.BinaryIndex.FixIndex.Models.FixStatusResult>.Empty);
|
||||
}
|
||||
|
||||
public Task<System.Collections.Immutable.ImmutableArray<BinaryVulnMatch>> LookupByFingerprintAsync(
|
||||
byte[] fingerprint,
|
||||
FingerprintLookupOptions? options = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.FromResult(System.Collections.Immutable.ImmutableArray<BinaryVulnMatch>.Empty);
|
||||
}
|
||||
|
||||
public Task<System.Collections.Immutable.ImmutableDictionary<string, System.Collections.Immutable.ImmutableArray<BinaryVulnMatch>>> LookupByFingerprintBatchAsync(
|
||||
IEnumerable<(string Key, byte[] Fingerprint)> fingerprints,
|
||||
FingerprintLookupOptions? options = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return Task.FromResult(System.Collections.Immutable.ImmutableDictionary<string, System.Collections.Immutable.ImmutableArray<BinaryVulnMatch>>.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BinaryFindingMapper.cs
|
||||
// Sprint: SPRINT_20251226_014_BINIDX
|
||||
// Task: SCANINT-08 — Create BinaryFindingMapper to convert matches to findings
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.BinaryIndex.Core.Services;
|
||||
using FixStatusResult = StellaOps.BinaryIndex.Core.Services.FixStatusResult;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Processing;
|
||||
|
||||
/// <summary>
|
||||
/// Maps binary vulnerability findings to the standard scanner finding format.
|
||||
/// Enables integration with the Findings Ledger and triage workflow.
|
||||
/// </summary>
|
||||
public sealed class BinaryFindingMapper
|
||||
{
|
||||
private readonly IBinaryVulnerabilityService _binaryVulnService;
|
||||
private readonly ILogger<BinaryFindingMapper> _logger;
|
||||
|
||||
public BinaryFindingMapper(
|
||||
IBinaryVulnerabilityService binaryVulnService,
|
||||
ILogger<BinaryFindingMapper> logger)
|
||||
{
|
||||
_binaryVulnService = binaryVulnService ?? throw new ArgumentNullException(nameof(binaryVulnService));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a single binary finding to a standard finding.
|
||||
/// </summary>
|
||||
public Finding MapToFinding(BinaryVulnerabilityFinding finding, string? distro = null, string? release = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(finding);
|
||||
|
||||
var findingId = GenerateFindingId(finding);
|
||||
var severity = GetSeverityFromCve(finding.CveId);
|
||||
|
||||
return new Finding
|
||||
{
|
||||
Id = findingId,
|
||||
Type = FindingType.BinaryVulnerability,
|
||||
Severity = severity,
|
||||
Title = $"Binary contains vulnerable code: {finding.CveId}",
|
||||
Description = GenerateDescription(finding),
|
||||
CveId = finding.CveId,
|
||||
Purl = finding.VulnerablePurl,
|
||||
Evidence = new BinaryFindingEvidence
|
||||
{
|
||||
BinaryKey = finding.BinaryKey,
|
||||
LayerDigest = finding.LayerDigest,
|
||||
MatchMethod = finding.MatchMethod,
|
||||
Confidence = finding.Confidence,
|
||||
Similarity = finding.Evidence?.Similarity,
|
||||
MatchedFunction = finding.Evidence?.MatchedFunction,
|
||||
BuildId = finding.Evidence?.BuildId
|
||||
},
|
||||
Remediation = GenerateRemediation(finding),
|
||||
ScanId = finding.ScanId,
|
||||
DetectedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps multiple binary findings to standard findings with fix status enrichment.
|
||||
/// </summary>
|
||||
public async Task<ImmutableArray<Finding>> MapToFindingsAsync(
|
||||
IEnumerable<BinaryVulnerabilityFinding> findings,
|
||||
string? distro,
|
||||
string? release,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var result = new List<Finding>();
|
||||
var findingsList = findings.ToList();
|
||||
|
||||
// Group by source package for batch fix status lookup
|
||||
var groupedByPurl = findingsList
|
||||
.GroupBy(f => ExtractSourcePackage(f.VulnerablePurl))
|
||||
.Where(g => !string.IsNullOrEmpty(g.Key));
|
||||
|
||||
foreach (var group in groupedByPurl)
|
||||
{
|
||||
var sourcePkg = group.Key!;
|
||||
var cveIds = group.Select(f => f.CveId).Distinct().ToList();
|
||||
|
||||
// Batch fix status lookup
|
||||
ImmutableDictionary<string, FixStatusResult>? fixStatuses = null;
|
||||
if (!string.IsNullOrEmpty(distro) && !string.IsNullOrEmpty(release))
|
||||
{
|
||||
try
|
||||
{
|
||||
fixStatuses = await _binaryVulnService.GetFixStatusBatchAsync(
|
||||
distro, release, sourcePkg, cveIds, ct).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to get fix status for {SourcePkg}", sourcePkg);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var finding in group)
|
||||
{
|
||||
var mapped = MapToFinding(finding, distro, release);
|
||||
|
||||
// Enrich with fix status if available
|
||||
if (fixStatuses != null && fixStatuses.TryGetValue(finding.CveId, out var fixStatus))
|
||||
{
|
||||
mapped = mapped with
|
||||
{
|
||||
FixStatus = MapFixStatus(fixStatus),
|
||||
FixedVersion = fixStatus.FixedVersion
|
||||
};
|
||||
}
|
||||
|
||||
result.Add(mapped);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle findings without valid PURLs
|
||||
foreach (var finding in findingsList.Where(f => string.IsNullOrEmpty(ExtractSourcePackage(f.VulnerablePurl))))
|
||||
{
|
||||
result.Add(MapToFinding(finding, distro, release));
|
||||
}
|
||||
|
||||
_logger.LogInformation("Mapped {Count} binary findings", result.Count);
|
||||
return result.ToImmutableArray();
|
||||
}
|
||||
|
||||
private static Guid GenerateFindingId(BinaryVulnerabilityFinding finding)
|
||||
{
|
||||
// Generate deterministic ID based on scan, CVE, and binary key
|
||||
var input = $"{finding.ScanId}:{finding.CveId}:{finding.BinaryKey}:{finding.LayerDigest}";
|
||||
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(input));
|
||||
return new Guid(hash.AsSpan()[..16]);
|
||||
}
|
||||
|
||||
private static Severity GetSeverityFromCve(string cveId)
|
||||
{
|
||||
// In production, this would look up CVSS from advisory data
|
||||
// For now, return Unknown and let downstream enrichment set it
|
||||
return Severity.Unknown;
|
||||
}
|
||||
|
||||
private static string GenerateDescription(BinaryVulnerabilityFinding finding)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"A binary file in the container image contains code affected by {finding.CveId}.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"**Detection Method:** {finding.MatchMethod}");
|
||||
sb.AppendLine($"**Confidence:** {finding.Confidence:P0}");
|
||||
|
||||
if (finding.Evidence?.MatchedFunction is not null)
|
||||
{
|
||||
sb.AppendLine($"**Vulnerable Function:** {finding.Evidence.MatchedFunction}");
|
||||
}
|
||||
|
||||
if (finding.Evidence?.BuildId is not null)
|
||||
{
|
||||
sb.AppendLine($"**Build-ID:** {finding.Evidence.BuildId}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateRemediation(BinaryVulnerabilityFinding finding)
|
||||
{
|
||||
return $"Update the package containing the binary to a version that includes the fix for {finding.CveId}. " +
|
||||
$"If using a distro package, check if a backported security update is available.";
|
||||
}
|
||||
|
||||
private static string? ExtractSourcePackage(string purl)
|
||||
{
|
||||
// Extract package name from PURL
|
||||
// e.g., "pkg:deb/debian/openssl@1.1.1" -> "openssl"
|
||||
if (string.IsNullOrEmpty(purl))
|
||||
return null;
|
||||
|
||||
var atIndex = purl.IndexOf('@');
|
||||
var slashIndex = purl.LastIndexOf('/', atIndex > 0 ? atIndex : purl.Length);
|
||||
|
||||
if (slashIndex >= 0)
|
||||
{
|
||||
var endIndex = atIndex > slashIndex ? atIndex : purl.Length;
|
||||
return purl[(slashIndex + 1)..endIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FindingFixStatus MapFixStatus(FixStatusResult status)
|
||||
{
|
||||
return status.State switch
|
||||
{
|
||||
FixState.Fixed => FindingFixStatus.Fixed,
|
||||
FixState.Vulnerable => FindingFixStatus.Vulnerable,
|
||||
FixState.NotAffected => FindingFixStatus.NotAffected,
|
||||
FixState.WontFix => FindingFixStatus.WontFix,
|
||||
_ => FindingFixStatus.Unknown
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Standard scanner finding.
|
||||
/// </summary>
|
||||
public sealed record Finding
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required FindingType Type { get; init; }
|
||||
public required Severity Severity { get; init; }
|
||||
public required string Title { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public string? CveId { get; init; }
|
||||
public string? Purl { get; init; }
|
||||
public required BinaryFindingEvidence Evidence { get; init; }
|
||||
public required string Remediation { get; init; }
|
||||
public Guid ScanId { get; init; }
|
||||
public DateTimeOffset DetectedAt { get; init; }
|
||||
public FindingFixStatus FixStatus { get; init; } = FindingFixStatus.Unknown;
|
||||
public string? FixedVersion { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evidence specific to binary vulnerability findings.
|
||||
/// </summary>
|
||||
public sealed record BinaryFindingEvidence
|
||||
{
|
||||
public required string BinaryKey { get; init; }
|
||||
public required string LayerDigest { get; init; }
|
||||
public required string MatchMethod { get; init; }
|
||||
public required decimal Confidence { get; init; }
|
||||
public decimal? Similarity { get; init; }
|
||||
public string? MatchedFunction { get; init; }
|
||||
public string? BuildId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finding type enumeration.
|
||||
/// </summary>
|
||||
public enum FindingType
|
||||
{
|
||||
PackageVulnerability,
|
||||
BinaryVulnerability,
|
||||
PolicyViolation,
|
||||
SecretExposure,
|
||||
MisconfigurationDebian
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Severity levels for findings.
|
||||
/// </summary>
|
||||
public enum Severity
|
||||
{
|
||||
Unknown,
|
||||
None,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Critical
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix status for findings.
|
||||
/// </summary>
|
||||
public enum FindingFixStatus
|
||||
{
|
||||
Unknown,
|
||||
Vulnerable,
|
||||
Fixed,
|
||||
NotAffected,
|
||||
WontFix
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix state from the binary index.
|
||||
/// </summary>
|
||||
public enum FixState
|
||||
{
|
||||
Unknown,
|
||||
Vulnerable,
|
||||
Fixed,
|
||||
NotAffected,
|
||||
WontFix
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BinaryLookupStageExecutor.cs
|
||||
// Sprint: SPRINT_20251226_014_BINIDX
|
||||
// Task: SCANINT-02 — Create IBinaryLookupStep in scan pipeline
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.BinaryIndex.Core.Models;
|
||||
using StellaOps.BinaryIndex.Core.Services;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
using StellaOps.Scanner.Worker.Extensions;
|
||||
|
||||
namespace StellaOps.Scanner.Worker.Processing;
|
||||
|
||||
/// <summary>
|
||||
/// Scan pipeline stage that performs binary vulnerability lookups.
|
||||
/// Runs after analyzers to correlate binary identities with known vulnerabilities.
|
||||
/// </summary>
|
||||
public sealed class BinaryLookupStageExecutor : IScanStageExecutor
|
||||
{
|
||||
private readonly BinaryVulnerabilityAnalyzer _analyzer;
|
||||
private readonly BinaryIndexOptions _options;
|
||||
private readonly ILogger<BinaryLookupStageExecutor> _logger;
|
||||
|
||||
public BinaryLookupStageExecutor(
|
||||
BinaryVulnerabilityAnalyzer analyzer,
|
||||
BinaryIndexOptions options,
|
||||
ILogger<BinaryLookupStageExecutor> logger)
|
||||
{
|
||||
_analyzer = analyzer ?? throw new ArgumentNullException(nameof(analyzer));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public string StageName => ScanStageNames.BinaryLookup;
|
||||
|
||||
public async ValueTask ExecuteAsync(ScanJobContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_options.Enabled)
|
||||
{
|
||||
_logger.LogDebug("Binary vulnerability analysis disabled, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Starting binary vulnerability lookup for scan {ScanId}",
|
||||
context.ScanId);
|
||||
|
||||
var allFindings = new List<BinaryVulnerabilityFinding>();
|
||||
var layerContexts = BuildLayerContexts(context);
|
||||
|
||||
foreach (var layerContext in layerContexts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _analyzer.AnalyzeLayerAsync(layerContext, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (result.Findings.Length > 0)
|
||||
{
|
||||
allFindings.AddRange(result.Findings);
|
||||
_logger.LogInformation(
|
||||
"Found {Count} binary vulnerabilities in layer {Layer}",
|
||||
result.Findings.Length,
|
||||
layerContext.LayerDigest);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Failed to analyze layer {Layer} for binary vulnerabilities",
|
||||
layerContext.LayerDigest);
|
||||
}
|
||||
}
|
||||
|
||||
// Store findings in analysis context for downstream stages
|
||||
context.Analysis.SetBinaryFindings(allFindings.ToImmutableArray());
|
||||
|
||||
_logger.LogInformation(
|
||||
"Binary vulnerability lookup complete for scan {ScanId}: {Count} findings",
|
||||
context.ScanId,
|
||||
allFindings.Count);
|
||||
}
|
||||
|
||||
private IReadOnlyList<BinaryLayerContext> BuildLayerContexts(ScanJobContext context)
|
||||
{
|
||||
var contexts = new List<BinaryLayerContext>();
|
||||
|
||||
// Get layer information from the scan context
|
||||
var layers = context.Analysis.GetLayers();
|
||||
if (layers == null || layers.Count == 0)
|
||||
{
|
||||
_logger.LogDebug("No layers found in scan context");
|
||||
return contexts;
|
||||
}
|
||||
|
||||
var distro = context.Analysis.GetDetectedDistro();
|
||||
var release = context.Analysis.GetDetectedRelease();
|
||||
|
||||
foreach (var layer in layers)
|
||||
{
|
||||
var binaryPaths = context.Analysis.GetBinaryPathsForLayer(layer.Digest);
|
||||
if (binaryPaths == null || binaryPaths.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
contexts.Add(new BinaryLayerContext
|
||||
{
|
||||
ScanId = Guid.Parse(context.ScanId),
|
||||
LayerDigest = layer.Digest,
|
||||
BinaryPaths = binaryPaths,
|
||||
DetectedDistro = distro,
|
||||
DetectedRelease = release,
|
||||
OpenFile = path => context.Analysis.OpenLayerFile(layer.Digest, path)
|
||||
});
|
||||
}
|
||||
|
||||
return contexts;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for ScanAnalysisStore to support binary analysis.
|
||||
/// </summary>
|
||||
public static class BinaryScanAnalysisStoreExtensions
|
||||
{
|
||||
private const string BinaryFindingsKey = "binary_findings";
|
||||
private const string LayersKey = "layers";
|
||||
private const string DistroKey = "detected_distro";
|
||||
private const string ReleaseKey = "detected_release";
|
||||
|
||||
public static void SetBinaryFindings(
|
||||
this ScanAnalysisStore store,
|
||||
ImmutableArray<BinaryVulnerabilityFinding> findings)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(store);
|
||||
store.Set(BinaryFindingsKey, findings);
|
||||
}
|
||||
|
||||
public static ImmutableArray<BinaryVulnerabilityFinding> GetBinaryFindings(
|
||||
this ScanAnalysisStore store)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(store);
|
||||
if (store.TryGet<ImmutableArray<BinaryVulnerabilityFinding>>(BinaryFindingsKey, out var findings) && !findings.IsDefault)
|
||||
{
|
||||
return findings;
|
||||
}
|
||||
return ImmutableArray<BinaryVulnerabilityFinding>.Empty;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<LayerInfo>? GetLayers(this ScanAnalysisStore store)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(store);
|
||||
if (store.TryGet<IReadOnlyList<LayerInfo>>(LayersKey, out var layers))
|
||||
{
|
||||
return layers;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? GetDetectedDistro(this ScanAnalysisStore store)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(store);
|
||||
if (store.TryGet<string>(DistroKey, out var distro))
|
||||
{
|
||||
return distro;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? GetDetectedRelease(this ScanAnalysisStore store)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(store);
|
||||
if (store.TryGet<string>(ReleaseKey, out var release))
|
||||
{
|
||||
return release;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<string>? GetBinaryPathsForLayer(
|
||||
this ScanAnalysisStore store,
|
||||
string layerDigest)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(store);
|
||||
var key = $"binary_paths_{layerDigest}";
|
||||
if (store.TryGet<IReadOnlyList<string>>(key, out var paths))
|
||||
{
|
||||
return paths;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Stream? OpenLayerFile(
|
||||
this ScanAnalysisStore store,
|
||||
string layerDigest,
|
||||
string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(store);
|
||||
if (store.TryGet<Func<string, string, Stream?>>("layer_file_opener", out var opener))
|
||||
{
|
||||
return opener?.Invoke(layerDigest, path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer metadata for binary analysis.
|
||||
/// </summary>
|
||||
public sealed record LayerInfo
|
||||
{
|
||||
public required string Digest { get; init; }
|
||||
public required string MediaType { get; init; }
|
||||
public long Size { get; init; }
|
||||
}
|
||||
@@ -20,6 +20,9 @@ public static class ScanStageNames
|
||||
// Sprint: SPRINT_3500_0001_0001 - Proof of Exposure
|
||||
public const string GeneratePoE = "generate-poe";
|
||||
|
||||
// Sprint: SPRINT_20251226_014_BINIDX - Binary Vulnerability Lookup
|
||||
public const string BinaryLookup = "binary-lookup";
|
||||
|
||||
public static readonly IReadOnlyList<string> Ordered = new[]
|
||||
{
|
||||
IngestReplay,
|
||||
@@ -27,6 +30,7 @@ public static class ScanStageNames
|
||||
PullLayers,
|
||||
BuildFilesystem,
|
||||
ExecuteAnalyzers,
|
||||
BinaryLookup,
|
||||
EpssEnrichment,
|
||||
ComposeArtifacts,
|
||||
Entropy,
|
||||
|
||||
@@ -27,6 +27,7 @@ using StellaOps.Scanner.Worker.Options;
|
||||
using StellaOps.Scanner.Worker.Processing;
|
||||
using StellaOps.Scanner.Worker.Processing.Entropy;
|
||||
using StellaOps.Scanner.Worker.Determinism;
|
||||
using StellaOps.Scanner.Worker.Extensions;
|
||||
using StellaOps.Scanner.Worker.Processing.Surface;
|
||||
using StellaOps.Scanner.Storage.Extensions;
|
||||
using StellaOps.Scanner.Storage;
|
||||
@@ -93,6 +94,10 @@ builder.Services.AddSingleton<IDelayScheduler, SystemDelayScheduler>();
|
||||
|
||||
builder.Services.AddEntryTraceAnalyzer();
|
||||
builder.Services.AddSingleton<IEntryTraceExecutionService, EntryTraceExecutionService>();
|
||||
|
||||
// BinaryIndex integration for binary vulnerability detection (Sprint: SPRINT_20251226_014_BINIDX)
|
||||
builder.Services.AddBinaryIndexIntegration(builder.Configuration);
|
||||
|
||||
builder.Services.AddSingleton<ReachabilityUnionWriter>();
|
||||
builder.Services.AddSingleton<ReachabilityUnionPublisher>();
|
||||
builder.Services.AddSingleton<IReachabilityUnionPublisherService, ReachabilityUnionPublisherService>();
|
||||
@@ -156,6 +161,7 @@ builder.Services.AddSingleton<NativeAnalyzerExecutor>();
|
||||
builder.Services.AddSingleton<IScanAnalyzerDispatcher, CompositeScanAnalyzerDispatcher>();
|
||||
builder.Services.AddSingleton<IScanStageExecutor, RegistrySecretStageExecutor>();
|
||||
builder.Services.AddSingleton<IScanStageExecutor, AnalyzerStageExecutor>();
|
||||
builder.Services.AddSingleton<IScanStageExecutor, BinaryLookupStageExecutor>();
|
||||
builder.Services.AddSingleton<IScanStageExecutor, EpssEnrichmentStageExecutor>();
|
||||
builder.Services.AddSingleton<IScanStageExecutor, Reachability.ReachabilityBuildStageExecutor>();
|
||||
builder.Services.AddSingleton<IScanStageExecutor, Reachability.ReachabilityPublishStageExecutor>();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Description>Ground-truth corpus benchmarking infrastructure for reachability analysis</Description>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.0-preview.1.25105.2" />
|
||||
<PackageReference Include="System.Text.Json" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../StellaOps.Scanner.Reachability/StellaOps.Scanner.Reachability.csproj" />
|
||||
|
||||
@@ -15,5 +15,8 @@
|
||||
<ProjectReference Include="../../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.Auth.Security/StellaOps.Auth.Security.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.Replay.Core/StellaOps.Replay.Core.csproj" />
|
||||
<ProjectReference Include="../StellaOps.Scanner.ProofSpine/StellaOps.Scanner.ProofSpine.csproj" />
|
||||
<ProjectReference Include="../../../Attestor/StellaOps.Attestor.Envelope/StellaOps.Attestor.Envelope.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<RootNamespace>StellaOps.Scanner.Orchestration</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-preview.7.24407.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -13,5 +13,8 @@
|
||||
Use SliceDataDto and JsonElement instead of ReachabilitySlice type. -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Replay.Core\StellaOps.Replay.Core.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Scanner.Evidence\StellaOps.Scanner.Evidence.csproj" />
|
||||
<ProjectReference Include="..\..\..\Attestor\StellaOps.Attestor.Envelope\StellaOps.Attestor.Envelope.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -8,6 +8,8 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.VulnSurfaces.Fingerprint;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.VulnSurfaces.Tests;
|
||||
|
||||
public class CecilMethodFingerprinterTests
|
||||
@@ -20,20 +22,23 @@ public class CecilMethodFingerprinterTests
|
||||
NullLogger<CecilMethodFingerprinter>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Ecosystem_ReturnsNuget()
|
||||
{
|
||||
Assert.Equal("nuget", _fingerprinter.Ecosystem);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FingerprintAsync_WithNullRequest_ThrowsArgumentNullException()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => _fingerprinter.FingerprintAsync(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FingerprintAsync_WithNonExistentPath_ReturnsEmptyResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -53,7 +58,8 @@ public class CecilMethodFingerprinterTests
|
||||
Assert.Empty(result.Methods);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FingerprintAsync_WithOwnAssembly_FindsMethods()
|
||||
{
|
||||
// Arrange - use the test assembly itself
|
||||
@@ -80,7 +86,8 @@ public class CecilMethodFingerprinterTests
|
||||
Assert.True(result.Methods.Count > 0, "Should find at least some methods");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FingerprintAsync_ComputesDeterministicHashes()
|
||||
{
|
||||
// Arrange - fingerprint twice
|
||||
@@ -109,7 +116,8 @@ public class CecilMethodFingerprinterTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FingerprintAsync_WithCancellation_RespectsCancellation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -142,7 +150,8 @@ public class CecilMethodFingerprinterTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FingerprintAsync_MethodKeyFormat_IsValid()
|
||||
{
|
||||
// Arrange
|
||||
@@ -172,7 +181,8 @@ public class CecilMethodFingerprinterTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FingerprintAsync_IncludesSignature()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -8,11 +8,13 @@ using StellaOps.Scanner.VulnSurfaces.CallGraph;
|
||||
using StellaOps.Scanner.VulnSurfaces.Models;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.VulnSurfaces.Tests;
|
||||
|
||||
public class InternalCallGraphTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddMethod_StoresMethod()
|
||||
{
|
||||
// Arrange
|
||||
@@ -38,7 +40,8 @@ public class InternalCallGraphTests
|
||||
Assert.Equal(1, graph.MethodCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddEdge_CreatesForwardAndReverseMapping()
|
||||
{
|
||||
// Arrange
|
||||
@@ -63,7 +66,8 @@ public class InternalCallGraphTests
|
||||
Assert.Equal(1, graph.EdgeCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetPublicMethods_ReturnsOnlyPublic()
|
||||
{
|
||||
// Arrange
|
||||
@@ -97,7 +101,8 @@ public class InternalCallGraphTests
|
||||
Assert.Equal("A::Public()", publicMethods[0].MethodKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetCallees_EmptyForUnknownMethod()
|
||||
{
|
||||
// Arrange
|
||||
@@ -114,7 +119,8 @@ public class InternalCallGraphTests
|
||||
Assert.Empty(callees);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetMethod_ReturnsNullForUnknown()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.VulnSurfaces.Fingerprint;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.VulnSurfaces.Tests;
|
||||
|
||||
public class MethodDiffEngineTests
|
||||
@@ -20,14 +21,16 @@ public class MethodDiffEngineTests
|
||||
NullLogger<MethodDiffEngine>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_WithNullRequest_ThrowsArgumentNullException()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => _diffEngine.DiffAsync(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_WithIdenticalFingerprints_ReturnsNoChanges()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +71,8 @@ public class MethodDiffEngineTests
|
||||
Assert.Equal(0, diff.TotalChanges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_WithModifiedMethod_ReturnsModified()
|
||||
{
|
||||
// Arrange
|
||||
@@ -112,7 +116,8 @@ public class MethodDiffEngineTests
|
||||
Assert.Empty(diff.Removed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_WithAddedMethod_ReturnsAdded()
|
||||
{
|
||||
// Arrange
|
||||
@@ -155,7 +160,8 @@ public class MethodDiffEngineTests
|
||||
Assert.Empty(diff.Removed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_WithRemovedMethod_ReturnsRemoved()
|
||||
{
|
||||
// Arrange
|
||||
@@ -198,7 +204,8 @@ public class MethodDiffEngineTests
|
||||
Assert.Equal("Test.Class::RemovedMethod", diff.Removed[0].MethodKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_WithMultipleChanges_ReturnsAllChanges()
|
||||
{
|
||||
// Arrange - simulate a fix that modifies one method, adds one, removes one
|
||||
@@ -247,7 +254,8 @@ public class MethodDiffEngineTests
|
||||
Assert.Equal(3, diff.TotalChanges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_TriggerMethods_AreModifiedOrRemoved()
|
||||
{
|
||||
// This test validates the key insight:
|
||||
@@ -298,7 +306,8 @@ public class MethodDiffEngineTests
|
||||
Assert.Empty(diff.Removed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DiffAsync_WithEmptyFingerprints_ReturnsNoChanges()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -14,6 +14,8 @@ using Moq.Protected;
|
||||
using StellaOps.Scanner.VulnSurfaces.Download;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.VulnSurfaces.Tests;
|
||||
|
||||
public class NuGetPackageDownloaderTests : IDisposable
|
||||
@@ -35,7 +37,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Ecosystem_ReturnsNuget()
|
||||
{
|
||||
// Arrange
|
||||
@@ -45,7 +48,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
Assert.Equal("nuget", downloader.Ecosystem);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadAsync_WithNullRequest_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +60,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
() => downloader.DownloadAsync(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadAsync_WithHttpError_ReturnsFailResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -93,7 +98,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
Assert.Null(result.ExtractedPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadAsync_WithValidNupkg_ReturnsSuccessResult()
|
||||
{
|
||||
// Arrange - create a mock .nupkg (which is just a zip file)
|
||||
@@ -135,7 +141,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
Assert.False(result.FromCache);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadAsync_WithCachedPackage_ReturnsCachedResult()
|
||||
{
|
||||
// Arrange - pre-create the cached directory
|
||||
@@ -162,7 +169,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
Assert.Equal(packageDir, result.ExtractedPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadAsync_WithCacheFalse_BypassesCache()
|
||||
{
|
||||
// Arrange - pre-create the cached directory
|
||||
@@ -210,7 +218,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadAsync_UsesCorrectUrl()
|
||||
{
|
||||
// Arrange
|
||||
@@ -250,7 +259,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
Assert.EndsWith(".nupkg", capturedRequest.RequestUri!.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadAsync_WithCustomRegistry_UsesCustomUrl()
|
||||
{
|
||||
// Arrange
|
||||
@@ -289,7 +299,8 @@ public class NuGetPackageDownloaderTests : IDisposable
|
||||
Assert.StartsWith("https://custom.nuget.feed.example.com/v3", capturedRequest.RequestUri!.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DownloadAsync_WithCancellation_HonorsCancellation()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -20,5 +20,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Scanner.VulnSurfaces\StellaOps.Scanner.VulnSurfaces.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -10,6 +10,7 @@ using StellaOps.Scanner.VulnSurfaces.Models;
|
||||
using StellaOps.Scanner.VulnSurfaces.Triggers;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.VulnSurfaces.Tests;
|
||||
|
||||
public class TriggerMethodExtractorTests
|
||||
@@ -21,7 +22,8 @@ public class TriggerMethodExtractorTests
|
||||
_extractor = new TriggerMethodExtractor(NullLogger<TriggerMethodExtractor>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_DirectPath_FindsTrigger()
|
||||
{
|
||||
// Arrange
|
||||
@@ -85,7 +87,8 @@ public class TriggerMethodExtractorTests
|
||||
Assert.False(trigger.IsInterfaceExpansion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_NoPath_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
@@ -124,7 +127,8 @@ public class TriggerMethodExtractorTests
|
||||
Assert.Empty(result.Triggers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_MultiplePublicMethods_FindsAllTriggers()
|
||||
{
|
||||
// Arrange
|
||||
@@ -174,7 +178,8 @@ public class TriggerMethodExtractorTests
|
||||
Assert.Contains(result.Triggers, t => t.TriggerMethodKey == "Class::Api2()");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_MaxDepthExceeded_DoesNotFindTrigger()
|
||||
{
|
||||
// Arrange
|
||||
@@ -231,7 +236,8 @@ public class TriggerMethodExtractorTests
|
||||
Assert.Empty(result.Triggers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_VirtualMethod_ReducesConfidence()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -4,10 +4,12 @@ using StellaOps.Scanner.VulnSurfaces.Services;
|
||||
using StellaOps.Scanner.VulnSurfaces.Storage;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.VulnSurfaces.Tests;
|
||||
|
||||
public sealed class VulnSurfaceServiceTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact(DisplayName = "GetAffectedSymbolsAsync returns sinks when surface exists")]
|
||||
public async Task GetAffectedSymbolsAsync_ReturnsSurfaceSinks()
|
||||
{
|
||||
@@ -50,6 +52,7 @@ public sealed class VulnSurfaceServiceTests
|
||||
Assert.Equal(surfaceGuid, repository.LastSurfaceId);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact(DisplayName = "GetAffectedSymbolsAsync falls back to package symbol provider")]
|
||||
public async Task GetAffectedSymbolsAsync_FallsBackToPackageSymbols()
|
||||
{
|
||||
@@ -64,6 +67,7 @@ public sealed class VulnSurfaceServiceTests
|
||||
Assert.Single(result.Symbols);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact(DisplayName = "GetAffectedSymbolsAsync returns heuristic when no data")]
|
||||
public async Task GetAffectedSymbolsAsync_ReturnsHeuristicWhenEmpty()
|
||||
{
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Advisory.Tests;
|
||||
|
||||
public sealed class AdvisoryClientTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact(DisplayName = "GetCveSymbolsAsync uses Concelier response and caches results")]
|
||||
public async Task GetCveSymbolsAsync_UsesConcelierAndCaches()
|
||||
{
|
||||
@@ -57,6 +59,7 @@ public sealed class AdvisoryClientTests
|
||||
Assert.NotNull(mapping2);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact(DisplayName = "GetCveSymbolsAsync falls back to bundle store on HTTP failure")]
|
||||
public async Task GetCveSymbolsAsync_FallsBackToBundle()
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Text.Json;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Advisory.Tests;
|
||||
|
||||
public sealed class FileAdvisoryBundleStoreTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact(DisplayName = "FileAdvisoryBundleStore resolves CVE IDs case-insensitively")]
|
||||
public async Task TryGetAsync_ResolvesCaseInsensitive()
|
||||
{
|
||||
|
||||
@@ -16,5 +16,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Scanner.Advisory\StellaOps.Scanner.Advisory.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,11 +2,13 @@ using System.IO;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Node;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests;
|
||||
|
||||
public class Phase22SmokeTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Phase22_Fixture_Matches_Golden()
|
||||
{
|
||||
var cancellationToken = TestContext.Current.CancellationToken;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<!-- Keep graph tight: only Lang.Node tests + core contracts. Reuse compiled binaries to avoid dragging full solution build. -->
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/StellaOps.Scanner.Analyzers.Lang.Node.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Core/StellaOps.Scanner.Core.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,11 +5,13 @@ using System.Threading.Tasks;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Node.Internal.Phase22;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Node.Tests;
|
||||
|
||||
public class NodePhase22SampleLoaderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TryLoadAsync_ReadsComponentsFromNdjson()
|
||||
{
|
||||
var root = Path.Combine("Fixtures");
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/StellaOps.Scanner.Analyzers.Lang.Node.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Core/StellaOps.Scanner.Core.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,6 +4,7 @@ using StellaOps.Scanner.Analyzers.Lang;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Ruby;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Ruby.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -18,7 +19,8 @@ public sealed class RubyBenchmarks
|
||||
private const int BenchmarkIterations = 10;
|
||||
private const int MaxAnalysisTimeMs = 1000;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SimpleApp_MeetsPerformanceTargetAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "simple-app");
|
||||
@@ -47,7 +49,8 @@ public sealed class RubyBenchmarks
|
||||
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Simple app analysis should complete in <{MaxAnalysisTimeMs}ms including policy scanning (actual: {avgMs:F2}ms)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ComplexApp_MeetsPerformanceTargetAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "complex-app");
|
||||
@@ -76,7 +79,8 @@ public sealed class RubyBenchmarks
|
||||
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Complex app analysis should complete in <{MaxAnalysisTimeMs}ms including policy scanning (actual: {avgMs:F2}ms)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RailsApp_MeetsPerformanceTargetAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "rails-app");
|
||||
@@ -105,7 +109,8 @@ public sealed class RubyBenchmarks
|
||||
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Rails app analysis should complete in <{MaxAnalysisTimeMs}ms including policy scanning (actual: {avgMs:F2}ms)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SinatraApp_MeetsPerformanceTargetAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "sinatra-app");
|
||||
@@ -134,7 +139,8 @@ public sealed class RubyBenchmarks
|
||||
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Sinatra app analysis should complete in <{MaxAnalysisTimeMs}ms including policy scanning (actual: {avgMs:F2}ms)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ContainerApp_MeetsPerformanceTargetAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "container-app");
|
||||
@@ -163,7 +169,8 @@ public sealed class RubyBenchmarks
|
||||
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Container app analysis should complete in <{MaxAnalysisTimeMs}ms including policy scanning (actual: {avgMs:F2}ms)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LegacyApp_MeetsPerformanceTargetAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "legacy-app");
|
||||
@@ -192,7 +199,8 @@ public sealed class RubyBenchmarks
|
||||
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Legacy app analysis should complete in <{MaxAnalysisTimeMs}ms including policy scanning (actual: {avgMs:F2}ms)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CliApp_MeetsPerformanceTargetAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "cli-app");
|
||||
@@ -221,7 +229,8 @@ public sealed class RubyBenchmarks
|
||||
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"CLI app analysis should complete in <{MaxAnalysisTimeMs}ms including policy scanning (actual: {avgMs:F2}ms)");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task MultipleRuns_ProduceDeterministicResultsAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "simple-app");
|
||||
|
||||
@@ -5,11 +5,14 @@ using StellaOps.Scanner.Analyzers.Lang.Tests.Harness;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Ruby.Tests;
|
||||
|
||||
public sealed class RubyLanguageAnalyzerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SimpleWorkspaceProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "simple-app");
|
||||
@@ -23,7 +26,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzerEmitsObservationPayloadWithSummaryAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "simple-app");
|
||||
@@ -73,7 +77,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
Assert.Equal("2.4.22", root.GetProperty("bundledWith").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ComplexWorkspaceProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "complex-app");
|
||||
@@ -87,7 +92,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CliWorkspaceProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "cli-app");
|
||||
@@ -101,7 +107,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RailsWorkspaceProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "rails-app");
|
||||
@@ -115,7 +122,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SinatraWorkspaceProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "sinatra-app");
|
||||
@@ -129,7 +137,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ContainerWorkspaceProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "container-app");
|
||||
@@ -143,7 +152,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ContainerWorkspaceDetectsRubyVersionAndNativeExtensionsAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "container-app");
|
||||
@@ -185,7 +195,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
Assert.True(nativeExtensions.GetArrayLength() >= 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LegacyWorkspaceProducesDeterministicOutputAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "legacy-app");
|
||||
@@ -199,7 +210,8 @@ public sealed class RubyLanguageAnalyzerTests
|
||||
TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LegacyWorkspaceDetectsCapabilitiesWithoutBundlerAsync()
|
||||
{
|
||||
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "legacy-app");
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang/StellaOps.Scanner.Analyzers.Lang.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.Lang.Ruby/StellaOps.Scanner.Analyzers.Lang.Ruby.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Core/StellaOps.Scanner.Core.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,11 +3,14 @@ using StellaOps.Scanner.Analyzers.Native;
|
||||
using StellaOps.Scanner.Analyzers.Native.Tests.Fixtures;
|
||||
using StellaOps.Scanner.Analyzers.Native.Tests.TestUtilities;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesMinimalElfWithNoDynamicSection()
|
||||
{
|
||||
// Minimal ELF64 with no dependencies (static binary scenario)
|
||||
@@ -21,7 +24,8 @@ public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
info.Runpath.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesElfWithDtNeeded()
|
||||
{
|
||||
// Build ELF with DT_NEEDED entries using the builder
|
||||
@@ -38,7 +42,8 @@ public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
info.Dependencies[2].Soname.Should().Be("libpthread.so.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesElfWithRpathAndRunpath()
|
||||
{
|
||||
// Build ELF with rpath and runpath using the builder
|
||||
@@ -53,7 +58,8 @@ public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
info.Runpath.Should().BeEquivalentTo(["$ORIGIN/../lib"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesElfWithInterpreterAndBuildId()
|
||||
{
|
||||
// Build ELF with interpreter and build ID using the builder
|
||||
@@ -67,7 +73,8 @@ public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
info.BinaryId.Should().Be("deadbeef0102030405060708090a0b0c");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DeduplicatesDtNeededEntries()
|
||||
{
|
||||
// ElfBuilder deduplicates internally, so add "duplicates" via builder
|
||||
@@ -85,7 +92,8 @@ public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
info.Dependencies[0].Soname.Should().Be("libc.so.6");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReturnsFalseForNonElfData()
|
||||
{
|
||||
var buffer = new byte[] { 0x00, 0x01, 0x02, 0x03 };
|
||||
@@ -96,7 +104,8 @@ public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReturnsFalseForPeFile()
|
||||
{
|
||||
var buffer = new byte[256];
|
||||
@@ -109,7 +118,8 @@ public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesElfWithVersionNeeds()
|
||||
{
|
||||
// Test that version needs (GLIBC_2.17, etc.) are properly extracted
|
||||
@@ -128,7 +138,8 @@ public class ElfDynamicSectionParserTests : NativeTestBase
|
||||
info.Dependencies[0].VersionNeeds.Should().Contain(v => v.Version == "GLIBC_2.28");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesElfWithWeakVersionNeeds()
|
||||
{
|
||||
// Test that weak version requirements (VER_FLG_WEAK) are properly detected
|
||||
|
||||
@@ -2,11 +2,14 @@ using System.Text;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class HeuristicScannerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_DetectsElfSonamePattern()
|
||||
{
|
||||
// Arrange - binary containing soname strings
|
||||
@@ -26,7 +29,8 @@ public class HeuristicScannerTests
|
||||
result.Edges.Should().OnlyContain(e => e.ReasonCode == HeuristicReasonCodes.StringDlopen);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_DetectsWindowsDllPattern()
|
||||
{
|
||||
// Arrange
|
||||
@@ -46,7 +50,8 @@ public class HeuristicScannerTests
|
||||
result.Edges.Should().OnlyContain(e => e.ReasonCode == HeuristicReasonCodes.StringLoadLibrary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_DetectsMachODylibPattern()
|
||||
{
|
||||
// Arrange
|
||||
@@ -66,7 +71,8 @@ public class HeuristicScannerTests
|
||||
result.Edges.Should().Contain(e => e.LibraryName == "@loader_path/libbaz.dylib");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_AssignsHighConfidenceToPathLikeStrings()
|
||||
{
|
||||
// Arrange
|
||||
@@ -86,7 +92,8 @@ public class HeuristicScannerTests
|
||||
simpleSoname.Confidence.Should().Be(HeuristicConfidence.Medium);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_DetectsPluginConfigReferences()
|
||||
{
|
||||
// Arrange
|
||||
@@ -106,7 +113,8 @@ public class HeuristicScannerTests
|
||||
result.PluginConfigs.Should().Contain("modules.conf");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_DetectsGoCgoImportDirective()
|
||||
{
|
||||
// Arrange - simulate Go binary with cgo import
|
||||
@@ -126,7 +134,8 @@ public class HeuristicScannerTests
|
||||
e.Confidence == HeuristicConfidence.High);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_DetectsGoCgoStaticImport()
|
||||
{
|
||||
// Arrange
|
||||
@@ -145,7 +154,8 @@ public class HeuristicScannerTests
|
||||
e.ReasonCode == HeuristicReasonCodes.GoCgoImport);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_DeduplicatesEdgesByLibraryName()
|
||||
{
|
||||
// Arrange - same library mentioned multiple times
|
||||
@@ -164,7 +174,8 @@ public class HeuristicScannerTests
|
||||
result.Edges.Should().ContainSingle(e => e.LibraryName == "libfoo.so");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_IncludesFileOffsetInEdge()
|
||||
{
|
||||
// Arrange
|
||||
@@ -182,7 +193,8 @@ public class HeuristicScannerTests
|
||||
edge.FileOffset.Should().Be(100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ScanForDynamicLoading_ReturnsOnlyLibraryEdges()
|
||||
{
|
||||
// Arrange
|
||||
@@ -200,7 +212,8 @@ public class HeuristicScannerTests
|
||||
e.ReasonCode == HeuristicReasonCodes.StringDlopen);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ScanForPluginConfigs_ReturnsOnlyConfigReferences()
|
||||
{
|
||||
// Arrange
|
||||
@@ -219,7 +232,8 @@ public class HeuristicScannerTests
|
||||
configs.Should().Contain("plugin.json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_EmptyStream_ReturnsEmptyResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -233,7 +247,8 @@ public class HeuristicScannerTests
|
||||
result.PluginConfigs.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Scan_NoValidStrings_ReturnsEmptyResult()
|
||||
{
|
||||
// Arrange - binary data with no printable strings
|
||||
@@ -247,7 +262,8 @@ public class HeuristicScannerTests
|
||||
result.Edges.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("libfoo.so.1", true)]
|
||||
[InlineData("libbar.so", true)]
|
||||
[InlineData("lib-baz_qux.so.2.3", true)]
|
||||
|
||||
@@ -7,7 +7,8 @@ namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class MachOLoadCommandParserTests : NativeTestBase
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesMinimalMachO64LittleEndian()
|
||||
{
|
||||
// Build minimal Mach-O 64-bit little-endian using builder
|
||||
@@ -20,7 +21,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
info.Slices[0].CpuType.Should().Be("x86_64");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesMinimalMachO64BigEndian()
|
||||
{
|
||||
// Build minimal Mach-O 64-bit big-endian using builder
|
||||
@@ -37,7 +39,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
info.Slices[0].CpuType.Should().Be("x86_64");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesMachOWithDylibs()
|
||||
{
|
||||
// Build Mach-O with dylib dependencies using builder
|
||||
@@ -55,7 +58,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
info.Slices[0].Dependencies[1].Path.Should().Be("/usr/lib/libc++.1.dylib");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesMachOWithRpath()
|
||||
{
|
||||
// Build Mach-O with rpaths using builder
|
||||
@@ -71,7 +75,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
info.Slices[0].Rpaths[1].Should().Be("@loader_path/../lib");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesMachOWithUuid()
|
||||
{
|
||||
// Build Mach-O with UUID using builder
|
||||
@@ -86,7 +91,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
info.Slices[0].Uuid.Should().MatchRegex(@"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesFatBinary()
|
||||
{
|
||||
// Build universal (fat) binary using builder
|
||||
@@ -100,7 +106,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
info.Slices[1].CpuType.Should().Be("arm64");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesWeakAndReexportDylibs()
|
||||
{
|
||||
// Build Mach-O with weak and reexport dylibs using builder
|
||||
@@ -115,7 +122,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
info.Slices[0].Dependencies.Should().Contain(d => d.ReasonCode == "macho-reexport");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DeduplicatesDylibs()
|
||||
{
|
||||
// Build Mach-O with duplicate dylibs - builder or parser should deduplicate
|
||||
@@ -129,7 +137,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
info.Slices[0].Dependencies.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReturnsFalseForNonMachO()
|
||||
{
|
||||
var buffer = new byte[] { (byte)'M', (byte)'Z', 0x00, 0x00 };
|
||||
@@ -140,7 +149,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReturnsFalseForElf()
|
||||
{
|
||||
var buffer = new byte[] { 0x7F, (byte)'E', (byte)'L', (byte)'F' };
|
||||
@@ -151,7 +161,8 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesVersionNumbers()
|
||||
{
|
||||
// Build Mach-O with versioned dylib using builder
|
||||
@@ -159,6 +170,7 @@ public class MachOLoadCommandParserTests : NativeTestBase
|
||||
.AddDylib("/usr/lib/libfoo.dylib", "1.2.3", "1.0.0")
|
||||
.Build();
|
||||
|
||||
using StellaOps.TestKit;
|
||||
var info = ParseMachO(macho);
|
||||
|
||||
info.Slices[0].Dependencies[0].CurrentVersion.Should().Be("1.2.3");
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -391,7 +393,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region Magic Detection Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Returns_Null_For_Empty_Stream()
|
||||
{
|
||||
using var stream = new MemoryStream([]);
|
||||
@@ -399,7 +402,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Returns_Null_For_Invalid_Magic()
|
||||
{
|
||||
var data = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 };
|
||||
@@ -408,7 +412,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Detects_64Bit_LittleEndian_MachO()
|
||||
{
|
||||
var data = BuildMachO64();
|
||||
@@ -421,7 +426,8 @@ public sealed class MachOReaderTests
|
||||
Assert.False(result.Identities[0].IsFatBinary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Detects_32Bit_MachO()
|
||||
{
|
||||
var data = BuildMachO32(cpuType: 7); // x86
|
||||
@@ -437,7 +443,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region LC_UUID Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Extracts_LC_UUID()
|
||||
{
|
||||
var uuid = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10 };
|
||||
@@ -450,7 +457,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Equal("0123456789abcdeffedcba9876543210", result.Identities[0].Uuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Returns_Null_Uuid_When_Not_Present()
|
||||
{
|
||||
var data = BuildMachO64(uuid: null);
|
||||
@@ -462,7 +470,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Null(result.Identities[0].Uuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_UUID_Is_Lowercase_Hex_No_Dashes()
|
||||
{
|
||||
var uuid = new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 0x34, 0x56, 0x78, 0x9A };
|
||||
@@ -482,7 +491,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region Export Trie Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Extracts_Exports_From_LC_DYLD_INFO_ONLY()
|
||||
{
|
||||
var data = BuildMachO64(exports: new[] { "_main", "_printf" }, exportsViaDyldInfoOnly: true);
|
||||
@@ -494,7 +504,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Equal(new[] { "_main", "_printf" }, result.Identities[0].Exports);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Extracts_Exports_From_LC_DYLD_EXPORTS_TRIE()
|
||||
{
|
||||
var data = BuildMachO64(exports: new[] { "_zeta", "_alpha" }, exportsViaDyldInfoOnly: false);
|
||||
@@ -510,7 +521,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region Platform Detection Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(MachOPlatform.MacOS)]
|
||||
[InlineData(MachOPlatform.iOS)]
|
||||
[InlineData(MachOPlatform.TvOS)]
|
||||
@@ -528,7 +540,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Equal(expectedPlatform, result.Identities[0].Platform);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Extracts_MinOs_Version()
|
||||
{
|
||||
var data = BuildMachO64(minOs: 0x000E0500); // 14.5.0
|
||||
@@ -539,7 +552,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Equal("14.5", result.Identities[0].MinOsVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Extracts_SDK_Version()
|
||||
{
|
||||
var data = BuildMachO64(sdk: 0x000F0000); // 15.0.0
|
||||
@@ -550,7 +564,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Equal("15.0", result.Identities[0].SdkVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Version_With_Patch()
|
||||
{
|
||||
var data = BuildMachO64(minOs: 0x000E0501); // 14.5.1
|
||||
@@ -565,7 +580,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region Code Signature Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_UnsignedBinary_HasNull_CodeSignature()
|
||||
{
|
||||
var data = BuildMachO64();
|
||||
@@ -577,7 +593,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Null(result.Identities[0].CodeSignature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_SignedBinary_Extracts_SigningId_TeamId_CdHash_Entitlements_And_HardenedRuntime()
|
||||
{
|
||||
var signingId = "com.stellaops.demo";
|
||||
@@ -615,7 +632,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region CPU Type Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(0x00000007, "i386")] // CPU_TYPE_X86
|
||||
[InlineData(0x01000007, "x86_64")] // CPU_TYPE_X86_64
|
||||
[InlineData(0x0000000C, "arm")] // CPU_TYPE_ARM
|
||||
@@ -634,7 +652,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region Fat Binary Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Handles_Fat_Binary()
|
||||
{
|
||||
var arm64Slice = BuildMachO64(cpuType: 0x0100000C, uuid: new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 });
|
||||
@@ -655,7 +674,8 @@ public sealed class MachOReaderTests
|
||||
Assert.NotEqual(result.Identities[0].Uuid, result.Identities[1].Uuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseFatBinary_Returns_Multiple_Identities()
|
||||
{
|
||||
var arm64Slice = BuildMachO64(cpuType: 0x0100000C);
|
||||
@@ -672,7 +692,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region TryExtractIdentity Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_Returns_True_For_Valid_MachO()
|
||||
{
|
||||
var data = BuildMachO64();
|
||||
@@ -685,7 +706,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Equal("arm64", identity.CpuType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_Returns_False_For_Invalid_Data()
|
||||
{
|
||||
var data = new byte[] { 0x00, 0x00, 0x00, 0x00 };
|
||||
@@ -697,7 +719,8 @@ public sealed class MachOReaderTests
|
||||
Assert.Null(identity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_Returns_First_Slice_For_Fat_Binary()
|
||||
{
|
||||
var arm64Slice = BuildMachO64(cpuType: 0x0100000C);
|
||||
@@ -718,7 +741,8 @@ public sealed class MachOReaderTests
|
||||
|
||||
#region Path and LayerDigest Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_Preserves_Path_And_LayerDigest()
|
||||
{
|
||||
var data = BuildMachO64();
|
||||
|
||||
@@ -3,6 +3,7 @@ using StellaOps.Scanner.Analyzers.Native;
|
||||
using StellaOps.Scanner.Analyzers.Native.Tests.Fixtures;
|
||||
using StellaOps.Scanner.Analyzers.Native.Tests.TestUtilities;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -13,7 +14,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
{
|
||||
#region ELF Parameterized Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(true, false)] // 64-bit, little-endian
|
||||
[InlineData(true, true)] // 64-bit, big-endian
|
||||
public void ElfBuilder_ParsesDependencies_AllFormats(bool is64Bit, bool isBigEndian)
|
||||
@@ -34,7 +36,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
info.Dependencies[1].Soname.Should().Be("libm.so.6");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("GLIBC_2.17", false)]
|
||||
[InlineData("GLIBC_2.28", false)]
|
||||
[InlineData("GLIBC_2.34", true)]
|
||||
@@ -58,7 +61,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
dep.VersionNeeds[0].IsWeak.Should().Be(isWeak);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ElfBuilder_LinuxX64Factory_CreatesValidElf()
|
||||
{
|
||||
// Arrange
|
||||
@@ -82,7 +86,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
|
||||
#region PE Parameterized Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(false)] // PE32 with 4-byte thunks
|
||||
[InlineData(true)] // PE32+ with 8-byte thunks
|
||||
public void PeBuilder_ParsesImports_CorrectBitness(bool is64Bit)
|
||||
@@ -104,7 +109,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
info.Dependencies[0].ImportedFunctions.Should().Contain("LoadLibraryA");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(PeSubsystem.WindowsConsole)]
|
||||
[InlineData(PeSubsystem.WindowsGui)]
|
||||
public void PeBuilder_SetsSubsystem_Correctly(PeSubsystem subsystem)
|
||||
@@ -121,7 +127,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
info.Subsystem.Should().Be(subsystem);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PeBuilder_Console64Factory_CreatesValidPe()
|
||||
{
|
||||
// Arrange
|
||||
@@ -140,7 +147,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
info.DelayLoadDependencies.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PeBuilder_WithManifest_CreatesValidPe()
|
||||
{
|
||||
// Arrange
|
||||
@@ -160,7 +168,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
|
||||
#region Mach-O Parameterized Tests
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(MachODylibKind.Load, "macho-loadlib")]
|
||||
[InlineData(MachODylibKind.Weak, "macho-weaklib")]
|
||||
[InlineData(MachODylibKind.Reexport, "macho-reexport")]
|
||||
@@ -181,7 +190,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
info.Slices[0].Dependencies[0].ReasonCode.Should().Be(expectedReason);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(MachOCpuType.X86_64, "x86_64")]
|
||||
[InlineData(MachOCpuType.Arm64, "arm64")]
|
||||
public void MachOBuilder_SetsCpuType_Correctly(MachOCpuType cpuType, string expectedName)
|
||||
@@ -201,7 +211,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
info.Slices[0].CpuType.Should().Be(expectedName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MachOBuilder_MacOSArm64Factory_CreatesValidMachO()
|
||||
{
|
||||
// Arrange
|
||||
@@ -225,7 +236,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
info.Slices[0].Uuid.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MachOBuilder_Universal_CreatesFatBinary()
|
||||
{
|
||||
// Arrange
|
||||
@@ -241,7 +253,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
info.Slices.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MachOBuilder_WithVersion_ParsesVersionNumbers()
|
||||
{
|
||||
// Arrange
|
||||
@@ -261,7 +274,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
|
||||
#region Cross-Format Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AllBuilders_ProduceParseable_Binaries()
|
||||
{
|
||||
// Arrange
|
||||
@@ -275,7 +289,8 @@ public class NativeBuilderParameterizedTests : NativeTestBase
|
||||
TryParseMachO(macho, out _).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AllBuilders_RejectWrongFormat()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -3,11 +3,14 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using StellaOps.Scanner.Analyzers.Native;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class NativeFormatDetectorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectsElf64LittleEndian()
|
||||
{
|
||||
var bytes = new byte[64];
|
||||
@@ -28,7 +31,8 @@ public class NativeFormatDetectorTests
|
||||
Assert.Equal("le", id.Endianness);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectsElfInterpreterAndBuildId()
|
||||
{
|
||||
// Minimal ELF64 with two program headers: PT_INTERP and PT_NOTE (GNU build-id)
|
||||
@@ -93,7 +97,8 @@ public class NativeFormatDetectorTests
|
||||
Assert.Equal("gnu-build-id:0102030405060708090a0b0c0d0e0f10", id.BuildId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectsPe()
|
||||
{
|
||||
var bytes = new byte[256];
|
||||
@@ -116,7 +121,8 @@ public class NativeFormatDetectorTests
|
||||
Assert.Equal("le", id.Endianness);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectsMachO64()
|
||||
{
|
||||
var bytes = new byte[32];
|
||||
@@ -134,7 +140,8 @@ public class NativeFormatDetectorTests
|
||||
Assert.Equal("darwin", id.OperatingSystem);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ExtractsMachOUuid()
|
||||
{
|
||||
var buffer = new byte[128];
|
||||
@@ -161,7 +168,8 @@ public class NativeFormatDetectorTests
|
||||
Assert.Equal($"macho-uuid:{Convert.ToHexString(uuid.ToByteArray()).ToLowerInvariant()}", id.Uuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReturnsUnknownForUnsupported()
|
||||
{
|
||||
var bytes = new byte[] { 0x00, 0x01, 0x02, 0x03 };
|
||||
|
||||
@@ -3,11 +3,14 @@ using FluentAssertions;
|
||||
using StellaOps.Scanner.Analyzers.Native.Observations;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class NativeObservationSerializerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialize_ProducesValidJson()
|
||||
{
|
||||
// Arrange
|
||||
@@ -22,7 +25,8 @@ public class NativeObservationSerializerTests
|
||||
parsed.RootElement.GetProperty("$schema").GetString().Should().Be("stellaops.native.observation@1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialize_OmitsNullProperties()
|
||||
{
|
||||
// Arrange
|
||||
@@ -36,7 +40,8 @@ public class NativeObservationSerializerTests
|
||||
json.Should().NotContain("\"build_id\"");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SerializePretty_ProducesFormattedJson()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +55,8 @@ public class NativeObservationSerializerTests
|
||||
json.Should().Contain(" ");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Deserialize_RestoresDocument()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +74,8 @@ public class NativeObservationSerializerTests
|
||||
restored.HeuristicEdges.Should().HaveCount(original.HeuristicEdges.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeSha256_ProducesConsistentHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -84,7 +91,8 @@ public class NativeObservationSerializerTests
|
||||
hash1.Should().MatchRegex("^[a-f0-9]+$");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SerializeToBytes_ProducesUtf8()
|
||||
{
|
||||
// Arrange
|
||||
@@ -99,7 +107,8 @@ public class NativeObservationSerializerTests
|
||||
json.Should().StartWith("{");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WriteAsync_WritesToStream()
|
||||
{
|
||||
// Arrange
|
||||
@@ -116,7 +125,8 @@ public class NativeObservationSerializerTests
|
||||
json.Should().Contain("stellaops.native.observation@1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadAsync_ReadsFromStream()
|
||||
{
|
||||
// Arrange
|
||||
@@ -132,7 +142,8 @@ public class NativeObservationSerializerTests
|
||||
doc!.Binary.Path.Should().Be(original.Binary.Path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Deserialize_EmptyString_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -230,7 +241,8 @@ public class NativeObservationSerializerTests
|
||||
|
||||
public class NativeObservationBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithBinary_CreatesDocument()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -243,7 +255,8 @@ public class NativeObservationBuilderTests
|
||||
doc.Binary.Format.Should().Be("elf");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithoutBinary_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -255,7 +268,8 @@ public class NativeObservationBuilderTests
|
||||
.WithMessage("*Binary*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddEntrypoint_AddsToList()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -272,7 +286,8 @@ public class NativeObservationBuilderTests
|
||||
doc.Entrypoints[0].Conditions.Should().Contain("linux");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddElfDependencies_AddsEdgesAndEnvironment()
|
||||
{
|
||||
// Arrange
|
||||
@@ -302,7 +317,8 @@ public class NativeObservationBuilderTests
|
||||
doc.Environment.Runpath.Should().Contain("/app/lib");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddPeDependencies_AddsEdgesAndSxs()
|
||||
{
|
||||
// Arrange
|
||||
@@ -339,7 +355,8 @@ public class NativeObservationBuilderTests
|
||||
doc.Environment.SxsDependencies![0].Name.Should().Be("Microsoft.VC90.CRT");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddMachODependencies_AddsEdgesAndRpaths()
|
||||
{
|
||||
// Arrange
|
||||
@@ -372,7 +389,8 @@ public class NativeObservationBuilderTests
|
||||
doc.Environment.MachORpaths.Should().Contain("@loader_path/../Frameworks");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddHeuristicResults_AddsEdgesAndPluginConfigs()
|
||||
{
|
||||
// Arrange
|
||||
@@ -396,7 +414,8 @@ public class NativeObservationBuilderTests
|
||||
doc.Environment.PluginConfigs.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddResolution_AddsExplainTrace()
|
||||
{
|
||||
// Arrange
|
||||
@@ -424,7 +443,8 @@ public class NativeObservationBuilderTests
|
||||
doc.Resolution[0].Steps[1].Found.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FullIntegration_BuildsCompleteDocument()
|
||||
{
|
||||
// Arrange & Act
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class ElfResolverTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithRpath_FindsLibraryInRpathDirectory()
|
||||
{
|
||||
// Arrange
|
||||
@@ -26,7 +28,8 @@ public class ElfResolverTests
|
||||
s.Found == true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithRunpath_IgnoresRpath()
|
||||
{
|
||||
// Arrange - library exists in rpath but not runpath
|
||||
@@ -47,7 +50,8 @@ public class ElfResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "runpath");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithLdLibraryPath_SearchesBeforeRunpath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +72,8 @@ public class ElfResolverTests
|
||||
result.Steps.First().SearchReason.Should().Be("ld_library_path");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithOriginExpansion_ExpandsOriginVariable()
|
||||
{
|
||||
// Arrange
|
||||
@@ -84,7 +89,8 @@ public class ElfResolverTests
|
||||
result.ResolvedPath.Should().Be("/app/bin/../lib/libfoo.so.1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithOriginBraceSyntax_ExpandsOriginVariable()
|
||||
{
|
||||
// Arrange
|
||||
@@ -100,7 +106,8 @@ public class ElfResolverTests
|
||||
result.ResolvedPath.Should().Be("/app/bin/../lib/libbar.so.2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_NotFound_ReturnsUnresolvedWithSteps()
|
||||
{
|
||||
// Arrange
|
||||
@@ -119,7 +126,8 @@ public class ElfResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "default" && !s.Found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithDefaultPaths_SearchesSystemDirectories()
|
||||
{
|
||||
// Arrange
|
||||
@@ -134,7 +142,8 @@ public class ElfResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "default");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_SearchOrder_FollowsCorrectPriority()
|
||||
{
|
||||
// Arrange - library exists in all locations
|
||||
@@ -158,7 +167,8 @@ public class ElfResolverTests
|
||||
|
||||
public class PeResolverTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_InApplicationDirectory_FindsDll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -174,7 +184,8 @@ public class PeResolverTests
|
||||
.Which.SearchReason.Should().Be("application_directory");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_InSystem32_FindsDll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -189,7 +200,8 @@ public class PeResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "system_directory");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_InSysWOW64_FindsDll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -203,7 +215,8 @@ public class PeResolverTests
|
||||
result.ResolvedPath.Should().Be("C:/Windows/SysWOW64/wow64.dll");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_InCurrentDirectory_FindsDll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -218,7 +231,8 @@ public class PeResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "current_directory" && s.Found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_InPathEnvironment_FindsDll()
|
||||
{
|
||||
// Arrange
|
||||
@@ -234,7 +248,8 @@ public class PeResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "path_environment" && s.Found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_SafeDllSearchOrder_ApplicationBeforeSystem()
|
||||
{
|
||||
// Arrange - DLL exists in both app dir and system32
|
||||
@@ -252,7 +267,8 @@ public class PeResolverTests
|
||||
result.Steps.First().SearchReason.Should().Be("application_directory");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_NotFound_ReturnsAllSearchedPaths()
|
||||
{
|
||||
// Arrange
|
||||
@@ -272,7 +288,8 @@ public class PeResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "path_environment");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithNullApplicationDirectory_SkipsAppDirSearch()
|
||||
{
|
||||
// Arrange
|
||||
@@ -289,7 +306,8 @@ public class PeResolverTests
|
||||
|
||||
public class MachOResolverTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithRpath_ExpandsAndFindsLibrary()
|
||||
{
|
||||
// Arrange
|
||||
@@ -306,7 +324,8 @@ public class MachOResolverTests
|
||||
.Which.SearchReason.Should().Be("rpath");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithMultipleRpaths_SearchesInOrder()
|
||||
{
|
||||
// Arrange
|
||||
@@ -324,7 +343,8 @@ public class MachOResolverTests
|
||||
result.Steps[1].Found.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithLoaderPath_ExpandsPlaceholder()
|
||||
{
|
||||
// Arrange
|
||||
@@ -345,7 +365,8 @@ public class MachOResolverTests
|
||||
.Which.SearchReason.Should().Be("loader_path");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithExecutablePath_ExpandsPlaceholder()
|
||||
{
|
||||
// Arrange
|
||||
@@ -365,7 +386,8 @@ public class MachOResolverTests
|
||||
.Which.SearchReason.Should().Be("executable_path");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_WithRpathContainingLoaderPath_ExpandsBoth()
|
||||
{
|
||||
// Arrange
|
||||
@@ -380,7 +402,8 @@ public class MachOResolverTests
|
||||
result.ResolvedPath.Should().Be("/app/bin/../lib/libfoo.dylib");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_AbsolutePath_ChecksDirectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -396,7 +419,8 @@ public class MachOResolverTests
|
||||
.Which.SearchReason.Should().Be("absolute_path");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_RelativePath_SearchesDefaultPaths()
|
||||
{
|
||||
// Arrange
|
||||
@@ -411,7 +435,8 @@ public class MachOResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "default_library_path");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_RpathNotFound_FallsBackToDefaultPaths()
|
||||
{
|
||||
// Arrange - library not in rpath but in default path
|
||||
@@ -428,7 +453,8 @@ public class MachOResolverTests
|
||||
result.Steps.Should().Contain(s => s.SearchReason == "default_library_path" && s.Found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_NotFound_ReturnsAllSearchedPaths()
|
||||
{
|
||||
// Arrange
|
||||
@@ -445,7 +471,8 @@ public class MachOResolverTests
|
||||
result.Steps.Should().OnlyContain(s => !s.Found);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Resolve_LoaderPathNotFound_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -463,7 +490,8 @@ public class MachOResolverTests
|
||||
|
||||
public class VirtualFileSystemTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FileExists_WithExistingFile_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -473,7 +501,8 @@ public class VirtualFileSystemTests
|
||||
fs.FileExists("/usr/lib/libc.so.6").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FileExists_WithNonExistingFile_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -483,7 +512,8 @@ public class VirtualFileSystemTests
|
||||
fs.FileExists("/usr/lib/missing.so").Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FileExists_IsCaseInsensitive()
|
||||
{
|
||||
// Arrange
|
||||
@@ -493,7 +523,8 @@ public class VirtualFileSystemTests
|
||||
fs.FileExists("/usr/lib/LIBC.SO.6").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DirectoryExists_WithExistingDirectory_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -504,7 +535,8 @@ public class VirtualFileSystemTests
|
||||
fs.DirectoryExists("/usr/lib/x86_64-linux-gnu").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NormalizePath_HandlesBackslashes()
|
||||
{
|
||||
// Arrange
|
||||
@@ -514,7 +546,8 @@ public class VirtualFileSystemTests
|
||||
fs.FileExists("C:\\Windows\\System32\\kernel32.dll").Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EnumerateFiles_ReturnsFilesInDirectory()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -7,7 +7,8 @@ namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class PeImportParserTests : NativeTestBase
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesMinimalPe32()
|
||||
{
|
||||
// Build minimal PE32 using builder
|
||||
@@ -23,7 +24,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
info.Subsystem.Should().Be(PeSubsystem.WindowsConsole);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesMinimalPe32Plus()
|
||||
{
|
||||
// Build minimal PE32+ using builder
|
||||
@@ -35,7 +37,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
info.Machine.Should().Be("x86_64");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesPeWithImports()
|
||||
{
|
||||
// Build PE with imports using builder
|
||||
@@ -52,7 +55,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
info.Dependencies[1].DllName.Should().Be("user32.dll");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DeduplicatesImports()
|
||||
{
|
||||
// Build PE with duplicate imports - builder or parser should deduplicate
|
||||
@@ -67,7 +71,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
info.Dependencies[0].DllName.Should().Be("kernel32.dll");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesDelayLoadImports()
|
||||
{
|
||||
// Build PE with delay imports using builder
|
||||
@@ -82,7 +87,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
info.DelayLoadDependencies[0].ReasonCode.Should().Be("pe-delayimport");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesSubsystem()
|
||||
{
|
||||
// Build PE with GUI subsystem using builder
|
||||
@@ -95,7 +101,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
info.Subsystem.Should().Be(PeSubsystem.WindowsGui);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReturnsFalseForNonPe()
|
||||
{
|
||||
var buffer = new byte[] { 0x7F, (byte)'E', (byte)'L', (byte)'F' };
|
||||
@@ -106,7 +113,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ReturnsFalseForTruncatedPe()
|
||||
{
|
||||
var buffer = new byte[] { (byte)'M', (byte)'Z' };
|
||||
@@ -117,7 +125,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesEmbeddedManifest()
|
||||
{
|
||||
// Build PE with SxS dependency manifest using builder
|
||||
@@ -126,13 +135,15 @@ public class PeImportParserTests : NativeTestBase
|
||||
"6595b64144ccf1df", "*")
|
||||
.Build();
|
||||
|
||||
using StellaOps.TestKit;
|
||||
var info = ParsePe(pe);
|
||||
|
||||
info.SxsDependencies.Should().HaveCountGreaterOrEqualTo(1);
|
||||
info.SxsDependencies[0].Name.Should().Be("Microsoft.Windows.Common-Controls");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesPe32PlusWithImportThunks()
|
||||
{
|
||||
// Test that 64-bit PE files correctly parse 8-byte import thunks
|
||||
@@ -150,7 +161,8 @@ public class PeImportParserTests : NativeTestBase
|
||||
info.Dependencies[0].ImportedFunctions.Should().Contain("LoadLibraryA");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParsesPeWithEmbeddedResourceManifest()
|
||||
{
|
||||
// Test that manifest is properly extracted from PE resources
|
||||
|
||||
@@ -3,6 +3,8 @@ using StellaOps.Scanner.Analyzers.Native;
|
||||
using StellaOps.Scanner.Analyzers.Native.Tests.Fixtures;
|
||||
using StellaOps.Scanner.Analyzers.Native.Tests.TestUtilities;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -12,7 +14,8 @@ public class PeReaderTests : NativeTestBase
|
||||
{
|
||||
#region Basic Parsing
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_InvalidData_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -26,7 +29,8 @@ public class PeReaderTests : NativeTestBase
|
||||
identity.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_TooShort_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -39,7 +43,8 @@ public class PeReaderTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_MissingMzSignature_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -54,7 +59,8 @@ public class PeReaderTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_ValidMinimalPe64_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -71,7 +77,8 @@ public class PeReaderTests : NativeTestBase
|
||||
identity.Subsystem.Should().Be(PeSubsystem.WindowsConsole);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_ValidMinimalPe32_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -90,7 +97,8 @@ public class PeReaderTests : NativeTestBase
|
||||
identity.Machine.Should().Be("x86");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_GuiSubsystem_ParsesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -112,7 +120,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region Parse Method
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ValidPeStream_ReturnsPeParseResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -128,7 +137,8 @@ public class PeReaderTests : NativeTestBase
|
||||
result.Identity.Is64Bit.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_InvalidStream_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -142,7 +152,8 @@ public class PeReaderTests : NativeTestBase
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ThrowsOnNullStream()
|
||||
{
|
||||
// Act & Assert
|
||||
@@ -154,7 +165,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region Machine Architecture
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(PeMachine.I386, "x86", false)]
|
||||
[InlineData(PeMachine.Amd64, "x86_64", true)]
|
||||
[InlineData(PeMachine.Arm64, "arm64", true)]
|
||||
@@ -179,7 +191,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region Exports
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_NoExports_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange - standard console app has no exports
|
||||
@@ -198,7 +211,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region Compiler Hints (Rich Header)
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_NoRichHeader_ReturnsEmptyHints()
|
||||
{
|
||||
// Arrange - builder-generated PEs don't have rich header
|
||||
@@ -214,7 +228,8 @@ public class PeReaderTests : NativeTestBase
|
||||
identity.RichHeaderHash.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_RichHeader_ExtractsCompilerHints()
|
||||
{
|
||||
// Arrange
|
||||
@@ -236,7 +251,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region CodeView Debug Info
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_NoDebugDirectory_ReturnsNullCodeView()
|
||||
{
|
||||
// Arrange - builder-generated PEs don't have debug directory
|
||||
@@ -253,7 +269,8 @@ public class PeReaderTests : NativeTestBase
|
||||
identity.PdbPath.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_CodeViewDebugInfo_ExtractsGuidAgeAndPdbPath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -274,7 +291,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region Version Resources
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_NoVersionResource_ReturnsNullVersions()
|
||||
{
|
||||
// Arrange - builder-generated PEs don't have version resources
|
||||
@@ -293,7 +311,8 @@ public class PeReaderTests : NativeTestBase
|
||||
identity.OriginalFilename.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_VersionResource_ExtractsStrings()
|
||||
{
|
||||
// Arrange
|
||||
@@ -316,7 +335,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region Golden Fixtures
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_Exports_ExtractsExportNames()
|
||||
{
|
||||
// Arrange
|
||||
@@ -331,7 +351,8 @@ public class PeReaderTests : NativeTestBase
|
||||
identity!.Exports.Should().ContainSingle().Which.Should().Be("mingw_export");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_MingwFixture_HasNoRichOrCodeView()
|
||||
{
|
||||
// Arrange
|
||||
@@ -354,7 +375,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region Determinism
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_SameInput_ReturnsSameOutput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -368,7 +390,8 @@ public class PeReaderTests : NativeTestBase
|
||||
identity1.Should().BeEquivalentTo(identity2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_DifferentInputs_ReturnsDifferentOutput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -387,7 +410,8 @@ public class PeReaderTests : NativeTestBase
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_InvalidPeOffset_ReturnsFalse()
|
||||
{
|
||||
// Arrange - Create data with MZ signature but invalid PE offset
|
||||
@@ -407,7 +431,8 @@ public class PeReaderTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_MissingPeSignature_ReturnsFalse()
|
||||
{
|
||||
// Arrange - Create data with MZ but missing PE signature
|
||||
@@ -424,7 +449,8 @@ public class PeReaderTests : NativeTestBase
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryExtractIdentity_InvalidMagic_ReturnsFalse()
|
||||
{
|
||||
// Arrange - Create data with PE signature but invalid magic
|
||||
|
||||
@@ -7,6 +7,8 @@ using StellaOps.Scanner.Analyzers.Native;
|
||||
using StellaOps.Scanner.Analyzers.Native.Observations;
|
||||
using StellaOps.Scanner.Analyzers.Native.Plugin;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -16,7 +18,8 @@ public sealed class PluginPackagingTests
|
||||
{
|
||||
#region INativeAnalyzerPlugin Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NativeAnalyzerPlugin_Properties_AreConfigured()
|
||||
{
|
||||
var plugin = new NativeAnalyzerPlugin();
|
||||
@@ -26,7 +29,8 @@ public sealed class PluginPackagingTests
|
||||
plugin.Version.Should().Be("1.0.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NativeAnalyzerPlugin_SupportedFormats_ContainsAllFormats()
|
||||
{
|
||||
var plugin = new NativeAnalyzerPlugin();
|
||||
@@ -36,7 +40,8 @@ public sealed class PluginPackagingTests
|
||||
plugin.SupportedFormats.Should().Contain(NativeFormat.MachO);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NativeAnalyzerPlugin_IsAvailable_ReturnsTrue()
|
||||
{
|
||||
var plugin = new NativeAnalyzerPlugin();
|
||||
@@ -47,7 +52,8 @@ public sealed class PluginPackagingTests
|
||||
available.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NativeAnalyzerPlugin_CreateAnalyzer_ReturnsAnalyzer()
|
||||
{
|
||||
var plugin = new NativeAnalyzerPlugin();
|
||||
@@ -65,7 +71,8 @@ public sealed class PluginPackagingTests
|
||||
|
||||
#region NativeAnalyzerPluginCatalog Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PluginCatalog_Constructor_RegistersBuiltInPlugin()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzerPluginCatalog>.Instance;
|
||||
@@ -75,7 +82,8 @@ public sealed class PluginPackagingTests
|
||||
catalog.Plugins[0].Name.Should().Be("Native Binary Analyzer");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PluginCatalog_Register_AddsPlugin()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzerPluginCatalog>.Instance;
|
||||
@@ -88,7 +96,8 @@ public sealed class PluginPackagingTests
|
||||
catalog.Plugins.Should().Contain(p => p.Name == "Test Plugin");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PluginCatalog_Register_IgnoresDuplicates()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzerPluginCatalog>.Instance;
|
||||
@@ -102,7 +111,8 @@ public sealed class PluginPackagingTests
|
||||
catalog.Plugins.Count(p => p.Name == "Test Plugin").Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PluginCatalog_Seal_PreventsModification()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzerPluginCatalog>.Instance;
|
||||
@@ -115,7 +125,8 @@ public sealed class PluginPackagingTests
|
||||
.WithMessage("*sealed*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PluginCatalog_LoadFromDirectory_DoesNotFailForMissingDirectory()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzerPluginCatalog>.Instance;
|
||||
@@ -126,7 +137,8 @@ public sealed class PluginPackagingTests
|
||||
act.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PluginCatalog_CreateAnalyzers_CreatesFromAvailablePlugins()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzerPluginCatalog>.Instance;
|
||||
@@ -140,7 +152,8 @@ public sealed class PluginPackagingTests
|
||||
analyzers.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PluginCatalog_CreateAnalyzers_SkipsUnavailablePlugins()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzerPluginCatalog>.Instance;
|
||||
@@ -162,7 +175,8 @@ public sealed class PluginPackagingTests
|
||||
|
||||
#region ServiceCollectionExtensions Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddNativeAnalyzer_RegistersServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
@@ -173,7 +187,8 @@ public sealed class PluginPackagingTests
|
||||
services.Should().Contain(s => s.ServiceType == typeof(INativeAnalyzer));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddNativeAnalyzer_WithOptions_ConfiguresOptions()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
@@ -191,7 +206,8 @@ public sealed class PluginPackagingTests
|
||||
options.Value.DefaultTimeout.Should().Be(TimeSpan.FromMinutes(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NativeAnalyzerServiceOptions_DefaultValues()
|
||||
{
|
||||
var options = new NativeAnalyzerServiceOptions();
|
||||
@@ -202,7 +218,8 @@ public sealed class PluginPackagingTests
|
||||
options.DefaultTimeout.Should().Be(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NativeAnalyzerServiceOptions_GetDefaultSearchPathsForFormat_ReturnsCorrectPaths()
|
||||
{
|
||||
var options = new NativeAnalyzerServiceOptions();
|
||||
@@ -218,7 +235,8 @@ public sealed class PluginPackagingTests
|
||||
unknownPaths.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AddNativeRuntimeCapture_RegistersAdapter()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
@@ -233,7 +251,8 @@ public sealed class PluginPackagingTests
|
||||
|
||||
#region NativeAnalyzerOptions Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void NativeAnalyzerOptions_DefaultValues()
|
||||
{
|
||||
var options = new NativeAnalyzerOptions();
|
||||
@@ -249,7 +268,8 @@ public sealed class PluginPackagingTests
|
||||
|
||||
#region INativeAnalyzer Integration Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NativeAnalyzer_AnalyzeAsync_ThrowsForUnknownFormat()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzer>.Instance;
|
||||
@@ -264,7 +284,8 @@ public sealed class PluginPackagingTests
|
||||
.WithMessage("*Unknown or unsupported*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NativeAnalyzer_AnalyzeBatchAsync_YieldsResults()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzer>.Instance;
|
||||
@@ -294,7 +315,8 @@ public sealed class PluginPackagingTests
|
||||
results[0].Binary.Format.Should().Be("elf");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NativeAnalyzer_AnalyzeAsync_ParsesElfBinary()
|
||||
{
|
||||
var logger = NullLogger<NativeAnalyzer>.Instance;
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace StellaOps.Scanner.Analyzers.Native.Tests;
|
||||
|
||||
public class RuntimeCaptureOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_DefaultOptions_ReturnsNoErrors()
|
||||
{
|
||||
// Arrange
|
||||
@@ -19,7 +20,8 @@ public class RuntimeCaptureOptionsTests
|
||||
errors.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_InvalidBufferSize_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -32,7 +34,8 @@ public class RuntimeCaptureOptionsTests
|
||||
errors.Should().Contain(e => e.Contains("BufferSize"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_NegativeCaptureDuration_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -45,7 +48,8 @@ public class RuntimeCaptureOptionsTests
|
||||
errors.Should().Contain(e => e.Contains("MaxCaptureDuration"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_ExcessiveCaptureDuration_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -58,7 +62,8 @@ public class RuntimeCaptureOptionsTests
|
||||
errors.Should().Contain(e => e.Contains("MaxCaptureDuration"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_SandboxWithoutRoot_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -79,7 +84,8 @@ public class RuntimeCaptureOptionsTests
|
||||
errors.Should().Contain(e => e.Contains("SandboxRoot"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_SandboxWithRoot_ReturnsNoSandboxErrors()
|
||||
{
|
||||
// Arrange
|
||||
@@ -102,7 +108,8 @@ public class RuntimeCaptureOptionsTests
|
||||
|
||||
public class RedactionOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ApplyRedaction_HomePath_IsRedacted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -116,7 +123,8 @@ public class RedactionOptionsTests
|
||||
result.Should().Contain("[REDACTED]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ApplyRedaction_WindowsUserPath_IsRedacted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -130,7 +138,8 @@ public class RedactionOptionsTests
|
||||
result.Should().Contain("[REDACTED]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ApplyRedaction_SystemPath_NotRedacted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -144,7 +153,8 @@ public class RedactionOptionsTests
|
||||
result.Should().Be("/usr/lib/libc.so.6");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ApplyRedaction_DisabledRedaction_NotRedacted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -158,7 +168,8 @@ public class RedactionOptionsTests
|
||||
result.Should().Be("/home/testuser/secret.so");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ApplyRedaction_SshPath_IsRedacted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -172,7 +183,8 @@ public class RedactionOptionsTests
|
||||
result.Should().Contain("[REDACTED]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ApplyRedaction_KeyFile_IsRedacted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -186,7 +198,8 @@ public class RedactionOptionsTests
|
||||
result.Should().Contain("[REDACTED]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_InvalidRegex_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -202,7 +215,8 @@ public class RedactionOptionsTests
|
||||
errors.Should().Contain(e => e.Contains("Invalid redaction regex"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_EmptyReplacement_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
@@ -222,7 +236,8 @@ public class RedactionOptionsTests
|
||||
|
||||
public class RuntimeEvidenceAggregatorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Aggregate_EmptySessions_ReturnsEmptyEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -237,7 +252,8 @@ public class RuntimeEvidenceAggregatorTests
|
||||
evidence.RuntimeEdges.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Aggregate_SingleSession_ReturnsCorrectSummary()
|
||||
{
|
||||
// Arrange
|
||||
@@ -293,7 +309,8 @@ public class RuntimeEvidenceAggregatorTests
|
||||
libfoo.CallerModules.Should().Contain("myapp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Aggregate_DuplicateLoads_AggregatesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -316,7 +333,8 @@ public class RuntimeEvidenceAggregatorTests
|
||||
evidence.UniqueLibraries[0].FirstSeen.Should().Be(baseTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Aggregate_FailedLoads_NotIncludedInSummary()
|
||||
{
|
||||
// Arrange
|
||||
@@ -335,7 +353,8 @@ public class RuntimeEvidenceAggregatorTests
|
||||
evidence.RuntimeEdges.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Aggregate_MultipleSessions_MergesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -363,7 +382,8 @@ public class RuntimeEvidenceAggregatorTests
|
||||
|
||||
public class RuntimeCaptureAdapterFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateForCurrentPlatform_ReturnsAdapter()
|
||||
{
|
||||
// Act
|
||||
@@ -381,7 +401,8 @@ public class RuntimeCaptureAdapterFactoryTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetAvailableAdapters_ReturnsAdaptersForCurrentPlatform()
|
||||
{
|
||||
// Act
|
||||
@@ -402,7 +423,8 @@ public class RuntimeCaptureAdapterFactoryTests
|
||||
|
||||
public class SandboxCaptureTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SandboxCapture_WithMockEvents_CapturesEvents()
|
||||
{
|
||||
// Arrange
|
||||
@@ -448,7 +470,8 @@ public class SandboxCaptureTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SandboxCapture_StateTransitions_AreCorrect()
|
||||
{
|
||||
// Arrange
|
||||
@@ -488,7 +511,8 @@ public class SandboxCaptureTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SandboxCapture_CannotStartWhileRunning()
|
||||
{
|
||||
// Arrange
|
||||
@@ -517,6 +541,7 @@ public class SandboxCaptureTests
|
||||
{
|
||||
await adapter.StartCaptureAsync(options);
|
||||
|
||||
using StellaOps.TestKit;
|
||||
// Act & Assert
|
||||
var act = async () => await adapter.StartCaptureAsync(options);
|
||||
await act.Should().ThrowAsync<InvalidOperationException>();
|
||||
@@ -528,7 +553,8 @@ public class SandboxCaptureTests
|
||||
|
||||
public class RuntimeEvidenceModelTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RuntimeLoadEvent_RecordEquality_Works()
|
||||
{
|
||||
// Arrange
|
||||
@@ -542,7 +568,8 @@ public class RuntimeEvidenceModelTests
|
||||
event1.Should().NotBe(event3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RuntimeLoadType_AllTypesHaveReasonCodes()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Scanner.Analyzers.Native/StellaOps.Scanner.Analyzers.Native.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS.Homebrew;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Homebrew.Tests;
|
||||
|
||||
public sealed class HomebrewPackageAnalyzerTests
|
||||
@@ -29,13 +30,15 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
_logger);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AnalyzerId_ReturnsHomebrew()
|
||||
{
|
||||
Assert.Equal("homebrew", _analyzer.AnalyzerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithValidCellar_ReturnsPackages()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +53,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
Assert.True(result.Packages.Count > 0, "Expected at least one package");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_FindsIntelCellarPackages()
|
||||
{
|
||||
// Arrange
|
||||
@@ -67,7 +71,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
Assert.Contains("pkg:brew/homebrew%2Fcore/openssl%403@3.1.0", openssl.PackageUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_FindsAppleSiliconCellarPackages()
|
||||
{
|
||||
// Arrange
|
||||
@@ -83,7 +88,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
Assert.Equal("arm64", jq.Architecture);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_PackageWithRevision_IncludesRevisionInPurl()
|
||||
{
|
||||
// Arrange
|
||||
@@ -99,7 +105,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
Assert.Equal("1", wget.Release);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsDependencies()
|
||||
{
|
||||
// Arrange
|
||||
@@ -115,7 +122,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
Assert.Contains("gettext", wget.Depends);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -133,7 +141,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
Assert.Equal("https://openssl.org/", openssl.VendorMetadata["homepage"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_SetsEvidenceSourceToHomebrewCellar()
|
||||
{
|
||||
// Arrange
|
||||
@@ -149,7 +158,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_DiscoversBinFiles()
|
||||
{
|
||||
// Arrange
|
||||
@@ -164,7 +174,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
Assert.Contains(wget.Files, f => f.Path.Contains("wget"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ResultsAreDeterministicallySorted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -182,7 +193,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_NoCellar_ReturnsEmptyPackages()
|
||||
{
|
||||
// Arrange - use temp directory without Cellar structure
|
||||
@@ -205,7 +217,8 @@ public sealed class HomebrewPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_PopulatesTelemetry()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -2,13 +2,16 @@ using System.Text;
|
||||
using StellaOps.Scanner.Analyzers.OS.Homebrew;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Homebrew.Tests;
|
||||
|
||||
public sealed class HomebrewReceiptParserTests
|
||||
{
|
||||
private readonly HomebrewReceiptParser _parser = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ValidReceipt_ReturnsExpectedValues()
|
||||
{
|
||||
// Arrange
|
||||
@@ -51,7 +54,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Equal("x86_64", receipt.Architecture);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithRevision_ReturnsCorrectRevision()
|
||||
{
|
||||
// Arrange
|
||||
@@ -77,7 +81,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Equal(1, receipt.Revision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_AppleSilicon_ReturnsArm64Architecture()
|
||||
{
|
||||
// Arrange
|
||||
@@ -100,7 +105,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Equal("arm64", receipt.Architecture);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithSourceInfo_ExtractsSourceUrlAndChecksum()
|
||||
{
|
||||
// Arrange
|
||||
@@ -126,7 +132,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Equal("sha256:abcdef123456", receipt.SourceChecksum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_MultipleDependencies_SortsAlphabetically()
|
||||
{
|
||||
// Arrange
|
||||
@@ -155,7 +162,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Equal("zlib", receipt.RuntimeDependencies[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_InvalidJson_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -169,7 +177,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Null(receipt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_EmptyJson_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -183,7 +192,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Null(receipt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_MissingName_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -202,7 +212,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Null(receipt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_TappedFrom_UsesTappedFromOverTap()
|
||||
{
|
||||
// Arrange
|
||||
@@ -224,7 +235,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Equal("custom/tap", receipt.Tap);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_FallbackVersion_UsesVersionFieldWhenVersionsStableMissing()
|
||||
{
|
||||
// Arrange - older receipt format uses version field directly
|
||||
@@ -245,7 +257,8 @@ public sealed class HomebrewReceiptParserTests
|
||||
Assert.Equal("2.0.0", receipt.Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_NormalizesArchitecture_AArch64ToArm64()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS/StellaOps.Scanner.Analyzers.OS.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Homebrew/StellaOps.Scanner.Analyzers.OS.Homebrew.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Scanner.Analyzers.OS.MacOsBundle;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.MacOsBundle.Tests;
|
||||
|
||||
public sealed class EntitlementsParserTests
|
||||
@@ -11,7 +12,8 @@ public sealed class EntitlementsParserTests
|
||||
|
||||
private readonly EntitlementsParser _parser = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ValidEntitlements_ReturnsEntitlements()
|
||||
{
|
||||
// Arrange
|
||||
@@ -25,7 +27,8 @@ public sealed class EntitlementsParserTests
|
||||
Assert.True(result.IsSandboxed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_DetectsHighRiskEntitlements()
|
||||
{
|
||||
// Arrange
|
||||
@@ -40,7 +43,8 @@ public sealed class EntitlementsParserTests
|
||||
Assert.Contains("com.apple.security.device.microphone", result.HighRiskEntitlements);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_CategorizeEntitlements()
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,7 +61,8 @@ public sealed class EntitlementsParserTests
|
||||
Assert.Contains("sandbox", result.Categories);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_NonExistentFile_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
@@ -70,7 +75,8 @@ public sealed class EntitlementsParserTests
|
||||
Assert.Same(BundleEntitlements.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FindEntitlementsFile_FindsXcentFile()
|
||||
{
|
||||
// Arrange
|
||||
@@ -84,7 +90,8 @@ public sealed class EntitlementsParserTests
|
||||
Assert.EndsWith(".xcent", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FindEntitlementsFile_NoBundlePath_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -94,7 +101,8 @@ public sealed class EntitlementsParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FindEntitlementsFile_NoEntitlements_ReturnsNull()
|
||||
{
|
||||
// Arrange - bundle without entitlements
|
||||
@@ -107,7 +115,8 @@ public sealed class EntitlementsParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HasEntitlement_ReturnsTrueForExistingEntitlement()
|
||||
{
|
||||
// Arrange
|
||||
@@ -119,7 +128,8 @@ public sealed class EntitlementsParserTests
|
||||
Assert.True(result.HasEntitlement("com.apple.security.device.camera"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void HasEntitlement_ReturnsFalseForMissingEntitlement()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using StellaOps.Scanner.Analyzers.OS.MacOsBundle;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.MacOsBundle.Tests;
|
||||
|
||||
public sealed class InfoPlistParserTests
|
||||
@@ -11,7 +12,8 @@ public sealed class InfoPlistParserTests
|
||||
|
||||
private readonly InfoPlistParser _parser = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ValidInfoPlist_ReturnsBundleInfo()
|
||||
{
|
||||
// Arrange
|
||||
@@ -29,7 +31,8 @@ public sealed class InfoPlistParserTests
|
||||
Assert.Equal("1.2.3", result.ShortVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ExtractsMinimumSystemVersion()
|
||||
{
|
||||
// Arrange
|
||||
@@ -43,7 +46,8 @@ public sealed class InfoPlistParserTests
|
||||
Assert.Equal("12.0", result.MinimumSystemVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ExtractsExecutable()
|
||||
{
|
||||
// Arrange
|
||||
@@ -57,7 +61,8 @@ public sealed class InfoPlistParserTests
|
||||
Assert.Equal("TestApp", result.Executable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ExtractsSupportedPlatforms()
|
||||
{
|
||||
// Arrange
|
||||
@@ -72,7 +77,8 @@ public sealed class InfoPlistParserTests
|
||||
Assert.Contains("MacOSX", result.SupportedPlatforms);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_NonExistentFile_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -85,7 +91,8 @@ public sealed class InfoPlistParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_MissingBundleIdentifier_ReturnsNull()
|
||||
{
|
||||
// Arrange - Create a temp file without CFBundleIdentifier
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS.MacOsBundle;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.MacOsBundle.Tests;
|
||||
|
||||
public sealed class MacOsBundleAnalyzerTests
|
||||
@@ -29,13 +30,15 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
_logger);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AnalyzerId_ReturnsMacosBundleIdentifier()
|
||||
{
|
||||
Assert.Equal("macos-bundle", _analyzer.AnalyzerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithValidBundles_ReturnsPackages()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +53,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.True(result.Packages.Count > 0, "Expected at least one bundle");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_FindsTestApp()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +72,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.Equal("Test Application", testApp.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVersionCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -88,7 +93,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.Equal("123", testApp.Release);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_BuildsCorrectPurl()
|
||||
{
|
||||
// Arrange
|
||||
@@ -105,7 +111,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.Contains("pkg:generic/macos-app/com.stellaops.testapp@1.2.3", testApp.PackageUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorFromBundleId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -122,7 +129,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.Equal("stellaops", testApp.SourcePackage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_SetsEvidenceSourceToMacOsBundle()
|
||||
{
|
||||
// Arrange
|
||||
@@ -138,7 +146,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -159,7 +168,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.Equal("MacOSX", testApp.VendorMetadata["macos:platforms"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_IncludesCodeResourcesHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -178,7 +188,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.StartsWith("sha256:", hash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_DetectsSandboxedApp()
|
||||
{
|
||||
// Arrange
|
||||
@@ -195,7 +206,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.Equal("true", sandboxedApp.VendorMetadata["macos:sandboxed"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_DetectsHighRiskEntitlements()
|
||||
{
|
||||
// Arrange
|
||||
@@ -216,7 +228,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.Contains("com.apple.security.device.microphone", highRisk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_DetectsCapabilityCategories()
|
||||
{
|
||||
// Arrange
|
||||
@@ -239,7 +252,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.Contains("sandbox", categories);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_IncludesFileEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -264,7 +278,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
Assert.True(infoPlist.IsConfigFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ResultsAreDeterministicallySorted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -282,7 +297,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_NoApplicationsDirectory_ReturnsEmptyPackages()
|
||||
{
|
||||
// Arrange - use temp directory without Applications
|
||||
@@ -305,7 +321,8 @@ public sealed class MacOsBundleAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_PopulatesTelemetry()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS/StellaOps.Scanner.Analyzers.OS.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.MacOsBundle/StellaOps.Scanner.Analyzers.OS.MacOsBundle.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS.Pkgutil;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Pkgutil.Tests;
|
||||
|
||||
public sealed class PkgutilPackageAnalyzerTests
|
||||
@@ -29,13 +30,15 @@ public sealed class PkgutilPackageAnalyzerTests
|
||||
_logger);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AnalyzerId_ReturnsPkgutil()
|
||||
{
|
||||
Assert.Equal("pkgutil", _analyzer.AnalyzerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithValidReceipts_ReturnsPackages()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +53,8 @@ public sealed class PkgutilPackageAnalyzerTests
|
||||
Assert.True(result.Packages.Count > 0, "Expected at least one package");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_FindsSafariPackage()
|
||||
{
|
||||
// Arrange
|
||||
@@ -66,7 +70,8 @@ public sealed class PkgutilPackageAnalyzerTests
|
||||
Assert.Contains("pkg:generic/apple/com.apple.pkg.Safari@17.1", safari.PackageUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorFromIdentifier()
|
||||
{
|
||||
// Arrange
|
||||
@@ -81,7 +86,8 @@ public sealed class PkgutilPackageAnalyzerTests
|
||||
Assert.Equal("apple", safari.SourcePackage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_SetsEvidenceSourceToPkgutilReceipt()
|
||||
{
|
||||
// Arrange
|
||||
@@ -97,7 +103,8 @@ public sealed class PkgutilPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -113,7 +120,8 @@ public sealed class PkgutilPackageAnalyzerTests
|
||||
Assert.Equal("/", safari.VendorMetadata["pkgutil:volume"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ResultsAreDeterministicallySorted()
|
||||
{
|
||||
// Arrange
|
||||
@@ -131,7 +139,8 @@ public sealed class PkgutilPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_NoReceiptsDirectory_ReturnsEmptyPackages()
|
||||
{
|
||||
// Arrange - use temp directory without receipts
|
||||
@@ -154,7 +163,8 @@ public sealed class PkgutilPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_PopulatesTelemetry()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS/StellaOps.Scanner.Analyzers.OS.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Pkgutil/StellaOps.Scanner.Analyzers.OS.Pkgutil.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -12,11 +12,14 @@ using StellaOps.Scanner.Core.Contracts;
|
||||
using StellaOps.Scanner.Analyzers.OS.Tests.TestUtilities;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Tests;
|
||||
|
||||
public sealed class OsAnalyzerDeterminismTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ApkAnalyzerMatchesGolden()
|
||||
{
|
||||
using var fixture = FixtureManager.UseFixture("apk", out var rootPath);
|
||||
@@ -28,7 +31,8 @@ public sealed class OsAnalyzerDeterminismTests
|
||||
GoldenAssert.MatchSnapshot(snapshot, FixtureManager.GetGoldenPath("apk.json"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DpkgAnalyzerMatchesGolden()
|
||||
{
|
||||
using var fixture = FixtureManager.UseFixture("dpkg", out var rootPath);
|
||||
@@ -40,7 +44,8 @@ public sealed class OsAnalyzerDeterminismTests
|
||||
GoldenAssert.MatchSnapshot(snapshot, FixtureManager.GetGoldenPath("dpkg.json"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RpmAnalyzerMatchesGolden()
|
||||
{
|
||||
var headers = new[]
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Apk/StellaOps.Scanner.Analyzers.OS.Apk.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Dpkg/StellaOps.Scanner.Analyzers.OS.Dpkg.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Rpm/StellaOps.Scanner.Analyzers.OS.Rpm.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -4,11 +4,13 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey.Tests;
|
||||
|
||||
public class ChocolateyAnalyzerPluginTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Name_ReturnsCorrectPluginName()
|
||||
{
|
||||
// Arrange
|
||||
@@ -21,7 +23,8 @@ public class ChocolateyAnalyzerPluginTests
|
||||
Assert.Equal("StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey", name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsAvailable_WithValidServiceProvider_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -37,7 +40,8 @@ public class ChocolateyAnalyzerPluginTests
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsAvailable_WithNullServiceProvider_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
@@ -50,7 +54,8 @@ public class ChocolateyAnalyzerPluginTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateAnalyzer_WithValidServiceProvider_ReturnsAnalyzer()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +73,8 @@ public class ChocolateyAnalyzerPluginTests
|
||||
Assert.Equal("windows-chocolatey", analyzer.AnalyzerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateAnalyzer_WithNullServiceProvider_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -3,6 +3,8 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey.Tests;
|
||||
|
||||
public class ChocolateyPackageAnalyzerTests
|
||||
@@ -25,13 +27,15 @@ public class ChocolateyPackageAnalyzerTests
|
||||
_logger);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AnalyzerId_ReturnsCorrectValue()
|
||||
{
|
||||
Assert.Equal("windows-chocolatey", _analyzer.AnalyzerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithNoChocolateyDirectory_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
@@ -55,7 +59,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithEmptyChocolateyLib_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
@@ -80,7 +85,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithNuspecFile_ReturnsPackageRecord()
|
||||
{
|
||||
// Arrange
|
||||
@@ -113,7 +119,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithMultiplePackages_ReturnsAllRecords()
|
||||
{
|
||||
// Arrange
|
||||
@@ -160,7 +167,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -198,7 +206,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithInstallScript_ComputesHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -231,7 +240,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_FallsBackToDirectoryParsing_WhenNoNuspec()
|
||||
{
|
||||
// Arrange
|
||||
@@ -264,7 +274,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_IncludesFileEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -307,7 +318,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ResultsAreSortedDeterministically()
|
||||
{
|
||||
// Arrange
|
||||
@@ -346,7 +358,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_SkipsHiddenDirectories()
|
||||
{
|
||||
// Arrange
|
||||
@@ -379,7 +392,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_HandlesLowerCaseChocolateyPath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -407,7 +421,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_TruncatesLongDescription()
|
||||
{
|
||||
// Arrange
|
||||
@@ -440,7 +455,8 @@ public class ChocolateyPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithCancellation_ThrowsOperationCanceledException()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
using StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey.Tests;
|
||||
|
||||
public class NuspecParserTests
|
||||
{
|
||||
private readonly NuspecParser _parser = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithValidNuspec_ReturnsMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -49,7 +51,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithOldNamespace_ReturnsMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -81,7 +84,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithOld2011Namespace_ReturnsMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -113,7 +117,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithNoNamespace_ReturnsMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -145,7 +150,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithMissingId_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -174,7 +180,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithMissingVersion_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -203,7 +210,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithInvalidXml_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -227,7 +235,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithNonExistentFile_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -237,7 +246,8 @@ public class NuspecParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithNullPath_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -247,7 +257,8 @@ public class NuspecParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithEmptyPath_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -257,7 +268,8 @@ public class NuspecParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithWhitespacePath_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -267,7 +279,8 @@ public class NuspecParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ComputesInstallScriptHash_FromToolsDirectory()
|
||||
{
|
||||
// Arrange
|
||||
@@ -302,7 +315,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ComputesInstallScriptHash_FromRootDirectory()
|
||||
{
|
||||
// Arrange
|
||||
@@ -336,7 +350,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_EnumeratesInstalledFiles()
|
||||
{
|
||||
// Arrange
|
||||
@@ -373,7 +388,8 @@ public class NuspecParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("git.2.42.0", "git", "2.42.0")]
|
||||
[InlineData("nodejs.20.10.0", "nodejs", "20.10.0")]
|
||||
[InlineData("7zip.23.01", "7zip", "23.01")]
|
||||
@@ -391,7 +407,8 @@ public class NuspecParserTests
|
||||
Assert.Equal(expectedVersion, result.Value.Version);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS/StellaOps.Scanner.Analyzers.OS.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey/StellaOps.Scanner.Analyzers.OS.Windows.Chocolatey.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Windows.Msi.Tests;
|
||||
|
||||
public class MsiDatabaseParserTests
|
||||
{
|
||||
private readonly MsiDatabaseParser _parser = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithValidMsiFile_ExtractsMetadata()
|
||||
{
|
||||
// Arrange - Create a minimal valid OLE compound document
|
||||
@@ -34,7 +36,8 @@ public class MsiDatabaseParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithVersionedFilename_ExtractsVersionFromName()
|
||||
{
|
||||
// Arrange
|
||||
@@ -59,7 +62,8 @@ public class MsiDatabaseParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithSpaceVersionedFilename_ExtractsVersionFromName()
|
||||
{
|
||||
// Arrange
|
||||
@@ -84,7 +88,8 @@ public class MsiDatabaseParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithUnversionedFilename_UsesDefaultVersion()
|
||||
{
|
||||
// Arrange
|
||||
@@ -109,7 +114,8 @@ public class MsiDatabaseParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithNonExistentFile_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -119,7 +125,8 @@ public class MsiDatabaseParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithInvalidMsiFile_ReturnsNull()
|
||||
{
|
||||
// Arrange - Create a file with invalid content
|
||||
@@ -140,7 +147,8 @@ public class MsiDatabaseParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithEmptyPath_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -150,7 +158,8 @@ public class MsiDatabaseParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithNullPath_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -160,7 +169,8 @@ public class MsiDatabaseParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("Product-1.0.msi", "Product", "1.0")]
|
||||
[InlineData("Product-1.0.0.msi", "Product", "1.0.0")]
|
||||
[InlineData("Product-1.0.0.1.msi", "Product", "1.0.0.1")]
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS.Windows.Msi;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Windows.Msi.Tests;
|
||||
|
||||
public class MsiPackageAnalyzerTests
|
||||
@@ -25,13 +26,15 @@ public class MsiPackageAnalyzerTests
|
||||
_logger);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AnalyzerId_ReturnsCorrectValue()
|
||||
{
|
||||
Assert.Equal("windows-msi", _analyzer.AnalyzerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithNoMsiDirectory_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
@@ -55,7 +58,8 @@ public class MsiPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithMsiFiles_ReturnsPackageRecords()
|
||||
{
|
||||
// Arrange
|
||||
@@ -94,7 +98,8 @@ public class MsiPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithNestedMsiFiles_DiscoversMsisRecursively()
|
||||
{
|
||||
// Arrange
|
||||
@@ -122,7 +127,8 @@ public class MsiPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithUserAppDataCache_ScansMsisInUserDirectories()
|
||||
{
|
||||
// Arrange
|
||||
@@ -149,7 +155,8 @@ public class MsiPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithInvalidMsiFile_SkipsInvalidFile()
|
||||
{
|
||||
// Arrange
|
||||
@@ -180,7 +187,8 @@ public class MsiPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ResultsAreSortedDeterministically()
|
||||
{
|
||||
// Arrange
|
||||
@@ -212,7 +220,8 @@ public class MsiPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithDuplicateMsiFiles_DeduplicatesByPath()
|
||||
{
|
||||
// Arrange
|
||||
@@ -241,7 +250,8 @@ public class MsiPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_SetsCorrectFileEvidence()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS/StellaOps.Scanner.Analyzers.OS.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Windows.Msi/StellaOps.Scanner.Analyzers.OS.Windows.Msi.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS/StellaOps.Scanner.Analyzers.OS.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.Windows.WinSxS/StellaOps.Scanner.Analyzers.OS.Windows.WinSxS.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Windows.WinSxS.Tests;
|
||||
|
||||
public class WinSxSManifestParserTests
|
||||
{
|
||||
private readonly WinSxSManifestParser _parser = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithValidManifest_ExtractsMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -44,7 +46,8 @@ public class WinSxSManifestParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithAmd64Architecture_ExtractsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
@@ -74,7 +77,8 @@ public class WinSxSManifestParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithMultipleFiles_ExtractsAllFiles()
|
||||
{
|
||||
// Arrange
|
||||
@@ -104,7 +108,8 @@ public class WinSxSManifestParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithKbReferenceInFilename_ExtractsKbReference()
|
||||
{
|
||||
// Arrange
|
||||
@@ -130,7 +135,8 @@ public class WinSxSManifestParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithNonExistentFile_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -140,7 +146,8 @@ public class WinSxSManifestParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithInvalidXml_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -160,7 +167,8 @@ public class WinSxSManifestParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithMissingAssemblyIdentity_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -183,7 +191,8 @@ public class WinSxSManifestParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_WithEmptyPath_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -193,7 +202,8 @@ public class WinSxSManifestParserTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildAssemblyIdentityString_BuildsCorrectFormat()
|
||||
{
|
||||
// Arrange
|
||||
@@ -218,7 +228,8 @@ public class WinSxSManifestParserTests
|
||||
Assert.Equal("microsoft.windows.common-controls_6.0.0.0_x86_6595b64144ccf1df_en-us", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildAssemblyIdentityString_WithNeutralLanguage_OmitsLanguage()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS.Windows.WinSxS;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Analyzers.OS.Windows.WinSxS.Tests;
|
||||
|
||||
public class WinSxSPackageAnalyzerTests
|
||||
@@ -25,13 +26,15 @@ public class WinSxSPackageAnalyzerTests
|
||||
_logger);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AnalyzerId_ReturnsCorrectValue()
|
||||
{
|
||||
Assert.Equal("windows-winsxs", _analyzer.AnalyzerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithNoWinSxSDirectory_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
@@ -55,7 +58,8 @@ public class WinSxSPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithManifestFiles_ReturnsPackageRecords()
|
||||
{
|
||||
// Arrange
|
||||
@@ -97,7 +101,8 @@ public class WinSxSPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorMetadata()
|
||||
{
|
||||
// Arrange
|
||||
@@ -132,7 +137,8 @@ public class WinSxSPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsPublisherFromAssemblyName()
|
||||
{
|
||||
// Arrange
|
||||
@@ -160,7 +166,8 @@ public class WinSxSPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_IncludesFileEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -201,7 +208,8 @@ public class WinSxSPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithInvalidManifest_SkipsAndContinues()
|
||||
{
|
||||
// Arrange
|
||||
@@ -233,7 +241,8 @@ public class WinSxSPackageAnalyzerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ResultsAreSortedDeterministically()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -9,6 +9,8 @@ using StellaOps.Scanner.Cache.FileCas;
|
||||
using StellaOps.Scanner.Cache.LayerCache;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Cache.Tests;
|
||||
|
||||
public sealed class LayerCacheRoundTripTests : IAsyncLifetime
|
||||
@@ -42,7 +44,8 @@ public sealed class LayerCacheRoundTripTests : IAsyncLifetime
|
||||
_fileCas = new FileContentAddressableStore(_options, NullLogger<FileContentAddressableStore>.Instance, _timeProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RoundTrip_Succeeds_And_Respects_Ttl_And_ImportExport()
|
||||
{
|
||||
var layerDigest = "sha256:abcd1234";
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Remove="..\StellaOps.Concelier.Testing\StellaOps.Concelier.Testing.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Remove="StellaOps.Concelier.Testing" />
|
||||
|
||||
@@ -2,11 +2,13 @@ using StellaOps.Scanner.CallGraph;
|
||||
using StellaOps.Scanner.CallGraph.Node;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class BenchmarkIntegrationTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("unsafe-eval", true)]
|
||||
[InlineData("guarded-eval", false)]
|
||||
public async Task NodeTraceExtractor_AlignsWithBenchmarkReachability(string caseName, bool expectSinkReachable)
|
||||
|
||||
@@ -8,11 +8,13 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.CallGraph.Binary;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class BinaryCallGraphExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BinaryEntrypointClassifier_ClassifiesMainFunction()
|
||||
{
|
||||
// Arrange
|
||||
@@ -34,7 +36,8 @@ public class BinaryCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.CliCommand, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BinaryEntrypointClassifier_ClassifiesInitArray()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +59,8 @@ public class BinaryCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.InitFunction, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BinaryEntrypointClassifier_ReturnsNullForInternalFunction()
|
||||
{
|
||||
// Arrange
|
||||
@@ -77,7 +81,8 @@ public class BinaryCallGraphExtractorTests
|
||||
Assert.False(result.HasValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DwarfDebugReader_HandlesNonExistentFile()
|
||||
{
|
||||
// Arrange
|
||||
@@ -89,7 +94,8 @@ public class BinaryCallGraphExtractorTests
|
||||
await reader.ReadAsync("/nonexistent/binary", default));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BinaryRelocation_HasCorrectProperties()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -110,7 +116,8 @@ public class BinaryCallGraphExtractorTests
|
||||
Assert.True(relocation.IsExternal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DwarfFunction_RecordsCorrectInfo()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -135,7 +142,8 @@ public class BinaryCallGraphExtractorTests
|
||||
Assert.True(func.IsExternal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BinarySymbol_TracksVisibility()
|
||||
{
|
||||
// Arrange & Act
|
||||
|
||||
@@ -2,11 +2,13 @@ using StellaOps.Scanner.CallGraph.Binary;
|
||||
using StellaOps.Scanner.CallGraph.Binary.Disassembly;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class BinaryDisassemblyTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void X86Disassembler_Extracts_Call_And_Jmp()
|
||||
{
|
||||
var disassembler = new X86Disassembler();
|
||||
@@ -26,7 +28,8 @@ public class BinaryDisassemblyTests
|
||||
Assert.Equal(0x100CUL, calls[1].TargetAddress);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DirectCallExtractor_Maps_Targets_To_Symbols()
|
||||
{
|
||||
var extractor = new DirectCallExtractor();
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Scanner.CallGraph.Binary.Disassembly;
|
||||
using StellaOps.Scanner.CallGraph.Binary.Analysis;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class BinaryTextSectionReaderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadsElfTextSection()
|
||||
{
|
||||
var textBytes = new byte[] { 0x90, 0x90, 0xC3, 0x90 };
|
||||
@@ -32,7 +34,8 @@ public class BinaryTextSectionReaderTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadsPeTextSection()
|
||||
{
|
||||
var textBytes = new byte[] { 0x90, 0x90, 0xC3, 0x90 };
|
||||
@@ -54,7 +57,8 @@ public class BinaryTextSectionReaderTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReadsMachOTextSection()
|
||||
{
|
||||
var textBytes = new byte[] { 0x1F, 0x20, 0x03, 0xD5 };
|
||||
@@ -76,7 +80,8 @@ public class BinaryTextSectionReaderTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StringScannerExtractsLibraryCandidates()
|
||||
{
|
||||
var textBytes = new byte[] { 0x90, 0x90, 0xC3, 0x90 };
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using StellaOps.Scanner.CallGraph.Caching;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class CircuitBreakerStateTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordFailure_TripsOpen_AfterThreshold()
|
||||
{
|
||||
var config = new CircuitBreakerConfig
|
||||
@@ -26,7 +28,8 @@ public class CircuitBreakerStateTests
|
||||
Assert.True(cb.IsOpen);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordSuccess_ResetsToClosed()
|
||||
{
|
||||
var config = new CircuitBreakerConfig { FailureThreshold = 1, TimeoutSeconds = 60, HalfOpenTimeout = 10 };
|
||||
|
||||
@@ -2,11 +2,14 @@ using StellaOps.Scanner.CallGraph;
|
||||
using StellaOps.Scanner.CallGraph.DotNet;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class DotNetCallGraphExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_SimpleProject_ProducesEntrypointAndSink()
|
||||
{
|
||||
await using var temp = await TempDirectory.CreateAsync();
|
||||
@@ -71,7 +74,8 @@ public class DotNetCallGraphExtractorTests
|
||||
Assert.NotEmpty(snapshot.EntrypointIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_IsDeterministic_ForSameInputs()
|
||||
{
|
||||
await using var temp = await TempDirectory.CreateAsync();
|
||||
|
||||
@@ -9,11 +9,13 @@ using StellaOps.Scanner.CallGraph.Go;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class GoCallGraphExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildFunctionId_CreatesCorrectFormat()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -23,7 +25,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.Equal("go:github.com/example/pkg.HandleRequest", id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildMethodId_CreatesCorrectFormat()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -33,7 +36,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.Equal("go:github.com/example/pkg.Server.Start", id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildExternalId_CreatesCorrectFormat()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -43,7 +47,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.Equal("go:external/fmt.Println", id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ParsesFunctionId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -61,7 +66,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.False(result.IsExternal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ParsesExternalId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -77,7 +83,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.True(result.IsExternal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void IsStdLib_ReturnsTrueForStandardLibrary()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
@@ -86,7 +93,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.False(GoSymbolIdBuilder.IsStdLib("go:external/github.com/gin-gonic/gin.New"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GoSsaResultParser_ParsesValidJson()
|
||||
{
|
||||
// Arrange
|
||||
@@ -120,7 +128,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.Single(result.Entrypoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GoSsaResultParser_ThrowsOnEmptyInput()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
@@ -128,7 +137,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.Throws<ArgumentException>(() => GoSsaResultParser.Parse(" "));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GoEntrypointClassifier_ClassifiesHttpHandler()
|
||||
{
|
||||
// Arrange
|
||||
@@ -164,7 +174,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.HttpHandler, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GoSinkMatcher_MatchesExecCommand()
|
||||
{
|
||||
// Arrange
|
||||
@@ -177,7 +188,8 @@ public class GoCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.CmdExec, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GoSinkMatcher_MatchesSqlQuery()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -10,6 +10,8 @@ using StellaOps.Scanner.Reachability;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -31,7 +33,8 @@ public class JavaCallGraphExtractorTests
|
||||
|
||||
#region JavaEntrypointClassifier Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_SpringRequestMapping_DetectedAsHttpHandler()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -61,7 +64,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.HttpHandler, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_SpringRestController_PublicMethodDetectedAsHttpHandler()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -91,7 +95,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.HttpHandler, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_JaxRsPath_DetectedAsHttpHandler()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -121,7 +126,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.HttpHandler, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_SpringScheduled_DetectedAsScheduledJob()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -151,7 +157,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.ScheduledJob, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_KafkaListener_DetectedAsMessageHandler()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -181,7 +188,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.MessageHandler, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_GrpcService_DetectedAsGrpcMethod()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -212,7 +220,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.GrpcMethod, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_MainMethod_DetectedAsCliCommand()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -242,7 +251,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.CliCommand, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_PrivateMethod_NotDetectedAsEntrypoint()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -276,7 +286,8 @@ public class JavaCallGraphExtractorTests
|
||||
|
||||
#region JavaSinkMatcher Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_RuntimeExec_DetectedAsCmdExec()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -286,7 +297,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.CmdExec, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_ProcessBuilderInit_DetectedAsCmdExec()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -296,7 +308,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.CmdExec, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_StatementExecute_DetectedAsSqlRaw()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -306,7 +319,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.SqlRaw, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_ObjectInputStream_DetectedAsUnsafeDeser()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -316,7 +330,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.UnsafeDeser, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_HttpClientExecute_DetectedAsSsrf()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -326,7 +341,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.Ssrf, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_FileWriter_DetectedAsPathTraversal()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -336,7 +352,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.PathTraversal, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_UnknownMethod_ReturnsNull()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -346,7 +363,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_XxeVulnerableParsing_DetectedAsXxe()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -356,7 +374,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.XxeInjection, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_ScriptEngineEval_DetectedAsCodeInjection()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
@@ -370,7 +389,8 @@ public class JavaCallGraphExtractorTests
|
||||
|
||||
#region JavaBytecodeAnalyzer Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaBytecodeAnalyzer_ValidClassHeader_Parsed()
|
||||
{
|
||||
var analyzer = new JavaBytecodeAnalyzer(NullLogger<JavaCallGraphExtractor>.Instance);
|
||||
@@ -394,7 +414,8 @@ public class JavaCallGraphExtractorTests
|
||||
// The important thing is it doesn't throw
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaBytecodeAnalyzer_InvalidMagic_ReturnsNull()
|
||||
{
|
||||
var analyzer = new JavaBytecodeAnalyzer(NullLogger<JavaCallGraphExtractor>.Instance);
|
||||
@@ -406,7 +427,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaBytecodeAnalyzer_EmptyArray_ReturnsNull()
|
||||
{
|
||||
var analyzer = new JavaBytecodeAnalyzer(NullLogger<JavaCallGraphExtractor>.Instance);
|
||||
@@ -420,7 +442,8 @@ public class JavaCallGraphExtractorTests
|
||||
|
||||
#region Integration Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_InvalidPath_ThrowsFileNotFound()
|
||||
{
|
||||
var request = new CallGraphExtractionRequest(
|
||||
@@ -432,7 +455,8 @@ public class JavaCallGraphExtractorTests
|
||||
() => _extractor.ExtractAsync(request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_WrongLanguage_ThrowsArgumentException()
|
||||
{
|
||||
await using var temp = await TempDirectory.CreateAsync();
|
||||
@@ -446,7 +470,8 @@ public class JavaCallGraphExtractorTests
|
||||
() => _extractor.ExtractAsync(request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_EmptyDirectory_ProducesEmptySnapshot()
|
||||
{
|
||||
await using var temp = await TempDirectory.CreateAsync();
|
||||
@@ -468,7 +493,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(_fixedTime, snapshot.ExtractedAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Extractor_Language_IsJava()
|
||||
{
|
||||
Assert.Equal("java", _extractor.Language);
|
||||
@@ -478,7 +504,8 @@ public class JavaCallGraphExtractorTests
|
||||
|
||||
#region Determinism Verification Tests (JCG-020)
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_SamePath_ProducesSameDigest()
|
||||
{
|
||||
// Arrange: Create a temp directory
|
||||
@@ -497,7 +524,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(snapshot1.GraphDigest, snapshot2.GraphDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExtractAsync_DifferentScanId_SameNodesAndEdges()
|
||||
{
|
||||
// Arrange: Create a temp directory
|
||||
@@ -523,7 +551,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(snapshot1.GraphDigest, snapshot2.GraphDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildNodeId_SameInputs_ProducesIdenticalIds()
|
||||
{
|
||||
// Act: Build node IDs multiple times with same inputs
|
||||
@@ -534,7 +563,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(id1, id2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BuildNodeId_DifferentDescriptors_ProducesDifferentIds()
|
||||
{
|
||||
// Act: Build node IDs with different descriptors (overloaded methods)
|
||||
@@ -545,7 +575,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.NotEqual(id1, id2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaEntrypointClassifier_SameInput_AlwaysSameResult()
|
||||
{
|
||||
var classifier = new JavaEntrypointClassifier();
|
||||
@@ -581,7 +612,8 @@ public class JavaCallGraphExtractorTests
|
||||
Assert.Equal(result2, result3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JavaSinkMatcher_SameInput_AlwaysSameResult()
|
||||
{
|
||||
var matcher = new JavaSinkMatcher();
|
||||
|
||||
@@ -12,6 +12,8 @@ using StellaOps.Scanner.CallGraph.JavaScript;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -35,7 +37,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
|
||||
#region Entrypoint Classifier Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_ExpressHandler_ReturnsHttpHandler()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -56,7 +59,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(EntrypointType.HttpHandler, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_FastifyRoute_ReturnsHttpHandler()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -79,7 +83,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(EntrypointType.HttpHandler, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_LambdaHandler_ReturnsLambda()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -99,7 +104,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(EntrypointType.Lambda, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_AzureFunction_WithHandler_ReturnsLambda()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -119,7 +125,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(EntrypointType.Lambda, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_CliWithRunName_ReturnsCliCommand()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -139,7 +146,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(EntrypointType.CliCommand, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_UnknownSocket_ReturnsNull()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -160,7 +168,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_NestHandler_ReturnsMessageHandler()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -181,7 +190,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(EntrypointType.MessageHandler, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_UnknownCron_ReturnsNull()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -202,7 +212,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_GraphQL_ReturnsHttpHandler()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -223,7 +234,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(EntrypointType.HttpHandler, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_NoMatch_ReturnsNull()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -247,7 +259,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
|
||||
#region Sink Matcher Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_ChildProcessExec_ReturnsCmdExec()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -257,7 +270,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.CmdExec, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_ChildProcessSpawn_ReturnsCmdExec()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -267,7 +281,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.CmdExec, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_ChildProcessFork_ReturnsCmdExec()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -277,7 +292,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.CmdExec, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_MysqlQuery_ReturnsSqlRaw()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -287,7 +303,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.SqlRaw, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_PgQuery_ReturnsSqlRaw()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -297,7 +314,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.SqlRaw, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_KnexRaw_ReturnsSqlRaw()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -307,7 +325,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.SqlRaw, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_FsReadFile_ReturnsPathTraversal()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -317,7 +336,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.PathTraversal, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_FsWriteFile_ReturnsPathTraversal()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -327,7 +347,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.PathTraversal, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_AxiosGet_ReturnsSsrf()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -337,7 +358,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.Ssrf, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_HttpRequest_ReturnsSsrf()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -347,7 +369,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.Ssrf, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_JsYamlLoad_ReturnsUnsafeDeser()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -357,7 +380,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.UnsafeDeser, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_Eval_ReturnsCodeInjection()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -367,7 +391,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.CodeInjection, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_VmRunInContext_ReturnsCodeInjection()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -377,7 +402,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.CodeInjection, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_EjsRender_ReturnsTemplateInjection()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -387,7 +413,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(SinkCategory.TemplateInjection, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_NoMatch_ReturnsNull()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
@@ -401,7 +428,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
|
||||
#region Extractor Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Extractor_Language_IsJavascript()
|
||||
{
|
||||
Assert.Equal("javascript", _extractor.Language);
|
||||
@@ -475,7 +503,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(snapshot1.GraphDigest, snapshot2.GraphDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointClassifier_SameInput_AlwaysSameResult()
|
||||
{
|
||||
var classifier = new JsEntrypointClassifier();
|
||||
@@ -499,7 +528,8 @@ public sealed class JavaScriptCallGraphExtractorTests : IAsyncLifetime
|
||||
Assert.Equal(result2, result3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsSinkMatcher_SameInput_AlwaysSameResult()
|
||||
{
|
||||
var matcher = new JsSinkMatcher();
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
using StellaOps.Scanner.CallGraph.Node;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class NodeCallGraphExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesValidJson()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +58,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Single(result.Entrypoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesNodeWithPosition()
|
||||
{
|
||||
// Arrange
|
||||
@@ -92,7 +95,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Equal(5, node.Position.Column);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesEdgeWithSite()
|
||||
{
|
||||
// Arrange
|
||||
@@ -127,7 +131,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Equal(25, edge.Site.Line);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ThrowsOnEmptyInput()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
@@ -135,7 +140,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Throws<ArgumentNullException>(() => BabelResultParser.Parse(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesNdjson()
|
||||
{
|
||||
// Arrange
|
||||
@@ -152,7 +158,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Equal("app", result.Module);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void JsEntrypointInfo_HasCorrectProperties()
|
||||
{
|
||||
// Arrange
|
||||
@@ -184,7 +191,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Equal("GET", ep.Method);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesSinks()
|
||||
{
|
||||
// Arrange
|
||||
@@ -229,7 +237,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Equal(42, sink.Site.Line);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesMultipleSinkCategories()
|
||||
{
|
||||
// Arrange
|
||||
@@ -269,7 +278,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Contains(result.Sinks, s => s.Category == "file_write");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesEmptySinks()
|
||||
{
|
||||
// Arrange
|
||||
@@ -290,7 +300,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Empty(result.Sinks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesMissingSinks()
|
||||
{
|
||||
// Arrange - sinks field omitted entirely
|
||||
@@ -310,7 +321,8 @@ public class NodeCallGraphExtractorTests
|
||||
Assert.Empty(result.Sinks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void BabelResultParser_ParsesSinkWithoutSite()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -9,11 +9,13 @@ using StellaOps.Scanner.CallGraph.Python;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class PythonCallGraphExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonEntrypointClassifier_ClassifiesFlaskRoute()
|
||||
{
|
||||
// Arrange
|
||||
@@ -40,7 +42,8 @@ public class PythonCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.HttpHandler, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonEntrypointClassifier_ClassifiesFastApiRoute()
|
||||
{
|
||||
// Arrange
|
||||
@@ -67,7 +70,8 @@ public class PythonCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.HttpHandler, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonEntrypointClassifier_ClassifiesCeleryTask()
|
||||
{
|
||||
// Arrange
|
||||
@@ -94,7 +98,8 @@ public class PythonCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.BackgroundJob, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonEntrypointClassifier_ClassifiesClickCommand()
|
||||
{
|
||||
// Arrange
|
||||
@@ -121,7 +126,8 @@ public class PythonCallGraphExtractorTests
|
||||
Assert.Equal(EntrypointType.CliCommand, result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonSinkMatcher_MatchesSubprocessCall()
|
||||
{
|
||||
// Arrange
|
||||
@@ -134,7 +140,8 @@ public class PythonCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.CmdExec, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonSinkMatcher_MatchesEval()
|
||||
{
|
||||
// Arrange
|
||||
@@ -147,7 +154,8 @@ public class PythonCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.CodeInjection, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonSinkMatcher_MatchesPickleLoads()
|
||||
{
|
||||
// Arrange
|
||||
@@ -160,7 +168,8 @@ public class PythonCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.UnsafeDeser, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonSinkMatcher_MatchesSqlAlchemyExecute()
|
||||
{
|
||||
// Arrange
|
||||
@@ -173,7 +182,8 @@ public class PythonCallGraphExtractorTests
|
||||
Assert.Equal(SinkCategory.SqlRaw, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PythonSinkMatcher_ReturnsNullForSafeFunction()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.CallGraph;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -10,7 +11,8 @@ namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
/// </summary>
|
||||
public class ReachabilityAnalyzerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_WhenSinkReachable_ReturnsShortestPath()
|
||||
{
|
||||
var entry = CallGraphNodeIds.Compute("dotnet:test:entry");
|
||||
@@ -46,7 +48,8 @@ public class ReachabilityAnalyzerTests
|
||||
Assert.Equal(new[] { entry, mid, sink }, result.Paths[0].NodeIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_WhenNoEntrypoints_ReturnsEmpty()
|
||||
{
|
||||
var snapshot = new CallGraphSnapshot(
|
||||
@@ -71,7 +74,8 @@ public class ReachabilityAnalyzerTests
|
||||
/// <summary>
|
||||
/// WIT-007A: Verify deterministic path ordering (SinkId ASC, EntrypointId ASC, PathLength ASC).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_PathsAreDeterministicallyOrdered_BySinkIdThenEntrypointIdThenLength()
|
||||
{
|
||||
// Arrange: create graph with multiple entrypoints and sinks
|
||||
@@ -121,7 +125,8 @@ public class ReachabilityAnalyzerTests
|
||||
/// <summary>
|
||||
/// WIT-007A: Verify that multiple runs produce identical results (determinism).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_ProducesIdenticalResults_OnMultipleRuns()
|
||||
{
|
||||
var entry = "entry:test";
|
||||
@@ -163,7 +168,8 @@ public class ReachabilityAnalyzerTests
|
||||
/// <summary>
|
||||
/// WIT-007A: Verify MaxTotalPaths limit is enforced.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_WithOptions_RespectsMaxTotalPathsLimit()
|
||||
{
|
||||
// Arrange: create graph with 5 sinks reachable from 1 entrypoint
|
||||
@@ -206,7 +212,8 @@ public class ReachabilityAnalyzerTests
|
||||
/// <summary>
|
||||
/// WIT-007A: Verify MaxDepth limit is enforced.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_WithOptions_RespectsMaxDepthLimit()
|
||||
{
|
||||
// Arrange: create a chain of 10 nodes
|
||||
@@ -250,7 +257,8 @@ public class ReachabilityAnalyzerTests
|
||||
/// <summary>
|
||||
/// WIT-007A: Verify node IDs in paths are ordered from entrypoint to sink.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_PathNodeIds_AreOrderedFromEntrypointToSink()
|
||||
{
|
||||
var entry = "entry:start";
|
||||
@@ -297,7 +305,8 @@ public class ReachabilityAnalyzerTests
|
||||
/// <summary>
|
||||
/// WIT-007B: Verify ExplicitSinks option allows targeting specific sinks not in snapshot.SinkIds.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_WithExplicitSinks_FindsPathsToSpecifiedSinksOnly()
|
||||
{
|
||||
// Arrange: graph with 3 reachable nodes, only 1 is in snapshot.SinkIds
|
||||
@@ -347,7 +356,8 @@ public class ReachabilityAnalyzerTests
|
||||
/// <summary>
|
||||
/// WIT-007B: Verify ExplicitSinks with empty array falls back to snapshot sinks.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_WithEmptyExplicitSinks_UsesSnapshotSinks()
|
||||
{
|
||||
var entry = "entry:start";
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Scanner.CallGraph\\StellaOps.Scanner.CallGraph.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using StellaOps.Scanner.CallGraph;
|
||||
using StellaOps.Scanner.CallGraph.Caching;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.CallGraph.Tests;
|
||||
|
||||
public class ValkeyCallGraphCacheServiceTests : IAsyncLifetime
|
||||
@@ -76,7 +77,8 @@ public class ValkeyCallGraphCacheServiceTests : IAsyncLifetime
|
||||
await _cache.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SetThenGet_CallGraph_RoundTrips()
|
||||
{
|
||||
var nodeId = CallGraphNodeIds.Compute("dotnet:test:entry");
|
||||
@@ -99,7 +101,8 @@ public class ValkeyCallGraphCacheServiceTests : IAsyncLifetime
|
||||
Assert.Equal(snapshot.GraphDigest, loaded.GraphDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SetThenGet_ReachabilityResult_RoundTrips()
|
||||
{
|
||||
var result = new ReachabilityAnalysisResult(
|
||||
|
||||
@@ -2,11 +2,14 @@ using System.Threading.Tasks;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Core.Tests;
|
||||
|
||||
public class ReachabilityGraphBuilderUnionTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ConvertsBuilderToUnionGraphAndWritesNdjson()
|
||||
{
|
||||
var builder = new ReachabilityGraphBuilder()
|
||||
|
||||
@@ -4,11 +4,14 @@ using StellaOps.Scanner.Core.Tests.Fakes;
|
||||
using StellaOps.Scanner.Reachability;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Core.Tests;
|
||||
|
||||
public class ReachabilityUnionPublisherTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PublishesZipToCas()
|
||||
{
|
||||
var graph = new ReachabilityUnionGraph(
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace StellaOps.Scanner.Core.Tests;
|
||||
|
||||
public class ReachabilityUnionWriterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WritesDeterministicFilesAndHashes()
|
||||
{
|
||||
var writer = new ReachabilityUnionWriter();
|
||||
@@ -62,6 +63,7 @@ public class ReachabilityUnionWriterTests
|
||||
.EnumerateArray()
|
||||
.Select(file => (Path: file.GetProperty("path").GetString(), Sha256: file.GetProperty("sha256").GetString()))
|
||||
.ToList();
|
||||
using StellaOps.TestKit;
|
||||
}
|
||||
|
||||
Assert.Contains(files, file => file.Path == result.Nodes.Path && file.Sha256 == result.Nodes.Sha256);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System.Text.Json;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Core.Tests;
|
||||
|
||||
public class ScanManifestTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_SameManifest_ProducesSameHash()
|
||||
{
|
||||
var manifest1 = CreateSampleManifest();
|
||||
@@ -18,7 +20,8 @@ public class ScanManifestTests
|
||||
Assert.StartsWith("sha256:", hash1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_DifferentSeed_ProducesDifferentHash()
|
||||
{
|
||||
var seed1 = new byte[32];
|
||||
@@ -32,7 +35,8 @@ public class ScanManifestTests
|
||||
Assert.NotEqual(manifest1.ComputeHash(), manifest2.ComputeHash());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_DifferentArtifactDigest_ProducesDifferentHash()
|
||||
{
|
||||
var manifest1 = CreateSampleManifest(artifactDigest: "sha256:abc123");
|
||||
@@ -41,7 +45,8 @@ public class ScanManifestTests
|
||||
Assert.NotEqual(manifest1.ComputeHash(), manifest2.ComputeHash());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_HashIsLowercaseHex()
|
||||
{
|
||||
var manifest = CreateSampleManifest();
|
||||
@@ -52,7 +57,8 @@ public class ScanManifestTests
|
||||
Assert.Matches(@"^[0-9a-f]{64}$", hexPart);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialization_RoundTrip_PreservesAllFields()
|
||||
{
|
||||
var manifest = CreateSampleManifest();
|
||||
@@ -71,7 +77,8 @@ public class ScanManifestTests
|
||||
Assert.Equal(manifest.Seed, deserialized.Seed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialization_JsonPropertyNames_AreCamelCase()
|
||||
{
|
||||
var manifest = CreateSampleManifest();
|
||||
@@ -84,7 +91,8 @@ public class ScanManifestTests
|
||||
Assert.Contains("\"concelierSnapshotHash\":", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToCanonicalJson_ProducesDeterministicOutput()
|
||||
{
|
||||
var manifest = CreateSampleManifest();
|
||||
@@ -95,7 +103,8 @@ public class ScanManifestTests
|
||||
Assert.Equal(json1, json2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Builder_CreatesValidManifest()
|
||||
{
|
||||
var seed = new byte[32];
|
||||
@@ -123,7 +132,8 @@ public class ScanManifestTests
|
||||
Assert.Equal("10", manifest.Knobs["maxDepth"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Builder_WithKnobs_MergesMultipleKnobs()
|
||||
{
|
||||
var manifest = ScanManifest.CreateBuilder("scan-001", "sha256:abc123")
|
||||
@@ -140,7 +150,8 @@ public class ScanManifestTests
|
||||
Assert.Equal("value4", manifest.Knobs["key4"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Builder_SeedMustBe32Bytes()
|
||||
{
|
||||
var builder = ScanManifest.CreateBuilder("scan-001", "sha256:abc123");
|
||||
@@ -149,7 +160,8 @@ public class ScanManifestTests
|
||||
Assert.Contains("32 bytes", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Record_WithExpression_CreatesModifiedCopy()
|
||||
{
|
||||
var original = CreateSampleManifest();
|
||||
@@ -160,7 +172,8 @@ public class ScanManifestTests
|
||||
Assert.Equal(original.ScanId, modified.ScanId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToJson_Indented_FormatsOutput()
|
||||
{
|
||||
var manifest = CreateSampleManifest();
|
||||
@@ -170,7 +183,8 @@ public class ScanManifestTests
|
||||
Assert.Contains(" ", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToJson_NotIndented_CompactOutput()
|
||||
{
|
||||
var manifest = CreateSampleManifest();
|
||||
@@ -179,7 +193,8 @@ public class ScanManifestTests
|
||||
Assert.DoesNotContain("\n", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void KnobsCollection_IsImmutable()
|
||||
{
|
||||
var manifest = CreateSampleManifest();
|
||||
|
||||
@@ -9,11 +9,14 @@ using StellaOps.Scanner.Diff;
|
||||
using StellaOps.Scanner.Core.Utility;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Diff.Tests;
|
||||
|
||||
public sealed class ComponentDifferTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Compute_CapturesAddedRemovedAndChangedComponents()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
@@ -173,7 +176,8 @@ public sealed class ComponentDifferTests
|
||||
Assert.False(removedJson.TryGetProperty("introducingLayer", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Compute_UsageView_FiltersComponents()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
@@ -218,7 +222,8 @@ public sealed class ComponentDifferTests
|
||||
Assert.False(parsed.RootElement.TryGetProperty("newImageDigest", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Compute_MetadataChange_WhenEvidenceDiffers()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
@@ -277,7 +282,8 @@ public sealed class ComponentDifferTests
|
||||
Assert.Equal(1, change.OldComponent!.Evidence.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Compute_MetadataChange_WhenBuildIdDiffers()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Diff/StellaOps.Scanner.Diff.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -5,13 +5,15 @@ using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Scanner.Emit.Lineage;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Emit.Lineage.Tests;
|
||||
|
||||
public class RebuildProofTests
|
||||
{
|
||||
#region RebuildProof Model Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RebuildProof_RequiredProperties_MustBeSet()
|
||||
{
|
||||
var proof = new RebuildProof
|
||||
@@ -31,7 +33,8 @@ public class RebuildProofTests
|
||||
proof.PolicyHash.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RebuildProof_WithFeedSnapshots_TracksAllFeeds()
|
||||
{
|
||||
var feeds = ImmutableArray.Create(
|
||||
@@ -69,7 +72,8 @@ public class RebuildProofTests
|
||||
proof.FeedSnapshots[1].EntryCount.Should().Be(15000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RebuildProof_WithAnalyzerVersions_TracksAllAnalyzers()
|
||||
{
|
||||
var analyzers = ImmutableArray.Create(
|
||||
@@ -103,7 +107,8 @@ public class RebuildProofTests
|
||||
proof.AnalyzerVersions[0].AnalyzerId.Should().Be("npm-analyzer");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RebuildProof_OptionalDsseSignature_IsNullByDefault()
|
||||
{
|
||||
var proof = new RebuildProof
|
||||
@@ -121,7 +126,8 @@ public class RebuildProofTests
|
||||
proof.ProofHash.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RebuildProof_WithSignature_StoresSignature()
|
||||
{
|
||||
var proof = new RebuildProof
|
||||
@@ -145,7 +151,8 @@ public class RebuildProofTests
|
||||
|
||||
#region FeedSnapshot Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FeedSnapshot_RequiredProperties_MustBeSet()
|
||||
{
|
||||
var snapshot = new FeedSnapshot
|
||||
@@ -161,7 +168,8 @@ public class RebuildProofTests
|
||||
snapshot.SnapshotHash.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FeedSnapshot_OptionalProperties_AreNullByDefault()
|
||||
{
|
||||
var snapshot = new FeedSnapshot
|
||||
@@ -180,7 +188,8 @@ public class RebuildProofTests
|
||||
|
||||
#region AnalyzerVersion Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AnalyzerVersion_RequiredProperties_MustBeSet()
|
||||
{
|
||||
var analyzer = new AnalyzerVersion
|
||||
@@ -195,7 +204,8 @@ public class RebuildProofTests
|
||||
analyzer.Version.Should().Be("2.0.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AnalyzerVersion_OptionalHashes_AreNullByDefault()
|
||||
{
|
||||
var analyzer = new AnalyzerVersion
|
||||
@@ -213,7 +223,8 @@ public class RebuildProofTests
|
||||
|
||||
#region RebuildVerification Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RebuildVerification_SuccessfulRebuild_HasMatchingHash()
|
||||
{
|
||||
var proof = new RebuildProof
|
||||
@@ -242,7 +253,8 @@ public class RebuildProofTests
|
||||
verification.ErrorMessage.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RebuildVerification_FailedRebuild_HasErrorMessage()
|
||||
{
|
||||
var proof = new RebuildProof
|
||||
@@ -269,7 +281,8 @@ public class RebuildProofTests
|
||||
verification.RebuiltSbomId.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RebuildVerification_MismatchRebuild_HasDifferences()
|
||||
{
|
||||
var proof = new RebuildProof
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Scanner.Emit.Lineage;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Emit.Lineage.Tests;
|
||||
|
||||
public class SbomDiffEngineTests
|
||||
@@ -25,7 +26,8 @@ public class SbomDiffEngineTests
|
||||
|
||||
#region Basic Diff Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_IdenticalComponents_ReturnsNoDelta()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -46,7 +48,8 @@ public class SbomDiffEngineTests
|
||||
diff.Summary.Unchanged.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_AddedComponent_DetectsAddition()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -67,7 +70,8 @@ public class SbomDiffEngineTests
|
||||
diff.Summary.Added.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_RemovedComponent_DetectsRemoval()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -89,7 +93,8 @@ public class SbomDiffEngineTests
|
||||
diff.Summary.IsBreaking.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_VersionUpgrade_DetectsVersionChange()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -107,7 +112,8 @@ public class SbomDiffEngineTests
|
||||
diff.Summary.IsBreaking.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_VersionDowngrade_MarksAsBreaking()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -121,7 +127,8 @@ public class SbomDiffEngineTests
|
||||
diff.Summary.IsBreaking.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_LicenseChange_DetectsLicenseChange()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -141,7 +148,8 @@ public class SbomDiffEngineTests
|
||||
|
||||
#region Complex Diff Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_MultipleChanges_TracksAll()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -170,7 +178,8 @@ public class SbomDiffEngineTests
|
||||
diff.Summary.IsBreaking.Should().BeTrue(); // Due to removal
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_EmptyFrom_AllAdditions()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -190,7 +199,8 @@ public class SbomDiffEngineTests
|
||||
diff.Summary.Unchanged.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_EmptyTo_AllRemovals()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -214,7 +224,8 @@ public class SbomDiffEngineTests
|
||||
|
||||
#region Determinism Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_SameInputs_ProducesSameOutput()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -239,7 +250,8 @@ public class SbomDiffEngineTests
|
||||
diff1.Deltas.Should().HaveCount(diff2.Deltas.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeDiff_DeltasAreSorted()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -267,7 +279,8 @@ public class SbomDiffEngineTests
|
||||
|
||||
#region CreatePointer Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreatePointer_SumsCorrectly()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -294,7 +307,8 @@ public class SbomDiffEngineTests
|
||||
pointer.DiffHash.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreatePointer_DiffHashIsDeterministic()
|
||||
{
|
||||
var fromId = SbomId.New();
|
||||
@@ -316,7 +330,8 @@ public class SbomDiffEngineTests
|
||||
|
||||
#region Summary Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DiffSummary_TotalComponents_CalculatesCorrectly()
|
||||
{
|
||||
var summary = new DiffSummary
|
||||
|
||||
@@ -5,13 +5,15 @@ using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Scanner.Emit.Lineage;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Emit.Lineage.Tests;
|
||||
|
||||
public class SbomLineageTests
|
||||
{
|
||||
#region SbomId Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomId_New_CreatesUniqueId()
|
||||
{
|
||||
var id1 = SbomId.New();
|
||||
@@ -20,7 +22,8 @@ public class SbomLineageTests
|
||||
id1.Should().NotBe(id2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomId_Parse_RoundTrips()
|
||||
{
|
||||
var original = SbomId.New();
|
||||
@@ -29,7 +32,8 @@ public class SbomLineageTests
|
||||
parsed.Should().Be(original);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomId_ToString_ReturnsGuidString()
|
||||
{
|
||||
var id = SbomId.New();
|
||||
@@ -42,7 +46,8 @@ public class SbomLineageTests
|
||||
|
||||
#region SbomLineage Model Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomLineage_RequiredProperties_MustBeSet()
|
||||
{
|
||||
var lineage = new SbomLineage
|
||||
@@ -58,7 +63,8 @@ public class SbomLineageTests
|
||||
lineage.ContentHash.Should().Be("sha256:def456");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomLineage_WithParent_TracksLineage()
|
||||
{
|
||||
var parentId = SbomId.New();
|
||||
@@ -78,7 +84,8 @@ public class SbomLineageTests
|
||||
child.Ancestors.Should().Contain(parentId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomLineage_WithDiffPointer_TracksChanges()
|
||||
{
|
||||
var diff = new SbomDiffPointer
|
||||
@@ -103,7 +110,8 @@ public class SbomLineageTests
|
||||
lineage.DiffFromParent!.TotalChanges.Should().Be(10);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomLineage_RootLineage_HasNoParent()
|
||||
{
|
||||
var root = new SbomLineage
|
||||
@@ -123,7 +131,8 @@ public class SbomLineageTests
|
||||
|
||||
#region SbomDiffPointer Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomDiffPointer_TotalChanges_SumsAllCategories()
|
||||
{
|
||||
var pointer = new SbomDiffPointer
|
||||
@@ -137,7 +146,8 @@ public class SbomLineageTests
|
||||
pointer.TotalChanges.Should().Be(23);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SbomDiffPointer_EmptyDiff_HasZeroChanges()
|
||||
{
|
||||
var pointer = new SbomDiffPointer
|
||||
|
||||
@@ -15,5 +15,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Scanner.Emit\StellaOps.Scanner.Emit.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -9,6 +9,8 @@ using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.EntryTrace.Tests;
|
||||
|
||||
public sealed class EntryTraceAnalyzerTests
|
||||
@@ -20,7 +22,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_CollapsesBundleExecWrapper()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -55,7 +58,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Contains(result.Edges, edge => edge.Relationship == "wrapper" && edge.FromNodeId == bundleNode.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_CollapsesDockerPhpEntrypointWrapper()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -89,7 +93,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal("docker-php-entrypoint", wrapperNode.Metadata?["wrapper.name"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_CollapsesNpmExecWrapper()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -136,7 +141,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal("npm", npmNode.Metadata?["wrapper.name"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_CollapsesYarnNodeWrapper()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -175,7 +181,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal("yarn node", yarnNode.Metadata?["wrapper.name"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_CollapsesPipenvRunWrapper()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -214,7 +221,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal("pipenv run", pipenvNode.Metadata?["wrapper.name"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_CollapsesPoetryRunWrapper()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -263,7 +271,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
return new EntryTraceAnalyzer(options, new EntryTraceMetrics(), NullLogger<EntryTraceAnalyzer>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_FollowsShellIncludeAndPythonModule()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -323,7 +332,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Contains(result.Edges, edge => edge.Relationship == "python-module" && edge.Metadata is { } metadata && metadata.TryGetValue("module", out var module) && module == "app.main");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_RecordsDiagnosticsForMissingInclude()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -353,7 +363,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal(EntryTraceUnknownReason.MissingFile, result.Diagnostics[0].Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_IsDeterministic()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -386,7 +397,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
second.Edges.Select(e => (e.FromNodeId, e.ToNodeId, e.Relationship)).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_HandlesCmdShellScript()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -413,7 +425,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Contains(result.Diagnostics, diagnostic => diagnostic.Reason == EntryTraceUnknownReason.UnsupportedSyntax);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_ClassifiesGoBinaryWithPlan()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -454,7 +467,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal("/", plan.WorkingDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_ExtractsJarManifestEvidence()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -497,7 +511,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal(terminal.Confidence, plan.Confidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_UsesHistoryCandidateWhenEntrypointMissing()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -533,7 +548,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Contains("/app/server.js", terminal.Arguments);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_DiscoversSupervisorCommand()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -569,7 +585,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Contains("app:app", terminal.Arguments);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_DiscoversEntrypointScriptCandidate()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -605,7 +622,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Contains("/srv/service.py", terminal.Arguments);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_DiscoversServiceRunScript()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -654,7 +672,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal(EntryTraceTerminalType.Native, terminal.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_PropagatesUserSwitchWrapper()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -690,7 +709,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal("app", edge.Metadata?["user"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_PropagatesEnvWrapperIntoPlan()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -725,7 +745,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal("true", edge.Metadata?["guarded"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_AccumulatesWorkingDirectoryFromShellCd()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
@@ -756,7 +777,8 @@ public sealed class EntryTraceAnalyzerTests
|
||||
Assert.Equal("/service", terminal.WorkingDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveAsync_HandlesInitShimAndGuardsEdge()
|
||||
{
|
||||
var fs = new TestRootFileSystem();
|
||||
|
||||
@@ -4,11 +4,13 @@ using System.Text;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.EntryTrace.Tests;
|
||||
|
||||
public sealed class EntryTraceImageContextFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_UsesEnvironmentAndEntrypointFromConfig()
|
||||
{
|
||||
var json = """
|
||||
@@ -52,7 +54,8 @@ public sealed class EntryTraceImageContextFactoryTests
|
||||
Assert.Equal("/custom/bin:/usr/bin", string.Join(":", imageContext.Context.Path));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_FallsBackToDefaultPathWhenMissing()
|
||||
{
|
||||
var json = """
|
||||
|
||||
@@ -9,11 +9,14 @@ using System.Text.Json;
|
||||
using StellaOps.Scanner.EntryTrace;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.EntryTrace.Tests;
|
||||
|
||||
public sealed class EntryTraceNdjsonWriterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialize_ProducesDeterministicNdjsonLines()
|
||||
{
|
||||
var (graph, metadata) = CreateSampleGraph();
|
||||
@@ -58,7 +61,8 @@ public sealed class EntryTraceNdjsonWriterTests
|
||||
Assert.Equal("gosu", capabilityJson.GetProperty("name").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Serialize_ProducesStableSha256Hash()
|
||||
{
|
||||
var (graph, metadata) = CreateSampleGraph();
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.Text;
|
||||
using StellaOps.Scanner.EntryTrace.FileSystem;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.EntryTrace.Tests;
|
||||
|
||||
public sealed class LayeredRootFileSystemTests : IDisposable
|
||||
@@ -17,7 +19,8 @@ public sealed class LayeredRootFileSystemTests : IDisposable
|
||||
Directory.CreateDirectory(_tempRoot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromDirectories_HandlesWhiteoutsAndResolution()
|
||||
{
|
||||
var layer1 = CreateLayerDirectory("layer1");
|
||||
@@ -65,7 +68,8 @@ public sealed class LayeredRootFileSystemTests : IDisposable
|
||||
Assert.DoesNotContain(optEntries, entry => entry.Path.EndsWith("setup.sh", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void TryReadBytes_ReturnsLimitedPreview()
|
||||
{
|
||||
var layer = CreateLayerDirectory("layer-bytes");
|
||||
@@ -84,7 +88,8 @@ public sealed class LayeredRootFileSystemTests : IDisposable
|
||||
Assert.Equal("abcd", Encoding.UTF8.GetString(preview.Span));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromArchives_ResolvesSymlinkAndWhiteout()
|
||||
{
|
||||
var layer1Path = Path.Combine(_tempRoot, "layer1.tar");
|
||||
@@ -135,7 +140,8 @@ public sealed class LayeredRootFileSystemTests : IDisposable
|
||||
Assert.False(fs.TryReadAllText("/opt/old.sh", out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromArchives_ResolvesHardLinkContent()
|
||||
{
|
||||
var baseLayer = Path.Combine(_tempRoot, "base.tar");
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using StellaOps.Scanner.EntryTrace.Parsing;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.EntryTrace.Tests;
|
||||
|
||||
public sealed class ShellParserTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Parse_ProducesDeterministicNodes()
|
||||
{
|
||||
const string script = """
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.EntryTrace/StellaOps.Scanner.EntryTrace.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
|
||||
@@ -9,11 +9,13 @@ using FluentAssertions;
|
||||
using StellaOps.Scanner.Evidence.Models;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Evidence.Tests;
|
||||
|
||||
public sealed class FuncProofBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithBinaryIdentity_SetsFileProperties()
|
||||
{
|
||||
// Arrange
|
||||
@@ -33,7 +35,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.SchemaVersion.Should().Be(FuncProofConstants.SchemaVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithSection_AddsSectionToProof()
|
||||
{
|
||||
// Arrange
|
||||
@@ -56,7 +59,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.Sections![0].Hash.Should().Be(sectionHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithMultipleSections_AddsAllSections()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -74,7 +78,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.Sections![2].Name.Should().Be(".data");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithFunction_AddsFunctionToProof()
|
||||
{
|
||||
// Arrange
|
||||
@@ -99,7 +104,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.Functions![0].Hash.Should().Be(functionHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithFunctionCallers_SetsCallersOnFunction()
|
||||
{
|
||||
// Arrange
|
||||
@@ -115,7 +121,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.Functions![0].Callers.Should().BeEquivalentTo(callers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithTrace_AddsTraceToProof()
|
||||
{
|
||||
// Arrange
|
||||
@@ -135,7 +142,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.Traces![0].Truncated.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithTruncatedTrace_SetsTruncatedFlag()
|
||||
{
|
||||
// Arrange
|
||||
@@ -151,7 +159,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.Traces![0].Truncated.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_WithMetadata_SetsMetadataProperties()
|
||||
{
|
||||
// Arrange
|
||||
@@ -172,7 +181,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.Metadata.CreatedAt.Should().Be(timestamp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_GeneratesProofId()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -186,7 +196,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof.ProofId.Should().HaveLength(64); // SHA-256 hex
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_SameInput_GeneratesSameProofId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -205,7 +216,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof1.ProofId.Should().Be(proof2.ProofId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DifferentInput_GeneratesDifferentProofId()
|
||||
{
|
||||
// Arrange & Act
|
||||
@@ -223,7 +235,8 @@ public sealed class FuncProofBuilderTests
|
||||
proof1.ProofId.Should().NotBe(proof2.ProofId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeSymbolDigest_DeterministicForSameInput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -238,7 +251,8 @@ public sealed class FuncProofBuilderTests
|
||||
digest1.Should().Be(digest2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeSymbolDigest_DifferentForDifferentOffset()
|
||||
{
|
||||
// Arrange
|
||||
@@ -252,7 +266,8 @@ public sealed class FuncProofBuilderTests
|
||||
digest1.Should().NotBe(digest2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeFunctionHash_DeterministicForSameInput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -266,7 +281,8 @@ public sealed class FuncProofBuilderTests
|
||||
hash1.Should().Be(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeFunctionHash_DifferentForDifferentInput()
|
||||
{
|
||||
// Arrange
|
||||
@@ -281,7 +297,8 @@ public sealed class FuncProofBuilderTests
|
||||
hash1.Should().NotBe(hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeProofId_DeterministicForSameProof()
|
||||
{
|
||||
// Arrange
|
||||
@@ -298,7 +315,8 @@ public sealed class FuncProofBuilderTests
|
||||
id1.Should().Be(id2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_FunctionOrdering_IsDeterministic()
|
||||
{
|
||||
// Arrange & Act
|
||||
|
||||
@@ -15,6 +15,7 @@ using StellaOps.Scanner.Evidence.Models;
|
||||
using StellaOps.Scanner.ProofSpine;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Evidence.Tests;
|
||||
|
||||
public sealed class FuncProofDsseServiceTests
|
||||
@@ -34,7 +35,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
_logger = NullLogger<FuncProofDsseService>.Instance;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAsync_WithValidProof_ReturnsSignedEnvelope()
|
||||
{
|
||||
// Arrange
|
||||
@@ -64,7 +66,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
result.EnvelopeJson.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAsync_WithNullProofId_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
@@ -81,7 +84,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => service.SignAsync(proof));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAsync_CallsSigningServiceWithCorrectPayloadType()
|
||||
{
|
||||
// Arrange
|
||||
@@ -109,7 +113,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
capturedPayloadType.Should().Be(FuncProofConstants.MediaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WithValidEnvelope_ReturnsSuccessResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -136,7 +141,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
result.FuncProof.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WithWrongPayloadType_ReturnsInvalidResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -155,7 +161,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
result.FailureReason.Should().Contain("Invalid payload type");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WithFailedSignature_ReturnsInvalidResult()
|
||||
{
|
||||
// Arrange
|
||||
@@ -180,7 +187,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
result.FailureReason.Should().Be("dsse_sig_mismatch");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ExtractPayload_WithValidEnvelope_ReturnsFuncProof()
|
||||
{
|
||||
// Arrange
|
||||
@@ -205,7 +213,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
extracted.BuildId.Should().Be(proof.BuildId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ExtractPayload_WithInvalidBase64_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -223,7 +232,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
extracted.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ExtractPayload_WithInvalidJson_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
@@ -241,7 +251,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
extracted.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ToUnsignedEnvelope_CreatesValidEnvelope()
|
||||
{
|
||||
// Arrange
|
||||
@@ -257,7 +268,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
envelope.Payload.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseEnvelope_WithValidJson_ReturnsEnvelope()
|
||||
{
|
||||
// Arrange
|
||||
@@ -278,7 +290,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
parsed!.PayloadType.Should().Be("test-type");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseEnvelope_WithInvalidJson_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
@@ -288,7 +301,8 @@ public sealed class FuncProofDsseServiceTests
|
||||
parsed.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ParseEnvelope_WithEmptyString_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
|
||||
@@ -14,6 +14,7 @@ using StellaOps.Scanner.Evidence;
|
||||
using StellaOps.Scanner.Evidence.Models;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Evidence.Tests;
|
||||
|
||||
public sealed class SbomFuncProofLinkerTests
|
||||
@@ -25,7 +26,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
_linker = new SbomFuncProofLinker(NullLogger<SbomFuncProofLinker>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LinkFuncProofEvidence_AddsEvidenceToComponent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -58,7 +60,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
frames!.Count.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LinkFuncProofEvidence_AddsExternalReference()
|
||||
{
|
||||
// Arrange
|
||||
@@ -86,7 +89,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
evidenceRef["url"]!.GetValue<string>().Should().Contain("oci://");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LinkFuncProofEvidence_ThrowsForNonCycloneDx()
|
||||
{
|
||||
// Arrange
|
||||
@@ -111,7 +115,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
.WithMessage("*CycloneDX*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LinkFuncProofEvidence_ThrowsForMissingComponent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -130,7 +135,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
.WithMessage("*not found*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ExtractFuncProofReferences_ReturnsEmptyForNoEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -143,7 +149,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
refs.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ExtractFuncProofReferences_FindsLinkedEvidence()
|
||||
{
|
||||
// Arrange
|
||||
@@ -167,7 +174,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
refs[0].FunctionCount.Should().Be(funcProof.Functions.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CreateEvidenceRef_PopulatesAllFields()
|
||||
{
|
||||
// Arrange
|
||||
@@ -189,7 +197,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
evidenceRef.TraceCount.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LinkFuncProofEvidence_IncludesProofProperties()
|
||||
{
|
||||
// Arrange
|
||||
@@ -224,7 +233,8 @@ public sealed class SbomFuncProofLinkerTests
|
||||
proofIdProperty!["value"]!.GetValue<string>().Should().Be(funcProof.ProofId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void LinkFuncProofEvidence_MergesWithExistingEvidence()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -27,5 +27,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Evidence/StellaOps.Scanner.Evidence.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.ProofSpine/StellaOps.Scanner.ProofSpine.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -6,6 +6,7 @@ using StellaOps.Scanner.Explainability.Assumptions;
|
||||
using StellaOps.Scanner.Explainability.Confidence;
|
||||
using StellaOps.Scanner.Explainability.Falsifiability;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Explainability.Tests;
|
||||
|
||||
public class RiskReportTests
|
||||
@@ -17,7 +18,8 @@ public class RiskReportTests
|
||||
_generator = new RiskReportGenerator(new EvidenceDensityScorer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_MinimalInput_CreatesReport()
|
||||
{
|
||||
var input = new RiskReportInput
|
||||
@@ -39,7 +41,8 @@ public class RiskReportTests
|
||||
result.GeneratedAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_WithSeverity_IncludesInExplanation()
|
||||
{
|
||||
var input = new RiskReportInput
|
||||
@@ -56,7 +59,8 @@ public class RiskReportTests
|
||||
result.Explanation.Should().Contain("CRITICAL");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_WithFixedVersion_RecommendsUpdate()
|
||||
{
|
||||
var input = new RiskReportInput
|
||||
@@ -74,7 +78,8 @@ public class RiskReportTests
|
||||
a.Action.Contains("Update") && a.Action.Contains("1.0.1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_WithoutFixedVersion_RecommendsMonitoring()
|
||||
{
|
||||
var input = new RiskReportInput
|
||||
@@ -91,7 +96,8 @@ public class RiskReportTests
|
||||
a.Action.Contains("Monitor") || a.Action.Contains("compensating"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_WithEvidenceFactors_CalculatesConfidence()
|
||||
{
|
||||
var input = new RiskReportInput
|
||||
@@ -113,7 +119,8 @@ public class RiskReportTests
|
||||
result.ConfidenceScore!.Score.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_WithAssumptions_IncludesInReport()
|
||||
{
|
||||
var assumptions = new AssumptionSet
|
||||
@@ -147,7 +154,8 @@ public class RiskReportTests
|
||||
result.DetailedNarrative.Should().Contain("Assumptions");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_WithFalsifiability_IncludesInReport()
|
||||
{
|
||||
var falsifiability = new FalsifiabilityCriteria
|
||||
@@ -183,7 +191,8 @@ public class RiskReportTests
|
||||
result.Explanation.Should().Contain("falsified");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_WithUnvalidatedAssumptions_RecommendsValidation()
|
||||
{
|
||||
var assumptions = new AssumptionSet
|
||||
@@ -212,7 +221,8 @@ public class RiskReportTests
|
||||
a.Action.Contains("Validate") || a.Action.Contains("assumption"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Generate_WithPartiallyEvaluatedFalsifiability_RecommendsCompletion()
|
||||
{
|
||||
var falsifiability = new FalsifiabilityCriteria
|
||||
@@ -242,7 +252,8 @@ public class RiskReportTests
|
||||
a.Action.Contains("falsifiability") || a.Action.Contains("evaluation"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecommendedAction_HasRequiredProperties()
|
||||
{
|
||||
var action = new RecommendedAction(
|
||||
@@ -257,7 +268,8 @@ public class RiskReportTests
|
||||
action.Effort.Should().Be(EffortLevel.Low);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(EffortLevel.Low)]
|
||||
[InlineData(EffortLevel.Medium)]
|
||||
[InlineData(EffortLevel.High)]
|
||||
|
||||
@@ -17,5 +17,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Scanner.Explainability\StellaOps.Scanner.Explainability.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -11,6 +11,8 @@ using StellaOps.Scanner.Worker.Orchestration;
|
||||
using StellaOps.Signals.Storage;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Scanner.Integration.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -41,7 +43,8 @@ public class PoEPipelineTests : IDisposable
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ScanWithVulnerability_GeneratesPoE_StoresInCas()
|
||||
{
|
||||
// Arrange
|
||||
@@ -98,7 +101,8 @@ public class PoEPipelineTests : IDisposable
|
||||
Assert.Equal(dsseBytes, artifact.DsseBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ScanWithUnreachableVuln_DoesNotGeneratePoE()
|
||||
{
|
||||
// Arrange
|
||||
@@ -124,7 +128,8 @@ public class PoEPipelineTests : IDisposable
|
||||
Assert.Empty(results);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PoEGeneration_ProducesDeterministicHash()
|
||||
{
|
||||
// Arrange
|
||||
@@ -141,7 +146,8 @@ public class PoEPipelineTests : IDisposable
|
||||
Assert.StartsWith("blake3:", hash1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PoEStorage_PersistsToCas_RetrievesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
<!-- Authority: Verdict manifests, signing, replay -->
|
||||
<ProjectReference Include="../../../Authority/__Libraries/StellaOps.Authority.Core/StellaOps.Authority.Core.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user