Files
coolify/tests/Feature/Proxy/RestartProxyTest.php
T
Andras Bacsai 0db2eae8de feat(proxy): show sidebar warning icon when proxy is not running
Add a proxyNotRunning flag to the server sidebar state. The Proxy menu
item's warning icon now also appears when a proxy is configured but its
status is not "running". The navbar sends the flag in the
proxy-configuration-state-changed event, so the icon updates live.

Add tests for the dispatched flag and the sidebar bindings.
2026-09-26 00:17:55 +02:00

245 lines
8.0 KiB
PHP

<?php
use App\Actions\Proxy\StartProxy;
use App\Enums\ProxyTypes;
use App\Models\AuditEvent;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Spatie\Activitylog\Models\Activity;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->withoutDefer();
InstanceSettings::forceCreate(['id' => 0]);
});
function setupProxyUser(string $role): array
{
$team = Team::factory()->create();
$user = User::factory()->create();
$user->teams()->attach($team, ['role' => $role]);
$server = Server::factory()->create([
'team_id' => $team->id,
'name' => 'Test Server',
'ip' => '192.168.1.100',
]);
return [$user, $team, $server];
}
function makeServerProxyRunning(Server $server): void
{
$server->settings()->update([
'is_reachable' => true,
'is_usable' => true,
]);
$server->proxy->status = 'running';
$server->proxy->type = ProxyTypes::TRAEFIK->value;
$server->save();
$server->refresh();
}
test('member cannot see proxy restart and stop buttons', function () {
[$user, $team, $server] = setupProxyUser('member');
makeServerProxyRunning($server);
// Mock proxySet to bypass SQLite boolean casting issue with force_disabled
$mock = Mockery::mock($server)->makePartial();
$mock->shouldReceive('proxySet')->andReturn(true);
$this->actingAs($user);
session(['currentTeam' => $team]);
Livewire::test('server.navbar', ['server' => $mock])
->assertDontSee('Restart Proxy')
->assertDontSee('Stop Proxy');
});
test('admin can see proxy restart and stop buttons', function () {
[$user, $team, $server] = setupProxyUser('admin');
makeServerProxyRunning($server);
$mock = Mockery::mock($server)->makePartial();
$mock->shouldReceive('proxySet')->andReturn(true);
$this->actingAs($user);
session(['currentTeam' => $team]);
Livewire::test('server.navbar', ['server' => $mock])
->assertSee('Restart Proxy')
->assertSee('Stop Proxy');
});
test('running proxy shows pending configuration warning when saved settings differ from applied settings', function () {
[$user, $team, $server] = setupProxyUser('admin');
makeServerProxyRunning($server);
$server->proxy->last_saved_settings = 'saved-hash';
$server->proxy->last_applied_settings = 'applied-hash';
$server->detected_traefik_version = '3.6.23';
$server->traefik_outdated_info = [
'current' => '3.6.23',
'latest' => '3.7.8',
'type' => 'minor_upgrade',
'upgrade_target' => 'v3.7',
];
$server->save();
expect($server->fresh()->hasPendingProxyConfiguration())->toBeTrue();
$this->actingAs($user);
session(['currentTeam' => $team]);
$component = Livewire::test('server.navbar', ['server' => $server->fresh()])
->assertSee('Changes pending')
->assertSee('Your configuration changed, please restart the proxy.')
->assertSee('Restart proxy');
$server->refresh();
$server->proxy->last_applied_settings = 'saved-hash';
$server->traefik_outdated_info = null;
$server->save();
$component->call('showNotification')
->assertDispatched('proxy-configuration-state-changed', pending: false, traefikOutdated: false)
->assertDontSee('Your configuration changed, please restart the proxy.');
});
test('running proxy hides pending configuration warning when saved settings match applied settings', function () {
[$user, $team, $server] = setupProxyUser('admin');
makeServerProxyRunning($server);
$server->proxy->last_saved_settings = 'matching-hash';
$server->proxy->last_applied_settings = 'matching-hash';
$server->save();
expect($server->fresh()->hasPendingProxyConfiguration())->toBeFalse();
$this->actingAs($user);
session(['currentTeam' => $team]);
$component = Livewire::test('server.navbar', ['server' => $server->fresh()])
->assertDontSee('Your configuration changed, please restart the proxy.');
$server->refresh();
$server->proxy->last_saved_settings = 'new-saved-hash';
$server->save();
$component->dispatch('refreshServerShow')
->assertDispatched('proxy-configuration-state-changed', pending: true, traefikOutdated: false)
->assertSee('Changes pending')
->assertSee('Your configuration changed, please restart the proxy.');
});
test('navbar tells the sidebar when the proxy is not running', function () {
[$user, $team, $server] = setupProxyUser('admin');
makeServerProxyRunning($server);
$this->actingAs($user);
session(['currentTeam' => $team]);
$component = Livewire::test('server.navbar', ['server' => $server->fresh()]);
$component->call('showNotification')
->assertDispatched('proxy-configuration-state-changed', proxyNotRunning: false);
$server->refresh();
$server->proxy->status = 'exited';
$server->save();
$component->call('showNotification')
->assertDispatched('proxy-configuration-state-changed', proxyNotRunning: true);
});
test('admin can stop a proxy while it is starting', function () {
[$user, $team, $server] = setupProxyUser('admin');
$server->proxy->status = 'starting';
$server->proxy->type = ProxyTypes::TRAEFIK->value;
$server->save();
$server->refresh();
$mock = Mockery::mock($server)->makePartial();
$mock->shouldReceive('proxySet')->andReturn(true);
$this->actingAs($user);
session(['currentTeam' => $team]);
Livewire::test('server.navbar', ['server' => $mock])
->assertSee('Stop Proxy')
->assertDontSee('Start Proxy');
});
test('member cannot see start proxy button', function () {
[$user, $team, $server] = setupProxyUser('member');
$server->proxy->status = 'exited';
$server->proxy->type = ProxyTypes::TRAEFIK->value;
$server->save();
$server->refresh();
$mock = Mockery::mock($server)->makePartial();
$mock->shouldReceive('proxySet')->andReturn(true);
$this->actingAs($user);
session(['currentTeam' => $team]);
Livewire::test('server.navbar', ['server' => $mock])
->assertDontSee('Start Proxy');
});
test('start proxy button shows a loading state while proxy startup actions run', function () {
[$user, $team, $server] = setupProxyUser('admin');
$server->proxy->status = 'exited';
$server->proxy->type = ProxyTypes::TRAEFIK->value;
$server->save();
$server->refresh();
$mock = Mockery::mock($server)->makePartial();
$mock->shouldReceive('proxySet')->andReturn(true);
$this->actingAs($user);
session(['currentTeam' => $team]);
$html = Livewire::test('server.navbar', ['server' => $mock])
->assertSeeHtml('wire:loading.attr="disabled"')
->assertSeeHtml('wire:target="checkProxy,startProxy"')
->html();
// The split action main button shows its loading state through the
// `.split-action-main:disabled` style while Livewire disables it.
preg_match_all('/<button\b[^>]*class="split-action-main"[^>]*>(?:(?!<\/button>).)*?Start Proxy/s', $html, $startButtons);
expect($startButtons[0])->not->toBeEmpty();
foreach ($startButtons[0] as $startButton) {
expect($startButton)
->toContain('wire:loading.attr="disabled"')
->toContain('wire:target="checkProxy,startProxy"');
}
expect(file_get_contents(resource_path('css/app.css')))
->toMatch('/\.split-action-main:disabled,\s*\.split-action-caret:disabled\s*\{[^}]*opacity:/');
});
test('starting a proxy records a team audit event', function () {
[$user, $team, $server] = setupProxyUser('admin');
$activity = Activity::create([
'description' => 'proxy start',
'properties' => ['team_id' => $team->id],
]);
StartProxy::shouldRun()->andReturn($activity);
$this->actingAs($user);
session(['currentTeam' => $team]);
Livewire::test('server.navbar', ['server' => $server])
->call('startProxy');
expect(AuditEvent::query()->sole()->event)->toBe('ui.proxy.started');
});