mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
test: fix stale and flaky tests
- Update tests whose expectations no longer matched intended changes (GitHub App redirect, wildcard domains, container IDs, nightly install folders, localhost metadata, Sentinel restart on save) - Seed the instance settings the 2FA profile test needs - Run the global Pest hooks for all test folders and release the Laravel application after each test, so tests do not leak state - Replace a class alias mock that broke later tests - Generate factory names that always pass name validation; Faker names with apostrophes or short words made form tests fail at random
This commit is contained in:
@@ -13,7 +13,7 @@ class ApplicationFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->unique()->name(),
|
||||
'name' => fake()->unique()->words(2, true),
|
||||
'destination_id' => 1,
|
||||
'git_repository' => fake()->url(),
|
||||
'git_branch' => fake()->word(),
|
||||
|
||||
@@ -9,7 +9,7 @@ class EnvironmentFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->unique()->word(),
|
||||
'name' => fake()->unique()->words(2, true),
|
||||
'project_id' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ class ProjectFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->unique()->company(),
|
||||
'name' => fake()->unique()->words(2, true),
|
||||
'team_id' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\StandaloneDocker;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ServiceFactory extends Factory
|
||||
@@ -9,8 +10,8 @@ class ServiceFactory extends Factory
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->unique()->word(),
|
||||
'destination_type' => \App\Models\StandaloneDocker::class,
|
||||
'name' => fake()->unique()->words(2, true),
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
'destination_id' => 1,
|
||||
'environment_id' => 1,
|
||||
'docker_compose_raw' => 'version: "3"',
|
||||
|
||||
@@ -119,6 +119,10 @@ describe('GitHub Source Create Component', function () {
|
||||
|
||||
$githubApp = GithubApp::where('name', 'redirect-test')->first();
|
||||
|
||||
$component->assertRedirect(route('source.github.show', ['github_app_uuid' => $githubApp->uuid]));
|
||||
$component->assertRedirect(route('source.github.show', [
|
||||
'github_app_uuid' => $githubApp->uuid,
|
||||
'previews' => 1,
|
||||
'runners' => 0,
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2316,7 +2316,7 @@ it('regenerates an application domain as a draft while preserving its url settin
|
||||
->assertSet('editingDomainParts.path', '/api');
|
||||
|
||||
expect($component->get('editingDomainParts')['host'])
|
||||
->toEndWith('.sslip.io')
|
||||
->toEndWith('.wildcard.example.net')
|
||||
->not->toBe('old.example.com')
|
||||
->and($this->application->fresh()->fqdn)->toBe('http://old.example.com/api')
|
||||
->and($this->application->fresh()->domain_port_overrides)->toMatchArray([
|
||||
|
||||
@@ -4,9 +4,17 @@ use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
Server::flushIdentityMap();
|
||||
// Changing is_metrics_enabled queues a Sentinel restart (ServerSetting::booted()); these tests
|
||||
// only check the enabled state, so keep the sync queue from starting Sentinel.
|
||||
Queue::fake();
|
||||
});
|
||||
|
||||
it('treats Sentinel as enabled for regular servers even when the legacy flag and metrics are disabled', function () {
|
||||
DB::table('instance_settings')->insert(['id' => 0]);
|
||||
$user = User::factory()->create();
|
||||
|
||||
@@ -164,12 +164,14 @@ it('collects localhost server details after a successful connection check', func
|
||||
'connection_timeout' => 10,
|
||||
]);
|
||||
|
||||
Process::fake([
|
||||
'*' => Process::sequence([
|
||||
Process::result(output: 'bin', exitCode: 0),
|
||||
Process::result(output: localhostMetadataProcessOutput(), exitCode: 0),
|
||||
]),
|
||||
]);
|
||||
// The connection check also runs SSH multiplexing commands, so answer by command, not by order.
|
||||
Process::fake(function ($process) {
|
||||
$command = is_array($process->command) ? implode(' ', $process->command) : $process->command;
|
||||
|
||||
return str_contains($command, '---PRETTY_NAME---')
|
||||
? Process::result(output: localhostMetadataProcessOutput(), exitCode: 0)
|
||||
: Process::result(output: 'bin', exitCode: 0);
|
||||
});
|
||||
|
||||
Livewire::test(Show::class, ['server_uuid' => $this->server->uuid])
|
||||
->call('checkLocalhostConnection');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Livewire\Profile\Index as ProfileIndex;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider;
|
||||
@@ -8,6 +9,10 @@ use Livewire\Livewire;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
InstanceSettings::query()->forceCreate(['id' => 0]);
|
||||
});
|
||||
|
||||
/**
|
||||
* @return array{input: string, copied: string}
|
||||
*/
|
||||
|
||||
@@ -99,6 +99,25 @@ it('caches parsed local service templates by bundle mtime', function () {
|
||||
});
|
||||
|
||||
it('keeps service template keys for service selection and docs links', function () {
|
||||
$path = base_path('templates/'.config('constants.services.file_name'));
|
||||
File::partialMock()
|
||||
->shouldReceive('exists')
|
||||
->with($path)
|
||||
->andReturn(true)
|
||||
->shouldReceive('get')
|
||||
->with($path)
|
||||
->andReturn(json_encode([
|
||||
'denoKV' => [
|
||||
'documentation' => 'https://coolify.io/docs',
|
||||
'slogan' => 'Deno KV.',
|
||||
'compose' => '',
|
||||
'tags' => null,
|
||||
'category' => 'database',
|
||||
'logo' => 'images/default.webp',
|
||||
'minversion' => '0.0.0',
|
||||
],
|
||||
]));
|
||||
|
||||
$services = collect((new Select)->loadServices()['services']);
|
||||
$denoKv = $services->firstWhere('id', 'denoKV');
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ function fakeTerminalContainer(string $state, bool $hasShell = true, array $runn
|
||||
|
||||
if (str_contains($command, 'docker ps -a')) {
|
||||
return Process::result(output: collect($runningContainers)
|
||||
->map(fn (string $name) => json_encode(['Names' => $name, 'State' => 'running', 'Labels' => '']))
|
||||
->map(fn (string $name) => json_encode(['ID' => md5($name), 'Names' => $name, 'State' => 'running', 'Labels' => '']))
|
||||
->implode("\n"));
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -70,19 +70,23 @@ function loadLazy(Testable $component): Testable
|
||||
| Test Hooks
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Global hooks that run before/after each test.
|
||||
| Global hooks that run before/after each test. A bare beforeEach() in this file
|
||||
| only applies to tests in this file, so register them with uses()->in().
|
||||
|
|
||||
*/
|
||||
beforeEach(function () {
|
||||
uses()->beforeEach(function () {
|
||||
// Flush the Once memoization cache to ensure tests get fresh data
|
||||
Once::flush();
|
||||
|
||||
// Flush the Server identity map cache to ensure tests get fresh data
|
||||
Server::flushIdentityMap();
|
||||
|
||||
})->in('Feature', 'Unit', 'v4');
|
||||
|
||||
uses()->beforeEach(function () {
|
||||
// Browser Livewire actions often dispatch events; disable broadcasting for host-side runs.
|
||||
config(['broadcasting.default' => 'null']);
|
||||
});
|
||||
})->in('v4/Browser');
|
||||
|
||||
function loginAndSkipBoarding(string $email = 'test@example.com', string $password = 'password'): mixed
|
||||
{
|
||||
|
||||
@@ -2,9 +2,25 @@
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
use CreatesApplication;
|
||||
|
||||
/**
|
||||
* Laravel flushes the application on tear down but leaves it set as the facade
|
||||
* application and container instance. Plain unit tests that mock facades or call
|
||||
* helpers afterwards would then resolve bindings from that empty application.
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
Facade::clearResolvedInstances();
|
||||
Facade::setFacadeApplication(null);
|
||||
Container::setInstance(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ function installScriptFileMode(string $path): string
|
||||
|
||||
$installScripts = [
|
||||
'stable install' => ['scripts/install.sh', ['source', 'ssh', 'images']],
|
||||
'nightly install' => ['other/nightly/install.sh', ['source', 'ssh']],
|
||||
'nightly install' => ['other/nightly/install.sh', ['source', 'ssh', 'images']],
|
||||
];
|
||||
$resourceDirectories = ['applications', 'databases', 'backups', 'services', 'proxy', 'sentinel'];
|
||||
|
||||
|
||||
@@ -2,24 +2,23 @@
|
||||
|
||||
use App\Events\RestoreJobFinished;
|
||||
use App\Events\S3RestoreJobFinished;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
/**
|
||||
* Tests for RestoreJobFinished and S3RestoreJobFinished events to ensure they handle
|
||||
* null server scenarios gracefully (when server is deleted during operation).
|
||||
*/
|
||||
describe('RestoreJobFinished null server handling', function () {
|
||||
afterEach(function () {
|
||||
Mockery::close();
|
||||
beforeEach(function () {
|
||||
Process::fake();
|
||||
});
|
||||
|
||||
it('handles null server gracefully in RestoreJobFinished event', function () {
|
||||
// Mock Server::find to return null (server was deleted)
|
||||
$mockServer = Mockery::mock('alias:'.Server::class);
|
||||
$mockServer->shouldReceive('find')
|
||||
->with(999)
|
||||
->andReturn(null);
|
||||
|
||||
// Server 999 does not exist (it was deleted during the restore)
|
||||
$data = [
|
||||
'scriptPath' => '/tmp/script.sh',
|
||||
'tmpPath' => '/tmp/backup.sql',
|
||||
@@ -29,15 +28,11 @@ describe('RestoreJobFinished null server handling', function () {
|
||||
|
||||
// Should not throw an error when server is null
|
||||
expect(fn () => new RestoreJobFinished($data))->not->toThrow(\Throwable::class);
|
||||
Process::assertNothingRan();
|
||||
});
|
||||
|
||||
it('handles null server gracefully in S3RestoreJobFinished event', function () {
|
||||
// Mock Server::find to return null (server was deleted)
|
||||
$mockServer = Mockery::mock('alias:'.Server::class);
|
||||
$mockServer->shouldReceive('find')
|
||||
->with(999)
|
||||
->andReturn(null);
|
||||
|
||||
// Server 999 does not exist (it was deleted during the restore)
|
||||
$data = [
|
||||
'containerName' => 'helper-container',
|
||||
'serverTmpPath' => '/tmp/downloaded.sql',
|
||||
@@ -49,6 +44,7 @@ describe('RestoreJobFinished null server handling', function () {
|
||||
|
||||
// Should not throw an error when server is null
|
||||
expect(fn () => new S3RestoreJobFinished($data))->not->toThrow(\Throwable::class);
|
||||
Process::assertNothingRan();
|
||||
});
|
||||
|
||||
it('handles empty serverId in RestoreJobFinished event', function () {
|
||||
|
||||
Reference in New Issue
Block a user