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. if (! RefreshDatabaseState::$migrated && $app->environment('testing')) { $db = $app->make('db')->connection(); if ($db->getDriverName() === 'pgsql' && in_array($db->getDatabaseName(), ['portal_services_test', 'investmoz_test', 'investmoz_browser_e2e_test', 'investmoz_guidance_20261008_test', 'investmoz_v2_20261008_test'], true) && (string) $db->getConfig('port') === '55441' && in_array($db->getConfig('host'), ['127.0.0.1', 'localhost'], true)) { $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 skipUnlessFortifyHas(string $feature, ?string $message = null): void { if (! Features::enabled($feature)) { $this->markTestSkipped($message ?? "Fortify feature [{$feature}] is not enabled."); } } }