feat(health): add oracle probe and normalize health checks
Refactor health check DTOs around a common CheckResult contract and remove the\nlegacy HealthChecks aggregate object.\n\nAdd a dedicated /api/v1/health/oracle endpoint with response mapping, OpenAPI\nschema updates, and Bruno request coverage.\n\nAlign feature, unit, integration, architecture and OpenAPI tests with the new\nset-based checks representation and make Oracle health tests deterministic in CI.\n\nUpdate command documentation to the uwil:* command namespace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
71f6f64f7f
commit
62c221f6b6
34 changed files with 641 additions and 328 deletions
16
README.md
16
README.md
|
|
@ -50,27 +50,27 @@ vendor/bin/phpcbf
|
|||
|
||||
```bash
|
||||
# Vérifier l'état de l'application
|
||||
php artisan health
|
||||
php artisan uwil:health
|
||||
|
||||
# List all Arcadia brands
|
||||
php artisan arcadia:brand:list
|
||||
php artisan uwil:arcadia:brand:list
|
||||
|
||||
# List all Arcadia clients
|
||||
php artisan arcadia:client:list
|
||||
php artisan uwil:arcadia:client:list
|
||||
|
||||
# Create a new Arcadia client (interactive wizard)
|
||||
php artisan arcadia:client:create
|
||||
php artisan uwil:arcadia:client:create
|
||||
|
||||
# Regenerate the secret of an API client
|
||||
php artisan arcadia:client:regenerate-secret
|
||||
php artisan uwil:arcadia:client:regenerate-secret
|
||||
|
||||
# Toggle the status of an API client
|
||||
php artisan arcadia:client:toggle-status
|
||||
php artisan uwil:arcadia:client:toggle-status
|
||||
|
||||
# Check schema and query conformity of the replicated Arcadia Oracle database
|
||||
php artisan arcadia:database:check-conformity
|
||||
php artisan uwil:arcadia:database:check-conformity
|
||||
|
||||
# Display or export security audit events since a specified date
|
||||
php artisan arcadia:security:audit-logs
|
||||
php artisan uwil:arcadia:security:audit-logs
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -159,6 +159,6 @@ curl -X GET "https://api.arcadia.ldc.fr/api/v1/brands" \
|
|||
#### Commande CLI Artisan
|
||||
|
||||
```bash
|
||||
php artisan arcadia:brand:list
|
||||
php artisan uwil:arcadia:brand:list
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
info:
|
||||
name: Oracle Health Check
|
||||
type: http
|
||||
seq: 5
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{ENDPOINT}}/api/{{APIV1}}/health/oracle"
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
|
|
@ -60,6 +60,30 @@ paths:
|
|||
schema:
|
||||
$ref: "#/components/schemas/HealthCheckResponse"
|
||||
|
||||
/health/oracle:
|
||||
get:
|
||||
tags:
|
||||
- System
|
||||
summary: Sonde dédiée à la connectivité Oracle
|
||||
description: |
|
||||
Vérifie exclusivement la connectivité à la base Oracle Arcadia.
|
||||
Cette sonde est destinée au monitoring externe (ex: UptimeRobot) et ne teste ni cache ni SMTP.
|
||||
operationId: checkOracleHealth
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: Connectivité Oracle opérationnelle.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OracleHealthCheckResponse"
|
||||
"503":
|
||||
description: Connectivité Oracle indisponible.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/OracleHealthCheckResponse"
|
||||
|
||||
/employees:
|
||||
get:
|
||||
tags:
|
||||
|
|
@ -681,6 +705,44 @@ components:
|
|||
- "null"
|
||||
description: Message d'erreur éventuel en cas d'échec.
|
||||
|
||||
OracleHealthCheckResponse:
|
||||
type: object
|
||||
description: Résultat de la sonde de connectivité Oracle.
|
||||
required:
|
||||
- status
|
||||
- timestamp
|
||||
- oracle
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- healthy
|
||||
- unhealthy
|
||||
description: État de santé global de la connectivité Oracle.
|
||||
example: "healthy"
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Horodatage de la vérification au format RFC 3339 UTC.
|
||||
example: "2026-08-21T14:30:00Z"
|
||||
oracle:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- connection
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- up
|
||||
- down
|
||||
description: État de la connectivité Oracle.
|
||||
example: "up"
|
||||
connection:
|
||||
type: string
|
||||
description: Nom de la connexion Oracle vérifiée.
|
||||
example: "arcadia"
|
||||
|
||||
Brand:
|
||||
type: object
|
||||
description: Représentation canonique d'une marque dans Arcadia API.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use Presentation\Api\V1\Http\Controllers\Brands\BrandsIndexController;
|
|||
use Presentation\Api\V1\Http\Controllers\Employees\EmployeeShowController;
|
||||
use Presentation\Api\V1\Http\Controllers\Employees\EmployeesIndexController;
|
||||
use Presentation\Api\V1\Http\Controllers\Health\HealthCheckController;
|
||||
use Presentation\Api\V1\Http\Controllers\Health\OracleHealthCheckController;
|
||||
use Presentation\Api\V1\Http\Controllers\IncoCompositions\IncoCompositionShowController;
|
||||
use Presentation\Api\V1\Http\Controllers\IncoSheets\IncoSheetsIndexController;
|
||||
use Presentation\Api\V1\Http\Controllers\IncoSheets\IncoSheetsSearchController;
|
||||
|
|
@ -32,6 +33,7 @@ Route::get('/', fn () => response()->json([
|
|||
|
||||
// * HEALTH CHECK
|
||||
Route::get('/health', HealthCheckController::class)->name('health');
|
||||
Route::get('/health/oracle', OracleHealthCheckController::class)->name('health.oracle');
|
||||
|
||||
// * OPENAPI
|
||||
Route::get('/openapi', function () {
|
||||
|
|
|
|||
|
|
@ -11,5 +11,7 @@ interface HealthChecker
|
|||
{
|
||||
public function check(): HealthCheckResult;
|
||||
|
||||
public function checkArcadiaConnectivity(): HealthCheckResult;
|
||||
|
||||
public function checkArcadiaConformity(): ArcadiaConformityResult;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Application\Health\Dto;
|
|||
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
|
||||
final readonly class CacheCheckResult
|
||||
final readonly class CacheCheckResult implements CheckResult
|
||||
{
|
||||
public function __construct(
|
||||
public ComponentStatus $status,
|
||||
|
|
@ -14,4 +14,14 @@ final readonly class CacheCheckResult
|
|||
public ?string $message = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->store;
|
||||
}
|
||||
|
||||
public function getStatus(): ComponentStatus
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
src/Application/Health/Dto/CheckResult.php
Normal file
14
src/Application/Health/Dto/CheckResult.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Health\Dto;
|
||||
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
|
||||
interface CheckResult
|
||||
{
|
||||
public function getName(): string;
|
||||
|
||||
public function getStatus(): ComponentStatus;
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ namespace Application\Health\Dto;
|
|||
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
|
||||
final readonly class DatabaseCheckResult
|
||||
final readonly class DatabaseCheckResult implements CheckResult
|
||||
{
|
||||
public function __construct(
|
||||
public ComponentStatus $status,
|
||||
|
|
@ -14,4 +14,14 @@ final readonly class DatabaseCheckResult
|
|||
public ?string $message = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
public function getStatus(): ComponentStatus
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ use Application\Health\Enums\HealthStatus;
|
|||
|
||||
final readonly class HealthCheckResult
|
||||
{
|
||||
/** @param \Application\Health\Dto\CheckResult[] $checks */
|
||||
public function __construct(
|
||||
public HealthStatus $status,
|
||||
public \DateTimeImmutable $timestamp,
|
||||
public HealthChecks $checks,
|
||||
public array $checks,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Health\Dto;
|
||||
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
|
||||
final readonly class HealthChecks
|
||||
{
|
||||
public function __construct(
|
||||
public DatabaseCheckResult $databaseDefault,
|
||||
public DatabaseCheckResult $databaseArcadia,
|
||||
public CacheCheckResult $cache,
|
||||
public MailCheckResult $mailSmtp,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isAllUp(): bool
|
||||
{
|
||||
return $this->databaseDefault->status === ComponentStatus::UP
|
||||
&& $this->databaseArcadia->status === ComponentStatus::UP
|
||||
&& $this->cache->status === ComponentStatus::UP
|
||||
&& $this->mailSmtp->status === ComponentStatus::UP;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ namespace Application\Health\Dto;
|
|||
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
|
||||
final readonly class MailCheckResult
|
||||
final readonly class MailCheckResult implements CheckResult
|
||||
{
|
||||
public function __construct(
|
||||
public ComponentStatus $status,
|
||||
|
|
@ -14,4 +14,14 @@ final readonly class MailCheckResult
|
|||
public ?string $message = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->transport;
|
||||
}
|
||||
|
||||
public function getStatus(): ComponentStatus
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,11 @@ namespace Infra\Jobs;
|
|||
|
||||
use Application\Health\Config\HealthAlertMailConfig;
|
||||
use Application\Health\Contracts\HealthChecker;
|
||||
use Application\Health\Dto\CacheCheckResult;
|
||||
use Application\Health\Dto\CheckResult;
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Dto\HealthCheckResult;
|
||||
use Application\Health\Dto\MailCheckResult;
|
||||
use Application\Health\Enums\HealthStatus;
|
||||
use Application\Health\Exceptions\SystemHealthDegradedException;
|
||||
use Illuminate\Contracts\Mail\Mailer;
|
||||
|
|
@ -64,12 +68,10 @@ final class HealthCheckJob
|
|||
LoggerInterface $logger,
|
||||
): void {
|
||||
$failedComponents = [];
|
||||
$checksArray = $this->formatChecks($result);
|
||||
$checks = [];
|
||||
|
||||
foreach ($checksArray as $component => $data) {
|
||||
if ($data['status'] === 'down') {
|
||||
$failedComponents[] = sprintf('%s (%s)', $component, $data['message'] ?? 'down');
|
||||
}
|
||||
foreach ($result->checks as $check) {
|
||||
$checks[$check->getName()] = $this->mapCheckToArray($check);
|
||||
}
|
||||
|
||||
$reason = implode(', ', $failedComponents);
|
||||
|
|
@ -77,7 +79,7 @@ final class HealthCheckJob
|
|||
'status' => $result->status->value,
|
||||
'previousStatus' => $previousStatus?->value,
|
||||
'timestamp' => Utc::toRfc3339($result->timestamp),
|
||||
'checks' => $checksArray,
|
||||
'checks' => $checks,
|
||||
]);
|
||||
|
||||
report(new SystemHealthDegradedException("System health check degraded: {$reason}"));
|
||||
|
|
@ -85,7 +87,7 @@ final class HealthCheckJob
|
|||
$mailable = new SystemHealthAlertMail(
|
||||
status: $result->status,
|
||||
occurredAt: $result->timestamp,
|
||||
checks: $checksArray,
|
||||
checks: $checks,
|
||||
previousStatus: $previousStatus,
|
||||
);
|
||||
|
||||
|
|
@ -99,7 +101,11 @@ final class HealthCheckJob
|
|||
HealthAlertMailConfig $mailConfig,
|
||||
LoggerInterface $logger,
|
||||
): void {
|
||||
$checksArray = $this->formatChecks($result);
|
||||
$checks = [];
|
||||
|
||||
foreach ($result->checks as $check) {
|
||||
$checks[$check->getName()] = $this->mapCheckToArray($check);
|
||||
}
|
||||
|
||||
$logger->info('System health check recovered: all components are healthy.', [
|
||||
'status' => $result->status->value,
|
||||
|
|
@ -110,7 +116,7 @@ final class HealthCheckJob
|
|||
$mailable = new SystemHealthAlertMail(
|
||||
status: $result->status,
|
||||
occurredAt: $result->timestamp,
|
||||
checks: $checksArray,
|
||||
checks: $checks,
|
||||
previousStatus: $previousStatus,
|
||||
);
|
||||
|
||||
|
|
@ -145,62 +151,43 @@ final class HealthCheckJob
|
|||
File::makeDirectory($dir, 0755, true);
|
||||
}
|
||||
|
||||
$checks = [];
|
||||
|
||||
foreach ($result->checks as $check) {
|
||||
$checks[$check->getName()] = $this->mapCheckToArray($check);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'status' => $result->status->value,
|
||||
'timestamp' => Utc::toRfc3339($result->timestamp),
|
||||
'checks' => $this->formatChecks($result),
|
||||
'checks' => $checks,
|
||||
];
|
||||
|
||||
File::put($filePath, (string) json_encode($payload, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* database_default: array{status: string, connection: string, message?: string|null},
|
||||
* database_arcadia: array{status: string, connection: string, message?: string|null},
|
||||
* cache: array{status: string, store: string, message?: string|null},
|
||||
* mail_smtp: array{status: string, transport: string, message?: string|null}
|
||||
* }
|
||||
*/
|
||||
private function formatChecks(HealthCheckResult $result): array
|
||||
/** @return array<string, string> */
|
||||
private function mapCheckToArray(CheckResult $check): array
|
||||
{
|
||||
$defaultDb = [
|
||||
'status' => $result->checks->databaseDefault->status->value,
|
||||
'connection' => $result->checks->databaseDefault->connection,
|
||||
];
|
||||
if ($result->checks->databaseDefault->message !== null) {
|
||||
$defaultDb['message'] = $result->checks->databaseDefault->message;
|
||||
}
|
||||
$noMessageMapper = fn (string | null $message) => $message ? $message : 'N/A';
|
||||
|
||||
$arcadiaDb = [
|
||||
'status' => $result->checks->databaseArcadia->status->value,
|
||||
'connection' => $result->checks->databaseArcadia->connection,
|
||||
];
|
||||
if ($result->checks->databaseArcadia->message !== null) {
|
||||
$arcadiaDb['message'] = $result->checks->databaseArcadia->message;
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'status' => $result->checks->cache->status->value,
|
||||
'store' => $result->checks->cache->store,
|
||||
];
|
||||
if ($result->checks->cache->message !== null) {
|
||||
$cache['message'] = $result->checks->cache->message;
|
||||
}
|
||||
|
||||
$mail = [
|
||||
'status' => $result->checks->mailSmtp->status->value,
|
||||
'transport' => $result->checks->mailSmtp->transport,
|
||||
];
|
||||
if ($result->checks->mailSmtp->message !== null) {
|
||||
$mail['message'] = $result->checks->mailSmtp->message;
|
||||
}
|
||||
|
||||
return [
|
||||
'database_default' => $defaultDb,
|
||||
'database_arcadia' => $arcadiaDb,
|
||||
'cache' => $cache,
|
||||
'mail_smtp' => $mail,
|
||||
];
|
||||
return match (true) {
|
||||
$check instanceof DatabaseCheckResult => [
|
||||
'status' => $check->status->value,
|
||||
'connection' => $check->connection,
|
||||
'message' => $noMessageMapper($check->message),
|
||||
],
|
||||
$check instanceof CacheCheckResult => [
|
||||
'status' => $check->status->value,
|
||||
'store' => $check->store,
|
||||
'message' => $noMessageMapper($check->message),
|
||||
],
|
||||
$check instanceof MailCheckResult => [
|
||||
'status' => $check->status->value,
|
||||
'transport' => $check->transport,
|
||||
'message' => $noMessageMapper($check->message),
|
||||
],
|
||||
default => throw new \InvalidArgumentException('Unsupported check result type'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use Application\Health\Dto\CacheCheckResult;
|
|||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Dto\DirectoryConformityCheck;
|
||||
use Application\Health\Dto\HealthCheckResult;
|
||||
use Application\Health\Dto\HealthChecks;
|
||||
use Application\Health\Dto\MailCheckResult;
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
use Application\Health\Enums\HealthStatus;
|
||||
|
|
@ -21,6 +20,7 @@ use Illuminate\Support\Facades\Config;
|
|||
use Illuminate\Support\Facades\DB;
|
||||
use Infra\Config\ArcadiaStorageConfig;
|
||||
use Shared\Time\Utc;
|
||||
use Application\Health\Dto\CheckResult;
|
||||
|
||||
final readonly class LaravelSystemHealthChecker implements HealthChecker
|
||||
{
|
||||
|
|
@ -37,14 +37,24 @@ final readonly class LaravelSystemHealthChecker implements HealthChecker
|
|||
$cache = $this->checkCache();
|
||||
$mailSmtp = $this->checkMailSmtp();
|
||||
|
||||
$checks = new HealthChecks(
|
||||
databaseDefault: $databaseDefault,
|
||||
databaseArcadia: $databaseArcadia,
|
||||
cache: $cache,
|
||||
mailSmtp: $mailSmtp,
|
||||
);
|
||||
$checks = [
|
||||
$databaseDefault,
|
||||
$databaseArcadia,
|
||||
$cache,
|
||||
$mailSmtp,
|
||||
];
|
||||
|
||||
$status = $checks->isAllUp() ? HealthStatus::HEALTHY : HealthStatus::UNHEALTHY;
|
||||
$status = array_reduce(
|
||||
$checks,
|
||||
function (HealthStatus $carry, CheckResult $check) {
|
||||
if ($carry !== HealthStatus::HEALTHY) {
|
||||
return $carry;
|
||||
}
|
||||
return $check->getStatus() === ComponentStatus::UP ?
|
||||
HealthStatus::HEALTHY : HealthStatus::UNHEALTHY;
|
||||
},
|
||||
HealthStatus::HEALTHY
|
||||
);
|
||||
|
||||
return new HealthCheckResult(
|
||||
status: $status,
|
||||
|
|
@ -53,6 +63,19 @@ final readonly class LaravelSystemHealthChecker implements HealthChecker
|
|||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function checkArcadiaConnectivity(): HealthCheckResult
|
||||
{
|
||||
$databaseArcadia = $this->checkArcadiaDatabase();
|
||||
|
||||
return new HealthCheckResult(
|
||||
status: $databaseArcadia->getStatus() === ComponentStatus::UP ?
|
||||
HealthStatus::HEALTHY : HealthStatus::UNHEALTHY,
|
||||
timestamp: Utc::now(),
|
||||
checks: [$databaseArcadia],
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function checkArcadiaConformity(): ArcadiaConformityResult
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Presentation\Api\V1\Http\Controllers\Health;
|
||||
|
||||
use Application\Health\Contracts\HealthChecker;
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Presentation\Api\V1\Http\Responses\Health\OracleHealthCheckResponse;
|
||||
|
||||
final readonly class OracleHealthCheckController
|
||||
{
|
||||
public function __construct(
|
||||
private HealthChecker $healthChecker,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(): JsonResponse
|
||||
{
|
||||
$result = $this->healthChecker->checkArcadiaConnectivity();
|
||||
$arcadia = null;
|
||||
|
||||
foreach ($result->checks as $check) {
|
||||
if ($check instanceof DatabaseCheckResult) {
|
||||
$arcadia = $check;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $arcadia instanceof DatabaseCheckResult) {
|
||||
$arcadia = new DatabaseCheckResult(
|
||||
status: ComponentStatus::DOWN,
|
||||
connection: 'arcadia',
|
||||
message: 'Arcadia connectivity check result is missing.',
|
||||
);
|
||||
}
|
||||
|
||||
return (new OracleHealthCheckResponse(
|
||||
result: $arcadia,
|
||||
timestamp: $result->timestamp,
|
||||
))->present();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Presentation\Api\V1\Http\Responses\Health;
|
||||
|
||||
use Application\Health\Dto\CacheCheckResult;
|
||||
use Application\Health\Dto\CheckResult;
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Dto\HealthCheckResult;
|
||||
use Application\Health\Dto\MailCheckResult;
|
||||
use Application\Health\Enums\HealthStatus;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Presentation\Api\V1\Contracts\ApiResponse;
|
||||
use Shared\Time\Utc;
|
||||
|
|
@ -31,61 +36,44 @@ final readonly class HealthCheckResponse implements ApiResponse
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* status: 'healthy'|'unhealthy',
|
||||
* timestamp: string,
|
||||
* checks: array{
|
||||
* database_default: array{status: 'up'|'down', connection: string, message?: string|null},
|
||||
* database_arcadia: array{status: 'up'|'down', connection: string, message?: string|null},
|
||||
* cache: array{status: 'up'|'down', store: string, message?: string|null},
|
||||
* mail_smtp: array{status: 'up'|'down', transport: string, message?: string|null}
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
/** @return array{status: 'healthy'|'unhealthy', timestamp: string, checks: array<string, array<string, string>>} */
|
||||
public function toArray(): array
|
||||
{
|
||||
$defaultDb = [
|
||||
'status' => $this->result->checks->databaseDefault->status->value,
|
||||
'connection' => $this->result->checks->databaseDefault->connection,
|
||||
];
|
||||
if ($this->result->checks->databaseDefault->message !== null) {
|
||||
$defaultDb['message'] = $this->result->checks->databaseDefault->message;
|
||||
}
|
||||
$checks = [];
|
||||
|
||||
$arcadiaDb = [
|
||||
'status' => $this->result->checks->databaseArcadia->status->value,
|
||||
'connection' => $this->result->checks->databaseArcadia->connection,
|
||||
];
|
||||
if ($this->result->checks->databaseArcadia->message !== null) {
|
||||
$arcadiaDb['message'] = $this->result->checks->databaseArcadia->message;
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'status' => $this->result->checks->cache->status->value,
|
||||
'store' => $this->result->checks->cache->store,
|
||||
];
|
||||
if ($this->result->checks->cache->message !== null) {
|
||||
$cache['message'] = $this->result->checks->cache->message;
|
||||
}
|
||||
|
||||
$mail = [
|
||||
'status' => $this->result->checks->mailSmtp->status->value,
|
||||
'transport' => $this->result->checks->mailSmtp->transport,
|
||||
];
|
||||
if ($this->result->checks->mailSmtp->message !== null) {
|
||||
$mail['message'] = $this->result->checks->mailSmtp->message;
|
||||
foreach ($this->result->checks as $check) {
|
||||
$checks[$check->getName()] = $this->mapCheckToArray($check);
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $this->result->status->value,
|
||||
'status' => $this->result->status === HealthStatus::HEALTHY ? 'healthy' : 'unhealthy',
|
||||
'timestamp' => Utc::toRfc3339($this->result->timestamp),
|
||||
'checks' => [
|
||||
'database_default' => $defaultDb,
|
||||
'database_arcadia' => $arcadiaDb,
|
||||
'cache' => $cache,
|
||||
'mail_smtp' => $mail,
|
||||
],
|
||||
'checks' => $checks,
|
||||
];
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
private function mapCheckToArray(CheckResult $check): array
|
||||
{
|
||||
$noMessageMapper = fn (string | null $message) => $message ? $message : 'N/A';
|
||||
|
||||
return match (true) {
|
||||
$check instanceof DatabaseCheckResult => [
|
||||
'status' => $check->status->value,
|
||||
'connection' => $check->connection,
|
||||
'message' => $noMessageMapper($check->message),
|
||||
],
|
||||
$check instanceof CacheCheckResult => [
|
||||
'status' => $check->status->value,
|
||||
'store' => $check->store,
|
||||
'message' => $noMessageMapper($check->message),
|
||||
],
|
||||
$check instanceof MailCheckResult => [
|
||||
'status' => $check->status->value,
|
||||
'transport' => $check->transport,
|
||||
'message' => $noMessageMapper($check->message),
|
||||
],
|
||||
default => throw new \InvalidArgumentException('Unsupported check result type'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Presentation\Api\V1\Http\Responses\Health;
|
||||
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Presentation\Api\V1\Contracts\ApiResponse;
|
||||
use Shared\Time\Utc;
|
||||
|
||||
final readonly class OracleHealthCheckResponse implements ApiResponse
|
||||
{
|
||||
public function __construct(
|
||||
private DatabaseCheckResult $result,
|
||||
private \DateTimeInterface $timestamp,
|
||||
) {
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function toResponse($request): JsonResponse
|
||||
{
|
||||
return $this->present();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function present(): JsonResponse
|
||||
{
|
||||
return new JsonResponse(
|
||||
data: $this->toArray(),
|
||||
status: $this->result->status === ComponentStatus::UP ? 200 : 503,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* status: 'healthy'|'unhealthy',
|
||||
* timestamp: string,
|
||||
* oracle: array{
|
||||
* status: 'up'|'down',
|
||||
* connection: string
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'status' => $this->result->status === ComponentStatus::UP ? 'healthy' : 'unhealthy',
|
||||
'timestamp' => Utc::toRfc3339($this->timestamp),
|
||||
'oracle' => [
|
||||
'status' => $this->result->status->value,
|
||||
'connection' => $this->result->connection,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,11 @@ declare(strict_types=1);
|
|||
namespace Presentation\Console\Commands;
|
||||
|
||||
use Application\Health\Contracts\HealthChecker;
|
||||
use Application\Health\Dto\CacheCheckResult;
|
||||
use Application\Health\Dto\CheckResult;
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Dto\HealthCheckResult;
|
||||
use Application\Health\Dto\MailCheckResult;
|
||||
use Illuminate\Console\Command;
|
||||
use Presentation\Api\V1\Http\Responses\Health\HealthCheckResponse;
|
||||
|
||||
|
|
@ -56,33 +60,34 @@ final class HealthCheckCommand extends Command
|
|||
|
||||
private function renderTable(HealthCheckResult $result): void
|
||||
{
|
||||
$rows = [
|
||||
[
|
||||
'Default Database Connectivity',
|
||||
$result->checks->databaseDefault->status->value,
|
||||
$result->checks->databaseDefault->connection . ($result->checks->databaseDefault->message !== null ?
|
||||
" ({$result->checks->databaseDefault->message})" : ''),
|
||||
],
|
||||
[
|
||||
'Arcadia Database Connectivity',
|
||||
$result->checks->databaseArcadia->status->value,
|
||||
$result->checks->databaseArcadia->connection . ($result->checks->databaseArcadia->message !== null ?
|
||||
" ({$result->checks->databaseArcadia->message})" : ''),
|
||||
],
|
||||
[
|
||||
'Cache Store Connectivity',
|
||||
$result->checks->cache->status->value,
|
||||
$result->checks->cache->store . ($result->checks->cache->message !== null ?
|
||||
" ({$result->checks->cache->message})" : ''),
|
||||
],
|
||||
[
|
||||
'Mail SMTP Connectivity',
|
||||
$result->checks->mailSmtp->status->value,
|
||||
$result->checks->mailSmtp->transport . ($result->checks->mailSmtp->message !== null ?
|
||||
" ({$result->checks->mailSmtp->message})" : ''),
|
||||
],
|
||||
];
|
||||
$rows = [];
|
||||
foreach ($result->checks as $check) {
|
||||
$rows[] = $this->mapCheckToArray($check);
|
||||
}
|
||||
|
||||
$this->table(['Component', 'Status', 'Details'], $rows);
|
||||
}
|
||||
|
||||
/** @return array{name: string, status: string, message: string} */
|
||||
private function mapCheckToArray(CheckResult $check): array
|
||||
{
|
||||
return match (true) {
|
||||
$check instanceof DatabaseCheckResult => [
|
||||
'name' => $check->getName(),
|
||||
'status' => $check->status->value,
|
||||
'message' => $check->message ?? 'N/A',
|
||||
],
|
||||
$check instanceof CacheCheckResult => [
|
||||
'name' => $check->getName(),
|
||||
'status' => $check->status->value,
|
||||
'message' => $check->message ?? 'N/A',
|
||||
],
|
||||
$check instanceof MailCheckResult => [
|
||||
'name' => $check->getName(),
|
||||
'status' => $check->status->value,
|
||||
'message' => $check->message ?? 'N/A',
|
||||
],
|
||||
default => throw new \InvalidArgumentException('Unsupported check result type'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use Application\Health\Dto\CheckResult;
|
||||
use Application\Security\Models\ValueObjects\Request\HttpMethod;
|
||||
use Shared\DDD\Event\Event;
|
||||
use Shared\DDD\Port\Directory;
|
||||
|
|
@ -111,7 +112,10 @@ describe('Application Architecture Rules', function (): void {
|
|||
->expect('Application\Health\Dto')
|
||||
->classes()
|
||||
->toBeReadonly()
|
||||
->toBeFinal();
|
||||
->toBeFinal()
|
||||
->ignoring([
|
||||
CheckResult::class,
|
||||
]);
|
||||
|
||||
arch('Application Config classes are final and readonly')
|
||||
->expect([
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
it('returns 200 healthy when all infrastructure components are operational', function (): void {
|
||||
// GIVEN
|
||||
/** @var TestCase $this */
|
||||
$databaseDefault = Config::string('database.default');
|
||||
$databaseArcadia = Config::string('database.arcadia_meta_model');
|
||||
$cacheStore = Config::string('cache.default');
|
||||
$mailTransport = Config::string('mail.default');
|
||||
|
||||
// WHEN
|
||||
$response = $this->getJson('/api/v1/health');
|
||||
|
|
@ -42,28 +46,20 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
->assertJsonStructure([
|
||||
'status',
|
||||
'timestamp',
|
||||
'checks' => [
|
||||
'database_default' => ['status', 'connection'],
|
||||
'database_arcadia' => ['status', 'connection'],
|
||||
'cache' => ['status', 'store'],
|
||||
'mail_smtp' => ['status', 'transport'],
|
||||
],
|
||||
'checks',
|
||||
])
|
||||
->assertJson([
|
||||
'status' => 'healthy',
|
||||
'checks' => [
|
||||
'database_default' => ['status' => 'up'],
|
||||
'database_arcadia' => ['status' => 'up'],
|
||||
'cache' => ['status' => 'up'],
|
||||
'mail_smtp' => ['status' => 'up'],
|
||||
],
|
||||
]);
|
||||
->assertJsonPath('status', 'healthy')
|
||||
->assertJsonPath('checks.' . $databaseDefault . '.status', 'up')
|
||||
->assertJsonPath('checks.' . $databaseArcadia . '.status', 'up')
|
||||
->assertJsonPath('checks.' . $cacheStore . '.status', 'up')
|
||||
->assertJsonPath('checks.' . $mailTransport . '.status', 'up');
|
||||
});
|
||||
|
||||
it('returns 503 unhealthy when default database connection fails', function () use (&$originalDbDefault): void {
|
||||
// GIVEN
|
||||
/** @var TestCase $this */
|
||||
Config::set('database.default', 'invalid_connection');
|
||||
$databaseDefault = Config::string('database.default');
|
||||
|
||||
try {
|
||||
// WHEN
|
||||
|
|
@ -71,12 +67,8 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
|
||||
// THEN
|
||||
$response->assertStatus(503)
|
||||
->assertJson([
|
||||
'status' => 'unhealthy',
|
||||
'checks' => [
|
||||
'database_default' => ['status' => 'down'],
|
||||
],
|
||||
]);
|
||||
->assertJsonPath('status', 'unhealthy')
|
||||
->assertJsonPath('checks.' . $databaseDefault . '.status', 'down');
|
||||
} finally {
|
||||
Config::set('database.default', $originalDbDefault);
|
||||
}
|
||||
|
|
@ -92,6 +84,7 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
'database' => 'invalid_arcadia_db',
|
||||
]);
|
||||
Config::set('database.arcadia_meta_model', 'invalid_arcadia_connection');
|
||||
$databaseArcadia = Config::string('database.arcadia_meta_model');
|
||||
|
||||
try {
|
||||
// WHEN
|
||||
|
|
@ -99,12 +92,8 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
|
||||
// THEN
|
||||
$response->assertStatus(503)
|
||||
->assertJson([
|
||||
'status' => 'unhealthy',
|
||||
'checks' => [
|
||||
'database_arcadia' => ['status' => 'down'],
|
||||
],
|
||||
]);
|
||||
->assertJsonPath('status', 'unhealthy')
|
||||
->assertJsonPath('checks.' . $databaseArcadia . '.status', 'down');
|
||||
} finally {
|
||||
Config::set('database.arcadia_meta_model', $originalArcadiaDb);
|
||||
}
|
||||
|
|
@ -114,6 +103,7 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
// GIVEN
|
||||
/** @var TestCase $this */
|
||||
Config::set('cache.default', 'invalid_cache_store');
|
||||
$cacheStore = Config::string('cache.default');
|
||||
|
||||
try {
|
||||
// WHEN
|
||||
|
|
@ -121,12 +111,8 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
|
||||
// THEN
|
||||
$response->assertStatus(503)
|
||||
->assertJson([
|
||||
'status' => 'unhealthy',
|
||||
'checks' => [
|
||||
'cache' => ['status' => 'down'],
|
||||
],
|
||||
]);
|
||||
->assertJsonPath('status', 'unhealthy')
|
||||
->assertJsonPath('checks.' . $cacheStore . '.status', 'down');
|
||||
} finally {
|
||||
Config::set('cache.default', $originalCache);
|
||||
}
|
||||
|
|
@ -142,6 +128,7 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
'port' => 65432, // Non-existent port
|
||||
'timeout' => 0.1,
|
||||
]);
|
||||
$mailTransport = Config::string('mail.default');
|
||||
|
||||
try {
|
||||
// WHEN
|
||||
|
|
@ -149,12 +136,8 @@ describe('Health Check API (GET /api/v1/health)', function (): void {
|
|||
|
||||
// THEN
|
||||
$response->assertStatus(503)
|
||||
->assertJson([
|
||||
'status' => 'unhealthy',
|
||||
'checks' => [
|
||||
'mail_smtp' => ['status' => 'down'],
|
||||
],
|
||||
]);
|
||||
->assertJsonPath('status', 'unhealthy')
|
||||
->assertJsonPath('checks.' . $mailTransport . '.status', 'down');
|
||||
} finally {
|
||||
Config::set('mail.default', $originalMail);
|
||||
Config::set('mail.mailers.smtp', $originalMailer);
|
||||
|
|
|
|||
67
tests/Feature/Api/V1/Health/OracleHealthCheckTest.php
Normal file
67
tests/Feature/Api/V1/Health/OracleHealthCheckTest.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature\Api\V1\Health;
|
||||
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Tests\TestCase;
|
||||
|
||||
describe('Oracle Health Check API (GET /api/v1/health/oracle)', function (): void {
|
||||
$originalArcadiaDb = null;
|
||||
|
||||
beforeEach(function () use (&$originalArcadiaDb): void {
|
||||
$originalArcadiaDb = Config::get('database.arcadia_meta_model');
|
||||
});
|
||||
|
||||
afterEach(function () use (&$originalArcadiaDb): void {
|
||||
Config::set('database.arcadia_meta_model', $originalArcadiaDb);
|
||||
});
|
||||
|
||||
it('returns 200 healthy when oracle connectivity is operational', function (): void {
|
||||
// GIVEN
|
||||
/** @var TestCase $this */
|
||||
Config::set('database.arcadia_meta_model', 'sqlite');
|
||||
|
||||
// WHEN
|
||||
$response = $this->getJson('/api/v1/health/oracle');
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200)
|
||||
->assertJsonStructure([
|
||||
'status',
|
||||
'timestamp',
|
||||
'oracle' => ['status', 'connection'],
|
||||
])
|
||||
->assertJson([
|
||||
'status' => 'healthy',
|
||||
'oracle' => ['status' => 'up'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns 503 unhealthy when oracle connectivity fails', function () use (&$originalArcadiaDb): void {
|
||||
// GIVEN
|
||||
/** @var TestCase $this */
|
||||
Config::set('database.connections.invalid_arcadia_connection', [
|
||||
'driver' => 'mysql',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 65433,
|
||||
'database' => 'invalid_arcadia_db',
|
||||
]);
|
||||
Config::set('database.arcadia_meta_model', 'invalid_arcadia_connection');
|
||||
|
||||
try {
|
||||
// WHEN
|
||||
$response = $this->getJson('/api/v1/health/oracle');
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(503)
|
||||
->assertJson([
|
||||
'status' => 'unhealthy',
|
||||
'oracle' => ['status' => 'down'],
|
||||
]);
|
||||
} finally {
|
||||
Config::set('database.arcadia_meta_model', $originalArcadiaDb);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -31,7 +31,7 @@ describe('CheckArcadiaConformityCommand', function (): void {
|
|||
$this->app->instance(HealthChecker::class, $healthChecker);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:database:check-conformity')
|
||||
$this->artisan('uwil:arcadia:database:check-conformity')
|
||||
// THEN
|
||||
->expectsOutput('Arcadia database conformity check passed. All directory probes are conform.')
|
||||
->assertSuccessful();
|
||||
|
|
@ -52,7 +52,7 @@ describe('CheckArcadiaConformityCommand', function (): void {
|
|||
$this->app->instance(HealthChecker::class, $healthChecker);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:database:check-conformity')
|
||||
$this->artisan('uwil:arcadia:database:check-conformity')
|
||||
// THEN
|
||||
->expectsOutput('Arcadia database conformity check failed. Dissonances detected.')
|
||||
->assertFailed();
|
||||
|
|
@ -73,7 +73,7 @@ describe('CheckArcadiaConformityCommand', function (): void {
|
|||
$this->app->instance(HealthChecker::class, $healthChecker);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:database:check-conformity', ['--json' => true])
|
||||
$this->artisan('uwil:arcadia:database:check-conformity', ['--json' => true])
|
||||
// THEN
|
||||
->expectsOutputToContain('"status": "healthy"')
|
||||
->assertSuccessful();
|
||||
|
|
@ -94,7 +94,7 @@ describe('CheckArcadiaConformityCommand', function (): void {
|
|||
$this->app->instance(HealthChecker::class, $healthChecker);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:database:check-conformity', ['--json' => true])
|
||||
$this->artisan('uwil:arcadia:database:check-conformity', ['--json' => true])
|
||||
// THEN
|
||||
->expectsOutputToContain('"status": "unhealthy"')
|
||||
->assertFailed();
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe('CreateClientCommand', function (): void {
|
|||
$clientName = 'Production Gateway';
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:create')
|
||||
$this->artisan('uwil:arcadia:client:create')
|
||||
// THEN
|
||||
->expectsQuestion('Client name (e.g. "Ones Branding website")', $clientName)
|
||||
->expectsConfirmation('Create this API client?', 'yes')
|
||||
|
|
@ -38,7 +38,7 @@ describe('CreateClientCommand', function (): void {
|
|||
$clientName = 'Cancelled Gateway';
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:create')
|
||||
$this->artisan('uwil:arcadia:client:create')
|
||||
// THEN
|
||||
->expectsQuestion('Client name (e.g. "Ones Branding website")', $clientName)
|
||||
->expectsConfirmation('Create this API client?', 'no')
|
||||
|
|
@ -51,7 +51,8 @@ describe('CreateClientCommand', function (): void {
|
|||
|
||||
it('fails when an empty name is provided', function (): void {
|
||||
// GIVEN & WHEN & THEN
|
||||
$this->artisan('arcadia:client:create')
|
||||
/** @var \Tests\TestCase $this */
|
||||
$this->artisan('uwil:arcadia:client:create')
|
||||
->expectsQuestion('Client name (e.g. "Ones Branding website")', '')
|
||||
->assertFailed();
|
||||
});
|
||||
|
|
@ -65,7 +66,7 @@ describe('CreateClientCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN & THEN
|
||||
$this->artisan('arcadia:client:create')
|
||||
$this->artisan('uwil:arcadia:client:create')
|
||||
->expectsQuestion('Client name (e.g. "Ones Branding website")', 'Existing Gateway')
|
||||
->assertFailed();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ describe('ExportSecurityAuditLogsCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:security:audit-logs', [
|
||||
$this->artisan('uwil:arcadia:security:audit-logs', [
|
||||
'--since' => '2026-08-01',
|
||||
'--to' => '2026-08-21 23:59:59',
|
||||
])
|
||||
|
|
@ -58,7 +58,7 @@ describe('ExportSecurityAuditLogsCommand', function (): void {
|
|||
/** @var \Tests\TestCase $this */
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:security:audit-logs', [
|
||||
$this->artisan('uwil:arcadia:security:audit-logs', [
|
||||
'--since' => '2026-08-01',
|
||||
'--to' => '2026-08-02',
|
||||
])
|
||||
|
|
@ -82,7 +82,7 @@ describe('ExportSecurityAuditLogsCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:security:audit-logs', [
|
||||
$this->artisan('uwil:arcadia:security:audit-logs', [
|
||||
'--since' => '2026-08-01',
|
||||
'--csv' => true,
|
||||
])
|
||||
|
|
@ -102,7 +102,7 @@ describe('ExportSecurityAuditLogsCommand', function (): void {
|
|||
it('fails when an invalid date format is provided for --since', function (): void {
|
||||
// GIVEN & WHEN & THEN
|
||||
/** @var \Tests\TestCase $this */
|
||||
$this->artisan('arcadia:security:audit-logs', [
|
||||
$this->artisan('uwil:arcadia:security:audit-logs', [
|
||||
'--since' => 'invalid-date-string',
|
||||
])
|
||||
->expectsOutputToContain("Invalid date format for --since: 'invalid-date-string'.")
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ describe('HealthCheckCommand', function (): void {
|
|||
/** @var \Tests\TestCase $this */
|
||||
|
||||
// WHEN
|
||||
$this->artisan('health:check')
|
||||
$this->artisan('uwil:health:check')
|
||||
// THEN
|
||||
->expectsOutput('Health check passed. All components are healthy.')
|
||||
->assertSuccessful();
|
||||
|
|
@ -46,7 +46,7 @@ describe('HealthCheckCommand', function (): void {
|
|||
Config::set('database.default', 'invalid_connection');
|
||||
|
||||
// WHEN
|
||||
$this->artisan('health:check')
|
||||
$this->artisan('uwil:health:check')
|
||||
// THEN
|
||||
->expectsOutput('Health check failed. One or more components are unhealthy.')
|
||||
->assertFailed();
|
||||
|
|
@ -57,7 +57,7 @@ describe('HealthCheckCommand', function (): void {
|
|||
/** @var \Tests\TestCase $this */
|
||||
|
||||
// WHEN
|
||||
$this->artisan('health:check', ['--json' => true])
|
||||
$this->artisan('uwil:health:check', ['--json' => true])
|
||||
// THEN
|
||||
->expectsOutputToContain('"status": "healthy"')
|
||||
->assertSuccessful();
|
||||
|
|
@ -69,7 +69,7 @@ describe('HealthCheckCommand', function (): void {
|
|||
Config::set('database.default', 'invalid_connection');
|
||||
|
||||
// WHEN
|
||||
$this->artisan('health:check', ['--json' => true])
|
||||
$this->artisan('uwil:health:check', ['--json' => true])
|
||||
// THEN
|
||||
->expectsOutputToContain('"status": "unhealthy"')
|
||||
->assertFailed();
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ describe('ListBrandsCommand', function (): void {
|
|||
/** @var \Tests\TestCase $this */
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:brand:list')
|
||||
$this->artisan('uwil:arcadia:brand:list')
|
||||
// THEN
|
||||
->expectsOutput('No brands found.')
|
||||
->assertSuccessful();
|
||||
|
|
@ -48,7 +48,7 @@ describe('ListBrandsCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:brand:list')
|
||||
$this->artisan('uwil:arcadia:brand:list')
|
||||
// THEN
|
||||
->expectsOutput('Showing page 1/1 (Total: 2 brand(s))')
|
||||
->assertSuccessful();
|
||||
|
|
@ -68,7 +68,7 @@ describe('ListBrandsCommand', function (): void {
|
|||
}
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:brand:list', ['--page' => '2', '--per-page' => '2'])
|
||||
$this->artisan('uwil:arcadia:brand:list', ['--page' => '2', '--per-page' => '2'])
|
||||
// THEN
|
||||
->expectsOutput('Showing page 2/2 (Total: 3 brand(s))')
|
||||
->assertSuccessful();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe('ListClientsCommand', function (): void {
|
|||
ApiClient::query()->delete();
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:list')
|
||||
$this->artisan('uwil:arcadia:client:list')
|
||||
// THEN
|
||||
->expectsOutput('No clients found.')
|
||||
->assertSuccessful();
|
||||
|
|
@ -48,7 +48,7 @@ describe('ListClientsCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:list')
|
||||
$this->artisan('uwil:arcadia:client:list')
|
||||
// THEN
|
||||
->expectsTable(
|
||||
['ID', 'Name', 'Identifier', 'Status', 'Last Updated At'],
|
||||
|
|
@ -90,7 +90,7 @@ describe('ListClientsCommand', function (): void {
|
|||
}
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:list', ['--page' => '2', '--per-page' => '2'])
|
||||
$this->artisan('uwil:arcadia:client:list', ['--page' => '2', '--per-page' => '2'])
|
||||
// THEN
|
||||
->expectsOutput('Showing page 2/2 (Total: 3 client(s))')
|
||||
->assertSuccessful();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe('RegenerateClientSecretCommand', function (): void {
|
|||
$invalidIdentifier = 'invalid-id';
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:regenerate-secret', ['identifier' => $invalidIdentifier])
|
||||
$this->artisan('uwil:arcadia:client:regenerate-secret', ['identifier' => $invalidIdentifier])
|
||||
// THEN
|
||||
->expectsOutput("Invalid client identifier '{$invalidIdentifier}'. Expected a valid client identifier.")
|
||||
->assertFailed();
|
||||
|
|
@ -30,7 +30,7 @@ describe('RegenerateClientSecretCommand', function (): void {
|
|||
$nonExistentIdentifier = 'arcadia_id_' . str_repeat('f', 32);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:regenerate-secret', ['identifier' => $nonExistentIdentifier])
|
||||
$this->artisan('uwil:arcadia:client:regenerate-secret', ['identifier' => $nonExistentIdentifier])
|
||||
// THEN
|
||||
->expectsOutput("Client '{$nonExistentIdentifier}' not found.")
|
||||
->assertFailed();
|
||||
|
|
@ -52,7 +52,7 @@ describe('RegenerateClientSecretCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:regenerate-secret', ['identifier' => $identifier])
|
||||
$this->artisan('uwil:arcadia:client:regenerate-secret', ['identifier' => $identifier])
|
||||
// THEN
|
||||
->expectsConfirmation('Do you want to proceed?', 'yes')
|
||||
->expectsOutputToContain("Secret for client '{$identifier}' regenerated successfully.")
|
||||
|
|
@ -80,7 +80,7 @@ describe('RegenerateClientSecretCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:regenerate-secret', ['identifier' => $identifier])
|
||||
$this->artisan('uwil:arcadia:client:regenerate-secret', ['identifier' => $identifier])
|
||||
// THEN
|
||||
->expectsConfirmation('Do you want to proceed?', 'no')
|
||||
->expectsOutput('Secret regeneration cancelled.')
|
||||
|
|
@ -106,7 +106,7 @@ describe('RegenerateClientSecretCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:regenerate-secret')
|
||||
$this->artisan('uwil:arcadia:client:regenerate-secret')
|
||||
// THEN
|
||||
->expectsQuestion('Search client (by name, ID, identifier, or slug):', $identifier)
|
||||
->expectsChoice('Search client (by name, ID, identifier, or slug):', $identifier, [
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe('ToggleClientStatusCommand', function (): void {
|
|||
$invalidId = 'not-a-valid-identifier';
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:toggle-status', ['identifier' => $invalidId])
|
||||
$this->artisan('uwil:arcadia:client:toggle-status', ['identifier' => $invalidId])
|
||||
// THEN
|
||||
->expectsOutput("Invalid client identifier '{$invalidId}'. Expected a valid client identifier.")
|
||||
->assertFailed();
|
||||
|
|
@ -30,7 +30,7 @@ describe('ToggleClientStatusCommand', function (): void {
|
|||
$nonExistentId = 'arcadia_id_' . str_repeat('9', 32);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:toggle-status', ['identifier' => $nonExistentId])
|
||||
$this->artisan('uwil:arcadia:client:toggle-status', ['identifier' => $nonExistentId])
|
||||
// THEN
|
||||
->expectsOutput("Client '{$nonExistentId}' not found.")
|
||||
->assertFailed();
|
||||
|
|
@ -49,7 +49,7 @@ describe('ToggleClientStatusCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:toggle-status', ['identifier' => (string) $client->identifier->value])
|
||||
$this->artisan('uwil:arcadia:client:toggle-status', ['identifier' => (string) $client->identifier->value])
|
||||
// THEN
|
||||
->expectsOutput("Client 'Production Gateway' (ID: {$client->id}) is now Inactive.")
|
||||
->assertSuccessful();
|
||||
|
|
@ -71,7 +71,7 @@ describe('ToggleClientStatusCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:toggle-status', ['identifier' => (string) $client->identifier->value])
|
||||
$this->artisan('uwil:arcadia:client:toggle-status', ['identifier' => (string) $client->identifier->value])
|
||||
// THEN
|
||||
->expectsOutput("Client 'Backup Gateway' (ID: {$client->id}) is now Active.")
|
||||
->assertSuccessful();
|
||||
|
|
@ -93,7 +93,7 @@ describe('ToggleClientStatusCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:toggle-status', ['identifier' => (string) $client->identifier->value, 'status' => 'activate'])
|
||||
$this->artisan('uwil:arcadia:client:toggle-status', ['identifier' => (string) $client->identifier->value, 'status' => 'activate'])
|
||||
// THEN
|
||||
->expectsOutput("Client 'Special Gateway' (ID: {$client->id}) is now Active.")
|
||||
->assertSuccessful();
|
||||
|
|
@ -115,7 +115,7 @@ describe('ToggleClientStatusCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:toggle-status', ['identifier' => (string) $client->identifier->value, 'status' => 'deactivate'])
|
||||
$this->artisan('uwil:arcadia:client:toggle-status', ['identifier' => (string) $client->identifier->value, 'status' => 'deactivate'])
|
||||
// THEN
|
||||
->expectsOutput("Client 'Special Gateway' (ID: {$client->id}) is now Inactive.")
|
||||
->assertSuccessful();
|
||||
|
|
@ -138,7 +138,7 @@ describe('ToggleClientStatusCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:toggle-status', ['identifier' => $identifier, 'status' => 'unknown'])
|
||||
$this->artisan('uwil:arcadia:client:toggle-status', ['identifier' => $identifier, 'status' => 'unknown'])
|
||||
// THEN
|
||||
->expectsOutput("Invalid status 'unknown'. Expected 'activate' or 'deactivate'.")
|
||||
->assertFailed();
|
||||
|
|
@ -158,7 +158,7 @@ describe('ToggleClientStatusCommand', function (): void {
|
|||
]);
|
||||
|
||||
// WHEN
|
||||
$this->artisan('arcadia:client:toggle-status')
|
||||
$this->artisan('uwil:arcadia:client:toggle-status')
|
||||
// THEN
|
||||
->expectsQuestion('Search client (by name, ID, identifier, or slug):', $identifier)
|
||||
->expectsChoice('Search client (by name, ID, identifier, or slug):', $identifier, [
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Tests\Feature\OpenApi\V1\Health;
|
||||
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Spectator\Spectator;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
|
@ -26,4 +27,24 @@ describe('Health Check OpenAPI Conformance', function (): void {
|
|||
$response->assertValidRequest();
|
||||
$response->assertValidResponse(200);
|
||||
});
|
||||
|
||||
it('validates GET /api/v1/health/oracle response against openapi spec', function (): void {
|
||||
// GIVEN
|
||||
/** @var TestCase $this */
|
||||
$originalArcadiaDb = Config::get('database.arcadia_meta_model');
|
||||
Config::set('database.arcadia_meta_model', 'sqlite');
|
||||
|
||||
try {
|
||||
// WHEN
|
||||
/** @var \Illuminate\Testing\TestResponse<\Symfony\Component\HttpFoundation\Response> $response */
|
||||
$response = $this->getJson('/api/v1/health/oracle');
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertValidRequest();
|
||||
$response->assertValidResponse(200);
|
||||
} finally {
|
||||
Config::set('database.arcadia_meta_model', $originalArcadiaDb);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ use Application\Health\Contracts\HealthChecker;
|
|||
use Application\Health\Dto\CacheCheckResult;
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Dto\HealthCheckResult;
|
||||
use Application\Health\Dto\HealthChecks;
|
||||
use Application\Health\Dto\MailCheckResult;
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
use Application\Health\Enums\HealthStatus;
|
||||
|
|
@ -51,12 +50,12 @@ describe('HealthCheckJob Integration Test', function (): void {
|
|||
$healthyResult = new HealthCheckResult(
|
||||
status: HealthStatus::HEALTHY,
|
||||
timestamp: Utc::now(),
|
||||
checks: new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
),
|
||||
checks: [
|
||||
'database_default' => new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
'database_arcadia' => new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
'cache' => new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
'mail_smtp' => new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
],
|
||||
);
|
||||
|
||||
$healthChecker->shouldReceive('check')->once()->andReturn($healthyResult);
|
||||
|
|
@ -83,12 +82,12 @@ describe('HealthCheckJob Integration Test', function (): void {
|
|||
$unhealthyResult = new HealthCheckResult(
|
||||
status: HealthStatus::UNHEALTHY,
|
||||
timestamp: Utc::now(),
|
||||
checks: new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::DOWN, 'sqlite', 'Connection refused'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
),
|
||||
checks: [
|
||||
'database_default' => new DatabaseCheckResult(ComponentStatus::DOWN, 'sqlite', 'Connection refused'),
|
||||
'database_arcadia' => new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
'cache' => new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
'mail_smtp' => new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
],
|
||||
);
|
||||
|
||||
$healthChecker->shouldReceive('check')->once()->andReturn($unhealthyResult);
|
||||
|
|
@ -124,12 +123,12 @@ describe('HealthCheckJob Integration Test', function (): void {
|
|||
$unhealthyResult = new HealthCheckResult(
|
||||
status: HealthStatus::UNHEALTHY,
|
||||
timestamp: Utc::now(),
|
||||
checks: new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::DOWN, 'arcadia', 'DB down'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
),
|
||||
checks: [
|
||||
'database_default' => new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
'database_arcadia' => new DatabaseCheckResult(ComponentStatus::DOWN, 'arcadia', 'DB down'),
|
||||
'cache' => new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
'mail_smtp' => new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
],
|
||||
);
|
||||
|
||||
$healthChecker->shouldReceive('check')->once()->andReturn($unhealthyResult);
|
||||
|
|
@ -161,12 +160,12 @@ describe('HealthCheckJob Integration Test', function (): void {
|
|||
$healthyResult = new HealthCheckResult(
|
||||
status: HealthStatus::HEALTHY,
|
||||
timestamp: Utc::now(),
|
||||
checks: new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
),
|
||||
checks: [
|
||||
'database_default' => new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
'database_arcadia' => new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
'cache' => new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
'mail_smtp' => new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
],
|
||||
);
|
||||
|
||||
$healthChecker->shouldReceive('check')->once()->andReturn($healthyResult);
|
||||
|
|
@ -198,12 +197,12 @@ describe('HealthCheckJob Integration Test', function (): void {
|
|||
$healthyResult = new HealthCheckResult(
|
||||
status: HealthStatus::HEALTHY,
|
||||
timestamp: Utc::now(),
|
||||
checks: new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
),
|
||||
checks: [
|
||||
'database_default' => new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
'database_arcadia' => new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
'cache' => new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
'mail_smtp' => new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
],
|
||||
);
|
||||
|
||||
$healthChecker->shouldReceive('check')->once()->andReturn($healthyResult);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ namespace Tests\Unit\Application\Health;
|
|||
use Application\Health\Dto\CacheCheckResult;
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Dto\HealthCheckResult;
|
||||
use Application\Health\Dto\HealthChecks;
|
||||
use Application\Health\Dto\MailCheckResult;
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
use Application\Health\Enums\HealthStatus;
|
||||
|
|
@ -79,36 +78,15 @@ describe('Health Check DTOs & Enums Unit Tests', function (): void {
|
|||
->and($mailFailed->message)->toBe('Connection refused');
|
||||
});
|
||||
|
||||
it('evaluates HealthChecks allUp correctly', function (): void {
|
||||
// GIVEN
|
||||
$checksUp = new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'database'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
);
|
||||
|
||||
$checksDown = new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::DOWN, 'sqlite', 'Failed'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'database'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
);
|
||||
|
||||
// WHEN & THEN
|
||||
expect($checksUp->isAllUp())->toBeTrue()
|
||||
->and($checksDown->isAllUp())->toBeFalse();
|
||||
});
|
||||
|
||||
it('instantiates HealthCheckResult and evaluates isHealthy', function (): void {
|
||||
// GIVEN
|
||||
$date = new \DateTimeImmutable('2026-08-21 14:30:00 UTC');
|
||||
$checks = new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::DOWN, 'arcadia', 'Timeout connecting to DB'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'database'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'smtp'),
|
||||
);
|
||||
$checks = [
|
||||
'database_default' => new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
'database_arcadia' => new DatabaseCheckResult(ComponentStatus::DOWN, 'arcadia', 'Timeout connecting to DB'),
|
||||
'cache' => new CacheCheckResult(ComponentStatus::UP, 'database'),
|
||||
'mail_smtp' => new MailCheckResult(ComponentStatus::UP, 'smtp'),
|
||||
];
|
||||
|
||||
$result = new HealthCheckResult(
|
||||
status: HealthStatus::UNHEALTHY,
|
||||
|
|
@ -120,6 +98,6 @@ describe('Health Check DTOs & Enums Unit Tests', function (): void {
|
|||
expect($result->isHealthy())->toBeFalse()
|
||||
->and($result->status)->toBe(HealthStatus::UNHEALTHY)
|
||||
->and($result->timestamp)->toBe($date)
|
||||
->and($result->checks->databaseArcadia->message)->toBe('Timeout connecting to DB');
|
||||
->and($result->checks['database_arcadia']->message)->toBe('Timeout connecting to DB');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ namespace Tests\Unit\Presentation\Api\V1\Http\Responses\Health;
|
|||
use Application\Health\Dto\CacheCheckResult;
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Dto\HealthCheckResult;
|
||||
use Application\Health\Dto\HealthChecks;
|
||||
use Application\Health\Dto\MailCheckResult;
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
use Application\Health\Enums\HealthStatus;
|
||||
|
|
@ -19,12 +18,15 @@ describe('HealthCheckResponse Unit Test', function (): void {
|
|||
it('serializes healthy HealthCheckResult into 200 JsonResponse and array', function (): void {
|
||||
// GIVEN
|
||||
$date = new \DateTimeImmutable('2026-08-27 15:00:00 UTC');
|
||||
$checks = new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
);
|
||||
$checks = [
|
||||
'database_default' => new DatabaseCheckResult(ComponentStatus::UP, 'sqlite'),
|
||||
'database_arcadia' => new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
'cache' => new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
'mail_smtp' => new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
];
|
||||
|
||||
$databaseDefaultKey = $checks['database_default']->getName();
|
||||
$databaseArcadiaKey = $checks['database_arcadia']->getName();
|
||||
|
||||
$result = new HealthCheckResult(HealthStatus::HEALTHY, $date, $checks);
|
||||
$response = new HealthCheckResponse($result);
|
||||
|
|
@ -37,19 +39,21 @@ describe('HealthCheckResponse Unit Test', function (): void {
|
|||
expect($json->getStatusCode())->toBe(200)
|
||||
->and($array['status'])->toBe('healthy')
|
||||
->and($array['timestamp'])->toBe('2026-08-27T15:00:00+00:00')
|
||||
->and($array['checks']['database_default']['status'])->toBe('up')
|
||||
->and($array['checks']['database_arcadia']['status'])->toBe('up');
|
||||
->and($array['checks'][$databaseDefaultKey]['status'])->toBe('up')
|
||||
->and($array['checks'][$databaseArcadiaKey]['status'])->toBe('up');
|
||||
});
|
||||
|
||||
it('serializes unhealthy HealthCheckResult into 503 JsonResponse and array', function (): void {
|
||||
// GIVEN
|
||||
$date = new \DateTimeImmutable('2026-08-27 15:00:00 UTC');
|
||||
$checks = new HealthChecks(
|
||||
databaseDefault: new DatabaseCheckResult(ComponentStatus::DOWN, 'sqlite', 'Connection refused'),
|
||||
databaseArcadia: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
cache: new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
mailSmtp: new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
);
|
||||
$checks = [
|
||||
'database_default' => new DatabaseCheckResult(ComponentStatus::DOWN, 'sqlite', 'Connection refused'),
|
||||
'database_arcadia' => new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
'cache' => new CacheCheckResult(ComponentStatus::UP, 'file'),
|
||||
'mail_smtp' => new MailCheckResult(ComponentStatus::UP, 'log'),
|
||||
];
|
||||
|
||||
$databaseDefaultKey = $checks['database_default']->getName();
|
||||
|
||||
$result = new HealthCheckResult(HealthStatus::UNHEALTHY, $date, $checks);
|
||||
$response = new HealthCheckResponse($result);
|
||||
|
|
@ -61,6 +65,6 @@ describe('HealthCheckResponse Unit Test', function (): void {
|
|||
// THEN
|
||||
expect($json->getStatusCode())->toBe(503)
|
||||
->and($array['status'])->toBe('unhealthy')
|
||||
->and($array['checks']['database_default']['message'])->toBe('Connection refused');
|
||||
->and($array['checks'][$databaseDefaultKey]['message'])->toBe('Connection refused');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Presentation\Api\V1\Http\Responses\Health;
|
||||
|
||||
use Application\Health\Dto\DatabaseCheckResult;
|
||||
use Application\Health\Enums\ComponentStatus;
|
||||
use Presentation\Api\V1\Http\Responses\Health\OracleHealthCheckResponse;
|
||||
|
||||
covers(OracleHealthCheckResponse::class);
|
||||
|
||||
describe('OracleHealthCheckResponse Unit Test', function (): void {
|
||||
it('serializes healthy oracle connectivity into 200 JsonResponse and array', function (): void {
|
||||
// GIVEN
|
||||
$date = new \DateTimeImmutable('2026-10-06 10:00:00 UTC');
|
||||
$response = new OracleHealthCheckResponse(
|
||||
result: new DatabaseCheckResult(ComponentStatus::UP, 'arcadia'),
|
||||
timestamp: $date,
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$array = $response->toArray();
|
||||
$json = $response->present();
|
||||
|
||||
// THEN
|
||||
expect($json->getStatusCode())->toBe(200)
|
||||
->and($array['status'])->toBe('healthy')
|
||||
->and($array['timestamp'])->toBe('2026-10-06T10:00:00+00:00')
|
||||
->and($array['oracle']['status'])->toBe('up')
|
||||
->and($array['oracle']['connection'])->toBe('arcadia');
|
||||
});
|
||||
|
||||
it('serializes unhealthy oracle connectivity into 503 JsonResponse and array', function (): void {
|
||||
// GIVEN
|
||||
$date = new \DateTimeImmutable('2026-10-06 10:00:00 UTC');
|
||||
$response = new OracleHealthCheckResponse(
|
||||
result: new DatabaseCheckResult(ComponentStatus::DOWN, 'arcadia', 'Connection refused'),
|
||||
timestamp: $date,
|
||||
);
|
||||
|
||||
// WHEN
|
||||
$array = $response->toArray();
|
||||
$json = $response->present();
|
||||
|
||||
// THEN
|
||||
expect($json->getStatusCode())->toBe(503)
|
||||
->and($array['status'])->toBe('unhealthy')
|
||||
->and($array['oracle']['status'])->toBe('down');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue