mirror of
https://github.com/tiennm99/coolify.git
synced 2026-04-17 17:21:04 +00:00
Merge branch 'next' into macau-v1
Resolved conflicts in ServerManagerJob.php by: - Keeping sentinel update check code from macau-v1 - Preserving sentinel restart code from next branch - Ensuring no duplicate code blocks
This commit is contained in:
@@ -214,3 +214,90 @@ it('sends immediate notifications when outdated traefik is detected', function (
|
||||
expect($notification->servers)->toHaveCount(1);
|
||||
expect($notification->servers->first()->outdatedInfo['type'])->toBe('patch_update');
|
||||
});
|
||||
|
||||
it('notification generates correct server proxy URLs', function () {
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'name' => 'Test Server',
|
||||
'team_id' => $team->id,
|
||||
'uuid' => 'test-uuid-123',
|
||||
]);
|
||||
|
||||
$server->outdatedInfo = [
|
||||
'current' => '3.5.0',
|
||||
'latest' => '3.5.6',
|
||||
'type' => 'patch_update',
|
||||
];
|
||||
|
||||
$notification = new TraefikVersionOutdated(collect([$server]));
|
||||
$mail = $notification->toMail($team);
|
||||
|
||||
// Verify the mail has the transformed servers with URLs
|
||||
expect($mail->viewData['servers'])->toHaveCount(1);
|
||||
expect($mail->viewData['servers'][0]['name'])->toBe('Test Server');
|
||||
expect($mail->viewData['servers'][0]['uuid'])->toBe('test-uuid-123');
|
||||
expect($mail->viewData['servers'][0]['url'])->toBe(base_url().'/server/test-uuid-123/proxy');
|
||||
expect($mail->viewData['servers'][0]['outdatedInfo'])->toBeArray();
|
||||
});
|
||||
|
||||
it('notification transforms multiple servers with URLs correctly', function () {
|
||||
$team = Team::factory()->create();
|
||||
$server1 = Server::factory()->create([
|
||||
'name' => 'Server 1',
|
||||
'team_id' => $team->id,
|
||||
'uuid' => 'uuid-1',
|
||||
]);
|
||||
$server1->outdatedInfo = [
|
||||
'current' => '3.5.0',
|
||||
'latest' => '3.5.6',
|
||||
'type' => 'patch_update',
|
||||
];
|
||||
|
||||
$server2 = Server::factory()->create([
|
||||
'name' => 'Server 2',
|
||||
'team_id' => $team->id,
|
||||
'uuid' => 'uuid-2',
|
||||
]);
|
||||
$server2->outdatedInfo = [
|
||||
'current' => '3.4.0',
|
||||
'latest' => '3.6.0',
|
||||
'type' => 'minor_upgrade',
|
||||
'upgrade_target' => 'v3.6',
|
||||
];
|
||||
|
||||
$servers = collect([$server1, $server2]);
|
||||
$notification = new TraefikVersionOutdated($servers);
|
||||
$mail = $notification->toMail($team);
|
||||
|
||||
// Verify both servers have URLs
|
||||
expect($mail->viewData['servers'])->toHaveCount(2);
|
||||
|
||||
expect($mail->viewData['servers'][0]['name'])->toBe('Server 1');
|
||||
expect($mail->viewData['servers'][0]['url'])->toBe(base_url().'/server/uuid-1/proxy');
|
||||
|
||||
expect($mail->viewData['servers'][1]['name'])->toBe('Server 2');
|
||||
expect($mail->viewData['servers'][1]['url'])->toBe(base_url().'/server/uuid-2/proxy');
|
||||
});
|
||||
|
||||
it('notification uses base_url helper not config app.url', function () {
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'name' => 'Test Server',
|
||||
'team_id' => $team->id,
|
||||
'uuid' => 'test-uuid',
|
||||
]);
|
||||
|
||||
$server->outdatedInfo = [
|
||||
'current' => '3.5.0',
|
||||
'latest' => '3.5.6',
|
||||
'type' => 'patch_update',
|
||||
];
|
||||
|
||||
$notification = new TraefikVersionOutdated(collect([$server]));
|
||||
$mail = $notification->toMail($team);
|
||||
|
||||
// Verify URL starts with base_url() not config('app.url')
|
||||
$generatedUrl = $mail->viewData['servers'][0]['url'];
|
||||
expect($generatedUrl)->toStartWith(base_url());
|
||||
expect($generatedUrl)->not->toContain('localhost');
|
||||
});
|
||||
|
||||
139
tests/Feature/Proxy/RestartProxyTest.php
Normal file
139
tests/Feature/Proxy/RestartProxyTest.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Proxy;
|
||||
|
||||
use App\Jobs\RestartProxyJob;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Livewire\Livewire;
|
||||
use Tests\TestCase;
|
||||
|
||||
class RestartProxyTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected User $user;
|
||||
|
||||
protected Team $team;
|
||||
|
||||
protected Server $server;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Create test user and team
|
||||
$this->user = User::factory()->create();
|
||||
$this->team = Team::factory()->create(['name' => 'Test Team']);
|
||||
$this->user->teams()->attach($this->team);
|
||||
|
||||
// Create test server
|
||||
$this->server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'name' => 'Test Server',
|
||||
'ip' => '192.168.1.100',
|
||||
]);
|
||||
|
||||
// Authenticate user
|
||||
$this->actingAs($this->user);
|
||||
}
|
||||
|
||||
public function test_restart_dispatches_job_for_all_servers()
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
Livewire::test('server.navbar', ['server' => $this->server])
|
||||
->call('restart');
|
||||
|
||||
// Assert job was dispatched
|
||||
Queue::assertPushed(RestartProxyJob::class, function ($job) {
|
||||
return $job->server->id === $this->server->id;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_restart_dispatches_job_for_localhost_server()
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
// Create localhost server (id = 0)
|
||||
$localhostServer = Server::factory()->create([
|
||||
'id' => 0,
|
||||
'team_id' => $this->team->id,
|
||||
'name' => 'Localhost',
|
||||
'ip' => 'host.docker.internal',
|
||||
]);
|
||||
|
||||
Livewire::test('server.navbar', ['server' => $localhostServer])
|
||||
->call('restart');
|
||||
|
||||
// Assert job was dispatched
|
||||
Queue::assertPushed(RestartProxyJob::class, function ($job) use ($localhostServer) {
|
||||
return $job->server->id === $localhostServer->id;
|
||||
});
|
||||
}
|
||||
|
||||
public function test_restart_shows_info_message()
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
Livewire::test('server.navbar', ['server' => $this->server])
|
||||
->call('restart')
|
||||
->assertDispatched('info', 'Proxy restart initiated. Monitor progress in activity logs.');
|
||||
}
|
||||
|
||||
public function test_unauthorized_user_cannot_restart_proxy()
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
// Create another user without access
|
||||
$unauthorizedUser = User::factory()->create();
|
||||
$this->actingAs($unauthorizedUser);
|
||||
|
||||
Livewire::test('server.navbar', ['server' => $this->server])
|
||||
->call('restart')
|
||||
->assertForbidden();
|
||||
|
||||
// Assert job was NOT dispatched
|
||||
Queue::assertNotPushed(RestartProxyJob::class);
|
||||
}
|
||||
|
||||
public function test_restart_prevents_concurrent_jobs_via_without_overlapping()
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
// Dispatch job twice
|
||||
Livewire::test('server.navbar', ['server' => $this->server])
|
||||
->call('restart');
|
||||
|
||||
Livewire::test('server.navbar', ['server' => $this->server])
|
||||
->call('restart');
|
||||
|
||||
// Assert job was pushed twice (WithoutOverlapping middleware will handle deduplication)
|
||||
Queue::assertPushed(RestartProxyJob::class, 2);
|
||||
|
||||
// Get the jobs
|
||||
$jobs = Queue::pushed(RestartProxyJob::class);
|
||||
|
||||
// Verify both jobs have WithoutOverlapping middleware
|
||||
foreach ($jobs as $job) {
|
||||
$middleware = $job['job']->middleware();
|
||||
$this->assertCount(1, $middleware);
|
||||
$this->assertInstanceOf(\Illuminate\Queue\Middleware\WithoutOverlapping::class, $middleware[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_restart_uses_server_team_id()
|
||||
{
|
||||
Queue::fake();
|
||||
|
||||
Livewire::test('server.navbar', ['server' => $this->server])
|
||||
->call('restart');
|
||||
|
||||
Queue::assertPushed(RestartProxyJob::class, function ($job) {
|
||||
return $job->server->team_id === $this->team->id;
|
||||
});
|
||||
}
|
||||
}
|
||||
186
tests/Feature/ServerStorageCheckIndependenceTest.php
Normal file
186
tests/Feature/ServerStorageCheckIndependenceTest.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ServerCheckJob;
|
||||
use App\Jobs\ServerManagerJob;
|
||||
use App\Jobs\ServerStorageCheckJob;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Queue::fake();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Carbon::setTestNow();
|
||||
});
|
||||
|
||||
it('does not dispatch storage check when sentinel is in sync', function () {
|
||||
// When: ServerManagerJob runs at 11 PM
|
||||
Carbon::setTestNow(Carbon::parse('2025-01-15 23:00:00', 'UTC'));
|
||||
|
||||
// Given: A server with Sentinel recently updated (in sync)
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'sentinel_updated_at' => now(),
|
||||
]);
|
||||
|
||||
$server->settings->update([
|
||||
'server_disk_usage_check_frequency' => '0 23 * * *',
|
||||
'server_timezone' => 'UTC',
|
||||
]);
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Then: ServerStorageCheckJob should NOT be dispatched (Sentinel handles it via PushServerUpdateJob)
|
||||
Queue::assertNotPushed(ServerStorageCheckJob::class);
|
||||
});
|
||||
|
||||
it('dispatches storage check when sentinel is out of sync', function () {
|
||||
// When: ServerManagerJob runs at 11 PM
|
||||
Carbon::setTestNow(Carbon::parse('2025-01-15 23:00:00', 'UTC'));
|
||||
|
||||
// Given: A server with Sentinel out of sync (last update 10 minutes ago)
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'sentinel_updated_at' => now()->subMinutes(10),
|
||||
]);
|
||||
|
||||
$server->settings->update([
|
||||
'server_disk_usage_check_frequency' => '0 23 * * *',
|
||||
'server_timezone' => 'UTC',
|
||||
]);
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Then: Both ServerCheckJob and ServerStorageCheckJob should be dispatched
|
||||
Queue::assertPushed(ServerCheckJob::class);
|
||||
Queue::assertPushed(ServerStorageCheckJob::class, function ($job) use ($server) {
|
||||
return $job->server->id === $server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches storage check when sentinel is disabled', function () {
|
||||
// When: ServerManagerJob runs at 11 PM
|
||||
Carbon::setTestNow(Carbon::parse('2025-01-15 23:00:00', 'UTC'));
|
||||
|
||||
// Given: A server with Sentinel disabled
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'sentinel_updated_at' => now()->subHours(24),
|
||||
]);
|
||||
|
||||
$server->settings->update([
|
||||
'server_disk_usage_check_frequency' => '0 23 * * *',
|
||||
'server_timezone' => 'UTC',
|
||||
'is_metrics_enabled' => false,
|
||||
]);
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Then: ServerStorageCheckJob should be dispatched
|
||||
Queue::assertPushed(ServerStorageCheckJob::class, function ($job) use ($server) {
|
||||
return $job->server->id === $server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('respects custom hourly storage check frequency when sentinel is out of sync', function () {
|
||||
// When: ServerManagerJob runs at the top of the hour (23:00)
|
||||
Carbon::setTestNow(Carbon::parse('2025-01-15 23:00:00', 'UTC'));
|
||||
|
||||
// Given: A server with hourly storage check frequency and Sentinel out of sync
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'sentinel_updated_at' => now()->subMinutes(10),
|
||||
]);
|
||||
|
||||
$server->settings->update([
|
||||
'server_disk_usage_check_frequency' => '0 * * * *',
|
||||
'server_timezone' => 'UTC',
|
||||
]);
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Then: ServerStorageCheckJob should be dispatched
|
||||
Queue::assertPushed(ServerStorageCheckJob::class, function ($job) use ($server) {
|
||||
return $job->server->id === $server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('handles VALID_CRON_STRINGS mapping correctly when sentinel is out of sync', function () {
|
||||
// When: ServerManagerJob runs at the top of the hour
|
||||
Carbon::setTestNow(Carbon::parse('2025-01-15 23:00:00', 'UTC'));
|
||||
|
||||
// Given: A server with 'hourly' string (should be converted to '0 * * * *') and Sentinel out of sync
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'sentinel_updated_at' => now()->subMinutes(10),
|
||||
]);
|
||||
|
||||
$server->settings->update([
|
||||
'server_disk_usage_check_frequency' => 'hourly',
|
||||
'server_timezone' => 'UTC',
|
||||
]);
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Then: ServerStorageCheckJob should be dispatched (hourly was converted to cron)
|
||||
Queue::assertPushed(ServerStorageCheckJob::class, function ($job) use ($server) {
|
||||
return $job->server->id === $server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('respects server timezone for storage checks when sentinel is out of sync', function () {
|
||||
// When: ServerManagerJob runs at 11 PM New York time (4 AM UTC next day)
|
||||
Carbon::setTestNow(Carbon::parse('2025-01-16 04:00:00', 'UTC'));
|
||||
|
||||
// Given: A server in America/New_York timezone (UTC-5) configured for 11 PM local time and Sentinel out of sync
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'sentinel_updated_at' => now()->subMinutes(10),
|
||||
]);
|
||||
|
||||
$server->settings->update([
|
||||
'server_disk_usage_check_frequency' => '0 23 * * *',
|
||||
'server_timezone' => 'America/New_York',
|
||||
]);
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Then: ServerStorageCheckJob should be dispatched
|
||||
Queue::assertPushed(ServerStorageCheckJob::class, function ($job) use ($server) {
|
||||
return $job->server->id === $server->id;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not dispatch storage check outside schedule', function () {
|
||||
// When: ServerManagerJob runs at 10 PM (not 11 PM)
|
||||
Carbon::setTestNow(Carbon::parse('2025-01-15 22:00:00', 'UTC'));
|
||||
|
||||
// Given: A server with daily storage check at 11 PM
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $team->id,
|
||||
'sentinel_updated_at' => now(),
|
||||
]);
|
||||
|
||||
$server->settings->update([
|
||||
'server_disk_usage_check_frequency' => '0 23 * * *',
|
||||
'server_timezone' => 'UTC',
|
||||
]);
|
||||
$job = new ServerManagerJob;
|
||||
$job->handle();
|
||||
|
||||
// Then: ServerStorageCheckJob should NOT be dispatched
|
||||
Queue::assertNotPushed(ServerStorageCheckJob::class);
|
||||
});
|
||||
Reference in New Issue
Block a user