- Implemented PathViewerComponent for visualizing reachability call paths. - Added RiskDriftCardComponent to display reachability drift results. - Created corresponding HTML templates and SCSS styles for both components. - Introduced test fixtures for reachability analysis in JSON format. - Enhanced user interaction with collapsible and expandable features in PathViewer. - Included risk trend visualization and summary metrics in RiskDriftCard.
198 lines
6.3 KiB
C#
198 lines
6.3 KiB
C#
// -----------------------------------------------------------------------------
|
|
// CecilMethodFingerprinterTests.cs
|
|
// Sprint: SPRINT_3700_0002_0001_vuln_surfaces_core
|
|
// Description: Unit tests for CecilMethodFingerprinter.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using StellaOps.Scanner.VulnSurfaces.Fingerprint;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Scanner.VulnSurfaces.Tests;
|
|
|
|
public class CecilMethodFingerprinterTests
|
|
{
|
|
private readonly CecilMethodFingerprinter _fingerprinter;
|
|
|
|
public CecilMethodFingerprinterTests()
|
|
{
|
|
_fingerprinter = new CecilMethodFingerprinter(
|
|
NullLogger<CecilMethodFingerprinter>.Instance);
|
|
}
|
|
|
|
[Fact]
|
|
public void Ecosystem_ReturnsNuget()
|
|
{
|
|
Assert.Equal("nuget", _fingerprinter.Ecosystem);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FingerprintAsync_WithNullRequest_ThrowsArgumentNullException()
|
|
{
|
|
await Assert.ThrowsAsync<ArgumentNullException>(
|
|
() => _fingerprinter.FingerprintAsync(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FingerprintAsync_WithNonExistentPath_ReturnsEmptyResult()
|
|
{
|
|
// Arrange
|
|
var request = new FingerprintRequest
|
|
{
|
|
PackagePath = "/nonexistent/path/to/package",
|
|
PackageName = "nonexistent",
|
|
Version = "1.0.0"
|
|
};
|
|
|
|
// Act
|
|
var result = await _fingerprinter.FingerprintAsync(request);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.True(result.Success);
|
|
Assert.Empty(result.Methods);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FingerprintAsync_WithOwnAssembly_FindsMethods()
|
|
{
|
|
// Arrange - use the test assembly itself
|
|
var testAssemblyPath = typeof(CecilMethodFingerprinterTests).Assembly.Location;
|
|
var assemblyDir = Path.GetDirectoryName(testAssemblyPath)!;
|
|
|
|
var request = new FingerprintRequest
|
|
{
|
|
PackagePath = assemblyDir,
|
|
PackageName = "test",
|
|
Version = "1.0.0",
|
|
IncludePrivateMethods = false
|
|
};
|
|
|
|
// Act
|
|
var result = await _fingerprinter.FingerprintAsync(request);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.True(result.Success);
|
|
Assert.NotEmpty(result.Methods);
|
|
|
|
// Should find this test class
|
|
Assert.True(result.Methods.Count > 0, "Should find at least some methods");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FingerprintAsync_ComputesDeterministicHashes()
|
|
{
|
|
// Arrange - fingerprint twice
|
|
var testAssemblyPath = typeof(CecilMethodFingerprinterTests).Assembly.Location;
|
|
var assemblyDir = Path.GetDirectoryName(testAssemblyPath)!;
|
|
|
|
var request = new FingerprintRequest
|
|
{
|
|
PackagePath = assemblyDir,
|
|
PackageName = "test",
|
|
Version = "1.0.0",
|
|
IncludePrivateMethods = false
|
|
};
|
|
|
|
// Act
|
|
var result1 = await _fingerprinter.FingerprintAsync(request);
|
|
var result2 = await _fingerprinter.FingerprintAsync(request);
|
|
|
|
// Assert - same methods should produce same hashes
|
|
Assert.Equal(result1.Methods.Count, result2.Methods.Count);
|
|
|
|
foreach (var (key, fp1) in result1.Methods)
|
|
{
|
|
Assert.True(result2.Methods.TryGetValue(key, out var fp2));
|
|
Assert.Equal(fp1.BodyHash, fp2.BodyHash);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FingerprintAsync_WithCancellation_RespectsCancellation()
|
|
{
|
|
// Arrange
|
|
using var cts = new CancellationTokenSource();
|
|
cts.Cancel();
|
|
|
|
var testAssemblyPath = typeof(CecilMethodFingerprinterTests).Assembly.Location;
|
|
var assemblyDir = Path.GetDirectoryName(testAssemblyPath)!;
|
|
|
|
var request = new FingerprintRequest
|
|
{
|
|
PackagePath = assemblyDir,
|
|
PackageName = "test",
|
|
Version = "1.0.0"
|
|
};
|
|
|
|
// Act - operation may either throw or return early
|
|
// since the token is already cancelled
|
|
try
|
|
{
|
|
await _fingerprinter.FingerprintAsync(request, cts.Token);
|
|
// If it doesn't throw, that's also acceptable behavior
|
|
// The key is that it should respect the cancellation token
|
|
Assert.True(true, "Method completed without throwing - acceptable if it checks token");
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// Expected behavior
|
|
Assert.True(true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FingerprintAsync_MethodKeyFormat_IsValid()
|
|
{
|
|
// Arrange
|
|
var testAssemblyPath = typeof(CecilMethodFingerprinterTests).Assembly.Location;
|
|
var assemblyDir = Path.GetDirectoryName(testAssemblyPath)!;
|
|
|
|
var request = new FingerprintRequest
|
|
{
|
|
PackagePath = assemblyDir,
|
|
PackageName = "test",
|
|
Version = "1.0.0",
|
|
IncludePrivateMethods = false
|
|
};
|
|
|
|
// Act
|
|
var result = await _fingerprinter.FingerprintAsync(request);
|
|
|
|
// Assert - keys should not be empty
|
|
foreach (var key in result.Methods.Keys)
|
|
{
|
|
Assert.NotEmpty(key);
|
|
// Method keys use "::" separator between type and method
|
|
// Some may be anonymous types like "<>f__AnonymousType0`2"
|
|
// Just verify they're non-empty and have reasonable format
|
|
Assert.True(key.Contains("::") || key.Contains("."),
|
|
$"Method key should contain :: or . separator: {key}");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FingerprintAsync_IncludesSignature()
|
|
{
|
|
// Arrange
|
|
var testAssemblyPath = typeof(CecilMethodFingerprinterTests).Assembly.Location;
|
|
var assemblyDir = Path.GetDirectoryName(testAssemblyPath)!;
|
|
|
|
var request = new FingerprintRequest
|
|
{
|
|
PackagePath = assemblyDir,
|
|
PackageName = "test",
|
|
Version = "1.0.0",
|
|
IncludePrivateMethods = false
|
|
};
|
|
|
|
// Act
|
|
var result = await _fingerprinter.FingerprintAsync(request);
|
|
|
|
// Assert - fingerprints should have signatures
|
|
var anyWithSignature = result.Methods.Values.Any(fp => !string.IsNullOrEmpty(fp.Signature));
|
|
Assert.True(anyWithSignature, "At least some methods should have signatures");
|
|
}
|
|
}
|