// 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; /// /// Unit tests for implementations. /// 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.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(0x01, 0x02, 0x03, 0x04), FunctionSize: 100, BasicBlockCount: 5, InstructionCount: 20, Metadata: ImmutableDictionary.Empty); } }