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'); }); });