172 lines
6.6 KiB
PHP
172 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Livewire\Release\Index;
|
|
use App\Models\AuditEvent;
|
|
use App\Models\User;
|
|
use App\Support\Confirm\ConfirmToken;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
class ReleasePageTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private string $dir;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
config()->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-rc1', 'message' => 'ok'])
|
|
);
|
|
|
|
$c->call('pollResult')->assertSet('pendingId', null)->assertSee('v0.10.0-rc1');
|
|
}
|
|
|
|
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_an_rc(): void
|
|
{
|
|
config()->set('clusev.version', '0.9.61-rc1');
|
|
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-rc1');
|
|
}
|
|
|
|
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_an_rc(): void
|
|
{
|
|
config()->set('clusev.version', '0.10.0-rc1');
|
|
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-rc1']);
|
|
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 an rc
|
|
config()->set('clusev.public_slug', ''); // degrade: no public-tags read on render
|
|
Http::fake();
|
|
|
|
$token = ConfirmToken::issue('releasePublic', ['tag' => 'v0.10.0-rc1']);
|
|
ConfirmToken::confirm($token); // the modal confirmation step
|
|
Livewire::test(Index::class)->call('applyDeployPublic', $token);
|
|
|
|
Http::assertNothingSent();
|
|
}
|
|
}
|