From f87d0c16bb4e252812a1c218111e14ab644b286b Mon Sep 17 00:00:00 2001 From: Mattias Geniar Date: Sat, 28 Feb 2026 20:58:00 +0100 Subject: [PATCH 1/8] Add CheckResult enum with warning state support and helper methods --- CHANGELOG.md | 7 +++ README.md | 44 +++++++++++++- src/Dto/Check.php | 11 ++++ src/Dto/CheckSummary.php | 9 ++- src/Dto/Monitor.php | 11 ++++ src/Enums/CheckResult.php | 38 +++++++++++++ .../Saloon/check-summary-warning.json | 21 +++++++ tests/OhDearTests/CheckResultTest.php | 57 +++++++++++++++++++ tests/OhDearTests/ChecksTest.php | 3 + tests/OhDearTests/MonitorsTest.php | 21 +++++++ 10 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/Enums/CheckResult.php create mode 100644 tests/Fixtures/Saloon/check-summary-warning.json create mode 100644 tests/OhDearTests/CheckResultTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c20517d..06a7dc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to `ohdear-php-sdk` will be documented in this file +## Add warning state support - 2026-02-28 + +### What changed + +- Added `CheckResult` backed string enum (`pending`, `succeeded`, `warning`, `failed`, `errored-or-timed-out`) with helper methods (`isUp()`, `isDown()`, `isPending()`, `isWarning()`) +- Added `checkResult()` method to `Check`, `Monitor`, and `CheckSummary` DTOs that returns the typed enum + ## Add missing API endpoints - 2026-02-17 ### What changed diff --git a/README.md b/README.md index e40a01e..453d874 100644 --- a/README.md +++ b/README.md @@ -152,9 +152,51 @@ $checkSummary = $ohDear->checkSummary($monitorId, CheckType::CertificateHealth); echo "Check result: {$checkSummary->result}\n"; echo "Summary: {$checkSummary->summary}\n"; + +// Use the checkResult() method to get the typed enum with helper methods +if ($checkSummary->checkResult()->isUp()) { + echo "Monitor is reachable\n"; +} + +if ($checkSummary->checkResult()->isWarning()) { + echo "Partial connectivity issue detected\n"; +} + +if ($checkSummary->checkResult()->isDown()) { + echo "Monitor is down\n"; +} ``` -You can request a summary for all available cases in the `CheckType` enum.` +You can request a summary for all available cases in the `CheckType` enum. + +#### Check result enum + +The `CheckResult` enum represents the possible states of a check. Access it via the `checkResult()` method available on `Check`, `Monitor`, and `CheckSummary` DTOs: + +```php +use OhDear\PhpSdk\Enums\CheckResult; + +$checkSummary = $ohDear->checkSummary($monitorId, CheckType::Uptime); + +// The raw string is still available +echo $checkSummary->result; // 'succeeded', 'warning', 'failed', etc. + +// Use checkResult() for the typed enum +$result = $checkSummary->checkResult(); + +// Available cases: +CheckResult::Pending; // 'pending' +CheckResult::Succeeded; // 'succeeded' +CheckResult::Warning; // 'warning' — primary location reports down, secondary confirms reachable +CheckResult::Failed; // 'failed' +CheckResult::ErroredOrTimedOut; // 'errored-or-timed-out' + +// Helper methods: +$result->isUp(); // true for Succeeded and Warning +$result->isDown(); // true for Failed and ErroredOrTimedOut +$result->isPending(); // true for Pending only +$result->isWarning(); // true for Warning only +``` #### Getting certificate health for a monitor diff --git a/src/Dto/Check.php b/src/Dto/Check.php index da090c1..4715c2d 100644 --- a/src/Dto/Check.php +++ b/src/Dto/Check.php @@ -2,6 +2,8 @@ namespace OhDear\PhpSdk\Dto; +use OhDear\PhpSdk\Enums\CheckResult; + class Check { public function __construct( @@ -17,6 +19,15 @@ public function __construct( public ?array $activeSnooze, ) {} + public function checkResult(): ?CheckResult + { + if ($this->latestRunResult === null) { + return null; + } + + return CheckResult::tryFrom($this->latestRunResult); + } + public static function fromResponse(array $data): self { return new self( diff --git a/src/Dto/CheckSummary.php b/src/Dto/CheckSummary.php index 44149b9..e00294e 100644 --- a/src/Dto/CheckSummary.php +++ b/src/Dto/CheckSummary.php @@ -2,6 +2,8 @@ namespace OhDear\PhpSdk\Dto; +use OhDear\PhpSdk\Enums\CheckResult; + class CheckSummary { public function __construct( @@ -9,11 +11,16 @@ public function __construct( public ?string $summary, ) {} + public function checkResult(): ?CheckResult + { + return CheckResult::tryFrom($this->result); + } + public static function fromResponse(array $data): self { return new self( result: $data['result'], - summary: $data['summary'], + summary: $data['summary'] ?? null, ); } } diff --git a/src/Dto/Monitor.php b/src/Dto/Monitor.php index 77fc585..5c04304 100644 --- a/src/Dto/Monitor.php +++ b/src/Dto/Monitor.php @@ -2,6 +2,8 @@ namespace OhDear\PhpSdk\Dto; +use OhDear\PhpSdk\Enums\CheckResult; + class Monitor { public function __construct( @@ -40,6 +42,15 @@ public function __construct( public ?string $updatedAt = null, ) {} + public function checkResult(): ?CheckResult + { + if ($this->summarizedCheckResult === null) { + return null; + } + + return CheckResult::tryFrom($this->summarizedCheckResult); + } + public static function fromResponse(array $data): self { return new self( diff --git a/src/Enums/CheckResult.php b/src/Enums/CheckResult.php new file mode 100644 index 0000000..6377521 --- /dev/null +++ b/src/Enums/CheckResult.php @@ -0,0 +1,38 @@ + true, + default => false, + }; + } + + public function isDown(): bool + { + return match ($this) { + self::Failed, self::ErroredOrTimedOut => true, + default => false, + }; + } + + public function isPending(): bool + { + return $this === self::Pending; + } + + public function isWarning(): bool + { + return $this === self::Warning; + } +} diff --git a/tests/Fixtures/Saloon/check-summary-warning.json b/tests/Fixtures/Saloon/check-summary-warning.json new file mode 100644 index 0000000..63d6ed8 --- /dev/null +++ b/tests/Fixtures/Saloon/check-summary-warning.json @@ -0,0 +1,21 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Sun, 10 Aug 2025 18:46:17 GMT", + "Content-Type": "application\/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "cloudflare", + "Vary": "Accept-Encoding", + "Cache-Control": "no-cache, private", + "X-Ratelimit-Limit": "500", + "X-Ratelimit-Remaining": "498", + "Access-Control-Allow-Origin": "*", + "X-Frame-Options": "SAMEORIGIN", + "X-Xss-Protection": "1; mode=block", + "X-Content-Type-Options": "nosniff", + "Cf-Cache-Status": "BYPASS" + }, + "data": "{\"result\":\"warning\",\"summary\":\"Primary location reports down, secondary confirms reachable.\"}", + "context": [] +} diff --git a/tests/OhDearTests/CheckResultTest.php b/tests/OhDearTests/CheckResultTest.php new file mode 100644 index 0000000..4152fb0 --- /dev/null +++ b/tests/OhDearTests/CheckResultTest.php @@ -0,0 +1,57 @@ +toHaveCount(5); + expect(CheckResult::Pending->value)->toBe('pending'); + expect(CheckResult::Succeeded->value)->toBe('succeeded'); + expect(CheckResult::Warning->value)->toBe('warning'); + expect(CheckResult::Failed->value)->toBe('failed'); + expect(CheckResult::ErroredOrTimedOut->value)->toBe('errored-or-timed-out'); +}); + +it('can be created from string values', function () { + expect(CheckResult::from('succeeded'))->toBe(CheckResult::Succeeded); + expect(CheckResult::from('warning'))->toBe(CheckResult::Warning); + expect(CheckResult::from('failed'))->toBe(CheckResult::Failed); + expect(CheckResult::from('pending'))->toBe(CheckResult::Pending); + expect(CheckResult::from('errored-or-timed-out'))->toBe(CheckResult::ErroredOrTimedOut); +}); + +it('returns null for unknown values with tryFrom', function () { + expect(CheckResult::tryFrom('unknown'))->toBeNull(); + expect(CheckResult::tryFrom(''))->toBeNull(); +}); + +it('correctly identifies up states', function () { + expect(CheckResult::Succeeded->isUp())->toBeTrue(); + expect(CheckResult::Warning->isUp())->toBeTrue(); + expect(CheckResult::Pending->isUp())->toBeFalse(); + expect(CheckResult::Failed->isUp())->toBeFalse(); + expect(CheckResult::ErroredOrTimedOut->isUp())->toBeFalse(); +}); + +it('correctly identifies down states', function () { + expect(CheckResult::Failed->isDown())->toBeTrue(); + expect(CheckResult::ErroredOrTimedOut->isDown())->toBeTrue(); + expect(CheckResult::Succeeded->isDown())->toBeFalse(); + expect(CheckResult::Warning->isDown())->toBeFalse(); + expect(CheckResult::Pending->isDown())->toBeFalse(); +}); + +it('correctly identifies pending state', function () { + expect(CheckResult::Pending->isPending())->toBeTrue(); + expect(CheckResult::Succeeded->isPending())->toBeFalse(); + expect(CheckResult::Warning->isPending())->toBeFalse(); + expect(CheckResult::Failed->isPending())->toBeFalse(); + expect(CheckResult::ErroredOrTimedOut->isPending())->toBeFalse(); +}); + +it('correctly identifies warning state', function () { + expect(CheckResult::Warning->isWarning())->toBeTrue(); + expect(CheckResult::Succeeded->isWarning())->toBeFalse(); + expect(CheckResult::Failed->isWarning())->toBeFalse(); + expect(CheckResult::Pending->isWarning())->toBeFalse(); + expect(CheckResult::ErroredOrTimedOut->isWarning())->toBeFalse(); +}); diff --git a/tests/OhDearTests/ChecksTest.php b/tests/OhDearTests/ChecksTest.php index 1be6f4f..7e2e18c 100644 --- a/tests/OhDearTests/ChecksTest.php +++ b/tests/OhDearTests/ChecksTest.php @@ -1,5 +1,6 @@ id)->toBe(940704); expect($check->enabled)->toBe(true); + expect($check->latestRunResult)->toBe('succeeded'); + expect($check->checkResult())->toBe(CheckResult::Succeeded); }); it('can disable a check', function () { diff --git a/tests/OhDearTests/MonitorsTest.php b/tests/OhDearTests/MonitorsTest.php index 10021c0..8c8467b 100644 --- a/tests/OhDearTests/MonitorsTest.php +++ b/tests/OhDearTests/MonitorsTest.php @@ -1,5 +1,6 @@ ohDear->monitor(82063); expect($monitor->url)->toBe('https://laravel.com'); + expect($monitor->summarizedCheckResult)->toBe('succeeded'); + expect($monitor->checkResult())->toBe(CheckResult::Succeeded); }); it('can create a monitor', function () { @@ -74,6 +77,24 @@ $checkSummary = $this->ohDear->checkSummary(82060, CheckType::CertificateHealth); expect($checkSummary->result)->toBe('succeeded'); + expect($checkSummary->checkResult())->toBe(CheckResult::Succeeded); + expect($checkSummary->checkResult()->isUp())->toBeTrue(); + expect($checkSummary->checkResult()->isDown())->toBeFalse(); + expect($checkSummary->checkResult()->isWarning())->toBeFalse(); +}); + +it('can get a warning check summary for a monitor', function () { + MockClient::global([ + GetCheckSummaryRequest::class => MockResponse::fixture('check-summary-warning'), + ]); + + $checkSummary = $this->ohDear->checkSummary(82060, CheckType::Uptime); + + expect($checkSummary->result)->toBe('warning'); + expect($checkSummary->checkResult())->toBe(CheckResult::Warning); + expect($checkSummary->checkResult()->isUp())->toBeTrue(); + expect($checkSummary->checkResult()->isDown())->toBeFalse(); + expect($checkSummary->checkResult()->isWarning())->toBeTrue(); }); it('can get notification destinations for a monitor', function () { From 58fabac14123c37ed26d800daa23c9acfbbb85d6 Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Wed, 25 Mar 2026 22:28:13 +0000 Subject: [PATCH 2/8] Updated to Saloon v4 --- .idea/.gitignore | 10 ++++++++++ .idea/material_theme_project_new.xml | 12 ++++++++++++ composer.json | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/material_theme_project_new.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..f3eb0ed --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/composer.json b/composer.json index b56d824..122de07 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require": { "php": "^8.1", "saloonphp/pagination-plugin": "^2.2", - "saloonphp/saloon": "^3.14" + "saloonphp/saloon": "^4.0" }, "require-dev": { "laravel/pint": "^1.27", From 3556d1685fc5a7af91a74745a0311dba77031960 Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Wed, 25 Mar 2026 22:29:41 +0000 Subject: [PATCH 3/8] Updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e40a01e..9698653 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Tests](https://github.com/ohdearapp/ohdear-php-sdk/workflows/run-tests/badge.svg) [![Total Downloads](https://img.shields.io/packagist/dt/ohdearapp/ohdear-php-sdk.svg?style=flat-square)](https://packagist.org/packages/ohdearapp/ohdear-php-sdk) -This package is the official PHP SDK for the [Oh Dear](https://ohdear.app) API, built with [Saloon](https://docs.saloon.dev/) v3. +This package is the official PHP SDK for the [Oh Dear](https://ohdear.app) API, built with [Saloon](https://docs.saloon.dev/) v4. ```php use OhDear\PhpSdk\OhDear; From 9658e7828a62ca55f83add7275b1c667a7ee313f Mon Sep 17 00:00:00 2001 From: Sammyjo20 <29132017+Sammyjo20@users.noreply.github.com> Date: Wed, 25 Mar 2026 22:30:23 +0000 Subject: [PATCH 4/8] Removed .idea --- .idea/.gitignore | 10 ---------- .idea/material_theme_project_new.xml | 12 ------------ 2 files changed, 22 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/material_theme_project_new.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index ab1f416..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml deleted file mode 100644 index f3eb0ed..0000000 --- a/.idea/material_theme_project_new.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file From d371c5194ce381123c2c252f2e0fd7d8652ba855 Mon Sep 17 00:00:00 2001 From: freekmurze Date: Thu, 26 Mar 2026 08:14:10 +0000 Subject: [PATCH 5/8] Update CHANGELOG --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c20517d..5282eca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to `ohdear-php-sdk` will be documented in this file +## 4.6.0 - 2026-03-26 + +### What's Changed + +* Chore | Upgrade to Saloon v4 by @Sammyjo20 in https://github.com/ohdearapp/ohdear-php-sdk/pull/70 + +### New Contributors + +* @Sammyjo20 made their first contribution in https://github.com/ohdearapp/ohdear-php-sdk/pull/70 + +**Full Changelog**: https://github.com/ohdearapp/ohdear-php-sdk/compare/4.4.3...4.6.0 + ## Add missing API endpoints - 2026-02-17 ### What changed From 95641fb3e346403ab450d04cba0b7de05e6731a3 Mon Sep 17 00:00:00 2001 From: Mattias Geniar Date: Mon, 30 Mar 2026 22:54:14 +0200 Subject: [PATCH 6/8] Ensure nullable --- src/Dto/CheckSummary.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Dto/CheckSummary.php b/src/Dto/CheckSummary.php index e00294e..914b180 100644 --- a/src/Dto/CheckSummary.php +++ b/src/Dto/CheckSummary.php @@ -7,19 +7,23 @@ class CheckSummary { public function __construct( - public string $result, + public ?string $result, public ?string $summary, ) {} public function checkResult(): ?CheckResult { + if ($this->result === null) { + return null; + } + return CheckResult::tryFrom($this->result); } public static function fromResponse(array $data): self { return new self( - result: $data['result'], + result: $data['result'] ?? null, summary: $data['summary'] ?? null, ); } From 348f3f6718a4d63fcc0fbf7bc12f217413e6ab3f Mon Sep 17 00:00:00 2001 From: Mattias Geniar Date: Sat, 28 Feb 2026 20:58:00 +0100 Subject: [PATCH 7/8] Add CheckResult enum with warning state support and helper methods --- CHANGELOG.md | 7 +++ README.md | 44 +++++++++++++- src/Dto/Check.php | 11 ++++ src/Dto/CheckSummary.php | 9 ++- src/Dto/Monitor.php | 11 ++++ src/Enums/CheckResult.php | 38 +++++++++++++ .../Saloon/check-summary-warning.json | 21 +++++++ tests/OhDearTests/CheckResultTest.php | 57 +++++++++++++++++++ tests/OhDearTests/ChecksTest.php | 3 + tests/OhDearTests/MonitorsTest.php | 21 +++++++ 10 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/Enums/CheckResult.php create mode 100644 tests/Fixtures/Saloon/check-summary-warning.json create mode 100644 tests/OhDearTests/CheckResultTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 5282eca..9fe0b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ All notable changes to `ohdear-php-sdk` will be documented in this file **Full Changelog**: https://github.com/ohdearapp/ohdear-php-sdk/compare/4.4.3...4.6.0 +## Add warning state support - 2026-02-28 + +### What changed + +- Added `CheckResult` backed string enum (`pending`, `succeeded`, `warning`, `failed`, `errored-or-timed-out`) with helper methods (`isUp()`, `isDown()`, `isPending()`, `isWarning()`) +- Added `checkResult()` method to `Check`, `Monitor`, and `CheckSummary` DTOs that returns the typed enum + ## Add missing API endpoints - 2026-02-17 ### What changed diff --git a/README.md b/README.md index 9698653..7196f70 100644 --- a/README.md +++ b/README.md @@ -152,9 +152,51 @@ $checkSummary = $ohDear->checkSummary($monitorId, CheckType::CertificateHealth); echo "Check result: {$checkSummary->result}\n"; echo "Summary: {$checkSummary->summary}\n"; + +// Use the checkResult() method to get the typed enum with helper methods +if ($checkSummary->checkResult()->isUp()) { + echo "Monitor is reachable\n"; +} + +if ($checkSummary->checkResult()->isWarning()) { + echo "Partial connectivity issue detected\n"; +} + +if ($checkSummary->checkResult()->isDown()) { + echo "Monitor is down\n"; +} ``` -You can request a summary for all available cases in the `CheckType` enum.` +You can request a summary for all available cases in the `CheckType` enum. + +#### Check result enum + +The `CheckResult` enum represents the possible states of a check. Access it via the `checkResult()` method available on `Check`, `Monitor`, and `CheckSummary` DTOs: + +```php +use OhDear\PhpSdk\Enums\CheckResult; + +$checkSummary = $ohDear->checkSummary($monitorId, CheckType::Uptime); + +// The raw string is still available +echo $checkSummary->result; // 'succeeded', 'warning', 'failed', etc. + +// Use checkResult() for the typed enum +$result = $checkSummary->checkResult(); + +// Available cases: +CheckResult::Pending; // 'pending' +CheckResult::Succeeded; // 'succeeded' +CheckResult::Warning; // 'warning' — primary location reports down, secondary confirms reachable +CheckResult::Failed; // 'failed' +CheckResult::ErroredOrTimedOut; // 'errored-or-timed-out' + +// Helper methods: +$result->isUp(); // true for Succeeded and Warning +$result->isDown(); // true for Failed and ErroredOrTimedOut +$result->isPending(); // true for Pending only +$result->isWarning(); // true for Warning only +``` #### Getting certificate health for a monitor diff --git a/src/Dto/Check.php b/src/Dto/Check.php index da090c1..4715c2d 100644 --- a/src/Dto/Check.php +++ b/src/Dto/Check.php @@ -2,6 +2,8 @@ namespace OhDear\PhpSdk\Dto; +use OhDear\PhpSdk\Enums\CheckResult; + class Check { public function __construct( @@ -17,6 +19,15 @@ public function __construct( public ?array $activeSnooze, ) {} + public function checkResult(): ?CheckResult + { + if ($this->latestRunResult === null) { + return null; + } + + return CheckResult::tryFrom($this->latestRunResult); + } + public static function fromResponse(array $data): self { return new self( diff --git a/src/Dto/CheckSummary.php b/src/Dto/CheckSummary.php index 44149b9..e00294e 100644 --- a/src/Dto/CheckSummary.php +++ b/src/Dto/CheckSummary.php @@ -2,6 +2,8 @@ namespace OhDear\PhpSdk\Dto; +use OhDear\PhpSdk\Enums\CheckResult; + class CheckSummary { public function __construct( @@ -9,11 +11,16 @@ public function __construct( public ?string $summary, ) {} + public function checkResult(): ?CheckResult + { + return CheckResult::tryFrom($this->result); + } + public static function fromResponse(array $data): self { return new self( result: $data['result'], - summary: $data['summary'], + summary: $data['summary'] ?? null, ); } } diff --git a/src/Dto/Monitor.php b/src/Dto/Monitor.php index 77fc585..5c04304 100644 --- a/src/Dto/Monitor.php +++ b/src/Dto/Monitor.php @@ -2,6 +2,8 @@ namespace OhDear\PhpSdk\Dto; +use OhDear\PhpSdk\Enums\CheckResult; + class Monitor { public function __construct( @@ -40,6 +42,15 @@ public function __construct( public ?string $updatedAt = null, ) {} + public function checkResult(): ?CheckResult + { + if ($this->summarizedCheckResult === null) { + return null; + } + + return CheckResult::tryFrom($this->summarizedCheckResult); + } + public static function fromResponse(array $data): self { return new self( diff --git a/src/Enums/CheckResult.php b/src/Enums/CheckResult.php new file mode 100644 index 0000000..6377521 --- /dev/null +++ b/src/Enums/CheckResult.php @@ -0,0 +1,38 @@ + true, + default => false, + }; + } + + public function isDown(): bool + { + return match ($this) { + self::Failed, self::ErroredOrTimedOut => true, + default => false, + }; + } + + public function isPending(): bool + { + return $this === self::Pending; + } + + public function isWarning(): bool + { + return $this === self::Warning; + } +} diff --git a/tests/Fixtures/Saloon/check-summary-warning.json b/tests/Fixtures/Saloon/check-summary-warning.json new file mode 100644 index 0000000..63d6ed8 --- /dev/null +++ b/tests/Fixtures/Saloon/check-summary-warning.json @@ -0,0 +1,21 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Sun, 10 Aug 2025 18:46:17 GMT", + "Content-Type": "application\/json", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Server": "cloudflare", + "Vary": "Accept-Encoding", + "Cache-Control": "no-cache, private", + "X-Ratelimit-Limit": "500", + "X-Ratelimit-Remaining": "498", + "Access-Control-Allow-Origin": "*", + "X-Frame-Options": "SAMEORIGIN", + "X-Xss-Protection": "1; mode=block", + "X-Content-Type-Options": "nosniff", + "Cf-Cache-Status": "BYPASS" + }, + "data": "{\"result\":\"warning\",\"summary\":\"Primary location reports down, secondary confirms reachable.\"}", + "context": [] +} diff --git a/tests/OhDearTests/CheckResultTest.php b/tests/OhDearTests/CheckResultTest.php new file mode 100644 index 0000000..4152fb0 --- /dev/null +++ b/tests/OhDearTests/CheckResultTest.php @@ -0,0 +1,57 @@ +toHaveCount(5); + expect(CheckResult::Pending->value)->toBe('pending'); + expect(CheckResult::Succeeded->value)->toBe('succeeded'); + expect(CheckResult::Warning->value)->toBe('warning'); + expect(CheckResult::Failed->value)->toBe('failed'); + expect(CheckResult::ErroredOrTimedOut->value)->toBe('errored-or-timed-out'); +}); + +it('can be created from string values', function () { + expect(CheckResult::from('succeeded'))->toBe(CheckResult::Succeeded); + expect(CheckResult::from('warning'))->toBe(CheckResult::Warning); + expect(CheckResult::from('failed'))->toBe(CheckResult::Failed); + expect(CheckResult::from('pending'))->toBe(CheckResult::Pending); + expect(CheckResult::from('errored-or-timed-out'))->toBe(CheckResult::ErroredOrTimedOut); +}); + +it('returns null for unknown values with tryFrom', function () { + expect(CheckResult::tryFrom('unknown'))->toBeNull(); + expect(CheckResult::tryFrom(''))->toBeNull(); +}); + +it('correctly identifies up states', function () { + expect(CheckResult::Succeeded->isUp())->toBeTrue(); + expect(CheckResult::Warning->isUp())->toBeTrue(); + expect(CheckResult::Pending->isUp())->toBeFalse(); + expect(CheckResult::Failed->isUp())->toBeFalse(); + expect(CheckResult::ErroredOrTimedOut->isUp())->toBeFalse(); +}); + +it('correctly identifies down states', function () { + expect(CheckResult::Failed->isDown())->toBeTrue(); + expect(CheckResult::ErroredOrTimedOut->isDown())->toBeTrue(); + expect(CheckResult::Succeeded->isDown())->toBeFalse(); + expect(CheckResult::Warning->isDown())->toBeFalse(); + expect(CheckResult::Pending->isDown())->toBeFalse(); +}); + +it('correctly identifies pending state', function () { + expect(CheckResult::Pending->isPending())->toBeTrue(); + expect(CheckResult::Succeeded->isPending())->toBeFalse(); + expect(CheckResult::Warning->isPending())->toBeFalse(); + expect(CheckResult::Failed->isPending())->toBeFalse(); + expect(CheckResult::ErroredOrTimedOut->isPending())->toBeFalse(); +}); + +it('correctly identifies warning state', function () { + expect(CheckResult::Warning->isWarning())->toBeTrue(); + expect(CheckResult::Succeeded->isWarning())->toBeFalse(); + expect(CheckResult::Failed->isWarning())->toBeFalse(); + expect(CheckResult::Pending->isWarning())->toBeFalse(); + expect(CheckResult::ErroredOrTimedOut->isWarning())->toBeFalse(); +}); diff --git a/tests/OhDearTests/ChecksTest.php b/tests/OhDearTests/ChecksTest.php index 1be6f4f..7e2e18c 100644 --- a/tests/OhDearTests/ChecksTest.php +++ b/tests/OhDearTests/ChecksTest.php @@ -1,5 +1,6 @@ id)->toBe(940704); expect($check->enabled)->toBe(true); + expect($check->latestRunResult)->toBe('succeeded'); + expect($check->checkResult())->toBe(CheckResult::Succeeded); }); it('can disable a check', function () { diff --git a/tests/OhDearTests/MonitorsTest.php b/tests/OhDearTests/MonitorsTest.php index 10021c0..8c8467b 100644 --- a/tests/OhDearTests/MonitorsTest.php +++ b/tests/OhDearTests/MonitorsTest.php @@ -1,5 +1,6 @@ ohDear->monitor(82063); expect($monitor->url)->toBe('https://laravel.com'); + expect($monitor->summarizedCheckResult)->toBe('succeeded'); + expect($monitor->checkResult())->toBe(CheckResult::Succeeded); }); it('can create a monitor', function () { @@ -74,6 +77,24 @@ $checkSummary = $this->ohDear->checkSummary(82060, CheckType::CertificateHealth); expect($checkSummary->result)->toBe('succeeded'); + expect($checkSummary->checkResult())->toBe(CheckResult::Succeeded); + expect($checkSummary->checkResult()->isUp())->toBeTrue(); + expect($checkSummary->checkResult()->isDown())->toBeFalse(); + expect($checkSummary->checkResult()->isWarning())->toBeFalse(); +}); + +it('can get a warning check summary for a monitor', function () { + MockClient::global([ + GetCheckSummaryRequest::class => MockResponse::fixture('check-summary-warning'), + ]); + + $checkSummary = $this->ohDear->checkSummary(82060, CheckType::Uptime); + + expect($checkSummary->result)->toBe('warning'); + expect($checkSummary->checkResult())->toBe(CheckResult::Warning); + expect($checkSummary->checkResult()->isUp())->toBeTrue(); + expect($checkSummary->checkResult()->isDown())->toBeFalse(); + expect($checkSummary->checkResult()->isWarning())->toBeTrue(); }); it('can get notification destinations for a monitor', function () { From 74b7c47e155dc74f61d9164120cb3e4ca9cc5583 Mon Sep 17 00:00:00 2001 From: Mattias Geniar Date: Mon, 30 Mar 2026 22:54:14 +0200 Subject: [PATCH 8/8] Ensure nullable --- src/Dto/CheckSummary.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Dto/CheckSummary.php b/src/Dto/CheckSummary.php index e00294e..914b180 100644 --- a/src/Dto/CheckSummary.php +++ b/src/Dto/CheckSummary.php @@ -7,19 +7,23 @@ class CheckSummary { public function __construct( - public string $result, + public ?string $result, public ?string $summary, ) {} public function checkResult(): ?CheckResult { + if ($this->result === null) { + return null; + } + return CheckResult::tryFrom($this->result); } public static function fromResponse(array $data): self { return new self( - result: $data['result'], + result: $data['result'] ?? null, summary: $data['summary'] ?? null, ); }