baseEnv([ 'APP_ENV' => 'dev', 'APP_DEBUG' => 'true', 'APP_CRYPTO_KEY' => '', 'APP_URL' => 'http://localhost:8080', ])); $this->assertSame([], $errors); } public function testValidateArrayRequiresAppUrlInDevelopment(): void { $errors = EnvValidator::validateArray($this->baseEnv([ 'APP_ENV' => 'dev', 'APP_URL' => '', ])); $this->assertContains('APP_URL must be set in development', $errors); } public function testValidateArrayRequiresProductionCryptoKeyAndUrl(): void { $errors = EnvValidator::validateArray($this->baseEnv([ 'APP_ENV' => 'prod', 'APP_DEBUG' => 'false', 'APP_CRYPTO_KEY' => '', 'APP_URL' => '', ])); $this->assertContains('APP_URL must be set in production', $errors); $this->assertContains('APP_CRYPTO_KEY must be set in production', $errors); } public function testValidateArrayAcceptsMissingAppUrlInTestEnv(): void { $errors = EnvValidator::validateArray($this->baseEnv([ 'APP_ENV' => 'test', 'APP_URL' => '', ])); $this->assertSame([], $errors); } public function testValidateArrayRejectsInvalidCryptoKeyFormat(): void { $errors = EnvValidator::validateArray($this->baseEnv([ 'APP_CRYPTO_KEY' => 'invalid-key-format', ])); $this->assertContains('APP_CRYPTO_KEY must be 64-char hex or base64 encoded 32-byte key', $errors); } public function testValidateArrayAcceptsValidHexCryptoKeyInProduction(): void { $errors = EnvValidator::validateArray($this->baseEnv([ 'APP_ENV' => 'prod', 'APP_DEBUG' => 'false', 'APP_URL' => 'https://example.test', 'APP_CRYPTO_KEY' => str_repeat('a', 64), 'TENANT_SCOPE_STRICT' => 'true', ])); $this->assertSame([], $errors); } public function testValidateArrayRequiresHttpsAppUrlInProduction(): void { $errors = EnvValidator::validateArray($this->baseEnv([ 'APP_ENV' => 'prod', 'APP_DEBUG' => 'false', 'APP_URL' => 'http://example.test', 'APP_CRYPTO_KEY' => str_repeat('a', 64), 'TENANT_SCOPE_STRICT' => 'true', ])); $this->assertContains('APP_URL must use https in production', $errors); } public function testValidateArrayRejectsLocalhostAppUrlInProduction(): void { $errors = EnvValidator::validateArray($this->baseEnv([ 'APP_ENV' => 'prod', 'APP_DEBUG' => 'false', 'APP_URL' => 'https://localhost', 'APP_CRYPTO_KEY' => str_repeat('a', 64), 'TENANT_SCOPE_STRICT' => 'true', ])); $this->assertContains('APP_URL must not use localhost or an IP host in production', $errors); } public function testValidateArrayRejectsIpHostAppUrlInProduction(): void { $errors = EnvValidator::validateArray($this->baseEnv([ 'APP_ENV' => 'prod', 'APP_DEBUG' => 'false', 'APP_URL' => 'https://127.0.0.1', 'APP_CRYPTO_KEY' => str_repeat('a', 64), 'TENANT_SCOPE_STRICT' => 'true', ])); $this->assertContains('APP_URL must not use localhost or an IP host in production', $errors); } /** * @param array $override * @return array */ private function baseEnv(array $override = []): array { $storagePath = sys_get_temp_dir(); return array_merge([ 'APP_NAME' => 'IMVS', 'APP_ENV' => 'dev', 'APP_DEBUG' => 'true', 'APP_TIMEZONE' => 'Europe/Berlin', 'APP_STORAGE_PATH' => $storagePath, 'APP_LOCALE' => 'de', 'APP_LOCALES' => 'de,en', 'APP_I18N_DOMAIN' => 'default', 'APP_URL' => 'http://localhost:8080', 'APP_CRYPTO_KEY' => '', 'SESSION_NAME' => 'IMVS', 'TENANT_SCOPE_STRICT' => 'true', 'DB_HOST' => 'db', 'DB_PORT' => '3306', 'DB_NAME' => 'imvs', 'DB_USER' => 'imvs', 'DB_PASS' => 'imvs', 'CACHE_SERVERS' => 'memcached:11211', 'FIREWALL_CONCURRENCY' => '10', 'FIREWALL_SPINLOCK_SECONDS' => '0.15', 'FIREWALL_INTERVAL_SECONDS' => '300', 'FIREWALL_CACHE_PREFIX' => 'fw_concurrency_', 'FIREWALL_REVERSE_PROXY' => 'false', ], $override); } }