RuntimeConfigurationCacheTest.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. <?php
  2. namespace Tests\Unit;
  3. use App\Services\SportClientService;
  4. use Illuminate\Container\Container;
  5. use Illuminate\Filesystem\Filesystem;
  6. use Illuminate\Foundation\Application;
  7. use Illuminate\Foundation\Bootstrap\LoadConfiguration;
  8. use Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables;
  9. use Illuminate\Http\Client\Factory;
  10. use Illuminate\Http\Client\Request;
  11. use Illuminate\Support\Env;
  12. use Illuminate\Support\Facades\Facade;
  13. use Illuminate\Support\Facades\Http;
  14. use PHPUnit\Framework\Attributes\DataProvider;
  15. use PHPUnit\Framework\Attributes\PreserveGlobalState;
  16. use PHPUnit\Framework\Attributes\RunInSeparateProcess;
  17. use PHPUnit\Framework\TestCase;
  18. class RuntimeConfigurationCacheTest extends TestCase
  19. {
  20. private string $temporaryRoot;
  21. private const ENVIRONMENT = [
  22. 'APP_ENV' => 'local',
  23. 'APP_URL' => 'https://app.example.test',
  24. 'FOOTBALL_APP_URL' => 'https://football-app.example.test',
  25. 'API_FOOTBALL_HOST' => 'https://football.example.test',
  26. 'API_FOOTBALL_KEY' => 'football-test-key',
  27. 'API_BASKETBALL_HOST' => 'https://basketball.example.test',
  28. 'API_BASKETBALL_KEY' => 'basketball-test-key',
  29. 'SEND_TELEGRAM' => 'true',
  30. 'unique_identification' => 'custom-test-salt',
  31. ];
  32. protected function tearDown(): void
  33. {
  34. Facade::clearResolvedInstances();
  35. Facade::setFacadeApplication(null);
  36. Container::setInstance(null);
  37. if (isset($this->temporaryRoot)) {
  38. (new Filesystem())->deleteDirectory($this->temporaryRoot);
  39. }
  40. parent::tearDown();
  41. }
  42. public static function configurationModes(): array
  43. {
  44. return [
  45. 'uncached, telegram enabled' => [false, true],
  46. 'cached, telegram enabled' => [true, true],
  47. 'cached, telegram disabled' => [true, false],
  48. ];
  49. }
  50. #[DataProvider('configurationModes')]
  51. #[RunInSeparateProcess]
  52. #[PreserveGlobalState(false)]
  53. public function test_runtime_configuration_survives_caching(bool $cached, bool $sendTelegram): void
  54. {
  55. // Bootstrap only configuration in a temporary application, without project providers or .env.
  56. $this->temporaryRoot = sys_get_temp_dir() . '/runtime-config-test-' . bin2hex(random_bytes(8));
  57. mkdir($this->temporaryRoot . '/config', 0700, true);
  58. mkdir($this->temporaryRoot . '/bootstrap/cache', 0700, true);
  59. foreach (['app', 'services'] as $file) {
  60. copy(dirname(__DIR__, 2) . '/config/' . $file . '.php', $this->temporaryRoot . '/config/' . $file . '.php');
  61. }
  62. $environment = array_replace(self::ENVIRONMENT, ['SEND_TELEGRAM' => $sendTelegram ? 'true' : 'false']);
  63. $contents = '';
  64. foreach ($environment as $key => $value) {
  65. $contents .= $key . '=' . $value . PHP_EOL;
  66. }
  67. file_put_contents($this->temporaryRoot . '/.env', $contents);
  68. $this->clearEnvironment();
  69. $app = $this->bootConfiguration();
  70. if ($cached) {
  71. // Use the same serialized config format as config:cache, then start with a fresh environment.
  72. file_put_contents($app->getCachedConfigPath(), '<?php return ' . var_export($app['config']->all(), true) . ';');
  73. $this->clearEnvironment();
  74. $app = $this->bootConfiguration();
  75. foreach (array_keys(self::ENVIRONMENT) as $key) {
  76. $this->assertNull(env($key), 'Cached bootstrap must skip loading ' . $key . ' from .env');
  77. }
  78. }
  79. $this->assertSame($cached, $app['config_loaded_from_cache']);
  80. $this->assertSame('local', config('app.env'));
  81. $this->assertSame('https://app.example.test', config('app.url'));
  82. $this->assertSame('https://football-app.example.test', config('app.football_url'));
  83. $this->assertSame('custom-test-salt', config('app.unique_identification'));
  84. $this->assertSame($sendTelegram, config('services.telegram.send_enabled'));
  85. $app->instance(Factory::class, new Factory());
  86. Facade::clearResolvedInstances();
  87. Facade::setFacadeApplication($app);
  88. Http::preventStrayRequests();
  89. Http::fake(['*' => Http::response(['response' => []])]);
  90. foreach ([1 => 'football', 2 => 'basketball'] as $type => $sport) {
  91. $endpoint = $type === 1 ? 'fixtures' : 'games';
  92. foreach (['get', 'post'] as $method) {
  93. $params = ['live' => 'all'];
  94. $this->assertSame(['response' => []], SportClientService::$method($type, $endpoint, $params));
  95. Http::assertSent(fn (Request $request) =>
  96. $request->method() === strtoupper($method)
  97. && strtok($request->url(), '?') === 'https://' . $sport . '.example.test/' . $endpoint
  98. && $request->hasHeader('x-apisports-key', $sport . '-test-key')
  99. && $request->data() === $params
  100. );
  101. }
  102. }
  103. Http::assertSentCount(4);
  104. }
  105. private function bootConfiguration(): Application
  106. {
  107. $app = new Application($this->temporaryRoot);
  108. (new LoadEnvironmentVariables())->bootstrap($app);
  109. (new LoadConfiguration())->bootstrap($app);
  110. return $app;
  111. }
  112. private function clearEnvironment(): void
  113. {
  114. foreach (array_merge(array_keys(self::ENVIRONMENT), ['APP_CONFIG_CACHE']) as $key) {
  115. unset($_ENV[$key], $_SERVER[$key]);
  116. putenv($key);
  117. }
  118. Env::enablePutenv();
  119. }
  120. }