mockWordPressFunctions(); // Create mocks $this->mockNonceManager = Mockery::mock(NonceManager::class); $this->mockCapabilityChecker = Mockery::mock(CapabilityChecker::class); $this->mockRateLimiter = Mockery::mock(RateLimiter::class); $this->mockInputSanitizer = Mockery::mock(InputSanitizer::class); $this->mockSecurityLogger = Mockery::mock(SecurityLogger::class); // Create validator with mocked dependencies $this->validator = $this->createValidatorWithMocks(); } /** * Test successful validation through all layers * * @return void */ public function testSuccessfulValidationAllLayers(): void { // Arrange - mock all layers to pass $this->mockAllLayersPass(); $request = [ 'action' => 'test_action', 'nonce' => 'valid_nonce', 'data' => 'test_data' ]; // Act $result = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Assert $this->assertTrue($result->isValid(), 'Validation should pass when all layers pass'); $this->assertEmpty($result->getError(), 'Should have no errors'); $this->assertLessThan(10.0, $result->getExecutionTime(), 'Should complete under 10ms'); // Verify all layers were called $this->assertNotNull($result->getLayerResult('nonce')); $this->assertNotNull($result->getLayerResult('capability')); $this->assertNotNull($result->getLayerResult('rate_limit')); $this->assertNotNull($result->getLayerResult('input')); $this->assertNotNull($result->getLayerResult('xss')); } /** * Test nonce validation failure * * @return void */ public function testNonceValidationFailure(): void { // Arrange - nonce fails, others would pass $this->mockNonceManager ->shouldReceive('validateNonce') ->once() ->andReturn(ValidationLayerResult::failure('Invalid nonce')); $this->mockSecurityLogger ->shouldReceive('logSecurityEvent') ->once() ->with('nonce_validation_failed', Mockery::any(), Mockery::any()); $request = [ 'action' => 'test_action', 'nonce' => 'invalid_nonce' ]; // Act $result = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Assert $this->assertFalse($result->isValid(), 'Validation should fail on nonce failure'); $this->assertStringContains('Invalid nonce', $result->getError()); $this->assertFalse($result->isLayerValid('nonce')); } /** * Test capability check failure * * @return void */ public function testCapabilityCheckFailure(): void { // Arrange - nonce passes, capability fails $this->mockNonceManager ->shouldReceive('validateNonce') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockCapabilityChecker ->shouldReceive('checkCapability') ->once() ->andReturn(ValidationLayerResult::failure('Insufficient capabilities')); $this->mockSecurityLogger ->shouldReceive('logSecurityEvent') ->once() ->with('capability_check_failed', Mockery::any(), Mockery::any()); $request = ['action' => 'test_action', 'nonce' => 'valid_nonce']; // Act $result = $this->validator->validateRequest($request, 'test_action', 'admin_capability'); // Assert $this->assertFalse($result->isValid(), 'Validation should fail on capability failure'); $this->assertTrue($result->isLayerValid('nonce')); $this->assertFalse($result->isLayerValid('capability')); } /** * Test rate limit exceeded * * @return void */ public function testRateLimitExceeded(): void { // Arrange - nonce and capability pass, rate limit fails $this->mockNonceManager ->shouldReceive('validateNonce') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockCapabilityChecker ->shouldReceive('checkCapability') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockRateLimiter ->shouldReceive('checkRateLimit') ->once() ->andReturn(ValidationLayerResult::failure('Rate limit exceeded: 61/60 requests in 60s')); $this->mockSecurityLogger ->shouldReceive('logSecurityEvent') ->once() ->with('rate_limit_exceeded', Mockery::any(), Mockery::any()); $request = ['action' => 'test_action', 'nonce' => 'valid_nonce']; // Act $result = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Assert $this->assertFalse($result->isValid(), 'Validation should fail on rate limit'); $this->assertStringContains('Rate limit exceeded', $result->getError()); $this->assertFalse($result->isLayerValid('rate_limit')); } /** * Test XSS attack detection * * @return void */ public function testXSSAttackDetection(): void { // Arrange - setup for XSS test $this->mockNonceManager ->shouldReceive('validateNonce') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockCapabilityChecker ->shouldReceive('checkCapability') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockRateLimiter ->shouldReceive('checkRateLimit') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockInputSanitizer ->shouldReceive('validateAndSanitize') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockSecurityLogger ->shouldReceive('logSecurityEvent') ->once() ->with('xss_protection_triggered', Mockery::any(), Mockery::any()); // XSS payload in request $request = [ 'action' => 'test_action', 'nonce' => 'valid_nonce', 'malicious_script' => '', 'iframe_injection' => '', 'javascript_url' => 'javascript:void(0)' ]; // Act $result = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Assert $this->assertFalse($result->isValid(), 'Should detect and block XSS attempts'); $this->assertStringContains('XSS detected', $result->getError()); $this->assertFalse($result->isLayerValid('xss')); } /** * Test input validation failure * * @return void */ public function testInputValidationFailure(): void { // Arrange $this->mockNonceManager ->shouldReceive('validateNonce') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockCapabilityChecker ->shouldReceive('checkCapability') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockRateLimiter ->shouldReceive('checkRateLimit') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockInputSanitizer ->shouldReceive('validateAndSanitize') ->once() ->andReturn(ValidationLayerResult::failure('Invalid input format')); $this->mockSecurityLogger ->shouldReceive('logSecurityEvent') ->once() ->with('input_validation_failed', Mockery::any(), Mockery::any()); $request = [ 'action' => 'test_action', 'nonce' => 'valid_nonce', 'invalid_email' => 'not-an-email', 'too_long_string' => str_repeat('a', 10000) ]; // Act $result = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Assert $this->assertFalse($result->isValid(), 'Should fail on input validation'); $this->assertStringContains('Invalid input format', $result->getError()); $this->assertFalse($result->isLayerValid('input')); } /** * Test performance monitoring * * @return void */ public function testPerformanceMonitoring(): void { // Arrange - simulate slow validation $this->mockNonceManager ->shouldReceive('validateNonce') ->once() ->andReturnUsing(function() { usleep(12000); // 12ms delay to exceed threshold return ValidationLayerResult::success(); }); $this->mockCapabilityChecker ->shouldReceive('checkCapability') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockRateLimiter ->shouldReceive('checkRateLimit') ->once() ->andReturn(ValidationLayerResult::success()); $this->mockInputSanitizer ->shouldReceive('validateAndSanitize') ->once() ->andReturn(ValidationLayerResult::success(['sanitized_data' => []])); $this->mockSecurityLogger ->shouldReceive('logActionResult') ->once(); $this->mockSecurityLogger ->shouldReceive('logPerformanceAlert') ->once() ->with('test_action', Mockery::type('float')); $request = ['action' => 'test_action', 'nonce' => 'valid_nonce']; // Act $result = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Assert $this->assertTrue($result->isValid(), 'Should still be valid despite slow performance'); $this->assertGreaterThan(10.0, $result->getExecutionTime(), 'Should record slow execution time'); $this->assertFalse($result->isPerformant(), 'Should not be considered performant'); } /** * Test security statistics * * @return void */ public function testSecurityStatistics(): void { // Arrange $this->mockRateLimiter ->shouldReceive('getStats') ->once() ->andReturn(['cache_size' => 5, 'blocked_ips' => 2]); $this->mockSecurityLogger ->shouldReceive('getRecentEvents') ->once() ->with(100) ->andReturn([['event' => 'test_event', 'severity' => 'info']]); $this->mockSecurityLogger ->shouldReceive('getErrorRateStats') ->once() ->andReturn(['total_errors_24h' => 10, 'hourly_stats' => []]); // Act $stats = $this->validator->getSecurityStats(); // Assert $this->assertArrayHasKey('cache_size', $stats); $this->assertArrayHasKey('rate_limit_stats', $stats); $this->assertArrayHasKey('security_events', $stats); $this->assertArrayHasKey('error_rates', $stats); $this->assertIsInt($stats['cache_size']); } /** * Test cache functionality * * @return void */ public function testValidationCaching(): void { // Arrange $this->mockAllLayersPass(); $request = ['action' => 'test_action', 'nonce' => 'valid_nonce']; // Act - first call should validate all layers $result1 = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Act - second identical call should use cache $result2 = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Assert $this->assertTrue($result1->isValid()); $this->assertTrue($result2->isValid()); $this->assertEquals($result1->isValid(), $result2->isValid()); } /** * Test exception handling * * @return void */ public function testExceptionHandling(): void { // Arrange - simulate exception in nonce validation $this->mockNonceManager ->shouldReceive('validateNonce') ->once() ->andThrow(new \Exception('Database connection failed')); $this->mockSecurityLogger ->shouldReceive('logSecurityEvent') ->once() ->with('security_validation_exception', Mockery::any(), Mockery::any(), Mockery::any()); $this->mockSecurityLogger ->shouldReceive('logActionResult') ->once() ->with('test_action', false); $request = ['action' => 'test_action', 'nonce' => 'valid_nonce']; // Act $result = $this->validator->validateRequest($request, 'test_action', 'manage_care_restrictions'); // Assert $this->assertFalse($result->isValid(), 'Should fail gracefully on exceptions'); $this->assertStringContains('Security validation failed', $result->getError()); } /** * Mock all security layers to pass validation * * @return void */ private function mockAllLayersPass(): void { $this->mockNonceManager ->shouldReceive('validateNonce') ->andReturn(ValidationLayerResult::success()); $this->mockCapabilityChecker ->shouldReceive('checkCapability') ->andReturn(ValidationLayerResult::success()); $this->mockRateLimiter ->shouldReceive('checkRateLimit') ->andReturn(ValidationLayerResult::success()); $inputResult = ValidationLayerResult::success(); $inputResult->setSanitizedData(['cleaned_data' => 'test']); $this->mockInputSanitizer ->shouldReceive('validateAndSanitize') ->andReturn($inputResult); $this->mockSecurityLogger ->shouldReceive('logActionResult') ->with(Mockery::any(), true); $this->mockSecurityLogger ->shouldReceive('getRecentErrorRate') ->andReturn(0.1); // Low error rate } /** * Create validator with mocked dependencies * * @return SecurityValidator */ private function createValidatorWithMocks(): SecurityValidator { // Use reflection to inject mocks $validator = new SecurityValidator(); $reflection = new \ReflectionClass($validator); $nonceManagerProp = $reflection->getProperty('nonceManager'); $nonceManagerProp->setAccessible(true); $nonceManagerProp->setValue($validator, $this->mockNonceManager); $capabilityCheckerProp = $reflection->getProperty('capabilityChecker'); $capabilityCheckerProp->setAccessible(true); $capabilityCheckerProp->setValue($validator, $this->mockCapabilityChecker); $rateLimiterProp = $reflection->getProperty('rateLimiter'); $rateLimiterProp->setAccessible(true); $rateLimiterProp->setValue($validator, $this->mockRateLimiter); $inputSanitizerProp = $reflection->getProperty('inputSanitizer'); $inputSanitizerProp->setAccessible(true); $inputSanitizerProp->setValue($validator, $this->mockInputSanitizer); $securityLoggerProp = $reflection->getProperty('securityLogger'); $securityLoggerProp->setAccessible(true); $securityLoggerProp->setValue($validator, $this->mockSecurityLogger); return $validator; } /** * Mock WordPress functions * * @return void */ private function mockWordPressFunctions(): void { if (!function_exists('get_current_user_id')) { function get_current_user_id() { return 1; } } if (!function_exists('current_time')) { function current_time($type = 'mysql', $gmt = false) { return date('Y-m-d H:i:s'); } } } /** * Clean up after tests * * @return void */ protected function tearDown(): void { Mockery::close(); parent::tearDown(); } }