- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
286 lines
8.3 KiB
PHP
286 lines
8.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Framework\Waf\MachineLearning\Detectors;
|
|
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\Core\ValueObjects\Percentage;
|
|
use App\Framework\Core\ValueObjects\Timestamp;
|
|
use App\Framework\DateTime\DateTime;
|
|
use App\Framework\MachineLearning\ValueObjects\AnomalyType;
|
|
use App\Framework\MachineLearning\ValueObjects\FeatureType;
|
|
use App\Framework\Waf\MachineLearning\Detectors\StatisticalAnomalyDetector;
|
|
use App\Framework\MachineLearning\ValueObjects\AnomalyDetection;
|
|
use App\Framework\MachineLearning\ValueObjects\Baseline;
|
|
use App\Framework\MachineLearning\ValueObjects\Feature;
|
|
|
|
// Hilfsfunktion zum Erstellen einer Baseline für Tests
|
|
function createTestBaselineSAD(?FeatureType $type = null): Baseline
|
|
{
|
|
$type = $type ?? FeatureType::STRUCTURAL_PATTERN;
|
|
$now = Timestamp::fromDateTime(DateTime::fromTimestamp(time()));
|
|
|
|
return new Baseline(
|
|
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 Feature(
|
|
type: FeatureType::STRUCTURAL_PATTERN,
|
|
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]->featureType)->toBe(FeatureType::STRUCTURAL_PATTERN);
|
|
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 Feature(
|
|
type: FeatureType::STRUCTURAL_PATTERN,
|
|
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 - braucht mehr Samples für IQR Outlier Detection
|
|
$detector = new StatisticalAnomalyDetector(
|
|
enabled: true,
|
|
confidenceThreshold: 0.0, // Low threshold to allow detection even without zScore in Feature
|
|
zScoreThreshold: 2.0,
|
|
extremeZScoreThreshold: 3.0,
|
|
minSampleSize: 20,
|
|
enableOutlierDetection: true,
|
|
enableTrendAnalysis: false,
|
|
featureHistory: [
|
|
'structural_pattern:test_feature' => [
|
|
10, 12, 9, 11, 10, 13, 8, 11, 10, 12,
|
|
9, 11, 10, 12, 11, 10, 9, 13, 11, 10,
|
|
12, 10, 11, 9, 10 // 25 Samples für robuste IQR
|
|
],
|
|
]
|
|
);
|
|
|
|
$feature = new Feature(
|
|
type: FeatureType::STRUCTURAL_PATTERN,
|
|
name: 'test_feature',
|
|
value: 30.0, // Deutlicher Ausreißer (normal: 8-13)
|
|
unit: 'count'
|
|
// No zScore to avoid triggering Z-score detection
|
|
);
|
|
|
|
// 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->getSupportedFeatureTypes();
|
|
|
|
// Assert
|
|
expect($supportedTypes)->toBeArray();
|
|
expect($supportedTypes)->toContain(FeatureType::FREQUENCY);
|
|
expect($supportedTypes)->toContain(FeatureType::STRUCTURAL_PATTERN);
|
|
expect($supportedTypes)->toContain(FeatureType::STRUCTURAL_PATTERN);
|
|
});
|
|
|
|
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 Feature(
|
|
type: FeatureType::STRUCTURAL_PATTERN,
|
|
name: 'test_feature',
|
|
value: 15.0,
|
|
unit: 'count'
|
|
);
|
|
|
|
$feature2 = new Feature(
|
|
type: FeatureType::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 Feature(
|
|
type: FeatureType::STRUCTURAL_PATTERN,
|
|
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 Feature(
|
|
type: FeatureType::STRUCTURAL_PATTERN,
|
|
name: 'test_feature',
|
|
value: 42.0,
|
|
unit: 'count'
|
|
);
|
|
|
|
$baseline = createTestBaselineSAD();
|
|
|
|
// Act
|
|
$anomalies = $detector->detectAnomalies([$feature], $baseline);
|
|
|
|
// Assert
|
|
expect($anomalies)->toBeEmpty();
|
|
});
|