Locks held until this test process exits. */ private static array $qaDatabaseLocks = []; public function createApplication(): Application { $app = parent::createApplication(); $connectionName = $app->make('config')->get('database.default'); $connection = $app->make('config')->get('database.connections.'.$connectionName, []); // Never let a test runner silently fall back to the developer/application database. // Keep this check before RefreshDatabase or any schema cleanup can open a connection. if ($app->environment('testing') && ($connection['driver'] ?? null) === 'pgsql') { $database = strtolower((string) ($connection['database'] ?? '')); $host = strtolower((string) ($connection['host'] ?? '')); $url = $connection['url'] ?? null; if ($database === 'investemoz' || ! str_ends_with($database, '_test') || ! in_array($host, ['127.0.0.1', 'localhost'], true) || ! in_array($url, [null, ''], true)) { throw new \LogicException('PHPUnit PostgreSQL target rejected: configure an explicit loopback *_test database with no DB_URL.'); } // Two test processes must not migrate/clean the same isolated database concurrently. $lockKey = hash('sha256', $host.':'.($connection['port'] ?? '').':'.$database); if (! isset(self::$qaDatabaseLocks[$lockKey])) { $handle = fopen($app->storagePath('framework/qa-database-'.$lockKey.'.lock'), 'c'); if ($handle === false) { throw new \LogicException('QA database execution lock unavailable.'); } if (! flock($handle, LOCK_EX | LOCK_NB)) { fclose($handle); throw new \LogicException('Another test process owns this QA database; run suites sequentially or use separate *_test databases.'); } self::$qaDatabaseLocks[$lockKey] = $handle; } } // migrate:fresh only clears the connection's public search path. This module owns a separate schema. $traits = class_uses_recursive(static::class); $rebuildsDatabase = isset($traits[RefreshDatabase::class]) || isset($traits[DatabaseMigrations::class]) || $this instanceof SchemaLifecycleTest; if ($rebuildsDatabase && ! RefreshDatabaseState::$migrated && $app->environment('testing')) { $db = $app->make('db')->connection(); if (IsolatedQaDatabase::allowsSchemaChanges()) { $db->statement('DROP SCHEMA IF EXISTS skills_edu CASCADE'); if ($db->selectOne("SELECT to_regclass('public.complementary_project_links') AS id")->id !== null) { $db->statement('DROP TRIGGER IF EXISTS project_dependency_check ON public.complementary_project_links'); $db->statement('DROP TRIGGER IF EXISTS project_dependency_lock ON public.complementary_project_links'); } $db->statement('DROP FUNCTION IF EXISTS public.check_project_dependency_graph() RESTRICT'); $db->statement('DROP FUNCTION IF EXISTS public.lock_project_dependency_graph() RESTRICT'); if ($db->selectOne("SELECT to_regclass('public.existing_business_locations') AS id")->id !== null) { $db->statement('DROP TRIGGER IF EXISTS existing_business_locations_hierarchy_trigger ON public.existing_business_locations'); } $db->statement('DROP FUNCTION IF EXISTS public.enforce_existing_business_location_hierarchy() RESTRICT'); } } return $app; } protected function setUp(): void { parent::setUp(); // HTTP feature tests must not depend on the developer's Vite SSR server. config(['inertia.ssr.enabled' => false]); } protected function withoutVite() { parent::withoutVite(); // Laravel's asset fake leaves font loading active. HTTP tests must not read mutable build assets. $this->app->make(Vite::class)->useFontsManifestFilename('__phpunit_fonts_disabled__.json'); return $this; } protected function skipUnlessFortifyHas(string $feature, ?string $message = null): void { if (! Features::enabled($feature)) { $this->markTestSkipped($message ?? "Fortify feature [{$feature}] is not enabled."); } } }