clock = new SystemClock(); }); describe('DDoSAssessment', function () { it('creates valid assessment with all required data', function () { $assessment = new DDoSAssessment( threatLevel: ThreatLevel::MEDIUM, attackPatterns: [AttackPattern::VOLUMETRIC_ATTACK], clientIp: IpAddress::from('192.168.1.100'), analysisResults: ['traffic_score' => 0.6], confidence: 0.8, recommendedAction: 'rate_limit', processingTime: Duration::fromMilliseconds(50), timestamp: $this->clock->time() ); expect($assessment->threatLevel)->toBe(ThreatLevel::MEDIUM); expect($assessment->attackPatterns)->toContain(AttackPattern::VOLUMETRIC_ATTACK); expect($assessment->clientIp->value)->toBe('192.168.1.100'); expect($assessment->confidence)->toBe(0.8); expect($assessment->recommendedAction)->toBe('rate_limit'); }); it('creates safe assessment for normal traffic', function () { $assessment = DDoSAssessment::createSafe($this->clock); expect($assessment->threatLevel)->toBe(ThreatLevel::LOW); expect($assessment->attackPatterns)->toBeEmpty(); expect($assessment->confidence)->toBeLessThan(0.3); expect($assessment->recommendedAction)->toBe('allow'); }); it('creates critical assessment for severe threats', function () { $assessment = DDoSAssessment::createCritical( clientIp: IpAddress::from('10.0.0.1'), attackPatterns: [AttackPattern::VOLUMETRIC_ATTACK, AttackPattern::BOT_ATTACK], analysisResults: ['threat_score' => 0.95], clock: $this->clock ); expect($assessment->threatLevel)->toBe(ThreatLevel::CRITICAL); expect($assessment->attackPatterns)->toHaveCount(2); expect($assessment->confidence)->toBeGreaterThan(0.9); expect($assessment->recommendedAction)->toBe('block'); }); it('determines if request should be blocked', function () { $lowThreat = new DDoSAssessment( threatLevel: ThreatLevel::LOW, attackPatterns: [], clientIp: IpAddress::from('192.168.1.100'), analysisResults: [], confidence: 0.2, recommendedAction: 'allow', processingTime: Duration::fromMilliseconds(10), timestamp: $this->clock->time() ); $highThreat = new DDoSAssessment( threatLevel: ThreatLevel::HIGH, attackPatterns: [AttackPattern::APPLICATION_LAYER_ATTACK], clientIp: IpAddress::from('192.168.1.100'), analysisResults: [], confidence: 0.8, recommendedAction: 'block', processingTime: Duration::fromMilliseconds(10), timestamp: $this->clock->time() ); expect($lowThreat->shouldBlock())->toBeFalse(); expect($highThreat->shouldBlock())->toBeTrue(); }); it('calculates threat score from analysis results', function () { $assessment = new DDoSAssessment( threatLevel: ThreatLevel::MEDIUM, attackPatterns: [], clientIp: IpAddress::from('192.168.1.100'), analysisResults: [ 'traffic_patterns' => ['threat_score' => 0.6], 'geo_anomalies' => ['threat_score' => 0.4], 'waf_analysis' => ['threat_score' => 0.8], ], confidence: 0.7, recommendedAction: 'rate_limit', processingTime: Duration::fromMilliseconds(25), timestamp: $this->clock->time() ); $threatScore = $assessment->getThreatScore(); expect($threatScore)->toBeBetween(0.0, 1.0); expect($threatScore)->toBeGreaterThan(0.5); // Should be influenced by high scores }); it('provides human readable summary', function () { $assessment = new DDoSAssessment( threatLevel: ThreatLevel::HIGH, attackPatterns: [AttackPattern::VOLUMETRIC_ATTACK, AttackPattern::BOT_ATTACK], clientIp: IpAddress::from('192.168.1.100'), analysisResults: ['threat_score' => 0.8], confidence: 0.9, recommendedAction: 'block', processingTime: Duration::fromMilliseconds(75), timestamp: $this->clock->time() ); $summary = $assessment->getSummary(); expect($summary)->toBeString(); expect($summary)->toContain('HIGH'); expect($summary)->toContain('192.168.1.100'); expect($summary)->toContain('VOLUMETRIC_ATTACK'); expect($summary)->toContain('BOT_ATTACK'); }); it('exports to array for serialization', function () { $assessment = new DDoSAssessment( threatLevel: ThreatLevel::MEDIUM, attackPatterns: [AttackPattern::DISTRIBUTED_ATTACK], clientIp: IpAddress::from('192.168.1.100'), analysisResults: ['geo_score' => 0.6], confidence: 0.75, recommendedAction: 'captcha_challenge', processingTime: Duration::fromMilliseconds(30), timestamp: $this->clock->time() ); $array = $assessment->toArray(); expect($array)->toHaveKeys([ 'threat_level', 'attack_patterns', 'client_ip', 'analysis_results', 'confidence', 'recommended_action', 'processing_time_ms', 'timestamp', ]); expect($array['threat_level'])->toBe('MEDIUM'); expect($array['client_ip'])->toBe('192.168.1.100'); expect($array['confidence'])->toBe(0.75); }); it('creates assessment from array data', function () { $data = [ 'threat_level' => 'HIGH', 'attack_patterns' => ['VOLUMETRIC_ATTACK'], 'client_ip' => '10.0.0.1', 'analysis_results' => ['traffic_score' => 0.8], 'confidence' => 0.85, 'recommended_action' => 'block', 'processing_time_ms' => 40, 'timestamp' => $this->clock->time()->format('c'), ]; $assessment = DDoSAssessment::fromArray($data, $this->clock); expect($assessment->threatLevel)->toBe(ThreatLevel::HIGH); expect($assessment->attackPatterns)->toContain(AttackPattern::VOLUMETRIC_ATTACK); expect($assessment->clientIp->value)->toBe('10.0.0.1'); expect($assessment->confidence)->toBe(0.85); }); it('validates confidence values', function () { expect(fn () => new DDoSAssessment( threatLevel: ThreatLevel::LOW, attackPatterns: [], clientIp: IpAddress::from('192.168.1.100'), analysisResults: [], confidence: 1.5, // Invalid confidence > 1.0 recommendedAction: 'allow', processingTime: Duration::fromMilliseconds(10), timestamp: $this->clock->time() ))->toThrow(InvalidArgumentException::class); expect(fn () => new DDoSAssessment( threatLevel: ThreatLevel::LOW, attackPatterns: [], clientIp: IpAddress::from('192.168.1.100'), analysisResults: [], confidence: -0.1, // Invalid confidence < 0.0 recommendedAction: 'allow', processingTime: Duration::fromMilliseconds(10), timestamp: $this->clock->time() ))->toThrow(InvalidArgumentException::class); }); it('compares threat levels correctly', function () { $lowAssessment = DDoSAssessment::createSafe($this->clock); $highAssessment = DDoSAssessment::createCritical( clientIp: IpAddress::from('10.0.0.1'), attackPatterns: [AttackPattern::VOLUMETRIC_ATTACK], analysisResults: [], clock: $this->clock ); expect($lowAssessment->isLessThreatThan($highAssessment))->toBeTrue(); expect($highAssessment->isMoreThreatThan($lowAssessment))->toBeTrue(); expect($lowAssessment->isMoreThreatThan($highAssessment))->toBeFalse(); }); it('identifies coordinated attacks', function () { $coordinatedAssessment = new DDoSAssessment( threatLevel: ThreatLevel::HIGH, attackPatterns: [ AttackPattern::VOLUMETRIC_ATTACK, AttackPattern::DISTRIBUTED_ATTACK, AttackPattern::COORDINATED_ATTACK, ], clientIp: IpAddress::from('192.168.1.100'), analysisResults: [], confidence: 0.9, recommendedAction: 'block', processingTime: Duration::fromMilliseconds(50), timestamp: $this->clock->time() ); expect($coordinatedAssessment->isCoordinatedAttack())->toBeTrue(); expect($coordinatedAssessment->getAttackComplexity())->toBe('high'); }); it('calculates assessment age', function () { $pastTime = $this->clock->time()->subtract(Duration::fromMinutes(5)); $assessment = new DDoSAssessment( threatLevel: ThreatLevel::MEDIUM, attackPatterns: [], clientIp: IpAddress::from('192.168.1.100'), analysisResults: [], confidence: 0.5, recommendedAction: 'rate_limit', processingTime: Duration::fromMilliseconds(20), timestamp: $pastTime ); $age = $assessment->getAge($this->clock->time()); expect($age->toMinutes())->toBeGreaterThan(4); expect($age->toMinutes())->toBeLessThan(6); }); it('determines if assessment is stale', function () { $oldTime = $this->clock->time()->subtract(Duration::fromMinutes(10)); $assessment = new DDoSAssessment( threatLevel: ThreatLevel::MEDIUM, attackPatterns: [], clientIp: IpAddress::from('192.168.1.100'), analysisResults: [], confidence: 0.5, recommendedAction: 'rate_limit', processingTime: Duration::fromMilliseconds(20), timestamp: $oldTime ); expect($assessment->isStale($this->clock->time(), Duration::fromMinutes(5)))->toBeTrue(); expect($assessment->isStale($this->clock->time(), Duration::fromMinutes(15)))->toBeFalse(); }); });