toBeTrue()
->and(Route::has('v5.home'))->toBeFalse()
->and(Route::has('v5.selection.update'))->toBeTrue()
->and(Route::has('v5.clusters.index'))->toBeTrue()
->and(Route::has('v5.clusters.show'))->toBeTrue()
->and(Route::has('v5.clusters.store'))->toBeTrue()
->and(Route::has('v5.clusters.destroy'))->toBeTrue()
->and(Route::has('v5.clusters.servers.store'))->toBeTrue()
->and(Route::has('v5.clusters.servers.update'))->toBeTrue()
->and(Route::has('v5.clusters.servers.check'))->toBeTrue()
->and(Route::has('v5.clusters.servers.coold-logs'))->toBeTrue()
->and(Route::has('v5.clusters.servers.corrosion-tables'))->toBeTrue()
->and(Route::has('v5.clusters.servers.bootstrap'))->toBeTrue()
->and(Route::has('v5.clusters.servers.destroy'))->toBeTrue()
->and(Route::has('v5.applications.nginx'))->toBeTrue()
->and(Route::has('v5.applications.refresh'))->toBeTrue()
->and(Route::has('v5.applications.position'))->toBeTrue()
->and(Route::has('v5.applications.ingress'))->toBeTrue()
->and(Route::has('v5.caddy-ingresses.position'))->toBeTrue()
->and(Route::has('v5.applications.destroy'))->toBeTrue()
->and(Route::has('v5.resource-connections.store'))->toBeTrue()
->and(Route::has('v5.resource-connections.update'))->toBeTrue()
->and(Route::has('v5.resource-connections.destroy'))->toBeTrue()
->and(Route::has('v5.realtime-test'))->toBeTrue()
->and(Route::has('v5.realtime-test.broadcast'))->toBeTrue()
->and(Route::has('v5.coolify.version'))->toBeFalse()
->and(Route::has('v5.coolify.bootstrap'))->toBeFalse();
});
it('uses separated v5 middleware groups', function () {
$kernel = app(Kernel::class);
$reflection = new ReflectionClass($kernel);
$property = $reflection->getProperty('middlewareGroups');
$property->setAccessible(true);
$groups = $property->getValue($kernel);
expect($groups)->toHaveKey('v5.web')
->and($groups)->toHaveKey('v5.authenticated')
->and($groups['v5.web'])->toContain(HandleInertiaRequests::class)
->and($groups['v5.web'])->not->toContain(CheckForcePasswordReset::class)
->and($groups['v5.web'])->not->toContain(DecideWhatToDoWithUser::class)
->and($groups['v5.authenticated'])->toContain('auth')
->and($groups['v5.authenticated'])->toContain('verified')
->and($groups['v5.authenticated'])->toContain(EnsureCurrentTeam::class);
});
it('reuses existing projects instead of creating v5 projects', function () {
expect(file_exists(database_path('migrations/2026_06_04_050157_v5_create_projects_table.php')))->toBeFalse()
->and(file_exists(app_path('Models/V5/Project.php')))->toBeFalse();
});
it('keeps long v5 application metadata inside the canvas card', function () {
$dashboardSource = file_get_contents(resource_path('js/v5/Pages/Dashboard.tsx'));
expect($dashboardSource)
->toContain('overflow-hidden')
->and($dashboardSource)->toContain('grid grid-cols-[auto_minmax(0,1fr)]')
->and($dashboardSource)->toContain('truncate text-right font-medium')
->and($dashboardSource)->toContain('truncate text-right font-mono');
});
it('does not render v5 application status messages on dashboard cards', function () {
$dashboardSource = file_get_contents(resource_path('js/v5/Pages/Dashboard.tsx'));
expect($dashboardSource)
->not->toContain('{application.statusMessage && (')
->not->toContain('{application.statusMessage}
');
});
it('lets users dismiss v5 dashboard notices', function () {
$dashboardSource = file_get_contents(resource_path('js/v5/Pages/Dashboard.tsx'));
expect($dashboardSource)
->toContain('aria-label="Dismiss notice"')
->toContain('onClick={() => setNotice(null)}')
->toContain('Dismiss notice');
});
it('uses the shared dialog and button components for the application ingress modal', function () {
$dashboardSource = file_get_contents(resource_path('js/v5/Pages/Dashboard.tsx'));
expect($dashboardSource)
->toContain('