ldc-arcadia-api/tests/Integration/Infra/Jobs/CheckArcadiaDatabaseConformityJobTest.php

227 lines
8.5 KiB
PHP
Raw Permalink Normal View History

<?php
declare(strict_types=1);
namespace Tests\Integration\Infra\Jobs;
use Application\Health\Config\HealthAlertMailConfig;
use Application\Health\Contracts\HealthChecker;
use Application\Health\Dto\ArcadiaConformityResult;
use Application\Health\Dto\DirectoryConformityCheck;
use Application\Health\Enums\ComponentStatus;
use Application\Health\Enums\HealthStatus;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Mail;
use Infra\Jobs\CheckArcadiaDatabaseConformityJob;
use Infra\Mail\ArcadiaConformityAlertMail;
use Psr\Log\LoggerInterface;
use Shared\Time\Utc;
use Tests\TestCase;
covers(CheckArcadiaDatabaseConformityJob::class);
uses(TestCase::class, RefreshDatabase::class);
describe('CheckArcadiaDatabaseConformityJob Integration Test', function (): void {
$tempStateFile = null;
beforeEach(function () use (&$tempStateFile): void {
$tempStateFile = sys_get_temp_dir() . '/arcadia_conformity_test_' . uniqid() . '.json';
if (File::exists($tempStateFile)) {
File::delete($tempStateFile);
}
Mail::fake();
});
afterEach(function () use (&$tempStateFile): void {
if ($tempStateFile !== null && File::exists($tempStateFile)) {
File::delete($tempStateFile);
}
});
it('does not send email on initial run when conform and saves state', function () use (&$tempStateFile): void {
// GIVEN
$job = new CheckArcadiaDatabaseConformityJob($tempStateFile);
$healthChecker = mock(HealthChecker::class);
$healthyResult = new ArcadiaConformityResult(
status: HealthStatus::HEALTHY,
timestamp: Utc::now(),
checks: [
new DirectoryConformityCheck('brands', ComponentStatus::UP, 'MARQUE', 10),
],
);
$healthChecker->shouldReceive('checkArcadiaConformity')->once()->andReturn($healthyResult);
// WHEN
$job->handle(
$healthChecker,
app(Mailer::class),
app(HealthAlertMailConfig::class),
app(LoggerInterface::class),
);
// THEN
expect(File::exists($tempStateFile))->toBeTrue();
$saved = json_decode((string) File::get($tempStateFile), true);
expect($saved['status'])->toBe('healthy')
->and($saved['checksum'])->toBe('healthy');
Mail::assertNothingSent();
});
it('sends alert email on initial run when non-conform and saves error checksum', function () use (&$tempStateFile): void {
// GIVEN
$job = new CheckArcadiaDatabaseConformityJob($tempStateFile);
$healthChecker = mock(HealthChecker::class);
$unhealthyResult = new ArcadiaConformityResult(
status: HealthStatus::UNHEALTHY,
timestamp: Utc::now(),
checks: [
new DirectoryConformityCheck('brands', ComponentStatus::DOWN, 'MARQUE', 0, 'Column LIB_MARQ missing'),
],
);
$healthChecker->shouldReceive('checkArcadiaConformity')->once()->andReturn($unhealthyResult);
// WHEN
$job->handle(
$healthChecker,
app(Mailer::class),
app(HealthAlertMailConfig::class),
app(LoggerInterface::class),
);
// THEN
expect(File::exists($tempStateFile))->toBeTrue();
$saved = json_decode((string) File::get($tempStateFile), true);
expect($saved['status'])->toBe('unhealthy')
->and($saved['checksum'])->toBe($unhealthyResult->checksum());
Mail::assertSent(
ArcadiaConformityAlertMail::class,
static function (ArcadiaConformityAlertMail $mail) use ($unhealthyResult): bool {
return $mail->status === HealthStatus::UNHEALTHY
&& $mail->checksum === $unhealthyResult->checksum()
&& $mail->previousStatus === null;
}
);
});
it('suppresses alert email when error persists with identical checksum', function () use (&$tempStateFile): void {
// GIVEN: previously saved state with specific checksum
$unhealthyResult = new ArcadiaConformityResult(
status: HealthStatus::UNHEALTHY,
timestamp: Utc::now(),
checks: [
new DirectoryConformityCheck(
'brands',
ComponentStatus::DOWN,
'MARQUE',
0,
'Column LIB_MARQ missing'
),
],
);
File::put($tempStateFile, (string) json_encode([
'status' => 'unhealthy',
'checksum' => $unhealthyResult->checksum(),
'timestamp' => Utc::now()->format(\DateTimeInterface::RFC3339),
]));
$job = new CheckArcadiaDatabaseConformityJob($tempStateFile);
$healthChecker = mock(HealthChecker::class);
$healthChecker->shouldReceive('checkArcadiaConformity')->once()->andReturn($unhealthyResult);
// WHEN
$job->handle(
$healthChecker,
app(Mailer::class),
app(HealthAlertMailConfig::class),
app(LoggerInterface::class),
);
// THEN
Mail::assertNothingSent();
});
it('sends new alert email when error evolves with different checksum', function () use (&$tempStateFile): void {
// GIVEN: previous error checksum
$previousChecksum = 'old_error_checksum_hash_value_12345';
File::put($tempStateFile, (string) json_encode([
'status' => 'unhealthy',
'checksum' => $previousChecksum,
'timestamp' => Utc::now()->format(\DateTimeInterface::RFC3339),
]));
$newUnhealthyResult = new ArcadiaConformityResult(
status: HealthStatus::UNHEALTHY,
timestamp: Utc::now(),
checks: [
new DirectoryConformityCheck('brands', ComponentStatus::DOWN, 'MARQUE', 0, 'Column LIB_MARQ missing'),
new DirectoryConformityCheck('employees', ComponentStatus::DOWN, 'RU_PERSONNE_SIRH', 0, 'Table not found'),
],
);
$job = new CheckArcadiaDatabaseConformityJob($tempStateFile);
$healthChecker = mock(HealthChecker::class);
$healthChecker->shouldReceive('checkArcadiaConformity')->once()->andReturn($newUnhealthyResult);
// WHEN
$job->handle(
$healthChecker,
app(Mailer::class),
app(HealthAlertMailConfig::class),
app(LoggerInterface::class),
);
// THEN
Mail::assertSent(ArcadiaConformityAlertMail::class, static function (ArcadiaConformityAlertMail $mail) use ($newUnhealthyResult): bool {
return $mail->status === HealthStatus::UNHEALTHY
&& $mail->checksum === $newUnhealthyResult->checksum()
&& $mail->previousStatus === HealthStatus::UNHEALTHY;
});
});
it('sends recovery email when transitioning from unhealthy to healthy', function () use (&$tempStateFile): void {
// GIVEN: previous unhealthy state
File::put($tempStateFile, (string) json_encode([
'status' => 'unhealthy',
'checksum' => 'any_previous_error_checksum',
'timestamp' => Utc::now()->format(\DateTimeInterface::RFC3339),
]));
$healthyResult = new ArcadiaConformityResult(
status: HealthStatus::HEALTHY,
timestamp: Utc::now(),
checks: [
new DirectoryConformityCheck('brands', ComponentStatus::UP, 'MARQUE', 10),
],
);
$job = new CheckArcadiaDatabaseConformityJob($tempStateFile);
$healthChecker = mock(HealthChecker::class);
$healthChecker->shouldReceive('checkArcadiaConformity')->once()->andReturn($healthyResult);
// WHEN
$job->handle(
$healthChecker,
app(Mailer::class),
app(HealthAlertMailConfig::class),
app(LoggerInterface::class),
);
// THEN
Mail::assertSent(
ArcadiaConformityAlertMail::class,
static function (ArcadiaConformityAlertMail $mail): bool {
return $mail->status === HealthStatus::HEALTHY
&& $mail->checksum === 'healthy'
&& $mail->previousStatus === HealthStatus::UNHEALTHY;
}
);
});
});