Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.VulnSurfaces.Tests/CecilMethodFingerprinterTests.cs
master 0dc71e760a 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.
2025-12-18 18:35:30 +02:00

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");
}
}