Files
michaelschiemer/tests/Framework/Waf/MachineLearning/Detectors/StatisticalAnomalyDetectorTest.php
Michael Schiemer 55a330b223 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
2025-08-11 20:13:26 +02:00

281 lines
8.0 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Framework\Waf\MachineLearning\Detectors;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\DateTime;
use App\Framework\Waf\MachineLearning\AnomalyType;
use App\Framework\Waf\MachineLearning\BehaviorType;
use App\Framework\Waf\MachineLearning\Detectors\StatisticalAnomalyDetector;
use App\Framework\Waf\MachineLearning\ValueObjects\AnomalyDetection;
use App\Framework\Waf\MachineLearning\ValueObjects\BehaviorBaseline;
use App\Framework\Waf\MachineLearning\ValueObjects\BehaviorFeature;
// Hilfsfunktion zum Erstellen einer Baseline für Tests
function createTestBaselineSAD(?BehaviorType $type = null): BehaviorBaseline
{
$type = $type ?? BehaviorType::PATH_PATTERNS;
$now = Timestamp::fromDateTime(DateTime::fromTimestamp(time()));
return new BehaviorBaseline(
type: $type,
identifier: 'test-client',
mean: 10.0,
standardDeviation: 5.0,
median: 10.0,
minimum: 5.0,
maximum: 25.0,
percentiles: [
25 => 7.5,
75 => 15.0,
90 => 18.0,
95 => 20.0,
99 => 22.0,
],
sampleCount: 20,
createdAt: $now,
lastUpdated: $now,
windowSize: Duration::fromMinutes(30),
confidence: 0.8
);
}
test('erkennt Z-Score-Anomalien korrekt', function () {
// Arrange
$detector = new StatisticalAnomalyDetector(
enabled: true,
confidenceThreshold: 0.5,
zScoreThreshold: 2.0,
extremeZScoreThreshold: 3.0,
minSampleSize: 5,
enableOutlierDetection: true,
enableTrendAnalysis: true,
featureHistory: []
);
$feature = new BehaviorFeature(
type: BehaviorType::PATH_PATTERNS,
name: 'test_feature',
value: 42.0,
unit: 'count'
);
$baseline = createTestBaselineSAD();
// Act
$anomalies = $detector->detectAnomalies([$feature], $baseline);
// Assert
expect($anomalies)->toHaveCount(1);
expect($anomalies[0])->toBeInstanceOf(AnomalyDetection::class);
expect($anomalies[0]->type)->toBe(AnomalyType::STATISTICAL_ANOMALY);
expect($anomalies[0]->behaviorType)->toBe(BehaviorType::PATH_PATTERNS);
expect($anomalies[0]->confidence->getValue())->toBeGreaterThan(50.0);
// Z-Score sollte (42 - 10) / 5 = 6.4 sein, was deutlich über dem Schwellenwert liegt
expect($anomalies[0]->anomalyScore)->toBeGreaterThan(0.7);
});
test('ignoriert Werte innerhalb des normalen Bereichs', function () {
// Arrange
$detector = new StatisticalAnomalyDetector(
enabled: true,
confidenceThreshold: 0.5,
zScoreThreshold: 2.0,
extremeZScoreThreshold: 3.0,
minSampleSize: 5,
enableOutlierDetection: true,
enableTrendAnalysis: true,
featureHistory: []
);
$feature = new BehaviorFeature(
type: BehaviorType::PATH_PATTERNS,
name: 'test_feature',
value: 12.0, // Nahe am Mittelwert
unit: 'count'
);
$baseline = createTestBaselineSAD();
// Act
$anomalies = $detector->detectAnomalies([$feature], $baseline);
// Assert
expect($anomalies)->toBeEmpty();
});
test('erkennt Ausreißer ohne Baseline', function () {
// Arrange
$detector = new StatisticalAnomalyDetector(
enabled: true,
confidenceThreshold: 0.5,
zScoreThreshold: 2.0,
extremeZScoreThreshold: 3.0,
minSampleSize: 5,
enableOutlierDetection: true,
enableTrendAnalysis: false,
featureHistory: [
BehaviorType::PATH_PATTERNS->value => [
'test_feature' => [10, 12, 9, 11, 10, 13, 8, 11, 10, 12],
],
]
);
$feature = new BehaviorFeature(
type: BehaviorType::PATH_PATTERNS,
name: 'test_feature',
value: 30.0, // Deutlicher Ausreißer
unit: 'count'
);
// Act
$anomalies = $detector->detectAnomalies([$feature], null);
// Assert
expect($anomalies)->not->toBeEmpty();
expect($anomalies[0]->type)->toBe(AnomalyType::OUTLIER_DETECTION);
});
test('unterstützt verschiedene Verhaltenstypen', function () {
// Arrange
$detector = new StatisticalAnomalyDetector(
enabled: true,
confidenceThreshold: 0.5,
zScoreThreshold: 2.0,
extremeZScoreThreshold: 3.0,
minSampleSize: 5,
enableOutlierDetection: true,
enableTrendAnalysis: true,
featureHistory: []
);
// Act
$supportedTypes = $detector->getSupportedBehaviorTypes();
// Assert
expect($supportedTypes)->toBeArray();
expect($supportedTypes)->toContain(BehaviorType::REQUEST_FREQUENCY);
expect($supportedTypes)->toContain(BehaviorType::PATH_PATTERNS);
expect($supportedTypes)->toContain(BehaviorType::PARAMETER_PATTERNS);
});
test('aktualisiert Modell mit neuen Daten', function () {
// Arrange
$detector = new StatisticalAnomalyDetector(
enabled: true,
confidenceThreshold: 0.5,
zScoreThreshold: 2.0,
extremeZScoreThreshold: 3.0,
minSampleSize: 5,
enableOutlierDetection: true,
enableTrendAnalysis: true,
featureHistory: []
);
$feature1 = new BehaviorFeature(
type: BehaviorType::PATH_PATTERNS,
name: 'test_feature',
value: 15.0,
unit: 'count'
);
$feature2 = new BehaviorFeature(
type: BehaviorType::REQUEST_FREQUENCY,
name: 'request_rate',
value: 5.0,
unit: 'requests/second'
);
// Act - Keine Assertion möglich, da featureHistory private ist
// Wir testen nur, dass keine Exception geworfen wird
$detector->updateModel([$feature1, $feature2]);
// Assert
expect(true)->toBeTrue(); // Dummy assertion
});
test('gibt Konfiguration korrekt zurück', function () {
// Arrange
$detector = new StatisticalAnomalyDetector(
enabled: true,
confidenceThreshold: 0.75,
zScoreThreshold: 2.5,
extremeZScoreThreshold: 4.0,
minSampleSize: 10,
enableOutlierDetection: true,
enableTrendAnalysis: false,
featureHistory: []
);
// Act
$config = $detector->getConfiguration();
// Assert
expect($config)->toBeArray();
expect($config['enabled'])->toBeTrue();
expect($config['confidence_threshold'])->toBe(0.75);
expect($config['z_score_threshold'])->toBe(2.5);
expect($config['extreme_z_score_threshold'])->toBe(4.0);
expect($config['min_sample_size'])->toBe(10);
expect($config['enable_outlier_detection'])->toBeTrue();
expect($config['enable_trend_analysis'])->toBeFalse();
});
test('kann Analyse durchführen wenn aktiviert', function () {
// Arrange
$detector = new StatisticalAnomalyDetector(
enabled: true,
confidenceThreshold: 0.5,
zScoreThreshold: 2.0,
extremeZScoreThreshold: 3.0,
minSampleSize: 5,
enableOutlierDetection: true,
enableTrendAnalysis: true,
featureHistory: []
);
$feature = new BehaviorFeature(
type: BehaviorType::PATH_PATTERNS,
name: 'test_feature',
value: 42.0,
unit: 'count'
);
// Act & Assert
expect($detector->isEnabled())->toBeTrue();
expect($detector->canAnalyze([$feature]))->toBeTrue();
});
test('gibt leere Ergebnisse zurück wenn deaktiviert', function () {
// Arrange
$detector = new StatisticalAnomalyDetector(
enabled: false,
confidenceThreshold: 0.5,
zScoreThreshold: 2.0,
extremeZScoreThreshold: 3.0,
minSampleSize: 5,
enableOutlierDetection: true,
enableTrendAnalysis: true,
featureHistory: []
);
$feature = new BehaviorFeature(
type: BehaviorType::PATH_PATTERNS,
name: 'test_feature',
value: 42.0,
unit: 'count'
);
$baseline = createTestBaselineSAD();
// Act
$anomalies = $detector->detectAnomalies([$feature], $baseline);
// Assert
expect($anomalies)->toBeEmpty();
});