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
@@ -1,121 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Notifications\Auth;
|
||||
|
||||
use App\Enums\ExtensionOwnerType;
|
||||
use App\Mail\DbTemplateMail;
|
||||
use App\Models\MailTemplate;
|
||||
use App\Models\User;
|
||||
use App\Notifications\Auth\PasswordChangedNotification;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* PasswordChangedNotification 테스트
|
||||
*
|
||||
* 비밀번호 변경 완료 알림의 채널 결정 및 이메일 생성을 검증합니다.
|
||||
*/
|
||||
class PasswordChangedNotificationTest 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* via()가 기본적으로 mail 채널을 반환하는지 확인
|
||||
*/
|
||||
public function test_via_returns_mail_channel_by_default(): void
|
||||
{
|
||||
$notification = new PasswordChangedNotification();
|
||||
$user = new User(['email' => 'test@example.com', 'name' => 'Test User']);
|
||||
|
||||
$channels = $notification->via($user);
|
||||
|
||||
$this->assertContains('mail', $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* toMail()이 DbTemplateMail을 반환하는지 확인
|
||||
*/
|
||||
public function test_to_mail_returns_db_template_mail(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('password_changed')->create();
|
||||
|
||||
$notification = new PasswordChangedNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(DbTemplateMail::class, $mailable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailable의 templateType이 password_changed인지 확인
|
||||
*/
|
||||
public function test_mailable_template_type_is_password_changed(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('password_changed')->create();
|
||||
|
||||
$notification = new PasswordChangedNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertEquals('password_changed', $mailable->getTemplateType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailable의 module이 core인지 확인
|
||||
*/
|
||||
public function test_mailable_module_is_core(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('password_changed')->create();
|
||||
|
||||
$notification = new PasswordChangedNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertEquals(ExtensionOwnerType::Core, $mailable->getExtensionType());
|
||||
$this->assertEquals('core', $mailable->getExtensionIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* 비활성 템플릿에서 스킵 인스턴스 반환
|
||||
*/
|
||||
public function test_to_mail_returns_skipped_when_template_inactive(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('password_changed')->inactive()->create();
|
||||
|
||||
$notification = new PasswordChangedNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(DbTemplateMail::class, $mailable);
|
||||
$this->assertTrue($mailable->isSkipped());
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 미존재 시 스킵 인스턴스 반환
|
||||
*/
|
||||
public function test_to_mail_returns_skipped_when_no_template(): void
|
||||
{
|
||||
$notification = new PasswordChangedNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(DbTemplateMail::class, $mailable);
|
||||
$this->assertTrue($mailable->isSkipped());
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Notifications\Auth;
|
||||
|
||||
use App\Enums\ExtensionOwnerType;
|
||||
use App\Mail\DbTemplateMail;
|
||||
use App\Models\MailTemplate;
|
||||
use App\Models\User;
|
||||
use App\Notifications\Auth\ResetPasswordNotification;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* ResetPasswordNotification 테스트
|
||||
*
|
||||
* 비밀번호 재설정 알림의 채널 결정 및 이메일 생성을 검증합니다.
|
||||
*/
|
||||
class ResetPasswordNotificationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private string $testToken = 'test-reset-token-12345';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Cache::flush();
|
||||
Config::set('app.name', 'G7 Test');
|
||||
Config::set('app.url', 'https://g7.test');
|
||||
}
|
||||
|
||||
/**
|
||||
* via()가 기본적으로 mail 채널을 반환하는지 확인
|
||||
*/
|
||||
public function test_via_returns_mail_channel_by_default(): void
|
||||
{
|
||||
$notification = new ResetPasswordNotification($this->testToken);
|
||||
$user = new User(['email' => 'test@example.com', 'name' => 'Test User']);
|
||||
|
||||
$channels = $notification->via($user);
|
||||
|
||||
$this->assertContains('mail', $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* toMail()이 DbTemplateMail을 반환하는지 확인
|
||||
*/
|
||||
public function test_to_mail_returns_db_template_mail(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('reset_password')->create();
|
||||
|
||||
$notification = new ResetPasswordNotification($this->testToken);
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(DbTemplateMail::class, $mailable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailable의 templateType이 reset_password인지 확인
|
||||
*/
|
||||
public function test_mailable_template_type_is_reset_password(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('reset_password')->create();
|
||||
|
||||
$notification = new ResetPasswordNotification($this->testToken);
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertEquals('reset_password', $mailable->getTemplateType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailable의 module이 core인지 확인
|
||||
*/
|
||||
public function test_mailable_module_is_core(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('reset_password')->create();
|
||||
|
||||
$notification = new ResetPasswordNotification($this->testToken);
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertEquals(ExtensionOwnerType::Core, $mailable->getExtensionType());
|
||||
$this->assertEquals('core', $mailable->getExtensionIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* 비활성 템플릿에서 스킵 인스턴스 반환
|
||||
*/
|
||||
public function test_to_mail_returns_skipped_when_template_inactive(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('reset_password')->inactive()->create();
|
||||
|
||||
$notification = new ResetPasswordNotification($this->testToken);
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(DbTemplateMail::class, $mailable);
|
||||
$this->assertTrue($mailable->isSkipped());
|
||||
}
|
||||
|
||||
/**
|
||||
* getToken()이 토큰을 반환하는지 확인
|
||||
*/
|
||||
public function test_get_token_returns_token(): void
|
||||
{
|
||||
$notification = new ResetPasswordNotification($this->testToken);
|
||||
|
||||
$this->assertEquals($this->testToken, $notification->getToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* action_url 변수를 포함하는 비밀번호 재설정 템플릿을 생성합니다.
|
||||
*/
|
||||
private function createResetPasswordTemplate(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('reset_password')->create([
|
||||
'subject' => [
|
||||
'ko' => '비밀번호 재설정',
|
||||
'en' => 'Reset Password',
|
||||
],
|
||||
'body' => [
|
||||
'ko' => '<p><a href="{action_url}">비밀번호 재설정</a></p>',
|
||||
'en' => '<p><a href="{action_url}">Reset Password</a></p>',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* redirect_prefix 없이 기본 /reset-password URL 생성
|
||||
*/
|
||||
public function test_to_mail_generates_default_reset_url(): void
|
||||
{
|
||||
$this->createResetPasswordTemplate();
|
||||
|
||||
$notification = new ResetPasswordNotification($this->testToken);
|
||||
$user = User::factory()->create(['email' => 'user@example.com']);
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
// renderedBody는 생성자 파라미터이므로 Reflection으로 접근
|
||||
$ref = new \ReflectionProperty($mailable, 'renderedBody');
|
||||
$body = $ref->getValue($mailable);
|
||||
$this->assertStringContainsString('/reset-password?', $body);
|
||||
$this->assertStringNotContainsString('/admin/reset-password?', $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* redirect_prefix='admin' 시 /admin/reset-password URL 생성
|
||||
*/
|
||||
public function test_to_mail_generates_admin_reset_url_with_redirect_prefix(): void
|
||||
{
|
||||
$this->createResetPasswordTemplate();
|
||||
|
||||
$notification = new ResetPasswordNotification($this->testToken, 'admin');
|
||||
$user = User::factory()->create(['email' => 'admin@example.com']);
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$ref = new \ReflectionProperty($mailable, 'renderedBody');
|
||||
$body = $ref->getValue($mailable);
|
||||
$this->assertStringContainsString('/admin/reset-password?', $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* redirect_prefix=null 시 기본 URL (하위 호환성)
|
||||
*/
|
||||
public function test_to_mail_null_redirect_prefix_uses_default_url(): void
|
||||
{
|
||||
$this->createResetPasswordTemplate();
|
||||
|
||||
$notification = new ResetPasswordNotification($this->testToken, null);
|
||||
$user = User::factory()->create(['email' => 'test@example.com']);
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$ref = new \ReflectionProperty($mailable, 'renderedBody');
|
||||
$body = $ref->getValue($mailable);
|
||||
$this->assertStringContainsString('/reset-password?', $body);
|
||||
$this->assertStringNotContainsString('/admin/reset-password?', $body);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Notifications\Auth;
|
||||
|
||||
use App\Enums\ExtensionOwnerType;
|
||||
use App\Mail\DbTemplateMail;
|
||||
use App\Models\MailTemplate;
|
||||
use App\Models\User;
|
||||
use App\Notifications\Auth\WelcomeNotification;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* WelcomeNotification 테스트
|
||||
*
|
||||
* 회원가입 완료 알림의 채널 결정 및 이메일 생성을 검증합니다.
|
||||
*/
|
||||
class WelcomeNotificationTest 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');
|
||||
}
|
||||
|
||||
/**
|
||||
* via()가 기본적으로 mail 채널을 반환하는지 확인
|
||||
*/
|
||||
public function test_via_returns_mail_channel_by_default(): void
|
||||
{
|
||||
$notification = new WelcomeNotification();
|
||||
$user = new User(['email' => 'test@example.com', 'name' => 'Test User']);
|
||||
|
||||
$channels = $notification->via($user);
|
||||
|
||||
$this->assertContains('mail', $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* toMail()이 DbTemplateMail을 반환하는지 확인
|
||||
*/
|
||||
public function test_to_mail_returns_db_template_mail(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('welcome')->create();
|
||||
|
||||
$notification = new WelcomeNotification();
|
||||
$user = User::factory()->create(['email' => 'test@example.com', 'name' => 'Test User']);
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(DbTemplateMail::class, $mailable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailable의 templateType이 welcome인지 확인
|
||||
*/
|
||||
public function test_mailable_template_type_is_welcome(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('welcome')->create();
|
||||
|
||||
$notification = new WelcomeNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertEquals('welcome', $mailable->getTemplateType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailable의 module이 core인지 확인
|
||||
*/
|
||||
public function test_mailable_module_is_core(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('welcome')->create();
|
||||
|
||||
$notification = new WelcomeNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertEquals(ExtensionOwnerType::Core, $mailable->getExtensionType());
|
||||
$this->assertEquals('core', $mailable->getExtensionIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* 비활성 템플릿에서 스킵 인스턴스 반환
|
||||
*/
|
||||
public function test_to_mail_returns_skipped_when_template_inactive(): void
|
||||
{
|
||||
MailTemplate::factory()->withType('welcome')->inactive()->create();
|
||||
|
||||
$notification = new WelcomeNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(DbTemplateMail::class, $mailable);
|
||||
$this->assertTrue($mailable->isSkipped());
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿 미존재 시 스킵 인스턴스 반환
|
||||
*/
|
||||
public function test_to_mail_returns_skipped_when_no_template(): void
|
||||
{
|
||||
$notification = new WelcomeNotification();
|
||||
$user = User::factory()->create();
|
||||
|
||||
$mailable = $notification->toMail($user);
|
||||
|
||||
$this->assertInstanceOf(DbTemplateMail::class, $mailable);
|
||||
$this->assertTrue($mailable->isSkipped());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Notifications;
|
||||
|
||||
use App\Contracts\Notifications\ChannelReadinessCheckerInterface;
|
||||
use App\Models\NotificationDefinition;
|
||||
use App\Models\NotificationTemplate;
|
||||
use App\Models\User;
|
||||
use App\Notifications\GenericNotification;
|
||||
use App\Services\NotificationDefinitionService;
|
||||
use App\Services\NotificationTemplateService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* GenericNotification 테스트
|
||||
*
|
||||
* DB 기반 범용 알림 클래스의 via(), toMail(), toArray() 동작을 검증합니다.
|
||||
*/
|
||||
class GenericNotificationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// readiness mock: 모든 채널 ready
|
||||
$this->app->singleton(ChannelReadinessCheckerInterface::class, function () {
|
||||
return new class implements ChannelReadinessCheckerInterface {
|
||||
public function isReady(string $channelId): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function check(string $channelId): array
|
||||
{
|
||||
return ['ready' => true, 'reason' => null];
|
||||
}
|
||||
|
||||
public function checkAll(array $channelIds): array
|
||||
{
|
||||
return array_fill_keys($channelIds, ['ready' => true, 'reason' => null]);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification을 상속하는지 확인
|
||||
*/
|
||||
public function test_extends_laravel_notification(): void
|
||||
{
|
||||
$notification = new GenericNotification('welcome', 'core.auth', ['name' => 'Test']);
|
||||
|
||||
$this->assertInstanceOf(Notification::class, $notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* getType()이 올바른 타입을 반환하는지 확인
|
||||
*/
|
||||
public function test_get_type_returns_correct_type(): void
|
||||
{
|
||||
$notification = new GenericNotification('welcome', 'core.auth');
|
||||
|
||||
$this->assertEquals('welcome', $notification->getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* getData()가 전달된 데이터를 반환하는지 확인
|
||||
*/
|
||||
public function test_get_data_returns_provided_data(): void
|
||||
{
|
||||
$data = ['name' => 'Test', 'app_name' => 'G7'];
|
||||
$notification = new GenericNotification('welcome', 'core.auth', $data);
|
||||
|
||||
$this->assertEquals($data, $notification->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* via()가 notification_definitions 테이블에서 채널을 조회하는지 확인
|
||||
*/
|
||||
public function test_via_reads_channels_from_definition(): void
|
||||
{
|
||||
NotificationDefinition::create([
|
||||
'type' => 'test_notification',
|
||||
'hook_prefix' => 'core.test',
|
||||
'extension_type' => 'core',
|
||||
'extension_identifier' => 'core',
|
||||
'name' => ['ko' => '테스트', 'en' => 'Test'],
|
||||
'variables' => [],
|
||||
'channels' => ['mail', 'database'],
|
||||
'hooks' => ['core.test.after_action'],
|
||||
'is_active' => true,
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
// 캐시를 무효화하여 최신 데이터 반영
|
||||
app(NotificationDefinitionService::class)->invalidateCache('test_notification');
|
||||
|
||||
$notification = new GenericNotification('test_notification', 'core.test');
|
||||
$user = new User(['email' => 'test@example.com']);
|
||||
|
||||
$channels = $notification->via($user);
|
||||
|
||||
$this->assertEquals(['mail', 'database'], $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 정의가 없을 때 via()가 기본 ['mail'] 채널을 반환하는지 확인
|
||||
*/
|
||||
public function test_via_returns_mail_default_when_no_definition(): void
|
||||
{
|
||||
$notification = new GenericNotification('nonexistent_type', 'core.auth');
|
||||
$user = new User(['email' => 'test@example.com']);
|
||||
|
||||
$channels = $notification->via($user);
|
||||
|
||||
$this->assertEquals(['mail'], $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* toArray()가 database 채널 템플릿을 사용하는지 확인
|
||||
*/
|
||||
public function test_to_array_uses_database_channel_template(): void
|
||||
{
|
||||
$definition = NotificationDefinition::create([
|
||||
'type' => 'test_db',
|
||||
'hook_prefix' => 'core.test',
|
||||
'extension_type' => 'core',
|
||||
'extension_identifier' => 'core',
|
||||
'name' => ['ko' => '테스트', 'en' => 'Test'],
|
||||
'variables' => [],
|
||||
'channels' => ['database'],
|
||||
'hooks' => [],
|
||||
'is_active' => true,
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
NotificationTemplate::create([
|
||||
'definition_id' => $definition->id,
|
||||
'channel' => 'database',
|
||||
'subject' => ['ko' => '{name}님 알림', 'en' => 'Notification for {name}'],
|
||||
'body' => ['ko' => '{app_name}에서 알림입니다', 'en' => 'Notification from {app_name}'],
|
||||
'is_active' => true,
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
app(NotificationTemplateService::class)->invalidateCache('test_db', 'database');
|
||||
|
||||
$notification = new GenericNotification('test_db', 'core.test', [
|
||||
'name' => '홍길동',
|
||||
'app_name' => 'G7',
|
||||
]);
|
||||
|
||||
$user = new User(['email' => 'test@example.com', 'name' => '홍길동']);
|
||||
$user->forceFill(['locale' => 'ko']);
|
||||
|
||||
$result = $notification->toArray($user);
|
||||
|
||||
$this->assertEquals('test_db', $result['type']);
|
||||
$this->assertEquals('홍길동님 알림', $result['subject']);
|
||||
$this->assertEquals('G7에서 알림입니다', $result['body']);
|
||||
}
|
||||
|
||||
/**
|
||||
* toArray()가 템플릿 없을 때 기본 데이터를 반환하는지 확인
|
||||
*/
|
||||
public function test_to_array_returns_basic_data_when_no_template(): void
|
||||
{
|
||||
$data = ['name' => 'Test'];
|
||||
$notification = new GenericNotification('no_template', 'core.test', $data);
|
||||
|
||||
$user = new User(['email' => 'test@example.com']);
|
||||
|
||||
$result = $notification->toArray($user);
|
||||
|
||||
$this->assertEquals('no_template', $result['type']);
|
||||
$this->assertEquals($data, $result['data']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Notifications;
|
||||
|
||||
use App\Models\NotificationDefinition;
|
||||
use App\Models\NotificationTemplate;
|
||||
use App\Models\User;
|
||||
use App\Notifications\GenericNotification;
|
||||
use App\Services\NotificationChannelService;
|
||||
use App\Services\NotificationTemplateService;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* GenericNotification::via() 채널 결정 테스트
|
||||
*
|
||||
* 채널 활성화 + readiness + 템플릿 존재 검증을 단계별로 확인합니다.
|
||||
* 핵심 회귀: 활성 템플릿이 없는 채널은 제외되어야 함 (빈 subject/body 방지).
|
||||
*/
|
||||
class GenericNotificationViaTest extends TestCase
|
||||
{
|
||||
private User $user;
|
||||
|
||||
private NotificationDefinition $definition;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// 이전 테스트의 캐시된 템플릿 결과가 간섭하지 않도록 무효화
|
||||
$templateService = app(NotificationTemplateService::class);
|
||||
$templateService->invalidateCache('test_via_check', 'mail');
|
||||
$templateService->invalidateCache('test_via_check', 'database');
|
||||
$templateService->invalidateCache('nonexistent_type_xyz', 'mail');
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
|
||||
// 테스트용 알림 정의 — mail + database 채널 활성화
|
||||
$this->definition = NotificationDefinition::updateOrCreate(
|
||||
['type' => 'test_via_check'],
|
||||
[
|
||||
'hook_prefix' => 'core.test',
|
||||
'extension_type' => 'core',
|
||||
'extension_identifier' => 'core',
|
||||
'name' => ['ko' => '테스트', 'en' => 'Test'],
|
||||
'variables' => [],
|
||||
'channels' => ['mail', 'database'],
|
||||
'hooks' => [],
|
||||
'is_active' => true,
|
||||
'is_default' => false,
|
||||
]
|
||||
);
|
||||
|
||||
// 기본: 모든 채널을 활성으로 mocking (공유 storage/app/settings/notifications.json의
|
||||
// 실제 값과 격리). 개별 테스트에서 mockChannelEnabled(['xxx' => false])로 재정의 가능.
|
||||
$this->mockChannelEnabled([]);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// 캐시 무효화 후 데이터 정리
|
||||
$templateService = app(NotificationTemplateService::class);
|
||||
$templateService->invalidateCache('test_via_check', 'mail');
|
||||
$templateService->invalidateCache('test_via_check', 'database');
|
||||
$templateService->invalidateCache('nonexistent_type_xyz', 'mail');
|
||||
|
||||
NotificationTemplate::where('definition_id', $this->definition->id)->delete();
|
||||
$this->definition->delete();
|
||||
|
||||
Mockery::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* 확장 설정에서 mail 채널이 OFF면 via()가 mail을 제외한다 (database만 반환).
|
||||
*/
|
||||
public function test_via_excludes_channel_disabled_by_extension_toggle(): void
|
||||
{
|
||||
$this->createTemplate('mail');
|
||||
$this->createTemplate('database');
|
||||
|
||||
$this->mockChannelEnabled([
|
||||
'mail' => false,
|
||||
'database' => true,
|
||||
]);
|
||||
|
||||
$notification = new GenericNotification('test_via_check', 'core.test');
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertNotContains('mail', $channels);
|
||||
$this->assertContains('database', $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 확장 설정에서 모든 채널이 OFF면 빈 배열 반환.
|
||||
*/
|
||||
public function test_via_returns_empty_when_all_channels_disabled_by_extension(): void
|
||||
{
|
||||
$this->createTemplate('mail');
|
||||
$this->createTemplate('database');
|
||||
|
||||
$this->mockChannelEnabled([
|
||||
'mail' => false,
|
||||
'database' => false,
|
||||
]);
|
||||
|
||||
$notification = new GenericNotification('test_via_check', 'core.test');
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertEmpty($channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* $channel 직접 지정 발송 경로에도 확장 토글이 적용된다.
|
||||
*/
|
||||
public function test_via_with_explicit_channel_honors_extension_toggle(): void
|
||||
{
|
||||
$this->createTemplate('mail');
|
||||
|
||||
$this->mockChannelEnabled(['mail' => false]);
|
||||
|
||||
$notification = new GenericNotification(
|
||||
type: 'test_via_check',
|
||||
hookPrefix: 'core.test',
|
||||
data: [],
|
||||
extensionType: 'core',
|
||||
extensionIdentifier: 'core',
|
||||
channel: 'mail'
|
||||
);
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertEmpty($channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* $channel 직접 지정 + 확장 토글 ON → 해당 채널이 그대로 반환.
|
||||
*/
|
||||
public function test_via_with_explicit_channel_returns_when_enabled(): void
|
||||
{
|
||||
$this->createTemplate('mail');
|
||||
|
||||
$this->mockChannelEnabled(['mail' => true]);
|
||||
|
||||
$notification = new GenericNotification(
|
||||
type: 'test_via_check',
|
||||
hookPrefix: 'core.test',
|
||||
data: [],
|
||||
extensionType: 'core',
|
||||
extensionIdentifier: 'core',
|
||||
channel: 'mail'
|
||||
);
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertSame(['mail'], $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationChannelService::isChannelEnabledForExtension()의 응답을 mocking.
|
||||
*
|
||||
* @param array<string, bool> $channelMap
|
||||
*/
|
||||
private function mockChannelEnabled(array $channelMap): void
|
||||
{
|
||||
$mock = Mockery::mock(NotificationChannelService::class);
|
||||
$mock->shouldReceive('isChannelEnabledForExtension')
|
||||
->andReturnUsing(function ($type, $identifier, $channel) use ($channelMap) {
|
||||
return $channelMap[$channel] ?? true;
|
||||
});
|
||||
$this->app->instance(NotificationChannelService::class, $mock);
|
||||
}
|
||||
|
||||
/**
|
||||
* 양쪽 채널 모두 활성 템플릿이 있으면 두 채널 모두 반환
|
||||
*/
|
||||
public function test_via_returns_both_channels_when_both_have_templates(): void
|
||||
{
|
||||
$this->createTemplate('mail');
|
||||
$this->createTemplate('database');
|
||||
|
||||
$notification = new GenericNotification('test_via_check', 'core.test');
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertContains('mail', $channels);
|
||||
$this->assertContains('database', $channels);
|
||||
$this->assertCount(2, $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* mail 템플릿만 있으면 mail만 반환, database 제외
|
||||
*/
|
||||
public function test_via_excludes_channel_without_template(): void
|
||||
{
|
||||
$this->createTemplate('mail');
|
||||
// database 템플릿 없음
|
||||
|
||||
$notification = new GenericNotification('test_via_check', 'core.test');
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertContains('mail', $channels);
|
||||
$this->assertNotContains('database', $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* database 템플릿만 있으면 database만 반환 (mail은 readiness도 영향)
|
||||
*/
|
||||
public function test_via_returns_only_database_when_mail_has_no_template(): void
|
||||
{
|
||||
// mail 템플릿 없음
|
||||
$this->createTemplate('database');
|
||||
|
||||
$notification = new GenericNotification('test_via_check', 'core.test');
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
// mail은 템플릿 없음 또는 readiness 실패로 제외
|
||||
$this->assertNotContains('mail', $channels);
|
||||
$this->assertContains('database', $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 비활성 템플릿은 없는 것으로 취급 — 채널 제외
|
||||
*/
|
||||
public function test_via_excludes_channel_with_inactive_template(): void
|
||||
{
|
||||
$this->createTemplate('mail');
|
||||
$this->createTemplate('database', isActive: false);
|
||||
|
||||
$notification = new GenericNotification('test_via_check', 'core.test');
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertContains('mail', $channels);
|
||||
$this->assertNotContains('database', $channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 채널에 템플릿이 없으면 빈 배열 반환
|
||||
*/
|
||||
public function test_via_returns_empty_when_no_templates(): void
|
||||
{
|
||||
// 템플릿 없음
|
||||
|
||||
$notification = new GenericNotification('test_via_check', 'core.test');
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertEmpty($channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 알림 정의가 없는 type은 기본 mail 채널이지만 템플릿도 없으므로 빈 배열
|
||||
*/
|
||||
public function test_via_unknown_type_returns_empty(): void
|
||||
{
|
||||
$notification = new GenericNotification('nonexistent_type_xyz', 'core.test');
|
||||
$channels = $notification->via($this->user);
|
||||
|
||||
$this->assertEmpty($channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* 테스트용 템플릿 생성 헬퍼
|
||||
*
|
||||
* @param string $channel
|
||||
* @param bool $isActive
|
||||
* @return NotificationTemplate
|
||||
*/
|
||||
private function createTemplate(string $channel, bool $isActive = true): NotificationTemplate
|
||||
{
|
||||
return NotificationTemplate::updateOrCreate(
|
||||
['definition_id' => $this->definition->id, 'channel' => $channel],
|
||||
[
|
||||
'subject' => ['ko' => '테스트 제목', 'en' => 'Test Subject'],
|
||||
'body' => ['ko' => '테스트 본문', 'en' => 'Test Body'],
|
||||
'is_active' => $isActive,
|
||||
'is_default' => false,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user