v7.0.0-beta.2 release
This commit is contained in:
1 parent
6595fd0eb5
commit
fa8eba7a0d
1165 files changed
+107118
-762127
No files matched your search
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Models\Concerns;
|
||||
|
||||
use App\Models\NotificationDefinition;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* mass update 시 HasUserOverrides trait 의 투명 추적 회귀 방지 테스트.
|
||||
*
|
||||
* `UserOverridesAwareBuilder` 가 `Model::where(...)->update(...)` 를 가로채
|
||||
* user_overrides 에 수정 필드를 자동 기록하는지 검증합니다.
|
||||
*
|
||||
* 요구사항: `use HasUserOverrides;` 선언만으로 모든 수정 경로(인스턴스
|
||||
* update/save, mass update) 에서 user_overrides 가 자동 기록되어야 함.
|
||||
*/
|
||||
class HasUserOverridesMassUpdateTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* 기본 definition 생성 헬퍼.
|
||||
*/
|
||||
private function createDefinition(array $overrides = []): NotificationDefinition
|
||||
{
|
||||
return NotificationDefinition::create(array_merge([
|
||||
'type' => 'mass-update-test',
|
||||
'hook_prefix' => 'core.auth',
|
||||
'extension_type' => 'core',
|
||||
'extension_identifier' => 'core',
|
||||
'name' => ['ko' => '원본'],
|
||||
'description' => ['ko' => '원본'],
|
||||
'channels' => ['mail'],
|
||||
'hooks' => [],
|
||||
'variables' => [],
|
||||
'is_active' => true,
|
||||
'is_default' => true,
|
||||
], $overrides));
|
||||
}
|
||||
|
||||
public function test_mass_update_records_user_overrides_for_trackable_fields(): void
|
||||
{
|
||||
$definition = $this->createDefinition();
|
||||
|
||||
// 사용자가 UI 에서 관리자 일괄 저장 시 흔히 발생하는 패턴.
|
||||
NotificationDefinition::where('id', $definition->id)->update([
|
||||
'name' => ['ko' => '사용자 수정'],
|
||||
]);
|
||||
|
||||
$reloaded = NotificationDefinition::find($definition->id);
|
||||
|
||||
$this->assertSame(['ko' => '사용자 수정'], $reloaded->name);
|
||||
$this->assertIsArray($reloaded->user_overrides);
|
||||
$this->assertContains('name', $reloaded->user_overrides, 'mass update 시에도 trackable 필드가 user_overrides 에 자동 기록');
|
||||
}
|
||||
|
||||
public function test_mass_update_accumulates_overrides_across_multiple_edits(): void
|
||||
{
|
||||
$definition = $this->createDefinition();
|
||||
|
||||
NotificationDefinition::where('id', $definition->id)->update([
|
||||
'name' => ['ko' => '수정1'],
|
||||
]);
|
||||
NotificationDefinition::where('id', $definition->id)->update([
|
||||
'is_active' => false,
|
||||
]);
|
||||
|
||||
$reloaded = NotificationDefinition::find($definition->id);
|
||||
|
||||
$this->assertContains('name', $reloaded->user_overrides);
|
||||
$this->assertContains('is_active', $reloaded->user_overrides, '여러 번의 mass update 가 누적 기록');
|
||||
}
|
||||
|
||||
public function test_mass_update_ignores_non_trackable_fields(): void
|
||||
{
|
||||
$definition = $this->createDefinition();
|
||||
|
||||
// hook_prefix 는 trackableFields 에 없음
|
||||
NotificationDefinition::where('id', $definition->id)->update([
|
||||
'hook_prefix' => 'changed.prefix',
|
||||
]);
|
||||
|
||||
$reloaded = NotificationDefinition::find($definition->id);
|
||||
|
||||
$this->assertSame('changed.prefix', $reloaded->hook_prefix);
|
||||
$this->assertEmpty($reloaded->user_overrides ?? [], 'trackable 외 필드는 user_overrides 기록 안 됨');
|
||||
}
|
||||
|
||||
public function test_mass_update_across_multiple_rows_tracks_each_independently(): void
|
||||
{
|
||||
$a = $this->createDefinition(['type' => 'row-a']);
|
||||
$b = $this->createDefinition(['type' => 'row-b']);
|
||||
|
||||
// 2행 동시 mass update
|
||||
NotificationDefinition::whereIn('id', [$a->id, $b->id])->update([
|
||||
'name' => ['ko' => '일괄수정'],
|
||||
]);
|
||||
|
||||
$reloadedA = NotificationDefinition::find($a->id);
|
||||
$reloadedB = NotificationDefinition::find($b->id);
|
||||
|
||||
$this->assertContains('name', $reloadedA->user_overrides ?? []);
|
||||
$this->assertContains('name', $reloadedB->user_overrides ?? []);
|
||||
}
|
||||
|
||||
public function test_seeder_context_bypasses_tracking_on_mass_update(): void
|
||||
{
|
||||
$definition = $this->createDefinition();
|
||||
|
||||
app()->instance('user_overrides.seeding', true);
|
||||
try {
|
||||
NotificationDefinition::where('id', $definition->id)->update([
|
||||
'name' => ['ko' => '시더 수정'],
|
||||
]);
|
||||
} finally {
|
||||
app()->forgetInstance('user_overrides.seeding');
|
||||
}
|
||||
|
||||
$reloaded = NotificationDefinition::find($definition->id);
|
||||
|
||||
$this->assertSame(['ko' => '시더 수정'], $reloaded->name);
|
||||
$this->assertEmpty(
|
||||
$reloaded->user_overrides ?? [],
|
||||
'시더 컨텍스트에서는 user_overrides 기록 안 됨 (성능 유지 + syncFromUpgrade 의미 보존)',
|
||||
);
|
||||
}
|
||||
|
||||
public function test_raw_db_facade_update_bypasses_tracking(): void
|
||||
{
|
||||
$definition = $this->createDefinition();
|
||||
|
||||
// 실제 대량 처리 성능이 필요한 경우를 위한 escape hatch — DB facade 사용.
|
||||
DB::table('notification_definitions')
|
||||
->where('id', $definition->id)
|
||||
->update(['name' => json_encode(['ko' => 'raw 수정'])]);
|
||||
|
||||
$reloaded = NotificationDefinition::find($definition->id);
|
||||
|
||||
$this->assertEmpty(
|
||||
$reloaded->user_overrides ?? [],
|
||||
'DB facade 직접 호출은 Eloquent Builder 를 거치지 않아 trait 미적용 — escape hatch',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Models\Concerns;
|
||||
|
||||
use App\Models\NotificationDefinition;
|
||||
use App\Models\NotificationTemplate;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* HasUserOverrides Trait 단위 테스트.
|
||||
*
|
||||
* NotificationTemplate 을 reference 모델로 사용하여 trait 의 8개 시나리오를 검증합니다.
|
||||
*
|
||||
* 검증 시나리오:
|
||||
* 1. 신규 생성 시 user_overrides 가 null 이고 자동 기록 안 됨
|
||||
* 2. 사용자 수정(일반 update) → trackable 필드가 user_overrides 에 자동 기록
|
||||
* 3. trackable 외 필드 수정 → 기록 안 됨
|
||||
* 4. syncFromUpgrade() 호출 시 user_overrides 에 기록된 필드는 갱신 스킵
|
||||
* 5. syncFromUpgrade() 호출 시 trackable 외 필드는 항상 갱신
|
||||
* 6. syncFromUpgrade() 후 user_overrides 자체는 변경 안 됨 (자동 기록 비활성)
|
||||
* 7. createFromUpgrade() 로 생성 시 자동 기록 비활성
|
||||
* 8. 동일 필드를 두 번 수정 시 user_overrides 에 중복 기록 안 됨
|
||||
*/
|
||||
class HasUserOverridesTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* 외래키 충족용 알림 정의 생성 후 NotificationTemplate 을 생성합니다.
|
||||
*/
|
||||
private function createTemplate(array $attributes = []): NotificationTemplate
|
||||
{
|
||||
$definition = NotificationDefinition::factory()->create();
|
||||
|
||||
return NotificationTemplate::create(array_merge([
|
||||
'definition_id' => $definition->id,
|
||||
'channel' => 'mail',
|
||||
'subject' => ['ko' => '초기 제목', 'en' => 'Initial Subject'],
|
||||
'body' => ['ko' => '초기 본문', 'en' => 'Initial Body'],
|
||||
'is_active' => true,
|
||||
'is_default' => true,
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
/**
|
||||
* 시나리오 1: 신규 생성 시 user_overrides 가 null.
|
||||
*/
|
||||
public function test_new_template_has_null_user_overrides(): void
|
||||
{
|
||||
$template = $this->createTemplate();
|
||||
|
||||
$this->assertNull($template->fresh()->user_overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시나리오 2: 사용자 수정 시 trackable 필드 자동 기록.
|
||||
*/
|
||||
public function test_user_update_records_trackable_fields(): void
|
||||
{
|
||||
$template = $this->createTemplate();
|
||||
$template->subject = ['ko' => '수정된 제목', 'en' => 'Modified'];
|
||||
$template->save();
|
||||
|
||||
$this->assertEquals(['subject'], $template->fresh()->user_overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시나리오 3: trackable 외 필드 수정 → 기록 안 됨.
|
||||
*/
|
||||
public function test_non_trackable_field_update_is_not_recorded(): void
|
||||
{
|
||||
$template = $this->createTemplate();
|
||||
// updated_by 는 trackableFields 에 없는 필드
|
||||
$template->updated_by = null;
|
||||
$template->is_default = false; // is_default 도 trackableFields 에 없음
|
||||
$template->save();
|
||||
|
||||
$this->assertNull($template->fresh()->user_overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시나리오 4: syncFromUpgrade 호출 시 user_overrides 에 기록된 필드는 갱신 스킵.
|
||||
*/
|
||||
public function test_sync_from_upgrade_preserves_overridden_fields(): void
|
||||
{
|
||||
$template = $this->createTemplate();
|
||||
$template->subject = ['ko' => '사용자 수정 제목', 'en' => 'User Modified'];
|
||||
$template->save();
|
||||
|
||||
// 시더가 다시 실행되는 상황 시뮬레이션
|
||||
$template->syncFromUpgrade([
|
||||
'subject' => ['ko' => '시더 제목', 'en' => 'Seeder Subject'],
|
||||
'body' => ['ko' => '시더 본문', 'en' => 'Seeder Body'],
|
||||
]);
|
||||
|
||||
$fresh = $template->fresh();
|
||||
$this->assertEquals(['ko' => '사용자 수정 제목', 'en' => 'User Modified'], $fresh->subject);
|
||||
$this->assertEquals(['ko' => '시더 본문', 'en' => 'Seeder Body'], $fresh->body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시나리오 5: syncFromUpgrade 호출 시 trackable 외 필드는 항상 갱신.
|
||||
*/
|
||||
public function test_sync_from_upgrade_always_updates_non_trackable_fields(): void
|
||||
{
|
||||
$template = $this->createTemplate(['is_default' => true]);
|
||||
|
||||
$template->syncFromUpgrade([
|
||||
'is_default' => false,
|
||||
]);
|
||||
|
||||
$this->assertFalse($template->fresh()->is_default);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시나리오 6: syncFromUpgrade 후 user_overrides 자체는 변경 안 됨 (자동 기록 비활성).
|
||||
*/
|
||||
public function test_sync_from_upgrade_does_not_record_overrides(): void
|
||||
{
|
||||
$template = $this->createTemplate();
|
||||
|
||||
$template->syncFromUpgrade([
|
||||
'subject' => ['ko' => '시더 제목', 'en' => 'Seeder Subject'],
|
||||
'body' => ['ko' => '시더 본문', 'en' => 'Seeder Body'],
|
||||
]);
|
||||
|
||||
$this->assertNull($template->fresh()->user_overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시나리오 7: createFromUpgrade 로 생성 시 자동 기록 비활성.
|
||||
*/
|
||||
public function test_create_from_upgrade_does_not_record_overrides(): void
|
||||
{
|
||||
$definition = NotificationDefinition::factory()->create();
|
||||
|
||||
$template = NotificationTemplate::createFromUpgrade([
|
||||
'definition_id' => $definition->id,
|
||||
'channel' => 'mail',
|
||||
'subject' => ['ko' => '시더 제목', 'en' => 'Seeder'],
|
||||
'body' => ['ko' => '시더 본문', 'en' => 'Seeder Body'],
|
||||
'is_active' => true,
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
$this->assertNull($template->fresh()->user_overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* 시나리오 8: 동일 필드를 두 번 수정 시 user_overrides 에 중복 기록 안 됨.
|
||||
*/
|
||||
public function test_repeated_user_update_does_not_duplicate_overrides(): void
|
||||
{
|
||||
$template = $this->createTemplate();
|
||||
$template->subject = ['ko' => '첫 수정', 'en' => 'First'];
|
||||
$template->save();
|
||||
|
||||
$template->subject = ['ko' => '두 번째 수정', 'en' => 'Second'];
|
||||
$template->save();
|
||||
|
||||
$this->assertEquals(['subject'], $template->fresh()->user_overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* 추가: syncOrCreateFromUpgrade — 존재하지 않으면 생성.
|
||||
*/
|
||||
public function test_sync_or_create_from_upgrade_creates_when_missing(): void
|
||||
{
|
||||
$definition = NotificationDefinition::factory()->create();
|
||||
|
||||
$template = NotificationTemplate::syncOrCreateFromUpgrade(
|
||||
['definition_id' => $definition->id, 'channel' => 'mail'],
|
||||
[
|
||||
'subject' => ['ko' => '시더 제목', 'en' => 'Seeder'],
|
||||
'body' => ['ko' => '시더 본문', 'en' => 'Seeder Body'],
|
||||
'is_active' => true,
|
||||
'is_default' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertNotNull($template->id);
|
||||
$this->assertNull($template->fresh()->user_overrides);
|
||||
}
|
||||
|
||||
/**
|
||||
* 추가: syncOrCreateFromUpgrade — 존재하면 syncFromUpgrade 호출.
|
||||
*/
|
||||
public function test_sync_or_create_from_upgrade_preserves_existing_overrides(): void
|
||||
{
|
||||
$template = $this->createTemplate();
|
||||
$template->subject = ['ko' => '사용자 수정', 'en' => 'User'];
|
||||
$template->save();
|
||||
|
||||
NotificationTemplate::syncOrCreateFromUpgrade(
|
||||
['definition_id' => $template->definition_id, 'channel' => 'mail'],
|
||||
[
|
||||
'subject' => ['ko' => '시더 덮어쓰기', 'en' => 'Seeder Override'],
|
||||
'body' => ['ko' => '시더 본문', 'en' => 'Seeder Body'],
|
||||
]
|
||||
);
|
||||
|
||||
$fresh = $template->fresh();
|
||||
$this->assertEquals(['ko' => '사용자 수정', 'en' => 'User'], $fresh->subject);
|
||||
$this->assertEquals(['ko' => '시더 본문', 'en' => 'Seeder Body'], $fresh->body);
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Models;
|
||||
|
||||
use App\Enums\ExtensionOwnerType;
|
||||
use App\Enums\MailSendStatus;
|
||||
use App\Models\MailSendLog;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* MailSendLog 모델 테스트
|
||||
*
|
||||
* 모델의 fillable, 캐스팅, 스코프 메서드를 검증합니다.
|
||||
*/
|
||||
class MailSendLogTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
// ========================================================================
|
||||
// fillable / casts
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* source 컬럼이 fillable에 포함
|
||||
*/
|
||||
public function test_source_is_fillable(): void
|
||||
{
|
||||
$log = MailSendLog::create([
|
||||
'recipient_email' => 'test@example.com',
|
||||
'subject' => 'Test',
|
||||
'template_type' => 'welcome',
|
||||
'extension_type' => ExtensionOwnerType::Core,
|
||||
'extension_identifier' => 'core',
|
||||
'source' => 'notification',
|
||||
'status' => MailSendStatus::Sent->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
|
||||
$this->assertEquals('notification', $log->source);
|
||||
}
|
||||
|
||||
/**
|
||||
* subject가 nullable
|
||||
*/
|
||||
public function test_subject_is_nullable(): void
|
||||
{
|
||||
$log = MailSendLog::create([
|
||||
'recipient_email' => 'test@example.com',
|
||||
'subject' => null,
|
||||
'extension_type' => ExtensionOwnerType::Core,
|
||||
'extension_identifier' => 'core',
|
||||
'status' => MailSendStatus::Sent->value,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
|
||||
$this->assertNull($log->subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* sent_at이 datetime으로 캐스팅됨
|
||||
*/
|
||||
public function test_sent_at_is_cast_to_datetime(): void
|
||||
{
|
||||
$log = MailSendLog::factory()->create();
|
||||
|
||||
$this->assertInstanceOf(\Carbon\Carbon::class, $log->sent_at);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// scopeByTemplateType
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* 특정 템플릿 유형으로 필터
|
||||
*/
|
||||
public function test_scope_by_template_type_filters_correctly(): void
|
||||
{
|
||||
MailSendLog::factory()->withTemplateType('welcome')->count(2)->create();
|
||||
MailSendLog::factory()->withTemplateType('password_reset')->count(3)->create();
|
||||
|
||||
$result = MailSendLog::byTemplateType('welcome')->get();
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* null 템플릿 유형으로 필터 (whereNull)
|
||||
*/
|
||||
public function test_scope_by_template_type_handles_null(): void
|
||||
{
|
||||
MailSendLog::factory()->create(['template_type' => null]);
|
||||
MailSendLog::factory()->withTemplateType('welcome')->create();
|
||||
|
||||
$result = MailSendLog::byTemplateType(null)->get();
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertNull($result->first()->template_type);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// scopeByExtension
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* 확장 타입별 필터
|
||||
*/
|
||||
public function test_scope_by_extension_filters_by_type(): void
|
||||
{
|
||||
MailSendLog::factory()->forExtension(ExtensionOwnerType::Core, 'core')->count(3)->create();
|
||||
MailSendLog::factory()->forExtension(ExtensionOwnerType::Module, 'sirsoft-board')->count(2)->create();
|
||||
|
||||
$result = MailSendLog::byExtension(ExtensionOwnerType::Core)->get();
|
||||
|
||||
$this->assertCount(3, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 확장 타입 + 식별자 필터
|
||||
*/
|
||||
public function test_scope_by_extension_filters_by_type_and_identifier(): void
|
||||
{
|
||||
MailSendLog::factory()->forExtension(ExtensionOwnerType::Module, 'sirsoft-board')->count(2)->create();
|
||||
MailSendLog::factory()->forExtension(ExtensionOwnerType::Module, 'sirsoft-ecommerce')->count(3)->create();
|
||||
|
||||
$result = MailSendLog::byExtension(ExtensionOwnerType::Module, 'sirsoft-board')->get();
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// scopeBySource
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* 발송 출처별 필터
|
||||
*/
|
||||
public function test_scope_by_source_filters_correctly(): void
|
||||
{
|
||||
MailSendLog::factory()->create(['source' => 'notification']);
|
||||
MailSendLog::factory()->create(['source' => 'test_mail']);
|
||||
MailSendLog::factory()->create(['source' => 'notification']);
|
||||
|
||||
$result = MailSendLog::bySource('notification')->get();
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* null 발송 출처로 필터 (whereNull)
|
||||
*/
|
||||
public function test_scope_by_source_handles_null(): void
|
||||
{
|
||||
MailSendLog::factory()->create(['source' => null]);
|
||||
MailSendLog::factory()->create(['source' => 'notification']);
|
||||
|
||||
$result = MailSendLog::bySource(null)->get();
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertNull($result->first()->source);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// scopeByStatus
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* 상태별 필터
|
||||
*/
|
||||
public function test_scope_by_status_filters_correctly(): void
|
||||
{
|
||||
MailSendLog::factory()->count(3)->create(['status' => MailSendStatus::Sent->value]);
|
||||
MailSendLog::factory()->failed()->count(2)->create();
|
||||
|
||||
$result = MailSendLog::byStatus(MailSendStatus::Failed->value)->get();
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Models;
|
||||
|
||||
use App\Models\MailTemplate;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* MailTemplate 모델 테스트
|
||||
*
|
||||
* 모델의 fillable, casts, 스코프, replaceVariables, 관계를 검증합니다.
|
||||
*/
|
||||
class MailTemplateTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
// ========================================================================
|
||||
// fillable / casts
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* type이 fillable에 포함
|
||||
*/
|
||||
public function test_type_is_fillable(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->withType('welcome')->create();
|
||||
|
||||
$this->assertEquals('welcome', $template->type);
|
||||
}
|
||||
|
||||
/**
|
||||
* subject가 array로 캐스팅됨
|
||||
*/
|
||||
public function test_subject_is_cast_to_array(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->create([
|
||||
'subject' => ['ko' => '한국어 제목', 'en' => 'English Subject'],
|
||||
]);
|
||||
|
||||
$this->assertIsArray($template->subject);
|
||||
$this->assertEquals('한국어 제목', $template->subject['ko']);
|
||||
}
|
||||
|
||||
/**
|
||||
* body가 array로 캐스팅됨
|
||||
*/
|
||||
public function test_body_is_cast_to_array(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->create([
|
||||
'body' => ['ko' => '<p>본문</p>', 'en' => '<p>Body</p>'],
|
||||
]);
|
||||
|
||||
$this->assertIsArray($template->body);
|
||||
$this->assertEquals('<p>본문</p>', $template->body['ko']);
|
||||
}
|
||||
|
||||
/**
|
||||
* variables가 array로 캐스팅됨
|
||||
*/
|
||||
public function test_variables_is_cast_to_array(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->create([
|
||||
'variables' => [['key' => 'name', 'description' => '이름']],
|
||||
]);
|
||||
|
||||
$this->assertIsArray($template->variables);
|
||||
$this->assertEquals('name', $template->variables[0]['key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* is_active가 boolean으로 캐스팅됨
|
||||
*/
|
||||
public function test_is_active_is_cast_to_boolean(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->create(['is_active' => 1]);
|
||||
|
||||
$this->assertIsBool($template->is_active);
|
||||
$this->assertTrue($template->is_active);
|
||||
}
|
||||
|
||||
/**
|
||||
* is_default가 boolean으로 캐스팅됨
|
||||
*/
|
||||
public function test_is_default_is_cast_to_boolean(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->create(['is_default' => 0]);
|
||||
|
||||
$this->assertIsBool($template->is_default);
|
||||
$this->assertFalse($template->is_default);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// 관계
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* updater 관계가 User를 반환
|
||||
*/
|
||||
public function test_updater_returns_user(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$template = MailTemplate::factory()->create(['updated_by' => $user->id]);
|
||||
|
||||
$this->assertInstanceOf(User::class, $template->updater);
|
||||
$this->assertEquals($user->id, $template->updater->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* updated_by가 null이면 updater도 null
|
||||
*/
|
||||
public function test_updater_returns_null_when_no_updater(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->create(['updated_by' => null]);
|
||||
|
||||
$this->assertNull($template->updater);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// scopeActive
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* active 스코프가 활성 템플릿만 반환
|
||||
*/
|
||||
public function test_scope_active_filters_correctly(): void
|
||||
{
|
||||
MailTemplate::factory()->count(2)->create(['is_active' => true]);
|
||||
MailTemplate::factory()->inactive()->create();
|
||||
|
||||
$result = MailTemplate::active()->get();
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// scopeByType
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* byType 스코프가 특정 유형만 반환
|
||||
*/
|
||||
public function test_scope_by_type_filters_correctly(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('welcome')->create();
|
||||
MailTemplate::factory()->withType('reset_password')->create();
|
||||
|
||||
$result = MailTemplate::byType('welcome')->get();
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertEquals('welcome', $result->first()->type);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// getLocalizedSubject / getLocalizedBody
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* getLocalizedSubject가 현재 로케일 제목을 반환
|
||||
*/
|
||||
public function test_get_localized_subject_returns_current_locale(): void
|
||||
{
|
||||
app()->setLocale('ko');
|
||||
|
||||
$template = MailTemplate::factory()->create([
|
||||
'subject' => ['ko' => '한국어 제목', 'en' => 'English Subject'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('한국어 제목', $template->getLocalizedSubject());
|
||||
}
|
||||
|
||||
/**
|
||||
* getLocalizedSubject가 지정된 로케일 제목을 반환
|
||||
*/
|
||||
public function test_get_localized_subject_with_specific_locale(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->create([
|
||||
'subject' => ['ko' => '한국어 제목', 'en' => 'English Subject'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('English Subject', $template->getLocalizedSubject('en'));
|
||||
}
|
||||
|
||||
/**
|
||||
* getLocalizedSubject가 fallback으로 ko를 반환
|
||||
*/
|
||||
public function test_get_localized_subject_falls_back_to_ko(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->create([
|
||||
'subject' => ['ko' => '한국어 제목'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('한국어 제목', $template->getLocalizedSubject('ja'));
|
||||
}
|
||||
|
||||
/**
|
||||
* getLocalizedBody가 현재 로케일 본문을 반환
|
||||
*/
|
||||
public function test_get_localized_body_returns_current_locale(): void
|
||||
{
|
||||
app()->setLocale('en');
|
||||
|
||||
$template = MailTemplate::factory()->create([
|
||||
'body' => ['ko' => '<p>본문</p>', 'en' => '<p>Body</p>'],
|
||||
]);
|
||||
|
||||
$this->assertEquals('<p>Body</p>', $template->getLocalizedBody());
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// replaceVariables
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* replaceVariables가 변수를 올바르게 치환
|
||||
*/
|
||||
public function test_replace_variables_substitutes_correctly(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->withVariables()->create();
|
||||
|
||||
$result = $template->replaceVariables([
|
||||
'name' => '홍길동',
|
||||
'app_name' => 'G7',
|
||||
], 'ko');
|
||||
|
||||
$this->assertEquals('[G7] 홍길동님 환영합니다', $result['subject']);
|
||||
$this->assertStringContainsString('홍길동님, G7에 가입해 주셔서', $result['body']);
|
||||
}
|
||||
|
||||
/**
|
||||
* replaceVariables가 누락된 변수는 그대로 유지
|
||||
*/
|
||||
public function test_replace_variables_keeps_unreplaced_variables(): void
|
||||
{
|
||||
$template = MailTemplate::factory()->withVariables()->create();
|
||||
|
||||
$result = $template->replaceVariables(['name' => '홍길동'], 'ko');
|
||||
|
||||
$this->assertStringContainsString('{app_name}', $result['subject']);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user