get(route('docker-builder.index')) ->assertRedirect(route('login')); }); test('authenticated users can visit the docker image builder page', function () { $user = User::factory()->create(); $this->actingAs($user) ->get(route('docker-builder.index')) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->component('docker/ImageBuilder') ->where('build', null), ); }); test('authenticated users can queue a docker image build from editor input', function () { Queue::fake(); $user = User::factory()->create(); $response = $this->actingAs($user) ->post(route('docker-builder.store'), [ 'dockerfile' => implode("\n", [ 'FROM alpine:3.20', 'RUN echo "building"', ]), 'image_name' => 'ghcr.io/acme/example', 'image_tag' => 'latest', 'environment' => [ ['key' => 'APP_ENV', 'value' => 'production'], ['key' => 'APP_NAME', 'value' => 'Docker Builder'], ], ]) ->assertSessionHasNoErrors(); $dockerImageBuild = DockerImageBuild::query()->sole(); $response->assertRedirect(route('docker-builder.index', [ 'build' => $dockerImageBuild->id, ])); expect($dockerImageBuild->user_id)->toBe($user->id) ->and($dockerImageBuild->image_name)->toBe('ghcr.io/acme/example') ->and($dockerImageBuild->image_tag)->toBe('latest') ->and($dockerImageBuild->dockerfile_content)->toContain('RUN echo "building"') ->and($dockerImageBuild->environment)->toBe([ ['key' => 'APP_ENV', 'value' => 'production'], ['key' => 'APP_NAME', 'value' => 'Docker Builder'], ]) ->and($dockerImageBuild->status)->toBe(DockerImageBuild::STATUS_QUEUED) ->and($dockerImageBuild->successful)->toBeNull() ->and($dockerImageBuild->queued_at)->not->toBeNull(); Queue::assertPushed(RunDockerImageBuild::class, function (RunDockerImageBuild $job) use ($dockerImageBuild): bool { return $job->dockerImageBuildId === $dockerImageBuild->id; }); }); test('authenticated users can view their docker build history', function () { $user = User::factory()->create(); $olderBuild = DockerImageBuild::factory()->for($user)->create([ 'status' => DockerImageBuild::STATUS_FAILED, 'build_output' => 'failed to solve', ]); $newerBuild = DockerImageBuild::factory()->for($user)->create([ 'status' => DockerImageBuild::STATUS_SUCCEEDED, 'successful' => true, 'exit_code' => 0, 'build_output' => 'Successfully built image', 'started_at' => now()->subMinute(), 'finished_at' => now(), ]); DockerImageBuild::factory()->create(); $this->actingAs($user) ->get(route('docker-builder.history')) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->component('docker/BuildHistory') ->has('builds', 2) ->where('builds.0.id', $newerBuild->id) ->where('builds.1.id', $olderBuild->id), ); }); test('authenticated users can fetch their saved build status', function () { $user = User::factory()->create(); $dockerImageBuild = DockerImageBuild::factory()->for($user)->create([ 'image_name' => 'ghcr.io/acme/example', 'image_tag' => 'stable', 'status' => DockerImageBuild::STATUS_RUNNING, 'build_output' => 'Step 1/3 : FROM alpine:3.20', 'started_at' => now(), ]); $this->actingAs($user) ->get(route('docker-builder.builds.show', $dockerImageBuild)) ->assertOk() ->assertJsonPath('build.id', $dockerImageBuild->id) ->assertJsonPath('build.image', 'ghcr.io/acme/example:stable') ->assertJsonPath('build.status', DockerImageBuild::STATUS_RUNNING) ->assertJsonPath('build.build_output', 'Step 1/3 : FROM alpine:3.20'); }); test('authenticated users cannot fetch builds that belong to someone else', function () { $user = User::factory()->create(); $dockerImageBuild = DockerImageBuild::factory()->create(); $this->actingAs($user) ->get(route('docker-builder.builds.show', $dockerImageBuild)) ->assertNotFound(); });