Files
Gnuboard7/tests/Feature/Mail/NotificationRecipientLocaleTest.php
2026-07-01 10:30:32 +09:00

187 lines
6.6 KiB
PHP

<?php
namespace Tests\Feature\Mail;
use App\Models\NotificationDefinition;
use App\Models\NotificationTemplate;
use App\Models\User;
use App\Notifications\BaseNotification;
use App\Notifications\GenericNotification;
use App\Services\NotificationDefinitionService;
use App\Services\NotificationTemplateService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Config;
use Tests\TestCase;
/**
* 알림 수신자 언어(locale) 결정 회귀 테스트 (A7)
*
* 알림 렌더는 요청자(app locale) 가 아닌 수신자 본인 언어(users.language)로
* 결정되어야 합니다. User 가 HasLocalePreference 를 구현하고, 렌더 3지점
* (toMail / toArray / Dispatcher) 이 BaseNotification::resolveNotifiableLocale()
* 헬퍼로 수신자 선호 로케일을 우선 해석하는지 검증합니다.
*/
class NotificationRecipientLocaleTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Cache::flush();
Config::set('app.name', 'G7 Test');
Config::set('app.url', 'https://g7.test');
Config::set('app.supported_locales', ['ko', 'en']);
}
/**
* User::preferredLocale() 이 users.language 를 SSoT 로 반환한다.
*/
public function test_preferred_locale_returns_user_language(): void
{
$user = User::factory()->create(['language' => 'en']);
$this->assertSame('en', $user->preferredLocale());
}
/**
* language 가 미지원/빈값이면 사이트 기본 로케일로 폴백한다.
*/
public function test_preferred_locale_falls_back_to_default(): void
{
Config::set('app.locale', 'ko');
$unsupported = User::factory()->create(['language' => 'fr']);
$this->assertSame('ko', $unsupported->preferredLocale());
// language 가 빈값(null) 인 수신자 — language 컬럼은 NOT NULL 이므로 모델 객체로 검증
$empty = new User(['language' => null]);
$this->assertSame('ko', $empty->preferredLocale());
}
/**
* 헬퍼는 HasLocalePreference 수신자의 preferredLocale() 을 우선한다.
*/
public function test_helper_prefers_recipient_locale_over_app_locale(): void
{
App::setLocale('en'); // 요청자/관리자 컨텍스트 = en
$recipient = User::factory()->create(['language' => 'ko']);
$this->assertSame('ko', BaseNotification::resolveNotifiableLocale($recipient));
}
/**
* contract 미구현 수신자는 현재 app locale 로 폴백한다.
*/
public function test_helper_falls_back_for_non_contract_notifiable(): void
{
App::setLocale('en');
$anonymous = new \Illuminate\Notifications\AnonymousNotifiable;
$this->assertSame('en', BaseNotification::resolveNotifiableLocale($anonymous));
}
/**
* toMail: 요청자 locale=en 이어도 수신자 language=ko 면 ko 본문으로 렌더.
*/
public function test_to_mail_renders_in_recipient_language(): void
{
$this->createDefinitionWithMailChannel('order_test', 'sirsoft-ecommerce', 'module', 'sirsoft-ecommerce', [
'subject' => ['ko' => '주문 안내', 'en' => 'Order notice'],
'body' => ['ko' => '<p>한국어 본문</p>', 'en' => '<p>English body</p>'],
]);
App::setLocale('en'); // 관리자(요청자) 컨텍스트
$recipient = User::factory()->create(['language' => 'ko', 'email' => 'ko@example.com']);
$notification = new GenericNotification('order_test', 'sirsoft-ecommerce', []);
$mailable = $notification->toMail($recipient);
$this->assertSame('주문 안내', $mailable->envelope()->subject);
$this->assertStringContainsString('한국어 본문', $mailable->content()->with['body']);
}
/**
* toArray(database): 수신자 language=ko 면 ko 본문으로 렌더.
*/
public function test_to_array_renders_in_recipient_language(): void
{
$this->createDefinitionWithDatabaseChannel('order_test_db', 'sirsoft-ecommerce', 'module', 'sirsoft-ecommerce', [
'subject' => ['ko' => '주문 알림', 'en' => 'Order alert'],
'body' => ['ko' => '한국어 알림 본문', 'en' => 'English alert body'],
]);
App::setLocale('en');
$recipient = User::factory()->create(['language' => 'ko']);
$notification = new GenericNotification('order_test_db', 'sirsoft-ecommerce', []);
$payload = $notification->toArray($recipient);
$this->assertSame('주문 알림', $payload['subject']);
$this->assertStringContainsString('한국어 알림 본문', $payload['body']);
}
private function createDefinitionWithMailChannel(
string $type,
string $hookPrefix,
string $extensionType,
string $extensionIdentifier,
array $templateData,
): NotificationDefinition {
return $this->createDefinition($type, $hookPrefix, $extensionType, $extensionIdentifier, 'mail', $templateData);
}
private function createDefinitionWithDatabaseChannel(
string $type,
string $hookPrefix,
string $extensionType,
string $extensionIdentifier,
array $templateData,
): NotificationDefinition {
return $this->createDefinition($type, $hookPrefix, $extensionType, $extensionIdentifier, 'database', $templateData);
}
private function createDefinition(
string $type,
string $hookPrefix,
string $extensionType,
string $extensionIdentifier,
string $channel,
array $templateData,
): NotificationDefinition {
$definition = NotificationDefinition::create([
'type' => $type,
'hook_prefix' => $hookPrefix,
'extension_type' => $extensionType,
'extension_identifier' => $extensionIdentifier,
'name' => ['ko' => $type, 'en' => $type],
'variables' => [],
'channels' => [$channel],
'hooks' => [],
'is_active' => true,
'is_default' => true,
]);
NotificationTemplate::create([
'definition_id' => $definition->id,
'channel' => $channel,
'subject' => $templateData['subject'],
'body' => $templateData['body'],
'is_active' => true,
'is_default' => true,
]);
app(NotificationDefinitionService::class)->invalidateCache($type);
app(NotificationTemplateService::class)->invalidateCache($type, $channel);
return $definition;
}
}