save('plugins', self::FAKE_PLUGIN, 'custom.css', '/* x */'); $this->fail('디렉토리를 만들 수 없는데 예외가 나지 않았다'); } catch (CustomAssetOperationException $e) { $this->assertSame('custom_assets.errors.directory_failed', $e->errorKey); foreach (['path', 'reason', 'owner', 'perms', 'process_user', 'hint'] as $param) { $this->assertArrayHasKey($param, $e->params, "실패 메시지 파라미터 {$param} 이 없다"); } // 사유 코드가 번역된 문장으로 실려야 한다 (키 노출 금지) $this->assertStringNotContainsString('custom_assets.', (string) $e->params['reason']); $this->assertStringNotContainsString('custom_assets.', (string) $e->params['hint']); $this->assertStringContainsString('chown', (string) $e->params['hint']); // 최종 문장에 치환 자리가 남아 있지 않다 $this->assertStringNotContainsString(':reason', $e->getMessage()); $this->assertStringNotContainsString(':hint', $e->getMessage()); } } /** * 사유 코드 4종은 ko/en 양쪽에 번역이 있다 — 없는 코드는 키 문자열이 화면에 나간다. * * @effects custom_directory_failure_explains_reason_and_action */ #[Test] public function 사유_코드_4종은_양_로케일에_번역이_있다(): void { foreach (['ko', 'en'] as $locale) { $messages = require base_path("lang/{$locale}/custom_assets.php"); foreach (['occupied_by_file', 'ancestor_not_writable', 'create_failed', 'not_writable'] as $code) { $this->assertNotEmpty($messages['errors']['reason'][$code] ?? null, "{$locale}: reason.{$code} 번역이 없다"); } $this->assertNotEmpty($messages['errors']['directory_failed_hint'] ?? null, "{$locale}: directory_failed_hint 가 없다"); $this->assertStringContainsString(':hint', $messages['errors']['directory_failed']); } } }