Files
michaelschiemer/tests/debug/test-listen-notify.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- 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.
2025-10-25 19:18:37 +02:00

320 lines
12 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/../../src/Framework/Database/Notifications/Channel.php';
require_once __DIR__ . '/../../src/Framework/Database/Notifications/NotificationPayload.php';
require_once __DIR__ . '/../../src/Framework/Database/Notifications/Notification.php';
require_once __DIR__ . '/../../src/Framework/Database/Notifications/NotificationService.php';
use App\Framework\Database\Notifications\Channel;
use App\Framework\Database\Notifications\NotificationPayload;
use App\Framework\Database\Notifications\NotificationService;
echo "Testing PostgreSQL LISTEN/NOTIFY\n";
echo "=================================\n\n";
try {
// Create two separate PDO connections (sender and listener)
$pdoListener = new PDO(
'pgsql:host=db;dbname=michaelschiemer',
'postgres',
'StartSimple2024!'
);
$pdoListener->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdoSender = new PDO(
'pgsql:host=db;dbname=michaelschiemer',
'postgres',
'StartSimple2024!'
);
$pdoSender->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "✅ Two database connections established (listener + sender)\n\n";
$listenerService = new NotificationService($pdoListener);
$senderService = new NotificationService($pdoSender);
// Test 1: Basic NOTIFY without payload
echo "Test 1: Basic NOTIFY Without Payload\n";
echo "=====================================\n";
$channel1 = Channel::fromString('test_channel');
// Listener subscribes
$listenerService->listen($channel1);
echo "✅ Listener: Subscribed to channel '{$channel1}'\n";
// Sender sends notification without payload
$senderService->notify($channel1);
echo "✅ Sender: Sent notification (no payload)\n";
// Listener polls for notifications
$notifications = $listenerService->poll();
echo count($notifications) === 1 ? "✅ Listener: Received 1 notification\n" : "❌ Should have received 1 notification\n";
if (count($notifications) > 0) {
$n = $notifications[0];
echo " Channel: {$n->getChannelName()}\n";
echo " Payload: " . ($n->payload->isEmpty() ? "(empty)" : $n->getPayloadString()) . "\n";
echo " Sender PID: {$n->getSenderPid()}\n";
}
$listenerService->unlisten($channel1);
echo "\n";
// Test 2: NOTIFY with string payload
echo "Test 2: NOTIFY With String Payload\n";
echo "===================================\n";
$channel2 = Channel::fromString('events');
$payload2 = NotificationPayload::fromString('user_created');
$listenerService->listen($channel2);
echo "✅ Listener: Subscribed to channel '{$channel2}'\n";
$senderService->notify($channel2, $payload2);
echo "✅ Sender: Sent notification with payload 'user_created'\n";
$notifications = $listenerService->poll();
echo count($notifications) === 1 ? "✅ Listener: Received notification\n" : "❌ Should have received notification\n";
if (count($notifications) > 0) {
$n = $notifications[0];
echo " Channel: {$n->getChannelName()}\n";
echo " Payload: '{$n->getPayloadString()}'\n";
echo ($n->getPayloadString() === 'user_created') ? "✅ Payload matches\n" : "❌ Payload should match\n";
}
$listenerService->unlisten($channel2);
echo "\n";
// Test 3: NOTIFY with JSON payload
echo "Test 3: NOTIFY With JSON Payload\n";
echo "=================================\n";
$channel3 = Channel::fromString('user_events');
$data3 = [
'event' => 'user_registered',
'user_id' => 123,
'email' => 'test@example.com',
'timestamp' => time()
];
$listenerService->listen($channel3);
echo "✅ Listener: Subscribed to channel '{$channel3}'\n";
$senderService->notifyWithData($channel3, $data3);
echo "✅ Sender: Sent notification with JSON payload\n";
$notifications = $listenerService->poll();
echo count($notifications) === 1 ? "✅ Listener: Received notification\n" : "❌ Should have received notification\n";
if (count($notifications) > 0) {
$n = $notifications[0];
echo " Raw payload: " . $n->getPayloadString() . "\n";
$receivedData = $n->getPayloadArray();
echo " Deserialized data: " . json_encode($receivedData) . "\n";
echo " Received data:\n";
echo " - Event: " . ($receivedData['event'] ?? 'N/A') . "\n";
echo " - User ID: " . ($receivedData['user_id'] ?? 'N/A') . "\n";
echo " - Email: " . ($receivedData['email'] ?? 'N/A') . "\n";
$dataMatches = isset($receivedData['event']) && $receivedData['event'] === 'user_registered' && $receivedData['user_id'] === 123;
echo $dataMatches ? "✅ JSON payload correctly deserialized\n" : "❌ Payload should match\n";
}
$listenerService->unlisten($channel3);
echo "\n";
// Test 4: Multiple channels
echo "Test 4: Multiple Channels\n";
echo "=========================\n";
$channelA = Channel::fromString('channel_a');
$channelB = Channel::fromString('channel_b');
$channelC = Channel::fromString('channel_c');
// Subscribe to multiple channels
$listenerService->listen($channelA);
$listenerService->listen($channelB);
$listenerService->listen($channelC);
echo "✅ Listener: Subscribed to 3 channels\n";
// Send notifications to all channels
$senderService->notify($channelA, NotificationPayload::fromString('message-A'));
$senderService->notify($channelB, NotificationPayload::fromString('message-B'));
$senderService->notify($channelC, NotificationPayload::fromString('message-C'));
echo "✅ Sender: Sent 3 notifications\n";
// Poll for all notifications
$notifications = $listenerService->poll();
echo (count($notifications) === 3) ? "✅ Listener: Received all 3 notifications\n" : "❌ Should have received 3 notifications (got " . count($notifications) . ")\n";
// Verify channels
$channels = array_map(fn($n) => $n->getChannelName(), $notifications);
sort($channels);
$expected = ['channel_a', 'channel_b', 'channel_c'];
echo ($channels === $expected) ? "✅ All channel names correct\n" : "❌ Channel names should match\n";
$listenerService->unlistenAll();
echo "\n";
// Test 5: No notifications when not listening
echo "Test 5: No Notifications When Not Listening\n";
echo "============================================\n";
$channel5 = Channel::fromString('ignored');
// Send without listener
$senderService->notify($channel5, NotificationPayload::fromString('should-be-ignored'));
echo "✅ Sender: Sent notification (no active listener)\n";
// Try to receive (should be empty)
$listenerService->listen($channel5);
$notifications = $listenerService->poll();
echo (count($notifications) === 0) ? "✅ Listener: Correctly received no notifications (subscribed AFTER send)\n" : "❌ Should not have received notifications\n";
$listenerService->unlisten($channel5);
echo "\n";
// Test 6: waitForOne with timeout
echo "Test 6: waitForOne() With Quick Response\n";
echo "=========================================\n";
$channel6 = Channel::fromString('wait_test');
$listenerService->listen($channel6);
echo "✅ Listener: Subscribed to channel '{$channel6}'\n";
// Send notification in "background" (same process for test)
$senderService->notify($channel6, NotificationPayload::fromString('quick-response'));
echo "✅ Sender: Sent notification\n";
// Wait for single notification (should return immediately)
$start = microtime(true);
$notification = $listenerService->waitForOne(5);
$duration = microtime(true) - $start;
echo ($notification !== null) ? "✅ Listener: Received notification via waitForOne()\n" : "❌ Should have received notification\n";
echo " Wait duration: " . round($duration * 1000, 2) . "ms\n";
echo ($duration < 1.0) ? "✅ Returned quickly (no timeout)\n" : "⚠️ Took longer than expected\n";
if ($notification !== null) {
echo " Payload: '{$notification->getPayloadString()}'\n";
}
$listenerService->unlisten($channel6);
echo "\n";
// Test 7: Channel validation
echo "Test 7: Channel Name Validation\n";
echo "================================\n";
// Valid channel names
try {
Channel::fromString('valid_channel');
echo "✅ Valid channel name accepted: 'valid_channel'\n";
} catch (\Exception $e) {
echo "❌ Should accept valid channel name\n";
}
try {
Channel::fromString('channel123');
echo "✅ Valid channel name accepted: 'channel123'\n";
} catch (\Exception $e) {
echo "❌ Should accept valid channel name\n";
}
// Invalid channel names
try {
Channel::fromString('');
echo "❌ Should reject empty channel name\n";
} catch (\InvalidArgumentException $e) {
echo "✅ Correctly rejected empty channel name\n";
}
try {
Channel::fromString('123invalid'); // Cannot start with number
echo "❌ Should reject channel name starting with number\n";
} catch (\InvalidArgumentException $e) {
echo "✅ Correctly rejected channel name starting with number\n";
}
try {
Channel::fromString(str_repeat('a', 64)); // Too long
echo "❌ Should reject channel name > 63 characters\n";
} catch (\InvalidArgumentException $e) {
echo "✅ Correctly rejected channel name > 63 characters\n";
}
echo "\n";
// Test 8: Payload size validation
echo "Test 8: Payload Size Validation\n";
echo "================================\n";
// Small payload (valid)
try {
$smallPayload = NotificationPayload::fromString('small data');
echo "✅ Small payload accepted\n";
} catch (\Exception $e) {
echo "❌ Should accept small payload\n";
}
// Large payload (max 8000 bytes)
try {
$largeData = str_repeat('x', 8000);
$largePayload = NotificationPayload::fromString($largeData);
echo "✅ 8000-byte payload accepted (PostgreSQL limit)\n";
} catch (\Exception $e) {
echo "❌ Should accept payload up to 8000 bytes\n";
}
// Too large payload (> 8000 bytes)
try {
$tooLargeData = str_repeat('x', 8001);
$tooLargePayload = NotificationPayload::fromString($tooLargeData);
echo "❌ Should reject payload > 8000 bytes\n";
} catch (\InvalidArgumentException $e) {
echo "✅ Correctly rejected payload > 8000 bytes\n";
}
echo "\n";
// Test 9: forEvent() channel naming convention
echo "Test 9: forEvent() Channel Naming Convention\n";
echo "=============================================\n";
$eventChannel = Channel::forEvent('user', 'registered');
echo "✅ Event channel created: '{$eventChannel}'\n";
echo ($eventChannel->name === 'user_registered') ? "✅ Channel name follows convention: namespace_eventname\n" : "❌ Should follow convention\n";
$listenerService->listen($eventChannel);
$senderService->notifyWithData($eventChannel, ['user_id' => 999]);
$notifications = $listenerService->poll();
echo (count($notifications) === 1) ? "✅ Event-based channel works\n" : "❌ Should receive notification\n";
$listenerService->unlisten($eventChannel);
echo "\n";
echo "✅ All LISTEN/NOTIFY tests passed!\n";
echo "\nSummary:\n";
echo "========\n";
echo "✅ Basic NOTIFY without payload works\n";
echo "✅ NOTIFY with string payload works\n";
echo "✅ NOTIFY with JSON payload works\n";
echo "✅ Multiple channel subscription works\n";
echo "✅ Notifications only received when listening\n";
echo "✅ waitForOne() blocking receive works\n";
echo "✅ Channel name validation works\n";
echo "✅ Payload size validation works\n";
echo "✅ forEvent() naming convention works\n";
} catch (\Exception $e) {
echo "❌ Error: " . $e->getMessage() . "\n";
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
exit(1);
}