feat: Add PathViewer and RiskDriftCard components with templates and styles
- 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.
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user