Files
Gnuboard7/tests/Feature/Extension/ModuleManagerNotificationDefinitionSyncTest.php
T
2026-05-11 11:29:41 +09:00

262 lines
9.0 KiB
PHP

<?php
namespace Tests\Feature\Extension;
use App\Extension\AbstractModule;
use App\Extension\Helpers\NotificationSyncHelper;
use App\Models\NotificationDefinition;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* ModuleManager 가 AbstractModule::getNotificationDefinitions() 결과를 DB 에 동기화하는지 검증.
*
* IdentityMessages / IdentityPolicies 와 동일한 declarative getter 패턴.
* `syncModuleNotificationDefinitions()` protected 동작을 helper 직접 호출로 재현하여
* Manager 계약이 helper 에 정확히 위임되는지 확인합니다.
*
* @since 7.0.0-beta.4
*/
class ModuleManagerNotificationDefinitionSyncTest extends TestCase
{
use RefreshDatabase;
/**
* @return void
*/
public function test_module_getNotificationDefinitions_is_upserted_via_helper(): void
{
$module = $this->makeFakeModule([
[
'type' => 'fake_event',
'hook_prefix' => 'fake-module',
'name' => ['ko' => '가짜 이벤트', 'en' => 'Fake Event'],
'description' => ['ko' => '테스트', 'en' => 'Test'],
'channels' => ['mail', 'database'],
'hooks' => ['fake-module.event.after_create'],
'variables' => [['key' => 'name', 'description' => '수신자']],
'templates' => [
[
'channel' => 'mail',
'recipients' => [['type' => 'trigger_user']],
'subject' => ['ko' => '제목', 'en' => 'Subject'],
'body' => ['ko' => '본문 {name}', 'en' => 'Body {name}'],
],
[
'channel' => 'database',
'recipients' => [['type' => 'trigger_user']],
'subject' => ['ko' => 'DB제목', 'en' => 'DB Subject'],
'body' => ['ko' => 'DB본문', 'en' => 'DB Body'],
],
],
],
]);
$helper = app(NotificationSyncHelper::class);
foreach ($module->getNotificationDefinitions() as $data) {
$data['extension_type'] = 'module';
$data['extension_identifier'] = $module->getIdentifier();
$definition = $helper->syncDefinition($data);
foreach ($data['templates'] ?? [] as $template) {
$helper->syncTemplate($definition->id, $template);
}
}
$this->assertDatabaseHas('notification_definitions', [
'type' => 'fake_event',
'extension_type' => 'module',
'extension_identifier' => 'fake-module',
]);
$defId = NotificationDefinition::query()
->where('extension_identifier', 'fake-module')
->where('type', 'fake_event')
->value('id');
$this->assertNotNull($defId);
$this->assertDatabaseHas('notification_templates', [
'definition_id' => $defId,
'channel' => 'mail',
]);
$this->assertDatabaseHas('notification_templates', [
'definition_id' => $defId,
'channel' => 'database',
]);
}
/**
* @return void
*/
public function test_cleanup_removes_definitions_not_in_current_declaration(): void
{
$helper = app(NotificationSyncHelper::class);
$helper->syncDefinition([
'type' => 'keep_me',
'hook_prefix' => 'fake-module',
'extension_type' => 'module',
'extension_identifier' => 'fake-module',
'name' => ['ko' => '유지', 'en' => 'Keep'],
'channels' => ['mail'],
'hooks' => [],
]);
$helper->syncDefinition([
'type' => 'stale_def',
'hook_prefix' => 'fake-module',
'extension_type' => 'module',
'extension_identifier' => 'fake-module',
'name' => ['ko' => '오래됨', 'en' => 'Stale'],
'channels' => ['mail'],
'hooks' => [],
]);
$this->assertDatabaseHas('notification_definitions', [
'extension_identifier' => 'fake-module',
'type' => 'stale_def',
]);
$helper->cleanupStaleDefinitions('module', 'fake-module', ['keep_me']);
$this->assertDatabaseHas('notification_definitions', [
'extension_identifier' => 'fake-module',
'type' => 'keep_me',
]);
$this->assertDatabaseMissing('notification_definitions', [
'extension_identifier' => 'fake-module',
'type' => 'stale_def',
]);
}
/**
* @return void
*/
public function test_empty_declaration_with_uninstall_clears_all_module_definitions(): void
{
$helper = app(NotificationSyncHelper::class);
$helper->syncDefinition([
'type' => 'temp_def',
'hook_prefix' => 'fake-module',
'extension_type' => 'module',
'extension_identifier' => 'fake-module',
'name' => ['ko' => '임시', 'en' => 'Temp'],
'channels' => ['mail'],
'hooks' => [],
]);
// uninstall(deleteData=true) 시뮬레이션 — 빈 currentTypes 로 cleanup
$helper->cleanupStaleDefinitions('module', 'fake-module', []);
$this->assertDatabaseMissing('notification_definitions', [
'extension_identifier' => 'fake-module',
]);
}
/**
* @return void
*/
public function test_user_overrides_are_preserved_across_resync(): void
{
$helper = app(NotificationSyncHelper::class);
$def = $helper->syncDefinition([
'type' => 'override_test',
'hook_prefix' => 'fake-module',
'extension_type' => 'module',
'extension_identifier' => 'fake-module',
'name' => ['ko' => '원본', 'en' => 'Original'],
'channels' => ['mail'],
'hooks' => [],
]);
// 운영자가 UI 에서 name 수정
NotificationDefinition::where('id', $def->id)->update([
'name' => ['ko' => '운영자 수정', 'en' => 'Edited'],
'user_overrides' => ['name'],
]);
// 2차 seed (모듈 update 시뮬레이션 — 모듈 코드는 원본 그대로)
$helper->syncDefinition([
'type' => 'override_test',
'hook_prefix' => 'fake-module',
'extension_type' => 'module',
'extension_identifier' => 'fake-module',
'name' => ['ko' => '원본', 'en' => 'Original'],
'channels' => ['mail'],
'hooks' => [],
]);
$row = NotificationDefinition::where('id', $def->id)->first();
$this->assertSame('운영자 수정', $row->name['ko'] ?? null, 'user_overrides 로 보존되어야 함');
}
/**
* 코어 config/core.php 의 notification_definitions 가 정확히 로드되는지 검증.
*
* @return void
*/
public function test_core_config_notification_definitions_are_present(): void
{
$coreDefs = config('core.notification_definitions', []);
$this->assertNotEmpty($coreDefs, 'config/core.php 에 notification_definitions 누락');
$this->assertArrayHasKey('welcome', $coreDefs);
$this->assertArrayHasKey('reset_password', $coreDefs);
$this->assertArrayHasKey('password_changed', $coreDefs);
// 각 정의에 templates 가 있는지
foreach (['welcome', 'reset_password', 'password_changed'] as $type) {
$this->assertNotEmpty($coreDefs[$type]['templates'] ?? [], "{$type}: templates 누락");
$this->assertNotEmpty($coreDefs[$type]['name'] ?? [], "{$type}: name 누락");
}
}
/**
* 테스트용 가짜 모듈 인스턴스 생성.
*
* @param array<int, array<string, mixed>> $definitions
* @return AbstractModule
*/
private function makeFakeModule(array $definitions): AbstractModule
{
$module = new class($definitions) extends AbstractModule
{
public function __construct(private array $definitionData)
{
}
public function getName(): string|array
{
return 'Fake Module';
}
public function getDescription(): string|array
{
return 'Test module for notification sync';
}
public function getVersion(): string
{
return '1.0.0';
}
public function getNotificationDefinitions(): array
{
return $this->definitionData;
}
};
$reflection = new \ReflectionClass($module);
while ($reflection && ! $reflection->hasProperty('identifier')) {
$reflection = $reflection->getParentClass();
}
if ($reflection) {
$prop = $reflection->getProperty('identifier');
$prop->setAccessible(true);
$prop->setValue($module, 'fake-module');
}
return $module;
}
}