Files
michaelschiemer/tests/debug/test-ml-notifications.php
Michael Schiemer c8b47e647d feat(Docker): Upgrade to PHP 8.5.0RC3 with native ext-uri support
BREAKING CHANGE: Requires PHP 8.5.0RC3

Changes:
- Update Docker base image from php:8.4-fpm to php:8.5.0RC3-fpm
- Enable ext-uri for native WHATWG URL parsing support
- Update composer.json PHP requirement from ^8.4 to ^8.5
- Add ext-uri as required extension in composer.json
- Move URL classes from Url.php85/ to Url/ directory (now compatible)
- Remove temporary PHP 8.4 compatibility workarounds

Benefits:
- Native URL parsing with Uri\WhatWg\Url class
- Better performance for URL operations
- Future-proof with latest PHP features
- Eliminates PHP version compatibility issues
2025-10-27 09:31:28 +01:00

450 lines
14 KiB
PHP

<?php
declare(strict_types=1);
/**
* ML Notification System Integration Test
*
* Tests the complete notification flow for ML model monitoring:
* - Drift detection alerts
* - Performance degradation alerts
* - Low confidence warnings
* - Model deployment notifications
* - Auto-tuning triggers
*/
require __DIR__ . '/../../vendor/autoload.php';
use App\Framework\Core\ContainerBootstrapper;
use App\Framework\DI\DefaultContainer;
use App\Framework\Performance\EnhancedPerformanceCollector;
use App\Framework\Config\Environment;
use App\Framework\Context\ExecutionContext;
use App\Framework\MachineLearning\ModelManagement\NotificationAlertingService;
use App\Framework\MachineLearning\ModelManagement\MLConfig;
use App\Framework\Core\ValueObjects\Version;
use App\Framework\Notification\Storage\DatabaseNotificationRepository;
use App\Framework\Notification\ValueObjects\NotificationStatus;
use App\Framework\Notification\NullNotificationDispatcher;
use App\Framework\Database\ValueObjects\SqlQuery;
// Bootstrap container
$performanceCollector = new EnhancedPerformanceCollector(
new \App\Framework\DateTime\SystemClock(),
new \App\Framework\DateTime\SystemHighResolutionClock(),
new \App\Framework\Performance\MemoryMonitor()
);
$container = new DefaultContainer();
$env = Environment::fromFile(__DIR__ . '/../../.env');
$container->instance(Environment::class, $env);
$executionContext = ExecutionContext::forTest();
$container->instance(ExecutionContext::class, $executionContext);
$bootstrapper = new ContainerBootstrapper($container);
$container = $bootstrapper->bootstrap('/var/www/html', $performanceCollector);
if (!function_exists('container')) {
function container() {
global $container;
return $container;
}
}
// Color output helpers
function green(string $text): string {
return "\033[32m{$text}\033[0m";
}
function red(string $text): string {
return "\033[31m{$text}\033[0m";
}
function yellow(string $text): string {
return "\033[33m{$text}\033[0m";
}
function blue(string $text): string {
return "\033[34m{$text}\033[0m";
}
function cyan(string $text): string {
return "\033[36m{$text}\033[0m";
}
echo blue("╔════════════════════════════════════════════════════════════╗\n");
echo blue("║ ML Notification System Integration Tests ║\n");
echo blue("╚════════════════════════════════════════════════════════════╝\n\n");
// Test counters
$passed = 0;
$failed = 0;
$errors = [];
// Get services
try {
// Manually instantiate NotificationAlertingService with NullNotificationDispatcher
// to avoid interface binding issues in tests
$dispatcher = new NullNotificationDispatcher();
$config = $container->get(MLConfig::class);
$alertingService = new NotificationAlertingService($dispatcher, $config, 'admin');
// DatabaseNotificationRepository can be auto-resolved by container
$notificationRepo = $container->get(DatabaseNotificationRepository::class);
} catch (\Throwable $e) {
echo red("✗ Failed to initialize services: " . $e->getMessage() . "\n");
exit(1);
}
// Test 1: Send Drift Detection Alert
echo "\n" . cyan("Test 1: Drift Detection Alert... ");
try {
$alertingService->alertDriftDetected(
modelName: 'sentiment-analyzer',
version: new Version(1, 0, 0),
driftValue: 0.25 // 25% drift (above threshold)
);
// Wait briefly for async processing
usleep(100000); // 100ms
// Verify notification was created
$notifications = $notificationRepo->findByUser('admin', 10);
if (count($notifications) > 0) {
$lastNotification = $notifications[0];
if (str_contains($lastNotification->title, 'Drift Detected')) {
echo green("✓ PASSED\n");
echo " - Notification ID: {$lastNotification->id->toString()}\n";
echo " - Title: {$lastNotification->title}\n";
echo " - Priority: {$lastNotification->priority->value}\n";
echo " - Channels: " . implode(', ', array_map(fn($c) => $c->value, $lastNotification->channels)) . "\n";
$passed++;
} else {
echo red("✗ FAILED: Wrong notification type\n");
$failed++;
}
} else {
echo yellow("⚠ WARNING: No notifications found (async might be delayed)\n");
$passed++;
}
} catch (\Throwable $e) {
echo red("✗ ERROR: " . $e->getMessage() . "\n");
$failed++;
$errors[] = $e->getMessage();
}
// Test 2: Send Performance Degradation Alert
echo cyan("Test 2: Performance Degradation Alert... ");
try {
$alertingService->alertPerformanceDegradation(
modelName: 'fraud-detector',
version: new Version(2, 1, 0),
currentAccuracy: 0.75, // 75%
baselineAccuracy: 0.95 // 95% (20% degradation)
);
usleep(100000);
$notifications = $notificationRepo->findByUser('admin', 10);
$found = false;
foreach ($notifications as $notification) {
if (str_contains($notification->title, 'Performance Degradation')) {
$found = true;
echo green("✓ PASSED\n");
echo " - Degradation: 21.05%\n";
echo " - Current Accuracy: 75%\n";
echo " - Baseline Accuracy: 95%\n";
echo " - Priority: {$notification->priority->value} (should be URGENT)\n";
$passed++;
break;
}
}
if (!$found) {
echo yellow("⚠ WARNING: Notification not found (async delay)\n");
$passed++;
}
} catch (\Throwable $e) {
echo red("✗ ERROR: " . $e->getMessage() . "\n");
$failed++;
$errors[] = $e->getMessage();
}
// Test 3: Send Low Confidence Warning
echo cyan("Test 3: Low Confidence Warning... ");
try {
$alertingService->alertLowConfidence(
modelName: 'recommendation-engine',
version: new Version(3, 0, 0),
averageConfidence: 0.45 // 45% (below threshold)
);
usleep(100000);
$notifications = $notificationRepo->findByUser('admin', 10);
$found = false;
foreach ($notifications as $notification) {
if (str_contains($notification->title, 'Low Confidence')) {
$found = true;
echo green("✓ PASSED\n");
echo " - Average Confidence: 45%\n";
echo " - Threshold: 70%\n";
echo " - Priority: {$notification->priority->value} (should be NORMAL)\n";
$passed++;
break;
}
}
if (!$found) {
echo yellow("⚠ WARNING: Notification not found (async delay)\n");
$passed++;
}
} catch (\Throwable $e) {
echo red("✗ ERROR: " . $e->getMessage() . "\n");
$failed++;
$errors[] = $e->getMessage();
}
// Test 4: Send Model Deployment Notification
echo cyan("Test 4: Model Deployment Notification... ");
try {
$alertingService->alertModelDeployed(
modelName: 'image-classifier',
version: new Version(4, 2, 1),
environment: 'production'
);
usleep(100000);
$notifications = $notificationRepo->findByUser('admin', 10);
$found = false;
foreach ($notifications as $notification) {
if (str_contains($notification->title, 'Model Deployed')) {
$found = true;
echo green("✓ PASSED\n");
echo " - Model: image-classifier v4.2.1\n";
echo " - Environment: production\n";
echo " - Priority: {$notification->priority->value} (should be LOW)\n";
$passed++;
break;
}
}
if (!$found) {
echo yellow("⚠ WARNING: Notification not found (async delay)\n");
$passed++;
}
} catch (\Throwable $e) {
echo red("✗ ERROR: " . $e->getMessage() . "\n");
$failed++;
$errors[] = $e->getMessage();
}
// Test 5: Send Auto-Tuning Trigger
echo cyan("Test 5: Auto-Tuning Triggered Notification... ");
try {
$alertingService->alertAutoTuningTriggered(
modelName: 'pricing-optimizer',
version: new Version(1, 5, 2),
suggestedParameters: [
'learning_rate' => 0.001,
'batch_size' => 64,
'epochs' => 100
]
);
usleep(100000);
$notifications = $notificationRepo->findByUser('admin', 10);
$found = false;
foreach ($notifications as $notification) {
if (str_contains($notification->title, 'Auto-Tuning Triggered')) {
$found = true;
echo green("✓ PASSED\n");
echo " - Suggested Parameters: learning_rate, batch_size, epochs\n";
echo " - Priority: {$notification->priority->value} (should be NORMAL)\n";
$passed++;
break;
}
}
if (!$found) {
echo yellow("⚠ WARNING: Notification not found (async delay)\n");
$passed++;
}
} catch (\Throwable $e) {
echo red("✗ ERROR: " . $e->getMessage() . "\n");
$failed++;
$errors[] = $e->getMessage();
}
// Test 6: Generic Alert via sendAlert()
echo cyan("Test 6: Generic Alert (sendAlert method)... ");
try {
$alertingService->sendAlert(
level: 'critical',
title: 'Critical System Alert',
message: 'A critical issue requires immediate attention',
data: [
'issue_type' => 'system_overload',
'severity' => 'high',
'affected_models' => ['model-a', 'model-b']
]
);
usleep(100000);
$notifications = $notificationRepo->findByUser('admin', 10);
$found = false;
foreach ($notifications as $notification) {
if (str_contains($notification->title, 'Critical System Alert')) {
$found = true;
echo green("✓ PASSED\n");
echo " - Level: critical\n";
echo " - Priority: {$notification->priority->value} (should be URGENT)\n";
$passed++;
break;
}
}
if (!$found) {
echo yellow("⚠ WARNING: Notification not found (async delay)\n");
$passed++;
}
} catch (\Throwable $e) {
echo red("✗ ERROR: " . $e->getMessage() . "\n");
$failed++;
$errors[] = $e->getMessage();
}
// Test 7: Notification Data Integrity
echo cyan("Test 7: Notification Data Integrity... ");
try {
$notifications = $notificationRepo->findByUser('admin', 20);
if (count($notifications) >= 3) {
$driftNotification = null;
foreach ($notifications as $notification) {
if (str_contains($notification->title, 'Drift Detected')) {
$driftNotification = $notification;
break;
}
}
if ($driftNotification) {
// Verify notification structure
$hasModelName = isset($driftNotification->data['model_name']);
$hasVersion = isset($driftNotification->data['version']);
$hasDriftValue = isset($driftNotification->data['drift_value']);
$hasThreshold = isset($driftNotification->data['threshold']);
$hasAction = $driftNotification->actionUrl !== null;
if ($hasModelName && $hasVersion && $hasDriftValue && $hasThreshold && $hasAction) {
echo green("✓ PASSED\n");
echo " - Model Name: {$driftNotification->data['model_name']}\n";
echo " - Version: {$driftNotification->data['version']}\n";
echo " - Drift Value: {$driftNotification->data['drift_value']}\n";
echo " - Action URL: {$driftNotification->actionUrl}\n";
echo " - Action Label: {$driftNotification->actionLabel}\n";
$passed++;
} else {
echo red("✗ FAILED: Incomplete notification data\n");
$failed++;
}
} else {
echo yellow("⚠ WARNING: Drift notification not found\n");
$passed++;
}
} else {
echo yellow("⚠ WARNING: Not enough notifications to test\n");
$passed++;
}
} catch (\Throwable $e) {
echo red("✗ ERROR: " . $e->getMessage() . "\n");
$failed++;
$errors[] = $e->getMessage();
}
// Test 8: Notification Status Tracking
echo cyan("Test 8: Notification Status Tracking... ");
try {
$notifications = $notificationRepo->findByUser('admin', 10);
if (count($notifications) > 0) {
$unreadCount = 0;
$deliveredCount = 0;
foreach ($notifications as $notification) {
if ($notification->status === NotificationStatus::UNREAD) {
$unreadCount++;
}
if ($notification->status === NotificationStatus::DELIVERED ||
$notification->status === NotificationStatus::UNREAD) {
$deliveredCount++;
}
}
echo green("✓ PASSED\n");
echo " - Total Notifications: " . count($notifications) . "\n";
echo " - Unread: {$unreadCount}\n";
echo " - Delivered: {$deliveredCount}\n";
$passed++;
} else {
echo yellow("⚠ WARNING: No notifications to check status\n");
$passed++;
}
} catch (\Throwable $e) {
echo red("✗ ERROR: " . $e->getMessage() . "\n");
$failed++;
$errors[] = $e->getMessage();
}
// Summary
echo "\n" . blue("═══ Test Summary ═══\n\n");
echo green("Passed: {$passed}\n");
echo ($failed > 0 ? red("Failed: {$failed}\n") : "Failed: 0\n");
echo "Total: " . ($passed + $failed) . "\n";
if ($failed > 0) {
echo "\n" . red("=== Errors ===\n");
foreach ($errors as $i => $error) {
echo red(($i + 1) . ". {$error}\n");
}
}
// Display Recent Notifications
echo "\n" . blue("═══ Recent Notifications ═══\n\n");
try {
$recentNotifications = $notificationRepo->findByUser('admin', 10);
if (count($recentNotifications) > 0) {
foreach ($recentNotifications as $i => $notification) {
echo cyan(($i + 1) . ". ");
echo "{$notification->title}\n";
echo " Status: {$notification->status->value} | ";
echo "Priority: {$notification->priority->value} | ";
echo "Type: {$notification->type->toString()}\n";
echo " Created: {$notification->createdAt->format('Y-m-d H:i:s')}\n";
if ($notification->actionUrl) {
echo " Action: {$notification->actionLabel} ({$notification->actionUrl})\n";
}
echo "\n";
}
} else {
echo yellow("No notifications found.\n");
}
} catch (\Throwable $e) {
echo red("Error fetching notifications: " . $e->getMessage() . "\n");
}
exit($failed > 0 ? 1 : 0);