Skip to content

Commit

Permalink
Added Check Monitor Status command
Browse files Browse the repository at this point in the history
  • Loading branch information
nasrulhazim committed Oct 16, 2024
1 parent b4e6a19 commit ec77661
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 24 deletions.
1 change: 1 addition & 0 deletions src/Actions/Monitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function create(array $data): Model
public function update(Model $monitor, array $data): Model
{
$monitor->update($data);

return $monitor;
}

Expand Down
13 changes: 13 additions & 0 deletions src/Actions/MonitorHistory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace CleaniqueCoders\AppPulse\Actions;

use CleaniqueCoders\AppPulse\Models\MonitorHistory as Model;

class MonitorHistory
{
public static function create(array $data): Model
{
return Model::create($data);
}
}
4 changes: 2 additions & 2 deletions src/AppPulseServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace CleaniqueCoders\AppPulse;

use CleaniqueCoders\AppPulse\Commands\AppPulseCommand;
use CleaniqueCoders\AppPulse\Commands\CheckMonitorStatusCommand;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;

Expand All @@ -20,6 +20,6 @@ public function configurePackage(Package $package): void
->hasConfigFile()
->hasViews()
->hasMigration('create_app_pulse_table')
->hasCommand(AppPulseCommand::class);
->hasCommand(CheckMonitorStatusCommand::class);
}
}
19 changes: 0 additions & 19 deletions src/Commands/AppPulseCommand.php

This file was deleted.

122 changes: 122 additions & 0 deletions src/Commands/CheckMonitorStatusCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

namespace CleaniqueCoders\AppPulse\Commands;

use CleaniqueCoders\AppPulse\Actions\MonitorHistory as MonitorHistoryAction;
use CleaniqueCoders\AppPulse\Models\Monitor;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;

class CheckMonitorStatusCommand extends Command
{
protected $signature = 'monitor:check-status';

protected $description = 'Check the status and SSL validity of all monitors';

public function handle()
{
$monitors = Monitor::all();

foreach ($monitors as $monitor) {
$this->checkMonitor($monitor);
}

$this->info('Monitor status check completed.');
}

protected function checkMonitor(Monitor $monitor)
{
try {
$startTime = microtime(true);
$response = Http::get($monitor->url);

Check failure on line 31 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\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}.");

Check failure on line 42 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\Monitor::$url.

if ($monitor->ssl_check) {

Check failure on line 44 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\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()}");

Check failure on line 55 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\Monitor::$url.
}
}

protected function checkSsl(Monitor $monitor)
{
$host = parse_url($monitor->url, PHP_URL_HOST);

Check failure on line 61 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\Monitor::$url.
$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([

Check failure on line 70 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Call to static method create() on an unknown class CleaniqueCoders\AppPulse\Commands\MonitorHistory.
'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.");

Check failure on line 76 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\Monitor::$url.

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}.");

Check failure on line 91 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\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}.");

Check failure on line 105 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\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.");

Check failure on line 120 in src/Commands/CheckMonitorStatusCommand.php

View workflow job for this annotation

GitHub Actions / phpstan

Access to an undefined property CleaniqueCoders\AppPulse\Models\Monitor::$url.
}
}
2 changes: 2 additions & 0 deletions src/Contracts/Monitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
130 changes: 130 additions & 0 deletions tests/CheckMonitorStatusTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

use CleaniqueCoders\AppPulse\Actions\MonitorHistory;
use CleaniqueCoders\AppPulse\Models\Monitor;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;

use function Pest\Laravel\assertDatabaseHas;

beforeEach(function () {
// Create fake storage for SSL checks if needed.
Storage::fake('local');

// Create a test monitor.
$this->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',
]);
});
5 changes: 2 additions & 3 deletions tests/MonitorActionTest.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<?php

use CleaniqueCoders\AppPulse\Models\Monitor;
use CleaniqueCoders\AppPulse\Actions\Monitor as MonitorAction;
use Illuminate\Foundation\Testing\RefreshDatabase;
use CleaniqueCoders\AppPulse\Models\Monitor;

beforeEach(function () {
$this->monitorAction = new MonitorAction();
$this->monitorAction = new MonitorAction;
});

it('can create a new monitor', function () {
Expand Down

0 comments on commit ec77661

Please sign in to comment.