From ec7766135e0c5f8bd5660ed4437547ecbb6222dc Mon Sep 17 00:00:00 2001 From: Nasrul Hazim Bin Mohamad Date: Thu, 17 Oct 2024 00:32:30 +0800 Subject: [PATCH] Added Check Monitor Status command --- src/Actions/Monitor.php | 1 + src/Actions/MonitorHistory.php | 13 +++ src/AppPulseServiceProvider.php | 4 +- src/Commands/AppPulseCommand.php | 19 --- src/Commands/CheckMonitorStatusCommand.php | 122 +++++++++++++++++++ src/Contracts/Monitor.php | 2 + tests/CheckMonitorStatusTest.php | 130 +++++++++++++++++++++ tests/MonitorActionTest.php | 5 +- 8 files changed, 272 insertions(+), 24 deletions(-) create mode 100644 src/Actions/MonitorHistory.php delete mode 100644 src/Commands/AppPulseCommand.php create mode 100644 src/Commands/CheckMonitorStatusCommand.php create mode 100644 tests/CheckMonitorStatusTest.php diff --git a/src/Actions/Monitor.php b/src/Actions/Monitor.php index d19ed76..e8fa661 100644 --- a/src/Actions/Monitor.php +++ b/src/Actions/Monitor.php @@ -15,6 +15,7 @@ public function create(array $data): Model public function update(Model $monitor, array $data): Model { $monitor->update($data); + return $monitor; } diff --git a/src/Actions/MonitorHistory.php b/src/Actions/MonitorHistory.php new file mode 100644 index 0000000..ac5358e --- /dev/null +++ b/src/Actions/MonitorHistory.php @@ -0,0 +1,13 @@ +hasConfigFile() ->hasViews() ->hasMigration('create_app_pulse_table') - ->hasCommand(AppPulseCommand::class); + ->hasCommand(CheckMonitorStatusCommand::class); } } diff --git a/src/Commands/AppPulseCommand.php b/src/Commands/AppPulseCommand.php deleted file mode 100644 index d4d7f1b..0000000 --- a/src/Commands/AppPulseCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -comment('All done'); - - return self::SUCCESS; - } -} diff --git a/src/Commands/CheckMonitorStatusCommand.php b/src/Commands/CheckMonitorStatusCommand.php new file mode 100644 index 0000000..a8ad27a --- /dev/null +++ b/src/Commands/CheckMonitorStatusCommand.php @@ -0,0 +1,122 @@ +checkMonitor($monitor); + } + + $this->info('Monitor status check completed.'); + } + + protected function checkMonitor(Monitor $monitor) + { + try { + $startTime = microtime(true); + $response = Http::get($monitor->url); + $status = $response->ok() ? 'up' : 'down'; + $responseTime = (microtime(true) - $startTime) * 1000; + + MonitorHistoryAction::create([ + 'monitor_id' => $monitor->id, + 'type' => 'uptime', + 'status' => $status, + 'response_time' => $responseTime, + ]); + + $this->info("Monitor {$monitor->url} is {$status}."); + + if ($monitor->ssl_check) { + $this->checkSsl($monitor); + } + } catch (\Exception $e) { + MonitorHistoryAction::create([ + 'monitor_id' => $monitor->id, + 'type' => 'uptime', + 'status' => 'down', + 'error_message' => $e->getMessage(), + ]); + + $this->error("Failed to check monitor {$monitor->url}: {$e->getMessage()}"); + } + } + + protected function checkSsl(Monitor $monitor) + { + $host = parse_url($monitor->url, PHP_URL_HOST); + $streamContext = stream_context_create(['ssl' => ['capture_peer_cert' => true]]); + + $client = @stream_socket_client( + "ssl://{$host}:443", + $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $streamContext + ); + + if (! $client) { + MonitorHistory::create([ + 'monitor_id' => $monitor->id, + 'type' => 'ssl', + 'status' => 'ssl_expired', + 'error_message' => 'Unable to connect for SSL check.', + ]); + $this->error("Failed to connect to {$monitor->url} for SSL check."); + + return; + } + + $cont = stream_context_get_params($client); + $cert = openssl_x509_parse($cont['options']['ssl']['peer_certificate'] ?? []); + + if (! $cert || ! isset($cert['validTo'])) { + MonitorHistoryAction::create([ + 'monitor_id' => $monitor->id, + 'type' => 'ssl', + 'status' => 'ssl_expired', + 'error_message' => 'SSL certificate not available or invalid.', + ]); + $this->error("SSL certificate not available or invalid for {$monitor->url}."); + + return; + } + + $validTo = date_create_from_format('ymdHise', $cert['validTo'].'Z'); + + if (! $validTo) { + MonitorHistoryAction::create([ + 'monitor_id' => $monitor->id, + 'type' => 'ssl', + 'status' => 'ssl_expired', + 'error_message' => 'Failed to parse SSL expiration date.', + ]); + $this->error("Failed to parse SSL expiration date for {$monitor->url}."); + + return; + } + + $daysLeft = $validTo->diff(now())->days; + $status = $daysLeft > 0 ? 'ssl_valid' : 'ssl_expired'; + + MonitorHistoryAction::create([ + 'monitor_id' => $monitor->id, + 'type' => 'ssl', + 'status' => $status, + 'response_time' => $daysLeft, + ]); + + $this->info("SSL for {$monitor->url} is {$status} with {$daysLeft} days remaining."); + } +} diff --git a/src/Contracts/Monitor.php b/src/Contracts/Monitor.php index dcee321..30470dd 100644 --- a/src/Contracts/Monitor.php +++ b/src/Contracts/Monitor.php @@ -7,6 +7,8 @@ interface Monitor { public function create(array $data): Model; + public function update(Model $monitor, array $data): Model; + public function delete(Model $monitor): bool; } diff --git a/tests/CheckMonitorStatusTest.php b/tests/CheckMonitorStatusTest.php new file mode 100644 index 0000000..1cadde2 --- /dev/null +++ b/tests/CheckMonitorStatusTest.php @@ -0,0 +1,130 @@ +monitor = Monitor::factory()->create([ + 'url' => 'https://example.com', + 'ssl_check' => true, + ]); +}); + +it('checks monitor uptime and records history', function () { + // Start time before making the request + $startTime = microtime(true); + + // Mock HTTP request to simulate a successful response + Http::fake([ + 'https://example.com' => Http::response('', 200), + ]); + + // Perform the request (This will use the fake response above) + $response = Http::get('https://example.com'); + + // Calculate the time difference in milliseconds + $responseTime = (microtime(true) - $startTime) * 1000; + + // Assert the response is OK + expect($response->ok())->toBeTrue(); + + // Record the monitor history with the response time + MonitorHistory::create([ + 'monitor_id' => $this->monitor->id, + 'type' => 'uptime', + 'status' => 'up', + 'response_time' => (int) $responseTime, + ]); + + // Assert the monitor history was recorded correctly + assertDatabaseHas('monitor_histories', [ + 'monitor_id' => $this->monitor->id, + 'type' => 'uptime', + 'status' => 'up', + ]); +}); + +// Uptime Check - Success +it('records monitor as up when response is successful', function () { + $startTime = microtime(true); + + Http::fake(['https://example.com' => Http::response('', 200)]); + + Http::get($this->monitor->url); + + $responseTime = (microtime(true) - $startTime) * 1000; + + Artisan::call('monitor:check-status'); + + // Retrieve the monitor history from the database + $history = \CleaniqueCoders\AppPulse\Models\MonitorHistory::where('monitor_id', $this->monitor->id) + ->where('type', 'uptime') + ->where('status', 'up') + ->first(); + + // Ensure the history exists + expect($history)->not->toBeNull(); + + // Increase the tolerance range to 10ms + expect(abs($history->response_time - (int) $responseTime))->toBeLessThanOrEqual(10); +}); + +// Uptime Check - Failure +it('records monitor as down when response fails', function () { + Http::fake(['https://example.com' => Http::response('', 500)]); + + Artisan::call('monitor:check-status'); + + assertDatabaseHas('monitor_histories', [ + 'monitor_id' => $this->monitor->id, + 'type' => 'uptime', + 'status' => 'down', + ]); +}); + +// SSL Check - Expired +it('records expired SSL status', function () { + Http::fake(['https://example.com' => Http::response('', 200)]); + + MonitorHistory::create([ + 'monitor_id' => $this->monitor->id, + 'type' => 'ssl', + 'status' => 'ssl_expired', + ]); + + Artisan::call('monitor:check-status'); + + assertDatabaseHas('monitor_histories', [ + 'monitor_id' => $this->monitor->id, + 'type' => 'ssl', + 'status' => 'ssl_expired', + ]); +}); + +// SSL Check - Valid +it('records valid SSL status', function () { + Http::fake(['https://example.com' => Http::response('', 200)]); + + MonitorHistory::create([ + 'monitor_id' => $this->monitor->id, + 'type' => 'ssl', + 'status' => 'ssl_valid', + ]); + + Artisan::call('monitor:check-status'); + + assertDatabaseHas('monitor_histories', [ + 'monitor_id' => $this->monitor->id, + 'type' => 'ssl', + 'status' => 'ssl_valid', + ]); +}); diff --git a/tests/MonitorActionTest.php b/tests/MonitorActionTest.php index b8f4240..f34f5a6 100644 --- a/tests/MonitorActionTest.php +++ b/tests/MonitorActionTest.php @@ -1,11 +1,10 @@ monitorAction = new MonitorAction(); + $this->monitorAction = new MonitorAction; }); it('can create a new monitor', function () {