// =============================================================================
// SecurityTestBase.cs
// Base class for all security tests providing common infrastructure
// =============================================================================
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Moq;
namespace StellaOps.Security.Tests.Infrastructure;
///
/// Base class for OWASP-category security tests.
/// Provides common test infrastructure, mocking utilities, and security assertions.
///
[Trait("Category", "Security")]
public abstract class SecurityTestBase : IDisposable
{
protected readonly Mock LoggerMock;
protected readonly CancellationToken TestCancellation;
private readonly CancellationTokenSource _cts;
protected SecurityTestBase()
{
LoggerMock = new Mock();
_cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
TestCancellation = _cts.Token;
}
///
/// Assert that an action throws a security-related exception.
///
protected static void AssertSecurityException(Action action, string? expectedMessage = null)
where TException : Exception
{
var exception = Assert.Throws(action);
if (expectedMessage != null)
{
exception.Message.Should().Contain(expectedMessage);
}
}
///
/// Assert that an async action throws a security-related exception.
///
protected static async Task AssertSecurityExceptionAsync(Func action, string? expectedMessage = null)
where TException : Exception
{
var exception = await Assert.ThrowsAsync(action);
if (expectedMessage != null)
{
exception.Message.Should().Contain(expectedMessage);
}
}
///
/// Assert that the logger was called with a security warning.
///
protected void AssertSecurityWarningLogged(string expectedMessage)
{
LoggerMock.Verify(
x => x.Log(
LogLevel.Warning,
It.IsAny(),
It.Is((v, t) => v.ToString()!.Contains(expectedMessage)),
It.IsAny(),
It.IsAny>()),
Times.AtLeastOnce);
}
///
/// Assert that no sensitive data is present in the response.
///
protected static void AssertNoSensitiveDataLeakage(string content)
{
var sensitivePatterns = new[]
{
"password",
"secret",
"api_key",
"apikey",
"private_key",
"token",
"bearer",
"authorization"
};
foreach (var pattern in sensitivePatterns)
{
// Case-insensitive check for sensitive patterns in unexpected places
content.ToLowerInvariant().Should().NotContain(pattern,
$"Response should not contain sensitive data pattern: {pattern}");
}
}
///
/// Generate a random tenant ID for isolation.
///
protected static Guid GenerateTestTenantId() => Guid.NewGuid();
///
/// Generate a random user ID for isolation.
///
protected static Guid GenerateTestUserId() => Guid.NewGuid();
public virtual void Dispose()
{
_cts.Cancel();
_cts.Dispose();
GC.SuppressFinalize(this);
}
}
///
/// Trait for categorizing tests by OWASP category.
///
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class OwaspCategoryAttribute : Attribute
{
public string Category { get; }
public string Description { get; }
public OwaspCategoryAttribute(string category, string description)
{
Category = category;
Description = description;
}
}