create([ 'image_name' => 'ghcr.io/acme/example', 'image_tag' => 'latest', 'dockerfile_content' => implode("\n", [ 'FROM alpine:3.20', 'RUN echo "building"', ]), 'environment' => [ ['key' => 'APP_ENV', 'value' => 'production'], ['key' => 'APP_NAME', 'value' => 'Docker Builder'], ], ]); Process::fake(function (PendingProcess $process) { expect($process->path)->not->toBeNull(); expect($process->command)->toBe([ 'docker', 'build', '--tag', 'ghcr.io/acme/example:latest', '.', ]); $dockerfile = file_get_contents($process->path.'/Dockerfile'); expect($dockerfile) ->toContain('FROM alpine:3.20') ->toContain('RUN echo "building"') ->toContain('ENV APP_ENV="production"') ->toContain('ENV APP_NAME="Docker Builder"'); return Process::result(implode("\n", [ 'Step 1/2 : FROM alpine:3.20', 'Successfully built ghcr.io/acme/example:latest', ])); }); (new RunDockerImageBuild($dockerImageBuild->id))->handle(); $dockerImageBuild->refresh(); expect($dockerImageBuild->status)->toBe(DockerImageBuild::STATUS_SUCCEEDED) ->and($dockerImageBuild->successful)->toBeTrue() ->and($dockerImageBuild->exit_code)->toBe(0) ->and($dockerImageBuild->build_output)->toContain('Successfully built ghcr.io/acme/example:latest') ->and($dockerImageBuild->started_at)->not->toBeNull() ->and($dockerImageBuild->finished_at)->not->toBeNull(); }); test('run docker image build job marks non-zero docker exits as failed', function () { $dockerImageBuild = DockerImageBuild::factory()->create([ 'image_name' => 'ghcr.io/acme/example', 'image_tag' => 'broken', ]); Process::fake(fn (PendingProcess $process) => Process::result( 'Step 1/1 : FROM missing:latest', 'failed to solve: missing image', 1, )); (new RunDockerImageBuild($dockerImageBuild->id))->handle(); $dockerImageBuild->refresh(); expect($dockerImageBuild->status)->toBe(DockerImageBuild::STATUS_FAILED) ->and($dockerImageBuild->successful)->toBeFalse() ->and($dockerImageBuild->exit_code)->toBe(1) ->and($dockerImageBuild->build_output)->toContain('failed to solve: missing image') ->and($dockerImageBuild->finished_at)->not->toBeNull(); }); test('run docker image build job records unexpected process exceptions as failed', function () { $dockerImageBuild = DockerImageBuild::factory()->create(); Process::fake(fn (PendingProcess $process) => throw new \RuntimeException('Docker is unavailable')); expect(fn () => (new RunDockerImageBuild($dockerImageBuild->id))->handle()) ->toThrow(\RuntimeException::class, 'Docker is unavailable'); $dockerImageBuild->refresh(); expect($dockerImageBuild->status)->toBe(DockerImageBuild::STATUS_FAILED) ->and($dockerImageBuild->successful)->toBeFalse() ->and($dockerImageBuild->exit_code)->toBe(1) ->and($dockerImageBuild->build_output)->toContain('Docker is unavailable') ->and($dockerImageBuild->finished_at)->not->toBeNull(); });