Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Secrets.Tests/PayloadMaskerTests.cs
StellaOps Bot 3098e84de4 save progress
2026-01-04 14:54:52 +02:00

172 lines
4.5 KiB
C#

using FluentAssertions;
using StellaOps.Scanner.Analyzers.Secrets;
namespace StellaOps.Scanner.Analyzers.Secrets.Tests;
[Trait("Category", "Unit")]
public sealed class PayloadMaskerTests
{
private readonly PayloadMasker _masker = new();
[Fact]
public void Mask_EmptySpan_ReturnsEmpty()
{
_masker.Mask(ReadOnlySpan<char>.Empty).Should().BeEmpty();
}
[Fact]
public void Mask_ShortValue_ReturnsMaskChars()
{
// Values shorter than prefix+suffix get masked placeholder
var result = _masker.Mask("abc".AsSpan());
result.Should().Contain("*");
}
[Fact]
public void Mask_StandardValue_PreservesPrefixAndSuffix()
{
var result = _masker.Mask("1234567890".AsSpan());
// Default: 4 char prefix, 2 char suffix
result.Should().StartWith("1234");
result.Should().EndWith("90");
result.Should().Contain("****");
}
[Fact]
public void Mask_AwsAccessKey_PreservesPrefix()
{
var awsKey = "AKIAIOSFODNN7EXAMPLE";
var result = _masker.Mask(awsKey.AsSpan());
result.Should().StartWith("AKIA");
result.Should().EndWith("LE");
result.Should().Contain("****");
}
[Fact]
public void Mask_WithPrefixHint_UsesCustomPrefixLength()
{
var apiKey = "sk-proj-abcdefghijklmnop";
// MaxExposedChars is 6, so prefix:8 + suffix:2 gets scaled down
var result = _masker.Mask(apiKey.AsSpan(), "prefix:4,suffix:2");
result.Should().StartWith("sk-p");
result.Should().Contain("****");
}
[Fact]
public void Mask_LongValue_MasksMiddle()
{
var longSecret = "verylongsecretthatexceeds100characters" +
"andshouldbemaskkedproperlywithoutexpo" +
"singtheentirecontentstoanyoneviewingit";
var result = _masker.Mask(longSecret.AsSpan());
// Should contain mask characters and be shorter than original
result.Should().Contain("****");
result.Length.Should().BeLessThan(longSecret.Length);
}
[Fact]
public void Mask_IsDeterministic()
{
var secret = "AKIAIOSFODNN7EXAMPLE";
var result1 = _masker.Mask(secret.AsSpan());
var result2 = _masker.Mask(secret.AsSpan());
result1.Should().Be(result2);
}
[Fact]
public void Mask_NeverExposesFullSecret()
{
var secret = "supersecretkey123";
var result = _masker.Mask(secret.AsSpan());
result.Should().NotBe(secret);
result.Should().Contain("*");
}
[Theory]
[InlineData("prefix:6,suffix:0")]
[InlineData("prefix:0,suffix:6")]
[InlineData("prefix:3,suffix:3")]
public void Mask_WithVariousHints_RespectsTotalLimit(string hint)
{
var secret = "abcdefghijklmnopqrstuvwxyz";
var result = _masker.Mask(secret.AsSpan(), hint);
var visibleChars = result.Replace("*", "").Length;
visibleChars.Should().BeLessThanOrEqualTo(PayloadMasker.MaxExposedChars);
}
[Fact]
public void Mask_EnforcesMinOutputLength()
{
var secret = "abcdefghijklmnop";
var result = _masker.Mask(secret.AsSpan());
result.Length.Should().BeGreaterThanOrEqualTo(PayloadMasker.MinOutputLength);
}
[Fact]
public void Mask_ByteOverload_DecodesUtf8()
{
var text = "secretpassword123";
var bytes = System.Text.Encoding.UTF8.GetBytes(text);
var result = _masker.Mask(bytes.AsSpan());
result.Should().Contain("****");
result.Should().StartWith("secr");
}
[Fact]
public void Mask_EmptyByteSpan_ReturnsEmpty()
{
_masker.Mask(ReadOnlySpan<byte>.Empty).Should().BeEmpty();
}
[Fact]
public void Mask_InvalidHint_UsesDefaults()
{
var secret = "abcdefghijklmnop";
var result1 = _masker.Mask(secret.AsSpan(), "invalid:hint:format");
var result2 = _masker.Mask(secret.AsSpan());
result1.Should().Be(result2);
}
[Fact]
public void Mask_UsesCorrectMaskChar()
{
var secret = "abcdefghijklmnop";
var result = _masker.Mask(secret.AsSpan());
result.Should().Contain(PayloadMasker.MaskChar.ToString());
}
[Fact]
public void Mask_MaskLengthLimited()
{
var longSecret = new string('x', 100);
var result = _masker.Mask(longSecret.AsSpan());
// Count mask characters
var maskCount = result.Count(c => c == PayloadMasker.MaskChar);
maskCount.Should().BeLessThanOrEqualTo(PayloadMasker.MaxMaskLength);
}
}