Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -0,0 +1,476 @@
using System.Collections.Immutable;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Scanner.Reachability.Slices;
using StellaOps.Scanner.WebService.Endpoints;
using StellaOps.Scanner.WebService.Services;
using Xunit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
/// Integration tests for slice query and replay endpoints.
/// </summary>
public sealed class SliceEndpointsTests : IClassFixture<ScannerApplicationFixture>
{
private readonly ScannerApplicationFixture _fixture;
private readonly HttpClient _client;
public SliceEndpointsTests(ScannerApplicationFixture fixture)
{
_fixture = fixture;
_client = fixture.CreateClient();
}
[Fact]
public async Task QuerySlice_WithValidCve_ReturnsSlice()
{
// Arrange
var request = new SliceQueryRequestDto
{
ScanId = "test-scan-001",
CveId = "CVE-2024-1234",
Symbols = new List<string> { "vulnerable_function" }
};
// Act
var response = await _client.PostAsJsonAsync("/api/slices/query", request);
// Assert
// Note: May return 404 if no test data, but validates endpoint registration
Assert.True(
response.StatusCode == HttpStatusCode.OK ||
response.StatusCode == HttpStatusCode.NotFound ||
response.StatusCode == HttpStatusCode.Unauthorized,
$"Unexpected status: {response.StatusCode}");
}
[Fact]
public async Task QuerySlice_WithoutScanId_ReturnsBadRequest()
{
// Arrange
var request = new SliceQueryRequestDto
{
CveId = "CVE-2024-1234"
};
// Act
var response = await _client.PostAsJsonAsync("/api/slices/query", request);
// Assert
Assert.True(
response.StatusCode == HttpStatusCode.BadRequest ||
response.StatusCode == HttpStatusCode.Unauthorized,
$"Expected BadRequest or Unauthorized, got {response.StatusCode}");
}
[Fact]
public async Task QuerySlice_WithoutCveOrSymbols_ReturnsBadRequest()
{
// Arrange
var request = new SliceQueryRequestDto
{
ScanId = "test-scan-001"
};
// Act
var response = await _client.PostAsJsonAsync("/api/slices/query", request);
// Assert
Assert.True(
response.StatusCode == HttpStatusCode.BadRequest ||
response.StatusCode == HttpStatusCode.Unauthorized,
$"Expected BadRequest or Unauthorized, got {response.StatusCode}");
}
[Fact]
public async Task GetSlice_WithValidDigest_ReturnsSlice()
{
// Arrange
var digest = "sha256:abc123";
// Act
var response = await _client.GetAsync($"/api/slices/{digest}");
// Assert
Assert.True(
response.StatusCode == HttpStatusCode.OK ||
response.StatusCode == HttpStatusCode.NotFound ||
response.StatusCode == HttpStatusCode.Unauthorized,
$"Unexpected status: {response.StatusCode}");
}
[Fact]
public async Task GetSlice_WithDsseAccept_ReturnsDsseEnvelope()
{
// Arrange
var digest = "sha256:abc123";
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/slices/{digest}");
request.Headers.Add("Accept", "application/dsse+json");
// Act
var response = await _client.SendAsync(request);
// Assert
Assert.True(
response.StatusCode == HttpStatusCode.OK ||
response.StatusCode == HttpStatusCode.NotFound ||
response.StatusCode == HttpStatusCode.Unauthorized,
$"Unexpected status: {response.StatusCode}");
}
[Fact]
public async Task ReplaySlice_WithValidDigest_ReturnsReplayResult()
{
// Arrange
var request = new SliceReplayRequestDto
{
SliceDigest = "sha256:abc123"
};
// Act
var response = await _client.PostAsJsonAsync("/api/slices/replay", request);
// Assert
Assert.True(
response.StatusCode == HttpStatusCode.OK ||
response.StatusCode == HttpStatusCode.NotFound ||
response.StatusCode == HttpStatusCode.Unauthorized,
$"Unexpected status: {response.StatusCode}");
}
[Fact]
public async Task ReplaySlice_WithoutDigest_ReturnsBadRequest()
{
// Arrange
var request = new SliceReplayRequestDto();
// Act
var response = await _client.PostAsJsonAsync("/api/slices/replay", request);
// Assert
Assert.True(
response.StatusCode == HttpStatusCode.BadRequest ||
response.StatusCode == HttpStatusCode.Unauthorized,
$"Expected BadRequest or Unauthorized, got {response.StatusCode}");
}
}
/// <summary>
/// Unit tests for SliceDiffComputer.
/// </summary>
public sealed class SliceDiffComputerTests
{
private readonly SliceDiffComputer _computer = new();
[Fact]
public void Compare_IdenticalSlices_ReturnsMatch()
{
// Arrange
var slice = CreateTestSlice();
// Act
var result = _computer.Compare(slice, slice);
// Assert
Assert.True(result.Match);
Assert.Empty(result.MissingNodes);
Assert.Empty(result.ExtraNodes);
Assert.Empty(result.MissingEdges);
Assert.Empty(result.ExtraEdges);
Assert.Null(result.VerdictDiff);
}
[Fact]
public void Compare_DifferentNodes_ReturnsDiff()
{
// Arrange
var original = CreateTestSlice();
var modified = original with
{
Subgraph = original.Subgraph with
{
Nodes = original.Subgraph.Nodes.Add(new SliceNode
{
Id = "extra-node",
Symbol = "extra_func",
Kind = SliceNodeKind.Intermediate
})
}
};
// Act
var result = _computer.Compare(original, modified);
// Assert
Assert.False(result.Match);
Assert.Empty(result.MissingNodes);
Assert.Single(result.ExtraNodes);
Assert.Contains("extra-node", result.ExtraNodes);
}
[Fact]
public void Compare_DifferentEdges_ReturnsDiff()
{
// Arrange
var original = CreateTestSlice();
var modified = original with
{
Subgraph = original.Subgraph with
{
Edges = original.Subgraph.Edges.RemoveAt(0)
}
};
// Act
var result = _computer.Compare(original, modified);
// Assert
Assert.False(result.Match);
Assert.Single(result.MissingEdges);
}
[Fact]
public void Compare_DifferentVerdict_ReturnsDiff()
{
// Arrange
var original = CreateTestSlice();
var modified = original with
{
Verdict = original.Verdict with
{
Status = SliceVerdictStatus.Unreachable
}
};
// Act
var result = _computer.Compare(original, modified);
// Assert
Assert.False(result.Match);
Assert.NotNull(result.VerdictDiff);
Assert.Contains("Status:", result.VerdictDiff);
}
[Fact]
public void ComputeCacheKey_SameInputs_ReturnsSameKey()
{
// Arrange
var symbols = new[] { "func_a", "func_b" };
var entrypoints = new[] { "main" };
// Act
var key1 = SliceDiffComputer.ComputeCacheKey("scan1", "CVE-2024-1234", symbols, entrypoints, null);
var key2 = SliceDiffComputer.ComputeCacheKey("scan1", "CVE-2024-1234", symbols, entrypoints, null);
// Assert
Assert.Equal(key1, key2);
}
[Fact]
public void ComputeCacheKey_DifferentInputs_ReturnsDifferentKey()
{
// Arrange
var symbols = new[] { "func_a", "func_b" };
// Act
var key1 = SliceDiffComputer.ComputeCacheKey("scan1", "CVE-2024-1234", symbols, null, null);
var key2 = SliceDiffComputer.ComputeCacheKey("scan2", "CVE-2024-1234", symbols, null, null);
// Assert
Assert.NotEqual(key1, key2);
}
[Fact]
public void ToSummary_MatchingSlices_ReturnsMatchMessage()
{
// Arrange
var result = new SliceDiffResult { Match = true };
// Act
var summary = result.ToSummary();
// Assert
Assert.Contains("match exactly", summary);
}
[Fact]
public void ToSummary_DifferingSlices_ReturnsDetailedDiff()
{
// Arrange
var result = new SliceDiffResult
{
Match = false,
MissingNodes = ImmutableArray.Create("node1", "node2"),
ExtraEdges = ImmutableArray.Create("edge1"),
VerdictDiff = "Status: reachable -> unreachable"
};
// Act
var summary = result.ToSummary();
// Assert
Assert.Contains("Missing nodes", summary);
Assert.Contains("Extra edges", summary);
Assert.Contains("Verdict changed", summary);
}
private static ReachabilitySlice CreateTestSlice()
{
return new ReachabilitySlice
{
Inputs = new SliceInputs
{
GraphDigest = "sha256:graph123"
},
Query = new SliceQuery
{
CveId = "CVE-2024-1234",
TargetSymbols = ImmutableArray.Create("vulnerable_func"),
Entrypoints = ImmutableArray.Create("main")
},
Subgraph = new SliceSubgraph
{
Nodes = ImmutableArray.Create(
new SliceNode { Id = "main", Symbol = "main", Kind = SliceNodeKind.Entrypoint },
new SliceNode { Id = "vuln", Symbol = "vulnerable_func", Kind = SliceNodeKind.Target }
),
Edges = ImmutableArray.Create(
new SliceEdge { From = "main", To = "vuln", Kind = SliceEdgeKind.Direct, Confidence = 1.0 }
)
},
Verdict = new SliceVerdict
{
Status = SliceVerdictStatus.Reachable,
Confidence = 0.95
},
Manifest = new Scanner.Core.ScanManifest()
};
}
}
/// <summary>
/// Unit tests for SliceCache.
/// </summary>
public sealed class SliceCacheTests
{
[Fact]
public void TryGet_EmptyCache_ReturnsFalse()
{
// Arrange
var options = Microsoft.Extensions.Options.Options.Create(new SliceCacheOptions());
using var cache = new SliceCache(options);
// Act
var found = cache.TryGet("nonexistent", out var entry);
// Assert
Assert.False(found);
Assert.Null(entry);
}
[Fact]
public void Set_ThenGet_ReturnsEntry()
{
// Arrange
var options = Microsoft.Extensions.Options.Options.Create(new SliceCacheOptions());
using var cache = new SliceCache(options);
var slice = CreateTestSlice();
// Act
cache.Set("key1", slice, "sha256:abc123");
var found = cache.TryGet("key1", out var entry);
// Assert
Assert.True(found);
Assert.NotNull(entry);
Assert.Equal("sha256:abc123", entry.Digest);
}
[Fact]
public void TryGet_IncrementsCacheStats()
{
// Arrange
var options = Microsoft.Extensions.Options.Options.Create(new SliceCacheOptions());
using var cache = new SliceCache(options);
var slice = CreateTestSlice();
cache.Set("key1", slice, "sha256:abc123");
// Act
cache.TryGet("key1", out _); // hit
cache.TryGet("missing", out _); // miss
var stats = cache.GetStats();
// Assert
Assert.Equal(1, stats.HitCount);
Assert.Equal(1, stats.MissCount);
Assert.Equal(0.5, stats.HitRate);
}
[Fact]
public void Clear_RemovesAllEntries()
{
// Arrange
var options = Microsoft.Extensions.Options.Options.Create(new SliceCacheOptions());
using var cache = new SliceCache(options);
var slice = CreateTestSlice();
cache.Set("key1", slice, "sha256:abc123");
cache.Set("key2", slice, "sha256:def456");
// Act
cache.Clear();
var stats = cache.GetStats();
// Assert
Assert.Equal(0, stats.ItemCount);
}
[Fact]
public void Invalidate_RemovesSpecificEntry()
{
// Arrange
var options = Microsoft.Extensions.Options.Options.Create(new SliceCacheOptions());
using var cache = new SliceCache(options);
var slice = CreateTestSlice();
cache.Set("key1", slice, "sha256:abc123");
cache.Set("key2", slice, "sha256:def456");
// Act
cache.Invalidate("key1");
// Assert
Assert.False(cache.TryGet("key1", out _));
Assert.True(cache.TryGet("key2", out _));
}
[Fact]
public void Disabled_NeverCaches()
{
// Arrange
var options = Microsoft.Extensions.Options.Options.Create(new SliceCacheOptions { Enabled = false });
using var cache = new SliceCache(options);
var slice = CreateTestSlice();
// Act
cache.Set("key1", slice, "sha256:abc123");
var found = cache.TryGet("key1", out _);
// Assert
Assert.False(found);
}
private static ReachabilitySlice CreateTestSlice()
{
return new ReachabilitySlice
{
Inputs = new SliceInputs { GraphDigest = "sha256:graph123" },
Query = new SliceQuery(),
Subgraph = new SliceSubgraph(),
Verdict = new SliceVerdict { Status = SliceVerdictStatus.Unknown, Confidence = 0.0 },
Manifest = new Scanner.Core.ScanManifest()
};
}
}