133 lines
4.4 KiB
PHP
133 lines
4.4 KiB
PHP
<?php
|
|
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\Events\StepAdvanced;
|
|
use App\Provisioning\Jobs\AdvanceRunJob;
|
|
use App\Provisioning\PipelineRegistry;
|
|
use App\Provisioning\RunRunner;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Tests\Support\Steps\FakeAdvanceStep;
|
|
use Tests\Support\Steps\FakeFailStep;
|
|
use Tests\Support\Steps\FakeRetryStep;
|
|
use Tests\Support\Steps\FakeThrowStep;
|
|
|
|
function bindPipeline(array $pipelines): void
|
|
{
|
|
app()->instance(PipelineRegistry::class, new PipelineRegistry($pipelines));
|
|
}
|
|
|
|
it('advances to the next step and queues the follow-up job', function () {
|
|
Queue::fake();
|
|
bindPipeline(['test' => [FakeAdvanceStep::class, FakeAdvanceStep::class]]);
|
|
$run = ProvisioningRun::factory()->create(['pipeline' => 'test', 'current_step' => 0, 'status' => 'pending']);
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
$run->refresh();
|
|
|
|
expect($run->current_step)->toBe(1)
|
|
->and($run->status)->toBe('running')
|
|
->and($run->events()->where('outcome', 'advanced')->count())->toBe(1);
|
|
Queue::assertPushed(AdvanceRunJob::class);
|
|
});
|
|
|
|
it('completes the run on the last step', function () {
|
|
Queue::fake();
|
|
bindPipeline(['test' => [FakeAdvanceStep::class]]);
|
|
$run = ProvisioningRun::factory()->create(['pipeline' => 'test', 'status' => 'pending']);
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
$run->refresh();
|
|
|
|
expect($run->status)->toBe('completed')->and($run->finished_at)->not->toBeNull();
|
|
Queue::assertNotPushed(AdvanceRunJob::class);
|
|
});
|
|
|
|
it('waits and increments attempt on retry', function () {
|
|
Queue::fake();
|
|
bindPipeline(['test' => [FakeRetryStep::class]]);
|
|
$run = ProvisioningRun::factory()->create(['pipeline' => 'test', 'max_attempts' => 5]);
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
$run->refresh();
|
|
|
|
expect($run->status)->toBe('waiting')
|
|
->and($run->attempt)->toBe(1)
|
|
->and($run->next_attempt_at)->not->toBeNull();
|
|
Queue::assertNotPushed(AdvanceRunJob::class);
|
|
});
|
|
|
|
it('fails once retries are exhausted', function () {
|
|
bindPipeline(['test' => [FakeRetryStep::class]]);
|
|
$run = ProvisioningRun::factory()->create(['pipeline' => 'test', 'max_attempts' => 1]);
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
|
|
expect($run->refresh()->status)->toBe('failed');
|
|
});
|
|
|
|
it('fails immediately on a fail result', function () {
|
|
bindPipeline(['test' => [FakeFailStep::class]]);
|
|
$run = ProvisioningRun::factory()->create(['pipeline' => 'test']);
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
$run->refresh();
|
|
|
|
expect($run->status)->toBe('failed')->and($run->error)->toBe('boom');
|
|
});
|
|
|
|
it('treats a thrown exception as a retry', function () {
|
|
bindPipeline(['test' => [FakeThrowStep::class]]);
|
|
$run = ProvisioningRun::factory()->create(['pipeline' => 'test', 'max_attempts' => 5]);
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
$run->refresh();
|
|
|
|
expect($run->status)->toBe('waiting')
|
|
->and($run->attempt)->toBe(1)
|
|
->and($run->events()->where('outcome', 'retry')->exists())->toBeTrue();
|
|
});
|
|
|
|
it('retries when a step exceeds its max duration', function () {
|
|
bindPipeline(['test' => [FakeAdvanceStep::class]]);
|
|
$run = ProvisioningRun::factory()->create([
|
|
'pipeline' => 'test',
|
|
'status' => 'running',
|
|
'started_at' => now()->subMinutes(10),
|
|
'max_attempts' => 5,
|
|
]);
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
$run->refresh();
|
|
|
|
expect($run->status)->toBe('waiting')
|
|
->and($run->events()->where('message', 'like', '%timed out%')->exists())->toBeTrue();
|
|
});
|
|
|
|
it('is a no-op while the run lock is held', function () {
|
|
bindPipeline(['test' => [FakeAdvanceStep::class, FakeAdvanceStep::class]]);
|
|
$run = ProvisioningRun::factory()->create(['pipeline' => 'test', 'current_step' => 0]);
|
|
|
|
$lock = Cache::lock('run:'.$run->uuid, 120);
|
|
expect($lock->get())->toBeTrue();
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
expect($run->refresh()->current_step)->toBe(0);
|
|
|
|
$lock->release();
|
|
});
|
|
|
|
it('broadcasts a StepAdvanced event', function () {
|
|
Event::fake([StepAdvanced::class]);
|
|
bindPipeline(['test' => [FakeAdvanceStep::class]]);
|
|
$run = ProvisioningRun::factory()->create(['pipeline' => 'test']);
|
|
|
|
app(RunRunner::class)->advance($run);
|
|
|
|
Event::assertDispatched(
|
|
StepAdvanced::class,
|
|
fn ($e) => $e->runUuid === $run->uuid && $e->status === 'completed',
|
|
);
|
|
});
|