// ============================================================================= // 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; } }