feat(Deployment): Integrate Ansible deployment via PHP deployment pipeline
- Create AnsibleDeployStage using framework's Process module for secure command execution - Integrate AnsibleDeployStage into DeploymentPipelineCommands for production deployments - Add force_deploy flag support in Ansible playbook to override stale locks - Use PHP deployment module as orchestrator (php console.php deploy:production) - Fix ErrorAggregationInitializer to use Environment class instead of $_ENV superglobal Architecture: - BuildStage → AnsibleDeployStage → HealthCheckStage for production - Process module provides timeout, error handling, and output capture - Ansible playbook supports rollback via rollback-git-based.yml - Zero-downtime deployments with health checks
This commit is contained in:
441
tests/debug/test-ml-notifications.php
Normal file
441
tests/debug/test-ml-notifications.php
Normal file
@@ -0,0 +1,441 @@
|
||||
<?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\NotificationRepository;
|
||||
use App\Framework\Notification\ValueObjects\NotificationStatus;
|
||||
|
||||
// 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 {
|
||||
$alertingService = $container->get(NotificationAlertingService::class);
|
||||
$notificationRepo = $container->get(NotificationRepository::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->getAll('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->getAll('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->getAll('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->getAll('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->getAll('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->getAll('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->getAll('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->getAll('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->getAll('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);
|
||||
Reference in New Issue
Block a user