Add notification-level deduplication keys and TTLs for deployment, backup, server, container, scheduled task, SSL, token, and transactional emails. Apply deduplication in email channels before sending rendered messages.
100 lines
3.8 KiB
PHP
100 lines
3.8 KiB
PHP
<?php
|
|
|
|
use App\Actions\Application\StopApplication;
|
|
use App\Models\Application;
|
|
use App\Notifications\Application\RestartLimitReached;
|
|
|
|
function applicationWithRestartState(array $attributes = []): Application
|
|
{
|
|
$application = new Application;
|
|
$application->forceFill(array_merge([
|
|
'status' => 'exited:unhealthy',
|
|
'restart_count' => 2,
|
|
'max_restart_count' => 2,
|
|
'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_count' => 1])->stoppedAfterRestartLimit())->toBeFalse()
|
|
->and(applicationWithRestartState(['max_restart_count' => 0])->stoppedAfterRestartLimit())->toBeFalse()
|
|
->and(applicationWithRestartState(['last_restart_type' => null])->stoppedAfterRestartLimit())->toBeFalse();
|
|
});
|
|
|
|
it('shows a stopped after restart limit warning in the status badge', function () {
|
|
$html = view('components.status.index', [
|
|
'resource' => applicationWithRestartState(),
|
|
'showRefreshButton' => false,
|
|
])->render();
|
|
|
|
expect($html)->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.status.index', [
|
|
'resource' => applicationWithRestartState([
|
|
'restart_count' => 0,
|
|
'last_restart_type' => null,
|
|
]),
|
|
'showRefreshButton' => false,
|
|
])->render();
|
|
|
|
expect($html)->not->toContain('Stopped after reaching restart limit');
|
|
});
|
|
|
|
it('uses a semantic dedupe key for restart limit notifications', function () {
|
|
$application = applicationWithRestartState();
|
|
$application->forceFill([
|
|
'name' => 'crashy-app',
|
|
'uuid' => 'application-uuid',
|
|
]);
|
|
$application->setRelation('environment', (object) [
|
|
'uuid' => 'environment-uuid',
|
|
'name' => 'production',
|
|
'project' => (object) ['uuid' => 'project-uuid'],
|
|
]);
|
|
|
|
$notification = new RestartLimitReached($application);
|
|
|
|
expect($notification->deduplicationKey((object) ['id' => 1], 'mail'))->toBe('restart-limit-reached:application:application-uuid:count:2')
|
|
->and($notification->deduplicateFor())->toBe(86400);
|
|
});
|
|
|
|
it('keeps restart tracking configurable when stopping an application', function () {
|
|
$method = new ReflectionMethod(StopApplication::class, 'handle');
|
|
$resetRestartCount = collect($method->getParameters())->firstWhere('name', 'resetRestartCount');
|
|
|
|
expect($resetRestartCount)->not->toBeNull()
|
|
->and($resetRestartCount->getDefaultValue())->toBeTrue();
|
|
});
|
|
|
|
it('uses the application link for restart limit notifications', function () {
|
|
$application = new class extends Application
|
|
{
|
|
public function link()
|
|
{
|
|
return 'https://coolify.test/project/link-from-model';
|
|
}
|
|
};
|
|
$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/link-from-model');
|
|
});
|