set('clusev.release_controls', true); config()->set('clusev.version', '0.9.58'); // Degrade public-tag reads by default so render() makes no live GitHub call; the // promotion tests that need a public repo set clusev.public_slug themselves. config()->set('clusev.public_slug', ''); $this->actingAs(User::factory()->create(['must_change_password' => false])); $this->dir = storage_path('app/restart-signal'); @mkdir($this->dir, 0775, true); @unlink($this->dir.'/release-request.json'); } protected function tearDown(): void { @unlink($this->dir.'/release-request.json'); foreach (glob($this->dir.'/release-result-*.json') ?: [] as $f) { @unlink($f); } parent::tearDown(); } public function test_deploy_staging_for_an_allowed_target_writes_a_request_and_audits(): void { Livewire::test(Index::class)->call('deployStaging', '0.10.0'); $payload = json_decode((string) file_get_contents($this->dir.'/release-request.json'), true); $this->assertSame('stage', $payload['action']); $this->assertSame('0.10.0', $payload['target']); $this->assertTrue(AuditEvent::where('action', 'deploy.staging_release')->exists()); } public function test_deploy_staging_rejects_a_target_not_in_the_allowed_set(): void { Livewire::test(Index::class)->call('deployStaging', '5.5.5'); $this->assertFileDoesNotExist($this->dir.'/release-request.json'); } public function test_deploy_staging_is_throttled_per_user(): void { $key = 'release-stage:'.auth()->id(); for ($i = 0; $i < 3; $i++) { RateLimiter::hit($key, 600); } Livewire::test(Index::class)->call('deployStaging', '0.10.0'); $this->assertFileDoesNotExist($this->dir.'/release-request.json'); } public function test_poll_result_surfaces_a_success(): void { $c = Livewire::test(Index::class)->call('deployStaging', '0.10.0'); $id = $c->get('pendingId'); file_put_contents( $this->dir."/release-result-{$id}.json", json_encode(['id' => $id, 'ok' => true, 'tag' => 'v0.10.0-beta1', 'message' => 'ok']) ); $c->call('pollResult')->assertSet('pendingId', null)->assertSee('v0.10.0-beta1'); } public function test_poll_result_audits_a_host_failure_as_an_error(): void { $c = Livewire::test(Index::class)->call('deployStaging', '0.10.0'); $id = $c->get('pendingId'); file_put_contents( $this->dir."/release-result-{$id}.json", json_encode(['id' => $id, 'ok' => false, 'tag' => null, 'message' => 'dirty-tree']) ); $c->call('pollResult')->assertSet('pendingId', null); $this->assertTrue(AuditEvent::where('action', 'deploy.staging_release_failed')->exists()); } public function test_mount_aborts_when_the_flag_is_off(): void { config()->set('clusev.release_controls', false); Livewire::test(Index::class)->assertStatus(404); } public function test_render_includes_the_live_pipeline_for_a_beta(): void { config()->set('clusev.version', '0.9.61-beta1'); config()->set('clusev.staging_slug', ''); // degrade: no GitHub calls config()->set('clusev.github_token', ''); Livewire::test(Index::class) ->assertViewHas('pipeline', fn ($p) => is_array($p) && $p['tag'] === 'v0.9.61-beta1'); } public function test_render_pipeline_is_null_for_a_stable_version(): void { config()->set('clusev.version', '0.9.60'); Livewire::test(Index::class)->assertViewHas('pipeline', null); } public function test_deploy_public_dispatches_and_audits_for_a_beta(): void { config()->set('clusev.version', '0.10.0-beta1'); config()->set('clusev.github_token', 'tok'); config()->set('clusev.staging_slug', 'acme/staging'); config()->set('clusev.release_branch', 'feat/v1-foundation'); Http::fake(['api.github.com/*' => Http::response('', 204)]); $token = ConfirmToken::issue('releasePublic', ['tag' => 'v0.10.0-beta1']); ConfirmToken::confirm($token); // the modal confirmation step Livewire::test(Index::class)->call('applyDeployPublic', $token); $this->assertTrue(AuditEvent::where('action', 'deploy.public')->exists()); } public function test_yank_only_dispatches_for_a_tag_that_exists_on_public(): void { config()->set('clusev.github_token', 'tok'); config()->set('clusev.staging_slug', 'acme/staging'); config()->set('clusev.public_slug', 'acme/public'); config()->set('clusev.release_branch', 'feat/v1-foundation'); Http::fake([ 'api.github.com/repos/acme/public/tags*' => Http::response([['name' => 'v0.9.9']]), 'api.github.com/*/dispatches' => Http::response('', 204), ]); // a tag NOT in publicTags is refused (no dispatch, no audit) $bad = ConfirmToken::issue('releaseYank', ['tag' => 'v9.9.9']); ConfirmToken::confirm($bad); // the modal confirmation step Livewire::test(Index::class)->call('applyYank', $bad); $this->assertFalse(AuditEvent::where('action', 'deploy.yank')->exists()); // the real public tag is dispatched + audited $good = ConfirmToken::issue('releaseYank', ['tag' => 'v0.9.9']); ConfirmToken::confirm($good); // the modal confirmation step Livewire::test(Index::class)->call('applyYank', $good); $this->assertTrue(AuditEvent::where('action', 'deploy.yank')->exists()); } public function test_publish_actions_are_a_no_op_for_a_stable_version(): void { config()->set('clusev.version', '0.10.0'); // not a beta config()->set('clusev.public_slug', ''); // degrade: no public-tags read on render Http::fake(); $token = ConfirmToken::issue('releasePublic', ['tag' => 'v0.10.0-beta1']); ConfirmToken::confirm($token); // the modal confirmation step Livewire::test(Index::class)->call('applyDeployPublic', $token); Http::assertNothingSent(); } }