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,367 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_4300_0002_0001
// Task: T4 - Unit Tests for Evidence Redaction Service
using System.Security.Claims;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Scanner.Evidence.Models;
using StellaOps.Scanner.Evidence.Privacy;
using Xunit;
namespace StellaOps.Scanner.Evidence.Tests.Privacy;
public sealed class EvidenceRedactionServiceTests
{
private readonly EvidenceRedactionService _service;
public EvidenceRedactionServiceTests()
{
_service = new EvidenceRedactionService(NullLogger<EvidenceRedactionService>.Instance);
}
[Fact]
public void Redact_Standard_RemovesSourceCode()
{
var bundle = CreateBundleWithSource();
var result = _service.Redact(bundle, EvidenceRedactionLevel.Standard);
var steps = result.Reachability!.Paths.SelectMany(p => p.Steps).ToList();
steps.Should().AllSatisfy(s => s.SourceCode.Should().BeNull());
}
[Fact]
public void Redact_Standard_KeepsFileHashes()
{
var bundle = CreateBundleWithSource();
var result = _service.Redact(bundle, EvidenceRedactionLevel.Standard);
var steps = result.Reachability!.Paths.SelectMany(p => p.Steps).ToList();
steps.Should().AllSatisfy(s => s.FileHash.Should().NotBeNull());
}
[Fact]
public void Redact_Standard_KeepsLineRanges()
{
var bundle = CreateBundleWithSource();
var result = _service.Redact(bundle, EvidenceRedactionLevel.Standard);
var steps = result.Reachability!.Paths.SelectMany(p => p.Steps).ToList();
steps.Should().AllSatisfy(s =>
{
s.Lines.Should().NotBeNull();
s.Lines.Should().HaveCount(2);
});
}
[Fact]
public void Redact_Standard_RedactsSymbolArguments()
{
var bundle = CreateBundleWithSource();
var result = _service.Redact(bundle, EvidenceRedactionLevel.Standard);
var firstStep = result.Reachability!.Paths.First().Steps.First();
firstStep.Node.Should().Be("MyClass.MyMethod(...)");
}
[Fact]
public void Redact_Standard_RemovesCallStackArguments()
{
var bundle = CreateBundleWithCallStack();
var result = _service.Redact(bundle, EvidenceRedactionLevel.Standard);
var frames = result.CallStack!.Frames.ToList();
frames.Should().AllSatisfy(f =>
{
f.Arguments.Should().BeNull();
f.Locals.Should().BeNull();
});
}
[Fact]
public void Redact_Minimal_RemovesPaths()
{
var bundle = CreateBundleWithPaths(5);
var result = _service.Redact(bundle, EvidenceRedactionLevel.Minimal);
result.Reachability!.Paths.Should().BeEmpty();
result.Reachability.PathCount.Should().Be(0);
}
[Fact]
public void Redact_Minimal_KeepsResultAndConfidence()
{
var bundle = CreateBundleWithPaths(5);
var result = _service.Redact(bundle, EvidenceRedactionLevel.Minimal);
result.Reachability!.Result.Should().Be("reachable");
result.Reachability.Confidence.Should().Be(0.95);
}
[Fact]
public void Redact_Minimal_RemovesCallStack()
{
var bundle = CreateBundleWithCallStack();
var result = _service.Redact(bundle, EvidenceRedactionLevel.Minimal);
result.CallStack.Should().BeNull();
}
[Fact]
public void Redact_Minimal_KeepsVexAndEpss()
{
var bundle = CreateBundleWithVexAndEpss();
var result = _service.Redact(bundle, EvidenceRedactionLevel.Minimal);
result.Vex.Should().NotBeNull();
result.Epss.Should().NotBeNull();
}
[Fact]
public void Redact_Full_NoChanges()
{
var bundle = CreateBundleWithSource();
var result = _service.Redact(bundle, EvidenceRedactionLevel.Full);
result.Should().Be(bundle);
}
[Fact]
public void DetermineLevel_SecurityAdmin_ReturnsFull()
{
var user = CreateUserWithRole("security_admin");
var level = _service.DetermineLevel(user);
level.Should().Be(EvidenceRedactionLevel.Full);
}
[Fact]
public void DetermineLevel_EvidenceFullScope_ReturnsFull()
{
var user = CreateUserWithScope("evidence:full");
var level = _service.DetermineLevel(user);
level.Should().Be(EvidenceRedactionLevel.Full);
}
[Fact]
public void DetermineLevel_SecurityAnalyst_ReturnsStandard()
{
var user = CreateUserWithRole("security_analyst");
var level = _service.DetermineLevel(user);
level.Should().Be(EvidenceRedactionLevel.Standard);
}
[Fact]
public void DetermineLevel_EvidenceStandardScope_ReturnsStandard()
{
var user = CreateUserWithScope("evidence:standard");
var level = _service.DetermineLevel(user);
level.Should().Be(EvidenceRedactionLevel.Standard);
}
[Fact]
public void DetermineLevel_NoScopes_ReturnsMinimal()
{
var user = CreateUserWithNoScopes();
var level = _service.DetermineLevel(user);
level.Should().Be(EvidenceRedactionLevel.Minimal);
}
[Fact]
public void RedactFields_SourceCode_RemovesOnlySourceCode()
{
var bundle = CreateBundleWithSource();
var result = _service.RedactFields(bundle, RedactableFields.SourceCode);
var steps = result.Reachability!.Paths.SelectMany(p => p.Steps).ToList();
steps.Should().AllSatisfy(s => s.SourceCode.Should().BeNull());
// Should keep other fields
steps.Should().AllSatisfy(s =>
{
s.FileHash.Should().NotBeNull();
s.Lines.Should().NotBeNull();
});
}
[Fact]
public void RedactFields_CallArguments_RemovesOnlyArguments()
{
var bundle = CreateBundleWithCallStack();
var result = _service.RedactFields(bundle, RedactableFields.CallArguments);
var frames = result.CallStack!.Frames.ToList();
frames.Should().AllSatisfy(f =>
{
f.Arguments.Should().BeNull();
f.Locals.Should().BeNull();
});
// Should keep function names
frames.Should().AllSatisfy(f => f.Function.Should().NotBeNull());
}
[Fact]
public void RedactFields_None_NoChanges()
{
var bundle = CreateBundleWithSource();
var result = _service.RedactFields(bundle, RedactableFields.None);
result.Should().Be(bundle);
}
// Helper methods for creating test data
private EvidenceBundle CreateBundleWithSource()
{
return new EvidenceBundle
{
Reachability = new ReachabilityEvidence
{
Result = "reachable",
Confidence = 0.95,
Paths = new[]
{
new ReachabilityPath
{
PathId = "path-1",
Steps = new[]
{
new ReachabilityStep
{
Node = "MyClass.MyMethod(string arg1, int arg2)",
FileHash = "sha256:abc123",
Lines = new[] { 10, 15 },
SourceCode = "var result = DoSomething(arg1, arg2);"
}
}
}
},
GraphDigest = "sha256:graph123"
}
};
}
private EvidenceBundle CreateBundleWithPaths(int pathCount)
{
var paths = Enumerable.Range(1, pathCount)
.Select(i => new ReachabilityPath
{
PathId = $"path-{i}",
Steps = new[]
{
new ReachabilityStep
{
Node = $"Function{i}",
FileHash = $"sha256:hash{i}",
Lines = new[] { i * 10, i * 10 + 5 }
}
}
})
.ToList();
return new EvidenceBundle
{
Reachability = new ReachabilityEvidence
{
Result = "reachable",
Confidence = 0.95,
Paths = paths,
GraphDigest = "sha256:graph123"
}
};
}
private EvidenceBundle CreateBundleWithCallStack()
{
return new EvidenceBundle
{
CallStack = new CallStackEvidence
{
Frames = new[]
{
new CallFrame
{
Function = "MyClass.MyMethod(...)",
FileHash = "sha256:abc123",
Line = 42,
Arguments = new Dictionary<string, string>
{
["arg1"] = "sensitive_value",
["arg2"] = "123"
},
Locals = new Dictionary<string, string>
{
["local1"] = "local_value"
}
}
}
}
};
}
private EvidenceBundle CreateBundleWithVexAndEpss()
{
return new EvidenceBundle
{
Reachability = new ReachabilityEvidence
{
Result = "reachable",
Confidence = 0.8,
Paths = new[]
{
new ReachabilityPath
{
PathId = "path-1",
Steps = new[]
{
new ReachabilityStep
{
Node = "SomeMethod",
FileHash = "sha256:xyz",
Lines = new[] { 1, 5 }
}
}
}
},
GraphDigest = "sha256:graph"
},
Vex = new VexEvidence
{
Status = "not_affected",
Justification = "vulnerable_code_not_in_execute_path"
},
Epss = new EpssEvidence
{
Score = 0.05,
Percentile = 0.75,
ModelDate = new DateOnly(2025, 12, 22),
CapturedAt = DateTimeOffset.UtcNow
}
};
}
private ClaimsPrincipal CreateUserWithRole(string role)
{
var claims = new[] { new Claim("role", role) };
var identity = new ClaimsIdentity(claims, "TestAuth");
return new ClaimsPrincipal(identity);
}
private ClaimsPrincipal CreateUserWithScope(string scope)
{
var claims = new[] { new Claim("scope", scope) };
var identity = new ClaimsIdentity(claims, "TestAuth");
return new ClaimsPrincipal(identity);
}
private ClaimsPrincipal CreateUserWithNoScopes()
{
var identity = new ClaimsIdentity(Array.Empty<Claim>(), "TestAuth");
return new ClaimsPrincipal(identity);
}
}

View File

@@ -0,0 +1,28 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Evidence/StellaOps.Scanner.Evidence.csproj" />
</ItemGroup>
</Project>