Merge remote-tracking branch 'origin/next' into feat/railpack

This commit is contained in:
Andras Bacsai committed 2026-04-29 08:56:23 +02:00
commit 9717d9ff5a
47 files changed
+2015 -83

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
<?php
use App\Models\Server;
afterEach(function () {
Mockery::close();
});
function makeServerForReachabilityTest(bool $isReachable, bool $notificationSent, int $unreachableCount): Server
{
$settings = Mockery::mock();
$settings->is_reachable = $isReachable;
$server = Mockery::mock(Server::class)->makePartial()->shouldAllowMockingProtectedMethods();
$server->shouldReceive('refresh')->andReturnSelf();
$server->shouldReceive('getAttribute')->with('settings')->andReturn($settings);
$server->shouldReceive('getAttribute')->with('unreachable_notification_sent')->andReturn($notificationSent);
$server->shouldReceive('getAttribute')->with('unreachable_count')->andReturn($unreachableCount);
return $server;
}
it('sends Reachable notification when reachable and notification was previously sent', function () {
$server = makeServerForReachabilityTest(isReachable: true, notificationSent: true, unreachableCount: 0);
$server->shouldReceive('sendReachableNotification')->once();
$server->shouldNotReceive('sendUnreachableNotification');
$server->isReachableChanged();
});
it('does not send any notification when reachable and notification was never sent', function () {
$server = makeServerForReachabilityTest(isReachable: true, notificationSent: false, unreachableCount: 0);
$server->shouldNotReceive('sendReachableNotification');
$server->shouldNotReceive('sendUnreachableNotification');
$server->isReachableChanged();
});
it('sends Unreachable notification when count >= 2 and not yet notified', function () {
$server = makeServerForReachabilityTest(isReachable: false, notificationSent: false, unreachableCount: 2);
$server->shouldReceive('sendUnreachableNotification')->once();
$server->shouldNotReceive('sendReachableNotification');
$server->isReachableChanged();
});
it('does not send Unreachable notification on first transient failure (count=1)', function () {
$server = makeServerForReachabilityTest(isReachable: false, notificationSent: false, unreachableCount: 1);
$server->shouldNotReceive('sendUnreachableNotification');
$server->shouldNotReceive('sendReachableNotification');
$server->isReachableChanged();
});
it('does not double-send Unreachable when already notified', function () {
$server = makeServerForReachabilityTest(isReachable: false, notificationSent: true, unreachableCount: 5);
$server->shouldNotReceive('sendUnreachableNotification');
$server->shouldNotReceive('sendReachableNotification');
$server->isReachableChanged();
});
@@ -0,0 +1,66 @@
<?php
/**
* Unit tests for LocalFileVolume content size handling.
*
* Related Issue: #4701 - Storages page becomes unusable when Docker volumes
* mount large host files. Coolify previously stored full file content in the
* encrypted `content` mediumText column, then serialized it to the Livewire
* payload, crashing the browser.
*/
use App\Models\LocalFileVolume;
use Tests\TestCase;
uses(TestCase::class);
it('exposes a 5 MiB content size limit', function () {
expect(LocalFileVolume::MAX_CONTENT_SIZE)->toBe(5_242_880);
});
it('exposes binary and too-large placeholder constants', function () {
expect(LocalFileVolume::BINARY_PLACEHOLDER)->toBe('[binary file]');
expect(LocalFileVolume::TOO_LARGE_PLACEHOLDER)->toBe('[file too large to display]');
});
it('flags is_too_large when content matches the placeholder', function () {
$volume = new LocalFileVolume;
$volume->content = LocalFileVolume::TOO_LARGE_PLACEHOLDER;
expect($volume->is_too_large)->toBeTrue();
expect($volume->is_binary)->toBeFalse();
});
it('flags is_binary when content matches the placeholder', function () {
$volume = new LocalFileVolume;
$volume->content = LocalFileVolume::BINARY_PLACEHOLDER;
expect($volume->is_binary)->toBeTrue();
expect($volume->is_too_large)->toBeFalse();
});
it('does not flag normal content as binary or too large', function () {
$volume = new LocalFileVolume;
$volume->content = "hello\nworld\n";
expect($volume->is_binary)->toBeFalse();
expect($volume->is_too_large)->toBeFalse();
});
it('does not flag empty content as binary or too large', function () {
$volume = new LocalFileVolume;
$volume->content = null;
expect($volume->is_binary)->toBeFalse();
expect($volume->is_too_large)->toBeFalse();
});
it('exposes the too-large flag via toArray for Livewire serialization', function () {
$volume = new LocalFileVolume;
$volume->content = LocalFileVolume::TOO_LARGE_PLACEHOLDER;
$array = $volume->toArray();
expect($array)->toHaveKey('is_too_large');
expect($array['is_too_large'])->toBeTrue();
});
+51
View File
@@ -1,11 +1,13 @@
<?php
use App\Events\ServerReachabilityChanged;
use App\Jobs\ServerCheckJob;
use App\Jobs\ServerConnectionCheckJob;
use App\Jobs\ServerManagerJob;
use App\Models\Server;
use Illuminate\Queue\TimeoutExceededException;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
uses(TestCase::class);
@@ -126,16 +128,21 @@ describe('shouldSkipDueToBackoff', function () {
describe('ServerConnectionCheckJob unreachable_count', function () {
it('increments unreachable_count on timeout', function () {
Event::fake([ServerReachabilityChanged::class]);
$settings = Mockery::mock();
$settings->is_reachable = true;
$settings->shouldReceive('update')
->with(['is_reachable' => false, 'is_usable' => false])
->once();
$server = Mockery::mock(Server::class)->makePartial()->shouldAllowMockingProtectedMethods();
$server->shouldReceive('getAttribute')->with('settings')->andReturn($settings);
$server->shouldReceive('getAttribute')->with('unreachable_notification_sent')->andReturn(false);
$server->shouldReceive('increment')->with('unreachable_count')->once();
$server->id = 1;
$server->name = 'test-server';
$server->unreachable_count = 1; // Will become 2 after increment in real code; mock keeps value as-is
$job = new ServerConnectionCheckJob($server);
$job->failed(new TimeoutExceededException);
@@ -152,6 +159,50 @@ describe('ServerConnectionCheckJob unreachable_count', function () {
});
});
describe('ServerConnectionCheckJob ServerReachabilityChanged dispatch', function () {
// ServerReachabilityChanged's constructor calls $server->isReachableChanged() — verifying that
// call is a clean proxy for "the event was dispatched", and avoids serializing a Mockery proxy
// through the event dispatcher (which trips Eloquent static method lookups on the proxy class).
$invoke = function (bool $wasReachable, bool $wasNotified, bool $isReachable, int $unreachableCount, bool $expectDispatch) {
$server = Mockery::mock(Server::class)->makePartial()->shouldAllowMockingProtectedMethods();
$server->shouldReceive('getAttribute')->with('unreachable_count')->andReturn($unreachableCount);
$server->shouldReceive('getAttribute')->with('id')->andReturn(1);
if ($expectDispatch) {
$server->shouldReceive('isReachableChanged')->once()->andReturnNull();
} else {
$server->shouldNotReceive('isReachableChanged');
}
$job = new ServerConnectionCheckJob($server);
$method = new ReflectionMethod($job, 'dispatchReachabilityChangedIfNeeded');
$method->invoke($job, $wasReachable, $wasNotified, $isReachable);
};
it('dispatches event when count crosses unreachable threshold', function () use ($invoke) {
$invoke(true, false, false, 2, true);
});
it('does not dispatch on first transient failure (count=1)', function () use ($invoke) {
$invoke(true, false, false, 1, false);
});
it('does not dispatch when already notified and still unreachable', function () use ($invoke) {
$invoke(false, true, false, 5, false);
});
it('dispatches recovery event when previously unreachable', function () use ($invoke) {
$invoke(false, false, true, 0, true);
});
it('dispatches recovery event when previously notified', function () use ($invoke) {
$invoke(true, true, true, 0, true);
});
it('does not dispatch when consistently reachable and never notified', function () use ($invoke) {
$invoke(true, false, true, 0, false);
});
});
describe('ServerCheckJob unreachable_count', function () {
it('increments unreachable_count on timeout', function () {
$server = Mockery::mock(Server::class)->makePartial()->shouldAllowMockingProtectedMethods();