'local', 'APP_URL' => 'https://app.example.test', 'FOOTBALL_APP_URL' => 'https://football-app.example.test', 'API_FOOTBALL_HOST' => 'https://football.example.test', 'API_FOOTBALL_KEY' => 'football-test-key', 'API_BASKETBALL_HOST' => 'https://basketball.example.test', 'API_BASKETBALL_KEY' => 'basketball-test-key', 'SEND_TELEGRAM' => 'true', 'unique_identification' => 'custom-test-salt', ]; protected function tearDown(): void { Facade::clearResolvedInstances(); Facade::setFacadeApplication(null); Container::setInstance(null); if (isset($this->temporaryRoot)) { (new Filesystem())->deleteDirectory($this->temporaryRoot); } parent::tearDown(); } public static function configurationModes(): array { return [ 'uncached, telegram enabled' => [false, true], 'cached, telegram enabled' => [true, true], 'cached, telegram disabled' => [true, false], ]; } #[DataProvider('configurationModes')] #[RunInSeparateProcess] #[PreserveGlobalState(false)] public function test_runtime_configuration_survives_caching(bool $cached, bool $sendTelegram): void { // Bootstrap only configuration in a temporary application, without project providers or .env. $this->temporaryRoot = sys_get_temp_dir() . '/runtime-config-test-' . bin2hex(random_bytes(8)); mkdir($this->temporaryRoot . '/config', 0700, true); mkdir($this->temporaryRoot . '/bootstrap/cache', 0700, true); foreach (['app', 'services'] as $file) { copy(dirname(__DIR__, 2) . '/config/' . $file . '.php', $this->temporaryRoot . '/config/' . $file . '.php'); } $environment = array_replace(self::ENVIRONMENT, ['SEND_TELEGRAM' => $sendTelegram ? 'true' : 'false']); $contents = ''; foreach ($environment as $key => $value) { $contents .= $key . '=' . $value . PHP_EOL; } file_put_contents($this->temporaryRoot . '/.env', $contents); $this->clearEnvironment(); $app = $this->bootConfiguration(); if ($cached) { // Use the same serialized config format as config:cache, then start with a fresh environment. file_put_contents($app->getCachedConfigPath(), 'all(), true) . ';'); $this->clearEnvironment(); $app = $this->bootConfiguration(); foreach (array_keys(self::ENVIRONMENT) as $key) { $this->assertNull(env($key), 'Cached bootstrap must skip loading ' . $key . ' from .env'); } } $this->assertSame($cached, $app['config_loaded_from_cache']); $this->assertSame('local', config('app.env')); $this->assertSame('https://app.example.test', config('app.url')); $this->assertSame('https://football-app.example.test', config('app.football_url')); $this->assertSame('custom-test-salt', config('app.unique_identification')); $this->assertSame($sendTelegram, config('services.telegram.send_enabled')); $app->instance(Factory::class, new Factory()); Facade::clearResolvedInstances(); Facade::setFacadeApplication($app); Http::preventStrayRequests(); Http::fake(['*' => Http::response(['response' => []])]); foreach ([1 => 'football', 2 => 'basketball'] as $type => $sport) { $endpoint = $type === 1 ? 'fixtures' : 'games'; foreach (['get', 'post'] as $method) { $params = ['live' => 'all']; $this->assertSame(['response' => []], SportClientService::$method($type, $endpoint, $params)); Http::assertSent(fn (Request $request) => $request->method() === strtoupper($method) && strtok($request->url(), '?') === 'https://' . $sport . '.example.test/' . $endpoint && $request->hasHeader('x-apisports-key', $sport . '-test-key') && $request->data() === $params ); } } Http::assertSentCount(4); } private function bootConfiguration(): Application { $app = new Application($this->temporaryRoot); (new LoadEnvironmentVariables())->bootstrap($app); (new LoadConfiguration())->bootstrap($app); return $app; } private function clearEnvironment(): void { foreach (array_merge(array_keys(self::ENVIRONMENT), ['APP_CONFIG_CACHE']) as $key) { unset($_ENV[$key], $_SERVER[$key]); putenv($key); } Env::enablePutenv(); } }