save progress
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user