Refactor code structure for improved readability and maintainability; optimize performance in key functions.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user