Ctrl K
ComunidadGuías…

tests/Feature/ApplicationStoppedAfterRestartLimitTest.php

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<?php

use App\Actions\Docker\GetContainersStatus;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\BaseModel;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\StandalonePostgresql;
use App\Notifications\Application\RestartLimitReached;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery\MockInterface;

uses(RefreshDatabase::class);

beforeEach(function () {
    InstanceSettings::forceCreate(['id' => 0, 'fqdn' => 'https://coolify.test']);
});

function applicationWithRestartState(array $attributes = []): Application
{
    $application = new Application;
    $application->forceFill(array_merge([
        'status' => 'exited:unhealthy',
        'container_present' => true,
        'restart_count' => 2,
        'max_restart_count' => 2,
        'restart_limit_reached' => true,
        'last_restart_type' => 'crash',
        'last_restart_at' => now(),
    ], $attributes));

    return $application;
}

it('detects applications stopped after reaching the crash restart limit', function () {
    expect(applicationWithRestartState()->stoppedAfterRestartLimit())->toBeTrue()
        ->and(applicationWithRestartState(['status' => 'running:unhealthy'])->stoppedAfterRestartLimit())->toBeFalse()
        ->and(applicationWithRestartState(['restart_limit_reached' => false])->stoppedAfterRestartLimit())->toBeFalse();
});

it('keeps the restart limit state after Docker resets its counter', function () {
    expect(applicationWithRestartState([
        'restart_count' => 0,
        'last_restart_type' => null,
        'last_restart_at' => null,
    ])->stoppedAfterRestartLimit())->toBeTrue();
});

it('preserves exited application state when the container snapshot is empty', function () {
    $application = Mockery::mock(Application::class)->makePartial();
    $application->setRelation('additional_servers', collect());
    $application->forceFill([
        'id' => 1,
        'status' => 'exited:unhealthy',
        'container_present' => true,
        'restart_limit_reached' => true,
    ]);
    $application->shouldNotReceive('update');

    $services = Mockery::mock();
    $services->shouldReceive('get')->once()->andReturn(collect());

    $server = Mockery::mock(Server::class, function (MockInterface $mock) use ($application, $services) {
        $mock->shouldReceive('isFunctional')->once()->andReturnTrue();
        $mock->shouldReceive('applications')->once()->andReturn(collect([$application]));
        $mock->shouldReceive('databases')->once()->andReturn(collect());
        $mock->shouldReceive('services')->once()->andReturn($services);
        $mock->shouldReceive('previews')->once()->andReturn(collect());
    })->makePartial();
    $server->setRelation('team', (object) ['id' => 1]);

    GetContainersStatus::run($server, collect(), collect());

    expect($application->container_present)->toBeTrue()
        ->and($application->restart_limit_reached)->toBeTrue();
});

it('does not infer the restart limit from an exited existing container', function () {
    expect(applicationWithRestartState([
        'container_present' => true,
        'restart_limit_reached' => false,
    ])->stoppedAfterRestartLimit())->toBeFalse();
});

it('shows a stopped after restart limit warning badge', function () {
    $html = view('components.application.restart-limit-warning', [
        'application' => applicationWithRestartState(),
    ])->render();

    expect($html)->toContain('Restart limit reached')
        ->not->toContain('Stopped after reaching restart limit (2/2).')
        ->and($html)->toContain('Container has crashed and Coolify stopped it after 2 restart attempts.');
});

it('does not show the restart limit warning for a normal manual stop', function () {
    $html = view('components.application.restart-limit-warning', [
        'application' => applicationWithRestartState([
            'restart_count' => 0,
            'last_restart_type' => null,
            'restart_limit_reached' => false,
        ]),
    ])->render();

    expect($html)->not->toContain('Restart limit reached');
});

it('builds restart limit notification urls from the instance base url', function () {
    $application = new Application;
    $application->forceFill([
        'name' => 'crashy-app',
        'uuid' => 'application-uuid',
        'restart_count' => 2,
        'max_restart_count' => 2,
    ]);
    $application->setRelation('environment', (object) [
        'uuid' => 'environment-uuid',
        'name' => 'production',
        'project' => (object) ['uuid' => 'project-uuid'],
    ]);

    $notification = new RestartLimitReached($application);

    expect($notification->resource_url)->toBe('https://coolify.test/project/project-uuid/environment/environment-uuid/application/application-uuid');
});

it('links preview, service resource and database restart limit notifications to their pages', function () {
    $environment = (object) ['uuid' => 'environment-uuid', 'name' => 'production', 'project' => (object) ['uuid' => 'project-uuid']];

    $application = new Application;
    $application->forceFill(['name' => 'app', 'uuid' => 'application-uuid']);
    $application->setRelation('environment', $environment);
    $preview = new ApplicationPreview;
    $preview->forceFill(['uuid' => 'preview-uuid', 'pull_request_id' => 42, 'restart_count' => 2, 'max_restart_count' => 2]);
    $preview->setRelation('application', $application);

    $service = new Service;
    $service->forceFill(['uuid' => 'service-uuid']);
    $service->setRelation('environment', $environment);
    $serviceApplication = new ServiceApplication;
    $serviceApplication->forceFill(['name' => 'database', 'uuid' => 'service-application-uuid', 'restart_count' => 2, 'max_restart_count' => 2]);
    $serviceApplication->setRelation('service', $service);

    $database = new StandalonePostgresql;
    $database->forceFill(['name' => 'postgres', 'uuid' => 'database-uuid', 'restart_count' => 2, 'max_restart_count' => 2]);
    $database->setRelation('environment', $environment);

    expect((new RestartLimitReached($preview))->resource_url)->toBe('https://coolify.test/project/project-uuid/environment/environment-uuid/application/application-uuid')
        ->and((new RestartLimitReached($serviceApplication))->resource_url)->toBe('https://coolify.test/project/project-uuid/environment/environment-uuid/service/service-uuid')
        ->and((new RestartLimitReached($database))->resource_url)->toBe('https://coolify.test/project/project-uuid/environment/environment-uuid/database/database-uuid');
});

it('uses the resolved environment project name in Slack restart limit notifications', function () {
    $environment = (object) [
        'uuid' => 'environment-uuid',
        'name' => 'production',
        'project' => (object) [
            'uuid' => 'project-uuid',
            'name' => 'Coolify',
        ],
    ];

    $application = new class extends Application
    {
        public function link(): string
        {
            return 'https://coolify.test/application';
        }
    };
    $application->forceFill(['name' => 'app']);
    $application->setRelation('environment', $environment);

    $preview = new ApplicationPreview;
    $preview->forceFill([
        'uuid' => 'preview-uuid',
        'pull_request_id' => 42,
        'restart_count' => 2,
        'max_restart_count' => 2,
    ]);
    $preview->setRelation('application', $application);

    $serviceResource = new class extends BaseModel {};
    $serviceResource->forceFill([
        'name' => 'database',
        'uuid' => 'service-resource-uuid',
        'restart_count' => 2,
        'max_restart_count' => 2,
    ]);
    $serviceResource->setRelation('service', new class($environment)
    {
        public function __construct(public object $environment) {}

        public function link(): string
        {
            return 'https://coolify.test/service';
        }
    });

    expect((new RestartLimitReached($preview))->toSlack()->description)
        ->toContain('*Project:* Coolify')
        ->and((new RestartLimitReached($serviceResource))->toSlack()->description)
        ->toContain('*Project:* Coolify');
});