124 lines
4.0 KiB
PHP
124 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Livewire\Patch\Index;
|
|
use App\Models\Server;
|
|
use App\Models\User;
|
|
use App\Services\MaintenanceService;
|
|
use App\Support\Confirm\ConfirmToken;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
use Mockery;
|
|
use Tests\TestCase;
|
|
|
|
class PatchComponentTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Mockery::close();
|
|
parent::tearDown();
|
|
}
|
|
|
|
private function admin(): User
|
|
{
|
|
return User::factory()->create(['must_change_password' => false]);
|
|
}
|
|
|
|
private function operator(): User
|
|
{
|
|
return User::factory()->operator()->create(['must_change_password' => false]);
|
|
}
|
|
|
|
private function viewer(): User
|
|
{
|
|
return User::factory()->viewer()->create(['must_change_password' => false]);
|
|
}
|
|
|
|
private function server(string $name = 'box', string $ip = '10.0.0.1'): Server
|
|
{
|
|
$s = Server::create(['name' => $name, 'ip' => $ip, 'ssh_port' => 22, 'status' => 'online']);
|
|
$s->credential()->create(['username' => 'root', 'auth_type' => 'password', 'secret' => 'x']);
|
|
|
|
return $s;
|
|
}
|
|
|
|
public function test_viewer_cannot_open_patch(): void
|
|
{
|
|
$this->actingAs($this->viewer())->get('/patch')->assertForbidden();
|
|
}
|
|
|
|
public function test_operator_can_open_patch(): void
|
|
{
|
|
$this->actingAs($this->operator())->get('/patch')->assertOk();
|
|
}
|
|
|
|
public function test_scan_shows_pending_and_security_counts(): void
|
|
{
|
|
$this->actingAs($this->operator());
|
|
$this->server('web1');
|
|
|
|
$maint = Mockery::mock(MaintenanceService::class);
|
|
$maint->shouldReceive('updateCounts')->once()->andReturn(['pending' => 12, 'security' => 3]);
|
|
app()->instance(MaintenanceService::class, $maint);
|
|
|
|
Livewire::test(Index::class)
|
|
->call('scan')
|
|
->assertOk()
|
|
->assertSet('ready', true)
|
|
->assertSee('12')
|
|
->assertViewHas('totalSecurity', 3);
|
|
}
|
|
|
|
public function test_scan_survives_an_unreachable_server(): void
|
|
{
|
|
$this->actingAs($this->operator());
|
|
$this->server('down');
|
|
|
|
$maint = Mockery::mock(MaintenanceService::class);
|
|
$maint->shouldReceive('updateCounts')->once()->andThrow(new \RuntimeException('ssh fail'));
|
|
app()->instance(MaintenanceService::class, $maint);
|
|
|
|
Livewire::test(Index::class)->call('scan')->assertSet('ready', true)->assertSee(__('patch.scan_error'));
|
|
}
|
|
|
|
public function test_patch_opens_a_confirm(): void
|
|
{
|
|
$this->actingAs($this->operator());
|
|
$server = $this->server();
|
|
|
|
Livewire::test(Index::class)->call('patch', $server->uuid)->assertDispatched('openModal');
|
|
}
|
|
|
|
public function test_apply_patch_runs_upgrades_over_the_sealed_server(): void
|
|
{
|
|
$this->actingAs($this->operator());
|
|
$server = $this->server();
|
|
|
|
$maint = Mockery::mock(MaintenanceService::class);
|
|
$maint->shouldReceive('applyUpgrades')->once()->with(Mockery::on(fn ($s) => $s->id === $server->id))->andReturn(['ok' => true, 'output' => 'upgraded 12 packages']);
|
|
$maint->shouldReceive('updateCounts')->andReturn(['pending' => 0, 'security' => 0]); // post-patch re-scan
|
|
app()->instance(MaintenanceService::class, $maint);
|
|
|
|
$token = ConfirmToken::issue('patchApply', ['uuid' => $server->uuid], 'patch.apply', $server->name, $server->id);
|
|
ConfirmToken::confirm($token);
|
|
|
|
Livewire::test(Index::class)
|
|
->call('applyPatch', $token)
|
|
->assertOk()
|
|
->assertSet("results.{$server->uuid}.output", 'upgraded 12 packages');
|
|
}
|
|
|
|
public function test_a_forged_patch_token_runs_nothing(): void
|
|
{
|
|
$this->actingAs($this->operator());
|
|
$maint = Mockery::mock(MaintenanceService::class);
|
|
$maint->shouldReceive('applyUpgrades')->never();
|
|
app()->instance(MaintenanceService::class, $maint);
|
|
|
|
Livewire::test(Index::class)->call('applyPatch', 'not-a-token')->assertSet('results', []);
|
|
}
|
|
}
|