Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
338
tests/Framework/DDoS/Response/AdaptiveResponseSystemTest.php
Normal file
338
tests/Framework/DDoS/Response/AdaptiveResponseSystemTest.php
Normal file
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\DateTime\SystemClock;
|
||||
use App\Framework\DDoS\DDoSConfig;
|
||||
use App\Framework\DDoS\Response\AdaptiveResponseSystem;
|
||||
use App\Framework\DDoS\ValueObjects\AttackPattern;
|
||||
use App\Framework\DDoS\ValueObjects\ThreatLevel;
|
||||
|
||||
require_once __DIR__ . '/../Helpers/TestHelpers.php';
|
||||
|
||||
beforeEach(function () {
|
||||
$this->clock = new SystemClock();
|
||||
$this->config = new DDoSConfig();
|
||||
$this->responseSystem = new AdaptiveResponseSystem($this->clock, $this->config);
|
||||
});
|
||||
|
||||
describe('AdaptiveResponseSystem', function () {
|
||||
|
||||
it('allows low threat requests through', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::LOW, 0.1);
|
||||
$request = createTestRequest('192.168.1.100', 'GET', '/api/users');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('allow');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
expect($response->httpStatusCode)->toBe(200);
|
||||
});
|
||||
|
||||
it('applies rate limiting for medium threats', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.5, [AttackPattern::VOLUMETRIC_ATTACK]);
|
||||
$request = createTestRequest('192.168.1.200', 'GET', '/api/data');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('rate_limit');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
expect($response->rateLimitHeaders)->toHaveKeys(['X-RateLimit-Limit', 'X-RateLimit-Remaining']);
|
||||
expect($response->httpStatusCode)->toBe(429);
|
||||
});
|
||||
|
||||
it('blocks high threat requests', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::HIGH, 0.8, [AttackPattern::BOT_ATTACK]);
|
||||
$request = createTestRequest('192.168.1.300', 'POST', '/api/sensitive');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('block');
|
||||
expect($response->shouldBlock)->toBeTrue();
|
||||
expect($response->httpStatusCode)->toBe(403);
|
||||
expect($response->blockDuration)->toBeInstanceOf(Duration::class);
|
||||
});
|
||||
|
||||
it('immediately blocks critical threats', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::CRITICAL, 0.95, [
|
||||
AttackPattern::VOLUMETRIC_ATTACK,
|
||||
AttackPattern::APPLICATION_LAYER_ATTACK,
|
||||
]);
|
||||
$request = createTestRequest('192.168.1.400', 'POST', '/admin/delete');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('block');
|
||||
expect($response->shouldBlock)->toBeTrue();
|
||||
expect($response->httpStatusCode)->toBe(403);
|
||||
expect($response->blockDuration->toMinutes())->toBeGreaterThan(60); // Long block
|
||||
});
|
||||
|
||||
it('issues captcha challenges for suspicious requests', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [AttackPattern::BOT_ATTACK]);
|
||||
$request = createTestRequest('192.168.1.500', 'GET', '/api/search');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('captcha_challenge');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
expect($response->challengeData)->toHaveKey('captcha_token');
|
||||
expect($response->httpStatusCode)->toBe(202);
|
||||
});
|
||||
|
||||
it('requires proof of work for sustained attacks', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::HIGH, 0.75, [AttackPattern::COORDINATED_ATTACK]);
|
||||
$request = createTestRequest('192.168.1.600', 'GET', '/api/expensive-operation');
|
||||
|
||||
// Enable proof of work in config
|
||||
$config = new DDoSConfig(enableProofOfWork: true);
|
||||
$responseSystem = new AdaptiveResponseSystem($this->clock, $config);
|
||||
|
||||
$response = $responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('proof_of_work');
|
||||
expect($response->challengeData)->toHaveKeys(['difficulty', 'challenge', 'algorithm']);
|
||||
expect($response->httpStatusCode)->toBe(202);
|
||||
});
|
||||
|
||||
it('adapts response based on attack patterns', function () {
|
||||
// Volumetric attack should trigger rate limiting
|
||||
$volumetricAssessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [AttackPattern::VOLUMETRIC_ATTACK]);
|
||||
$request1 = createTestRequest('192.168.1.700', 'GET', '/api/data');
|
||||
|
||||
$response1 = $this->responseSystem->executeResponse($volumetricAssessment, $request1);
|
||||
expect($response1->action)->toBe('rate_limit');
|
||||
|
||||
// Application layer attack should trigger blocking
|
||||
$appLayerAssessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [AttackPattern::APPLICATION_LAYER_ATTACK]);
|
||||
$request2 = createTestRequest('192.168.1.701', 'POST', '/api/upload');
|
||||
|
||||
$response2 = $this->responseSystem->executeResponse($appLayerAssessment, $request2);
|
||||
expect($response2->action)->toBe('block');
|
||||
});
|
||||
|
||||
it('escalates responses for repeated offenses', function () {
|
||||
$clientIp = '192.168.1.800';
|
||||
|
||||
// First offense - rate limit
|
||||
$assessment1 = createThreatAssessment(ThreatLevel::MEDIUM, 0.5);
|
||||
$request1 = createTestRequest($clientIp, 'GET', '/api/data1');
|
||||
$response1 = $this->responseSystem->executeResponse($assessment1, $request1);
|
||||
expect($response1->action)->toBe('rate_limit');
|
||||
|
||||
// Record the offense
|
||||
$this->responseSystem->recordOffense($clientIp, $assessment1);
|
||||
|
||||
// Second offense - should escalate to block
|
||||
$assessment2 = createThreatAssessment(ThreatLevel::MEDIUM, 0.5);
|
||||
$request2 = createTestRequest($clientIp, 'GET', '/api/data2');
|
||||
$response2 = $this->responseSystem->executeResponse($assessment2, $request2);
|
||||
expect($response2->action)->toBe('block');
|
||||
});
|
||||
|
||||
it('applies geographic blocking when enabled', function () {
|
||||
$config = new DDoSConfig(
|
||||
enableGeographicBlocking: true,
|
||||
blockedCountries: ['CN', 'RU']
|
||||
);
|
||||
$responseSystem = new AdaptiveResponseSystem($this->clock, $config);
|
||||
|
||||
$assessment = createThreatAssessment(ThreatLevel::LOW, 0.2);
|
||||
$request = createTestRequest('203.0.113.195', 'GET', '/api/test'); // Assume this IP is from CN
|
||||
|
||||
// Mock geographic detection
|
||||
$responseSystem->setGeographicInfo('203.0.113.195', 'CN');
|
||||
|
||||
$response = $responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('block');
|
||||
expect($response->blockReason)->toContain('geographic');
|
||||
});
|
||||
|
||||
it('allows trusted IPs through regardless of threat level', function () {
|
||||
$config = new DDoSConfig(trustedIps: ['192.168.1.999']);
|
||||
$responseSystem = new AdaptiveResponseSystem($this->clock, $config);
|
||||
|
||||
$assessment = createThreatAssessment(ThreatLevel::CRITICAL, 0.95);
|
||||
$request = createTestRequest('192.168.1.999', 'GET', '/admin/critical');
|
||||
|
||||
$response = $responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('allow');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
});
|
||||
|
||||
it('bypasses protection for exempt paths', function () {
|
||||
$config = new DDoSConfig(exemptPaths: ['/health', '/monitoring']);
|
||||
$responseSystem = new AdaptiveResponseSystem($this->clock, $config);
|
||||
|
||||
$assessment = createThreatAssessment(ThreatLevel::HIGH, 0.8);
|
||||
$request = createTestRequest('192.168.1.100', 'GET', '/health/check');
|
||||
|
||||
$response = $responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('allow');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
});
|
||||
|
||||
it('provides detailed response metrics', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6);
|
||||
$request = createTestRequest('192.168.1.100', 'GET', '/api/test');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->metrics)->toHaveKeys([
|
||||
'processing_time_ms',
|
||||
'decision_confidence',
|
||||
'escalation_level',
|
||||
'historical_offenses',
|
||||
]);
|
||||
|
||||
expect($response->metrics['processing_time_ms'])->toBeLessThan(100);
|
||||
expect($response->metrics['decision_confidence'])->toBeBetween(0.0, 1.0);
|
||||
});
|
||||
|
||||
it('handles circuit breaker integration', function () {
|
||||
$config = new DDoSConfig(enableCircuitBreakerIntegration: true);
|
||||
$responseSystem = new AdaptiveResponseSystem($this->clock, $config);
|
||||
|
||||
// Simulate circuit breaker open state
|
||||
$responseSystem->setCircuitBreakerState('open');
|
||||
|
||||
$assessment = createThreatAssessment(ThreatLevel::LOW, 0.1);
|
||||
$request = createTestRequest('192.168.1.100', 'GET', '/api/test');
|
||||
|
||||
$response = $responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('rate_limit'); // Fallback during circuit breaker open
|
||||
expect($response->responseHeaders)->toHaveKey('X-Circuit-Breaker-State');
|
||||
});
|
||||
|
||||
it('generates adaptive rate limits based on system load', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.5);
|
||||
$request = createTestRequest('192.168.1.100', 'GET', '/api/data');
|
||||
|
||||
// Simulate high system load
|
||||
$this->responseSystem->setSystemLoad(0.9);
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('rate_limit');
|
||||
expect($response->rateLimitHeaders['X-RateLimit-Limit'])->toBeLessThan(60); // Stricter limits under load
|
||||
});
|
||||
|
||||
it('logs security events for blocked requests', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::HIGH, 0.8);
|
||||
$request = createTestRequest('192.168.1.100', 'POST', '/api/sensitive');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->securityEventLogged)->toBeTrue();
|
||||
expect($response->logContext)->toHaveKeys([
|
||||
'client_ip',
|
||||
'threat_level',
|
||||
'attack_patterns',
|
||||
'response_action',
|
||||
]);
|
||||
});
|
||||
|
||||
it('provides response recommendations', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [AttackPattern::VOLUMETRIC_ATTACK]);
|
||||
$request = createTestRequest('192.168.1.100', 'GET', '/api/data');
|
||||
|
||||
$recommendations = $this->responseSystem->getResponseRecommendations($assessment, $request);
|
||||
|
||||
expect($recommendations)->toBeArray();
|
||||
expect($recommendations)->toContain('implement_rate_limiting');
|
||||
expect($recommendations)->toContain('monitor_traffic_patterns');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Response Strategy Selection', function () {
|
||||
|
||||
it('selects appropriate strategy for different attack types', function () {
|
||||
$strategies = [
|
||||
[AttackPattern::VOLUMETRIC_ATTACK, 'rate_limit'],
|
||||
[AttackPattern::BOT_ATTACK, 'captcha_challenge'],
|
||||
[AttackPattern::APPLICATION_LAYER_ATTACK, 'block'],
|
||||
[AttackPattern::DISTRIBUTED_ATTACK, 'rate_limit'],
|
||||
[AttackPattern::COORDINATED_ATTACK, 'block'],
|
||||
];
|
||||
|
||||
foreach ($strategies as [$pattern, $expectedAction]) {
|
||||
$assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6, [$pattern]);
|
||||
$request = createTestRequest('192.168.1.100', 'GET', '/api/test');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
expect($response->action)->toBe($expectedAction);
|
||||
}
|
||||
});
|
||||
|
||||
it('combines multiple strategies for complex attacks', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::HIGH, 0.8, [
|
||||
AttackPattern::VOLUMETRIC_ATTACK,
|
||||
AttackPattern::BOT_ATTACK,
|
||||
AttackPattern::APPLICATION_LAYER_ATTACK,
|
||||
]);
|
||||
$request = createTestRequest('192.168.1.100', 'POST', '/api/critical');
|
||||
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
|
||||
expect($response->action)->toBe('block'); // Most restrictive action
|
||||
expect($response->additionalMeasures)->toContain('enhanced_logging');
|
||||
expect($response->additionalMeasures)->toContain('alert_security_team');
|
||||
});
|
||||
|
||||
it('adjusts strategy based on request context', function () {
|
||||
// Same threat level but different paths should get different responses
|
||||
$assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.6);
|
||||
|
||||
$publicRequest = createTestRequest('192.168.1.100', 'GET', '/api/public-data');
|
||||
$adminRequest = createTestRequest('192.168.1.100', 'GET', '/admin/users');
|
||||
|
||||
$publicResponse = $this->responseSystem->executeResponse($assessment, $publicRequest);
|
||||
$adminResponse = $this->responseSystem->executeResponse($assessment, $adminRequest);
|
||||
|
||||
// Admin endpoint should be more strictly protected
|
||||
expect($adminResponse->action)->toBeIn(['block', 'captcha_challenge']);
|
||||
expect($publicResponse->action)->toBeIn(['allow', 'rate_limit']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Performance', function () {
|
||||
|
||||
it('completes response execution within time limit', function () {
|
||||
$assessment = createThreatAssessment(ThreatLevel::MEDIUM, 0.5);
|
||||
$request = createTestRequest('192.168.1.100', 'GET', '/api/test');
|
||||
|
||||
$start = microtime(true);
|
||||
$response = $this->responseSystem->executeResponse($assessment, $request);
|
||||
$duration = microtime(true) - $start;
|
||||
|
||||
expect($duration)->toBeLessThan(0.05); // Should complete within 50ms
|
||||
expect($response)->not()->toBeNull();
|
||||
});
|
||||
|
||||
it('handles high request volume efficiently', function () {
|
||||
$start = microtime(true);
|
||||
|
||||
// Process 100 responses
|
||||
for ($i = 1; $i <= 100; $i++) {
|
||||
$assessment = createThreatAssessment(ThreatLevel::LOW, 0.1);
|
||||
$ip = '192.168.1.' . ($i % 254 + 1);
|
||||
$request = createTestRequest($ip, 'GET', "/api/test{$i}");
|
||||
$this->responseSystem->executeResponse($assessment, $request);
|
||||
}
|
||||
|
||||
$duration = microtime(true) - $start;
|
||||
$avgPerResponse = $duration / 100;
|
||||
|
||||
expect($avgPerResponse)->toBeLessThan(0.01); // Average <10ms per response
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Helper functions are now in ../Helpers/TestHelpers.php
|
||||
272
tests/Framework/DDoS/Response/ValueObjects/DDoSResponseTest.php
Normal file
272
tests/Framework/DDoS/Response/ValueObjects/DDoSResponseTest.php
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\DateTime\SystemClock;
|
||||
use App\Framework\DDoS\Response\ValueObjects\DDoSResponse;
|
||||
|
||||
require_once __DIR__ . '/../../Helpers/TestHelpers.php';
|
||||
|
||||
beforeEach(function () {
|
||||
$this->clock = new SystemClock();
|
||||
});
|
||||
|
||||
describe('DDoSResponse', function () {
|
||||
|
||||
it('creates allow response correctly', function () {
|
||||
$response = DDoSResponse::allow();
|
||||
|
||||
expect($response->action)->toBe('allow');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
expect($response->httpStatusCode)->toBe(200);
|
||||
expect($response->blockDuration)->toBeNull();
|
||||
});
|
||||
|
||||
it('creates rate limit response with headers', function () {
|
||||
$response = DDoSResponse::rateLimit(
|
||||
limit: 60,
|
||||
remaining: 45,
|
||||
resetTime: $this->clock->time()->add(Duration::fromMinutes(1))
|
||||
);
|
||||
|
||||
expect($response->action)->toBe('rate_limit');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
expect($response->httpStatusCode)->toBe(429);
|
||||
expect($response->rateLimitHeaders)->toHaveKeys([
|
||||
'X-RateLimit-Limit',
|
||||
'X-RateLimit-Remaining',
|
||||
'X-RateLimit-Reset',
|
||||
]);
|
||||
expect($response->rateLimitHeaders['X-RateLimit-Limit'])->toBe('60');
|
||||
expect($response->rateLimitHeaders['X-RateLimit-Remaining'])->toBe('45');
|
||||
});
|
||||
|
||||
it('creates block response with duration', function () {
|
||||
$blockDuration = Duration::fromMinutes(30);
|
||||
$response = DDoSResponse::block(
|
||||
duration: $blockDuration,
|
||||
reason: 'Malicious activity detected'
|
||||
);
|
||||
|
||||
expect($response->action)->toBe('block');
|
||||
expect($response->shouldBlock)->toBeTrue();
|
||||
expect($response->httpStatusCode)->toBe(403);
|
||||
expect($response->blockDuration)->toBe($blockDuration);
|
||||
expect($response->blockReason)->toBe('Malicious activity detected');
|
||||
});
|
||||
|
||||
it('creates captcha challenge response', function () {
|
||||
$challengeData = [
|
||||
'captcha_token' => 'abc123',
|
||||
'challenge_url' => '/captcha/verify',
|
||||
'expires_at' => time() + 300,
|
||||
];
|
||||
|
||||
$response = DDoSResponse::captchaChallenge($challengeData);
|
||||
|
||||
expect($response->action)->toBe('captcha_challenge');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
expect($response->httpStatusCode)->toBe(202);
|
||||
expect($response->challengeData)->toBe($challengeData);
|
||||
expect($response->challengeData['captcha_token'])->toBe('abc123');
|
||||
});
|
||||
|
||||
it('creates proof of work challenge response', function () {
|
||||
$challengeData = [
|
||||
'difficulty' => 4,
|
||||
'challenge' => 'find_hash_with_4_leading_zeros',
|
||||
'algorithm' => 'sha256',
|
||||
'expires_at' => time() + 600,
|
||||
];
|
||||
|
||||
$response = DDoSResponse::proofOfWork($challengeData);
|
||||
|
||||
expect($response->action)->toBe('proof_of_work');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
expect($response->httpStatusCode)->toBe(202);
|
||||
expect($response->challengeData)->toBe($challengeData);
|
||||
expect($response->challengeData['difficulty'])->toBe(4);
|
||||
});
|
||||
|
||||
it('adds custom response headers', function () {
|
||||
$response = DDoSResponse::allow()
|
||||
->withHeader('X-DDoS-Protection', 'Active')
|
||||
->withHeader('X-Request-ID', 'req-123');
|
||||
|
||||
expect($response->responseHeaders)->toHaveKeys([
|
||||
'X-DDoS-Protection',
|
||||
'X-Request-ID',
|
||||
]);
|
||||
expect($response->responseHeaders['X-DDoS-Protection'])->toBe('Active');
|
||||
expect($response->responseHeaders['X-Request-ID'])->toBe('req-123');
|
||||
});
|
||||
|
||||
it('sets security event logging', function () {
|
||||
$logContext = [
|
||||
'client_ip' => '192.168.1.100',
|
||||
'threat_level' => 'HIGH',
|
||||
'attack_patterns' => ['VOLUMETRIC_ATTACK'],
|
||||
];
|
||||
|
||||
$response = DDoSResponse::block(Duration::fromMinutes(15), 'High threat detected')
|
||||
->withSecurityEvent($logContext);
|
||||
|
||||
expect($response->securityEventLogged)->toBeTrue();
|
||||
expect($response->logContext)->toBe($logContext);
|
||||
});
|
||||
|
||||
it('adds response metrics', function () {
|
||||
$metrics = [
|
||||
'processing_time_ms' => 25,
|
||||
'decision_confidence' => 0.8,
|
||||
'escalation_level' => 2,
|
||||
'historical_offenses' => 3,
|
||||
];
|
||||
|
||||
$response = DDoSResponse::rateLimit(60, 30, $this->clock->time())
|
||||
->withMetrics($metrics);
|
||||
|
||||
expect($response->metrics)->toBe($metrics);
|
||||
expect($response->metrics['processing_time_ms'])->toBe(25);
|
||||
expect($response->metrics['decision_confidence'])->toBe(0.8);
|
||||
});
|
||||
|
||||
it('adds additional protective measures', function () {
|
||||
$measures = ['enhanced_logging', 'alert_security_team', 'increase_monitoring'];
|
||||
|
||||
$response = DDoSResponse::block(Duration::fromHours(1), 'Critical threat')
|
||||
->withAdditionalMeasures($measures);
|
||||
|
||||
expect($response->additionalMeasures)->toBe($measures);
|
||||
expect($response->additionalMeasures)->toContain('enhanced_logging');
|
||||
expect($response->additionalMeasures)->toContain('alert_security_team');
|
||||
});
|
||||
|
||||
it('converts to HTTP response array', function () {
|
||||
$response = DDoSResponse::rateLimit(60, 45, $this->clock->time())
|
||||
->withHeader('X-Protection', 'Active')
|
||||
->withMetrics(['processing_time_ms' => 30]);
|
||||
|
||||
$httpResponse = $response->toHttpResponse();
|
||||
|
||||
expect($httpResponse)->toHaveKeys([
|
||||
'status_code',
|
||||
'headers',
|
||||
'body',
|
||||
]);
|
||||
|
||||
expect($httpResponse['status_code'])->toBe(429);
|
||||
expect($httpResponse['headers'])->toHaveKey('X-RateLimit-Limit');
|
||||
expect($httpResponse['headers'])->toHaveKey('X-Protection');
|
||||
expect($httpResponse['body'])->toContain('Rate limit exceeded');
|
||||
});
|
||||
|
||||
it('exports to array for serialization', function () {
|
||||
$response = DDoSResponse::block(Duration::fromMinutes(30), 'Threat detected')
|
||||
->withMetrics(['confidence' => 0.9])
|
||||
->withSecurityEvent(['ip' => '192.168.1.100']);
|
||||
|
||||
$array = $response->toArray();
|
||||
|
||||
expect($array)->toHaveKeys([
|
||||
'action',
|
||||
'should_block',
|
||||
'http_status_code',
|
||||
'block_duration_seconds',
|
||||
'block_reason',
|
||||
'response_headers',
|
||||
'metrics',
|
||||
'security_event_logged',
|
||||
'log_context',
|
||||
]);
|
||||
|
||||
expect($array['action'])->toBe('block');
|
||||
expect($array['should_block'])->toBeTrue();
|
||||
expect($array['block_duration_seconds'])->toBe(1800); // 30 minutes
|
||||
});
|
||||
|
||||
it('creates response from array data', function () {
|
||||
$data = [
|
||||
'action' => 'rate_limit',
|
||||
'should_block' => false,
|
||||
'http_status_code' => 429,
|
||||
'rate_limit_headers' => [
|
||||
'X-RateLimit-Limit' => '60',
|
||||
'X-RateLimit-Remaining' => '30',
|
||||
],
|
||||
'response_headers' => ['X-Protection' => 'Active'],
|
||||
'metrics' => ['processing_time_ms' => 40],
|
||||
];
|
||||
|
||||
$response = DDoSResponse::fromArray($data);
|
||||
|
||||
expect($response->action)->toBe('rate_limit');
|
||||
expect($response->shouldBlock)->toBeFalse();
|
||||
expect($response->httpStatusCode)->toBe(429);
|
||||
expect($response->rateLimitHeaders['X-RateLimit-Limit'])->toBe('60');
|
||||
expect($response->responseHeaders['X-Protection'])->toBe('Active');
|
||||
expect($response->metrics['processing_time_ms'])->toBe(40);
|
||||
});
|
||||
|
||||
it('validates response consistency', function () {
|
||||
$blockResponse = DDoSResponse::block(Duration::fromMinutes(15), 'Threat');
|
||||
$allowResponse = DDoSResponse::allow();
|
||||
|
||||
expect($blockResponse->shouldBlock)->toBeTrue();
|
||||
expect($blockResponse->action)->toBe('block');
|
||||
expect($blockResponse->httpStatusCode)->toBe(403);
|
||||
|
||||
expect($allowResponse->shouldBlock)->toBeFalse();
|
||||
expect($allowResponse->action)->toBe('allow');
|
||||
expect($allowResponse->httpStatusCode)->toBe(200);
|
||||
});
|
||||
|
||||
it('handles challenge expiration', function () {
|
||||
$challengeData = [
|
||||
'captcha_token' => 'abc123',
|
||||
'expires_at' => time() - 300, // Expired 5 minutes ago
|
||||
];
|
||||
|
||||
$response = DDoSResponse::captchaChallenge($challengeData);
|
||||
|
||||
expect($response->isChallengeExpired())->toBeTrue();
|
||||
|
||||
$challengeData['expires_at'] = time() + 300; // Expires in 5 minutes
|
||||
$response = DDoSResponse::captchaChallenge($challengeData);
|
||||
|
||||
expect($response->isChallengeExpired())->toBeFalse();
|
||||
});
|
||||
|
||||
it('calculates response severity', function () {
|
||||
$allowResponse = DDoSResponse::allow();
|
||||
$rateLimitResponse = DDoSResponse::rateLimit(60, 30, $this->clock->time());
|
||||
$blockResponse = DDoSResponse::block(Duration::fromHours(1), 'Critical');
|
||||
|
||||
expect($allowResponse->getSeverity())->toBe('low');
|
||||
expect($rateLimitResponse->getSeverity())->toBe('medium');
|
||||
expect($blockResponse->getSeverity())->toBe('high');
|
||||
});
|
||||
|
||||
it('provides response recommendations', function () {
|
||||
$response = DDoSResponse::block(Duration::fromMinutes(30), 'Volumetric attack')
|
||||
->withAdditionalMeasures(['monitor_traffic', 'alert_ops']);
|
||||
|
||||
$recommendations = $response->getRecommendations();
|
||||
|
||||
expect($recommendations)->toBeArray();
|
||||
expect($recommendations)->toContain('Review traffic patterns');
|
||||
expect($recommendations)->toContain('Consider IP reputation check');
|
||||
});
|
||||
|
||||
it('tracks response effectiveness', function () {
|
||||
$response = DDoSResponse::rateLimit(60, 30, $this->clock->time());
|
||||
|
||||
// Simulate tracking effectiveness
|
||||
$response->recordEffectiveness(0.8, 'Successfully reduced request rate');
|
||||
|
||||
expect($response->getEffectivenessScore())->toBe(0.8);
|
||||
expect($response->getEffectivenessNote())->toBe('Successfully reduced request rate');
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user