Files
breadcrumb-the-shire/tests/Service/Scheduler/SchedulerRunServiceTest.php

143 lines
5.8 KiB
PHP
Raw Normal View History

2026-02-23 12:58:19 +01:00
<?php
namespace MintyPHP\Tests\Service\Scheduler;
use MintyPHP\Repository\Scheduler\ScheduledJobRepository;
use MintyPHP\Repository\Scheduler\ScheduledJobRunRepository;
use MintyPHP\Repository\Scheduler\SchedulerRuntimeRepository;
2026-03-04 15:56:58 +01:00
use MintyPHP\Repository\Support\DatabaseSessionRepository;
use MintyPHP\Service\Audit\AuditRecorderInterface;
2026-02-23 12:58:19 +01:00
use MintyPHP\Service\Scheduler\ScheduleCalculator;
use MintyPHP\Service\Scheduler\ScheduledJobRegistry;
use MintyPHP\Service\Scheduler\ScheduledJobService;
use MintyPHP\Service\Scheduler\SchedulerRunService;
use PHPUnit\Framework\TestCase;
class SchedulerRunServiceTest extends TestCase
{
public function testRunJobNowRejectsInvalidRequest(): void
{
$scheduledJobService = $this->createMock(ScheduledJobService::class);
$scheduledJobService->expects($this->once())->method('ensureSystemJobs');
$systemAuditService = $this->createMock(AuditRecorderInterface::class);
2026-03-04 15:56:58 +01:00
$systemAuditService->expects($this->once())
->method('record')
->with(
'scheduler.run',
'failed',
$this->callback(static function (array $context): bool {
$metadata = is_array($context['metadata'] ?? null) ? $context['metadata'] : [];
return ($metadata['trigger_type'] ?? '') === 'manual'
&& ($metadata['run_status'] ?? '') === 'failed';
})
);
2026-02-23 12:58:19 +01:00
$service = new SchedulerRunService(
$scheduledJobService,
$this->createMock(ScheduledJobRepository::class),
$this->createMock(ScheduledJobRunRepository::class),
$this->createMock(SchedulerRuntimeRepository::class),
$this->createMock(ScheduledJobRegistry::class),
2026-03-04 15:56:58 +01:00
new ScheduleCalculator(),
$this->createMock(DatabaseSessionRepository::class),
$systemAuditService
2026-02-23 12:58:19 +01:00
);
$result = $service->runJobNow(0, 0);
$this->assertFalse($result['ok']);
$this->assertSame('invalid_request', $result['error']);
}
2026-03-04 15:56:58 +01:00
public function testRunJobNowSkippedRunIsAuditedAsSuccessOutcome(): void
{
$scheduledJobService = $this->createMock(ScheduledJobService::class);
$scheduledJobService->expects($this->once())->method('ensureSystemJobs');
$scheduledJobRepository = $this->createMock(ScheduledJobRepository::class);
$scheduledJobRepository->expects($this->once())
->method('find')
->with(7)
->willReturn([
'id' => 7,
'job_key' => 'user_lifecycle_run',
'enabled' => 1,
'next_run_at' => gmdate('Y-m-d H:i:s'),
'catchup_once' => 1,
]);
$scheduledJobRepository->expects($this->once())
->method('markRunning')
2026-03-05 08:26:51 +01:00
->with(7, $this->isString())
2026-03-04 15:56:58 +01:00
->willReturn(false);
$scheduledJobRunRepository = $this->createMock(ScheduledJobRunRepository::class);
$scheduledJobRunRepository->expects($this->once())
->method('create')
2026-03-05 08:26:51 +01:00
->with($this->isArray())
2026-03-04 15:56:58 +01:00
->willReturn(1);
$databaseSessionRepository = $this->createMock(DatabaseSessionRepository::class);
$databaseSessionRepository->expects($this->once())
->method('acquireAdvisoryLock')
->with('scheduled_jobs_runner', 0)
->willReturn(true);
$databaseSessionRepository->expects($this->once())
->method('releaseAdvisoryLock')
->with('scheduled_jobs_runner');
$records = [];
$systemAuditService = $this->createMock(AuditRecorderInterface::class);
2026-03-04 15:56:58 +01:00
$systemAuditService->expects($this->exactly(2))
->method('record')
->willReturnCallback(static function (string $eventType, string $outcome, array $context) use (&$records): int {
$records[] = [
'event_type' => $eventType,
'outcome' => $outcome,
'context' => $context,
];
return 1;
});
$service = new SchedulerRunService(
$scheduledJobService,
$scheduledJobRepository,
$scheduledJobRunRepository,
$this->createMock(SchedulerRuntimeRepository::class),
$this->createMock(ScheduledJobRegistry::class),
new ScheduleCalculator(),
$databaseSessionRepository,
$systemAuditService
);
$result = $service->runJobNow(7, 42);
$this->assertTrue($result['ok']);
$this->assertSame('skipped', $result['status']);
$this->assertSame('job_already_running', $result['error']);
$this->assertCount(2, $records);
$jobRecord = null;
$runRecord = null;
foreach ($records as $record) {
2026-03-05 08:26:51 +01:00
if ($record['event_type'] === 'scheduler.job.run') {
2026-03-04 15:56:58 +01:00
$jobRecord = $record;
}
2026-03-05 08:26:51 +01:00
if ($record['event_type'] === 'scheduler.run') {
2026-03-04 15:56:58 +01:00
$runRecord = $record;
}
}
$this->assertNotNull($jobRecord);
$this->assertNotNull($runRecord);
$this->assertSame('success', $jobRecord['outcome']);
$this->assertSame('success', $runRecord['outcome']);
$jobMetadata = is_array($jobRecord['context']['metadata'] ?? null) ? $jobRecord['context']['metadata'] : [];
$runMetadata = is_array($runRecord['context']['metadata'] ?? null) ? $runRecord['context']['metadata'] : [];
$this->assertSame('manual', $jobMetadata['trigger_type'] ?? null);
$this->assertSame('skipped', $jobMetadata['run_status'] ?? null);
$this->assertSame('manual', $runMetadata['trigger_type'] ?? null);
$this->assertSame('skipped', $runMetadata['run_status'] ?? null);
}
2026-02-23 12:58:19 +01:00
}