- Implemented unit tests for PathConfidenceScorer to evaluate path scoring under various conditions, including empty constraints, known and unknown constraints, environmental dependencies, and custom weights. - Developed tests for PathEnumerator to ensure correct path enumeration from simple scripts, handling known environments, and respecting maximum paths and depth limits. - Created tests for ShellSymbolicExecutor to validate execution of shell scripts, including handling of commands, branching, and environment tracking. - Added tests for SymbolicState to verify state management, variable handling, constraint addition, and environment dependency collection.
255 lines
7.5 KiB
C#
255 lines
7.5 KiB
C#
// Licensed to StellaOps under the AGPL-3.0-or-later license.
|
|
|
|
using System.Collections.Immutable;
|
|
using StellaOps.Scanner.EntryTrace.Binary;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Scanner.EntryTrace.Tests.Binary;
|
|
|
|
/// <summary>
|
|
/// Unit tests for <see cref="IFingerprintIndex"/> implementations.
|
|
/// </summary>
|
|
public sealed class FingerprintIndexTests
|
|
{
|
|
[Fact]
|
|
public async Task InMemoryIndex_Add_IncreasesCount()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
var fingerprint = CreateFingerprint("test-001");
|
|
|
|
// Act
|
|
await index.AddAsync(fingerprint, "pkg:npm/lodash@4.17.21", "lodash", null);
|
|
|
|
// Assert
|
|
var stats = index.GetStatistics();
|
|
Assert.Equal(1, stats.TotalFingerprints);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InMemoryIndex_LookupExact_FindsMatch()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
var fingerprint = CreateFingerprint("test-001");
|
|
|
|
await index.AddAsync(fingerprint, "pkg:npm/lodash@4.17.21", "_.map", null);
|
|
|
|
// Act
|
|
var matches = await index.LookupAsync(fingerprint);
|
|
|
|
// Assert
|
|
Assert.Single(matches);
|
|
Assert.Equal("_.map", matches[0].FunctionName);
|
|
Assert.Equal("pkg:npm/lodash@4.17.21", matches[0].SourcePackage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InMemoryIndex_LookupExactAsync_FindsMatch()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
var fingerprint = CreateFingerprint("test-001");
|
|
|
|
await index.AddAsync(fingerprint, "pkg:npm/lodash@4.17.21", "_.map", null);
|
|
|
|
// Act
|
|
var match = await index.LookupExactAsync(fingerprint);
|
|
|
|
// Assert
|
|
Assert.NotNull(match);
|
|
Assert.Equal("_.map", match.FunctionName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InMemoryIndex_LookupSimilar_LimitsResults()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
|
|
// Add many fingerprints
|
|
for (var i = 0; i < 20; i++)
|
|
{
|
|
var fp = CreateFingerprint($"test-{i:D3}");
|
|
await index.AddAsync(fp, $"pkg:npm/lib{i}@1.0.0", $"func_{i}", null);
|
|
}
|
|
|
|
var queryFp = CreateFingerprint("query");
|
|
|
|
// Act
|
|
var matches = await index.LookupAsync(queryFp, minSimilarity: 0.1f, maxResults: 5);
|
|
|
|
// Assert
|
|
Assert.True(matches.Length <= 5);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InMemoryIndex_Clear_RemovesAll()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
var fp = CreateFingerprint($"test-{i:D3}");
|
|
await index.AddAsync(fp, $"pkg:npm/lib{i}@1.0.0", $"func_{i}", null);
|
|
}
|
|
|
|
// Act
|
|
await index.ClearAsync();
|
|
|
|
// Assert
|
|
var stats = index.GetStatistics();
|
|
Assert.Equal(0, stats.TotalFingerprints);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InMemoryIndex_Statistics_TracksPackages()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
|
|
var fp1 = CreateFingerprint("test-001");
|
|
var fp2 = CreateFingerprint("test-002");
|
|
var fp3 = CreateFingerprint("test-003");
|
|
|
|
await index.AddAsync(fp1, "pkg:npm/lodash@4.17.21", "func_a", null);
|
|
await index.AddAsync(fp2, "pkg:npm/lodash@4.17.21", "func_b", null);
|
|
await index.AddAsync(fp3, "pkg:npm/express@4.18.0", "func_c", null);
|
|
|
|
// Act
|
|
var stats = index.GetStatistics();
|
|
|
|
// Assert
|
|
Assert.Equal(3, stats.TotalFingerprints);
|
|
Assert.Equal(2, stats.TotalPackages);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task VulnerableIndex_TracksVulnerabilities()
|
|
{
|
|
// Arrange
|
|
var index = new VulnerableFingerprintIndex();
|
|
var fp = CreateFingerprint("test-001");
|
|
|
|
// Act
|
|
await index.AddVulnerableAsync(
|
|
fp,
|
|
"pkg:npm/lodash@4.17.20",
|
|
"_.template",
|
|
"CVE-2021-23337",
|
|
"4.17.0-4.17.20",
|
|
VulnerabilitySeverity.High);
|
|
|
|
// Assert
|
|
var matches = await index.LookupAsync(fp);
|
|
Assert.Single(matches);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task VulnerableIndex_CheckVulnerable_ReturnsMatch()
|
|
{
|
|
// Arrange
|
|
var index = new VulnerableFingerprintIndex();
|
|
var fp = CreateFingerprint("test-001");
|
|
|
|
await index.AddVulnerableAsync(
|
|
fp,
|
|
"pkg:npm/lodash@4.17.20",
|
|
"_.template",
|
|
"CVE-2021-23337",
|
|
"4.17.0-4.17.20",
|
|
VulnerabilitySeverity.High);
|
|
|
|
// Act
|
|
var match = await index.CheckVulnerableAsync(fp, 0x1000);
|
|
|
|
// Assert
|
|
Assert.NotNull(match);
|
|
Assert.Equal("CVE-2021-23337", match.VulnerabilityId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task VulnerableIndex_Statistics_TracksVulns()
|
|
{
|
|
// Arrange
|
|
var index = new VulnerableFingerprintIndex();
|
|
var fp1 = CreateFingerprint("test-001");
|
|
var fp2 = CreateFingerprint("test-002");
|
|
|
|
await index.AddVulnerableAsync(
|
|
fp1, "pkg:npm/lodash@4.17.20", "_.template", "CVE-2021-23337", "4.17.x", VulnerabilitySeverity.High);
|
|
await index.AddVulnerableAsync(
|
|
fp2, "pkg:npm/moment@2.29.0", "moment.locale", "CVE-2022-24785", "2.29.x", VulnerabilitySeverity.Medium);
|
|
|
|
// Act
|
|
var stats = index.GetStatistics();
|
|
|
|
// Assert
|
|
Assert.Equal(2, stats.TotalFingerprints);
|
|
Assert.True(stats.TotalVulnerabilities >= 2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InMemoryIndex_AddBatch_AddsMultiple()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
|
|
var matches = Enumerable.Range(0, 10)
|
|
.Select(i => new FingerprintMatch(
|
|
Fingerprint: CreateFingerprint($"test-{i:D3}"),
|
|
FunctionName: $"func_{i}",
|
|
SourcePackage: "pkg:npm/test@1.0.0",
|
|
SourceVersion: "1.0.0",
|
|
SourceFile: null,
|
|
SourceLine: null,
|
|
VulnerabilityIds: ImmutableArray<string>.Empty,
|
|
Similarity: 1.0f,
|
|
MatchedAt: DateTimeOffset.UtcNow))
|
|
.ToList();
|
|
|
|
// Act
|
|
foreach (var match in matches)
|
|
{
|
|
await index.AddAsync(match);
|
|
}
|
|
|
|
// Assert
|
|
var stats = index.GetStatistics();
|
|
Assert.Equal(10, stats.TotalFingerprints);
|
|
}
|
|
|
|
[Fact]
|
|
public void InMemoryIndex_Count_ReturnsCorrectValue()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
|
|
// Assert initial
|
|
Assert.Equal(0, index.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void InMemoryIndex_IndexedPackages_ReturnsEmptyInitially()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryFingerprintIndex();
|
|
|
|
// Assert
|
|
Assert.Empty(index.IndexedPackages);
|
|
}
|
|
|
|
private static CodeFingerprint CreateFingerprint(string id)
|
|
{
|
|
return new CodeFingerprint(
|
|
Id: id,
|
|
Algorithm: FingerprintAlgorithm.BasicBlockHash,
|
|
Hash: ImmutableArray.Create<byte>(0x01, 0x02, 0x03, 0x04),
|
|
FunctionSize: 100,
|
|
BasicBlockCount: 5,
|
|
InstructionCount: 20,
|
|
Metadata: ImmutableDictionary<string, string>.Empty);
|
|
}
|
|
}
|