From 0ac819e4ad174ec7baf0284730c34da9dfbbf971 Mon Sep 17 00:00:00 2001 From: boban Date: Tue, 23 Jun 2026 01:49:01 +0200 Subject: [PATCH] feat(release): wire the live pipeline + test-server setting into the page Co-Authored-By: Claude Opus 4.8 --- app/Livewire/Release/Index.php | 36 +++++++++++++++++++++++++++++++ tests/Feature/ReleasePageTest.php | 35 ++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/app/Livewire/Release/Index.php b/app/Livewire/Release/Index.php index 857d9cb..5447fd6 100644 --- a/app/Livewire/Release/Index.php +++ b/app/Livewire/Release/Index.php @@ -3,6 +3,8 @@ namespace App\Livewire\Release; use App\Models\AuditEvent; +use App\Models\Setting; +use App\Services\PipelineStatus; use App\Services\ReleaseBridge; use App\Services\ReleasePlanner; use App\Support\Confirm\ConfirmToken; @@ -38,9 +40,14 @@ class Index extends Component /** The tag of the last successfully-pushed staging beta (shown until the next deploy). */ public ?string $lastTag = null; + /** The configured test-server URL (bound to the inline input). */ + public string $testServer = ''; + public function mount(): void { abort_unless((bool) config('clusev.release_controls'), 404); + + $this->testServer = (string) Setting::get('pipeline_test_server', ''); } /** Opens the wire-elements/modal confirm dialog (R5) before cutting a staging beta of $target. */ @@ -147,6 +154,34 @@ class Index extends Component $this->reset(['pendingId', 'pendingSince', 'pendingTarget']); } + /** wire:poll target while the pipeline is in flight — drops the cached step results so the next + * render re-fetches the live GitHub/test-server status. */ + public function pollPipeline(PipelineStatus $pipeline): void + { + $pipeline->refresh(); + } + + /** Save (or clear) the test-server URL whose /version.json the pipeline polls. */ + public function saveTestServer(string $url): void + { + $url = trim($url); + if ($url === '') { + Setting::put('pipeline_test_server', ''); + $this->testServer = ''; + $this->dispatch('notify', message: __('release.test_server_cleared')); + + return; + } + if (filter_var($url, FILTER_VALIDATE_URL) === false || ! preg_match('#^https?://#i', $url)) { + $this->dispatch('notify', message: __('release.test_server_invalid'), level: 'error'); + + return; + } + Setting::put('pipeline_test_server', $url); + $this->testServer = $url; + $this->dispatch('notify', message: __('release.test_server_saved')); + } + /** * Open the shared ConfirmAction modal (R5) for the staging release. The issued token carries NO * audit descriptor — applyDeployStaging → deployStaging audits exactly once itself. @@ -176,6 +211,7 @@ class Index extends Component return view('livewire.release.index', [ 'current' => $current, 'targets' => app(ReleasePlanner::class)->proposedTargets($current), + 'pipeline' => app(PipelineStatus::class)->forTrackedBeta(), ])->title(__('release.title')); } } diff --git a/tests/Feature/ReleasePageTest.php b/tests/Feature/ReleasePageTest.php index 4f458c3..e49fb09 100644 --- a/tests/Feature/ReleasePageTest.php +++ b/tests/Feature/ReleasePageTest.php @@ -96,4 +96,39 @@ class ReleasePageTest extends TestCase 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_save_test_server_persists_a_valid_url(): void + { + Livewire::test(Index::class)->call('saveTestServer', 'http://staging.local'); + $this->assertSame('http://staging.local', \App\Models\Setting::get('pipeline_test_server')); + } + + public function test_save_test_server_rejects_a_bad_url(): void + { + Livewire::test(Index::class)->call('saveTestServer', 'not-a-url'); + $this->assertNull(\App\Models\Setting::get('pipeline_test_server')); + } + + public function test_save_test_server_empty_clears_it(): void + { + \App\Models\Setting::put('pipeline_test_server', 'http://staging.local'); + Livewire::test(Index::class)->call('saveTestServer', ''); + $this->assertSame('', (string) \App\Models\Setting::get('pipeline_test_server', '')); + } }