Files
Gnuboard7/tests/Unit/Cache/Migration/NotificationCacheTest.php
T
2026-04-20 20:37:49 +09:00

281 lines
11 KiB
PHP

<?php
namespace Tests\Unit\Cache\Migration;
use App\Contracts\Extension\CacheInterface;
use App\Contracts\Repositories\NotificationDefinitionRepositoryInterface;
use App\Contracts\Repositories\NotificationTemplateRepositoryInterface;
use App\Extension\Cache\CoreCacheDriver;
use App\Models\NotificationDefinition;
use App\Models\NotificationTemplate;
use App\Services\NotificationDefinitionService;
use App\Services\NotificationTemplateService;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Cache;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
/**
* 그룹 D 이관 검증 테스트 (NotificationDefinitionService, NotificationTemplateService)
*
* 계획서 §13 D-1, D-2 의 10개 테스트 케이스를 검증합니다.
* - NotificationDefinitionService: 정의 캐시 생성/히트/무효화/all_active (5건)
* - NotificationTemplateService: 템플릿 캐시 생성/히트/수정/토글/복원 (5건)
*
* 실제 CoreCacheDriver(array store)를 사용하며, Repository는 mock 처리합니다.
*/
class NotificationCacheTest extends TestCase
{
private CoreCacheDriver $cache;
protected function setUp(): void
{
parent::setUp();
Cache::flush();
// 훅 리스너가 큐 직렬화를 거쳐 model을 잃어버리는 것을 방지
Bus::fake();
$this->cache = new CoreCacheDriver('array');
$this->app->instance(CacheInterface::class, $this->cache);
}
protected function tearDown(): void
{
Cache::flush();
parent::tearDown();
}
// ========================================================================
// D-1. NotificationDefinitionService
// ========================================================================
/**
* D-1-1: 알림 정의 캐시 생성 — resolve() 호출 시 새 키에 저장.
*/
#[Test]
public function d_1_1_definition_cache_stored_under_new_key(): void
{
$definition = new NotificationDefinition(['type' => 'order.created']);
$repo = $this->mock(NotificationDefinitionRepositoryInterface::class);
$repo->shouldReceive('getActiveByType')->with('order.created')->once()->andReturn($definition);
$service = app(NotificationDefinitionService::class);
$service->resolve('order.created');
$this->assertTrue($this->cache->has('notification.definition.order.created'));
$this->assertSame(
'g7:core:notification.definition.order.created',
$this->cache->resolveKey('notification.definition.order.created')
);
}
/**
* D-1-2: 알림 정의 캐시 히트 — 두 번째 호출은 Repository 미호출.
*/
#[Test]
public function d_1_2_definition_cache_hit_skips_repository(): void
{
$definition = new NotificationDefinition(['type' => 'order.created']);
$repo = $this->mock(NotificationDefinitionRepositoryInterface::class);
$repo->shouldReceive('getActiveByType')->with('order.created')->once()->andReturn($definition);
$service = app(NotificationDefinitionService::class);
$service->resolve('order.created');
$service->resolve('order.created');
}
/**
* D-1-3: 정의 수정 시 type 키 + all_active 키 모두 무효화.
*/
#[Test]
public function d_1_3_update_definition_invalidates_both_keys(): void
{
$this->cache->put('notification.definition.order.created', 'cached');
$this->cache->put('notification.definition.all_active', 'cached');
$definition = new NotificationDefinition(['type' => 'order.created']);
$repo = $this->mock(NotificationDefinitionRepositoryInterface::class);
$repo->shouldReceive('update')->andReturn($definition);
$service = app(NotificationDefinitionService::class);
$service->updateDefinition($definition, ['title' => 'new']);
$this->assertFalse($this->cache->has('notification.definition.order.created'));
$this->assertFalse($this->cache->has('notification.definition.all_active'));
}
/**
* D-1-4: 활성/비활성 토글 시 캐시 무효화.
*/
#[Test]
public function d_1_4_toggle_active_invalidates_cache(): void
{
$this->cache->put('notification.definition.order.created', 'cached');
$this->cache->put('notification.definition.all_active', 'cached');
$definition = new NotificationDefinition(['type' => 'order.created', 'is_active' => true]);
$repo = $this->mock(NotificationDefinitionRepositoryInterface::class);
$repo->shouldReceive('update')->andReturn($definition);
$service = app(NotificationDefinitionService::class);
$service->toggleActive($definition);
$this->assertFalse($this->cache->has('notification.definition.order.created'));
$this->assertFalse($this->cache->has('notification.definition.all_active'));
}
/**
* D-1-5: 전체 활성 정의 캐시 생성 + 히트.
*/
#[Test]
public function d_1_5_all_active_cache_created_and_hit(): void
{
$definitions = new EloquentCollection([
new NotificationDefinition(['type' => 'a']),
new NotificationDefinition(['type' => 'b']),
]);
$repo = $this->mock(NotificationDefinitionRepositoryInterface::class);
$repo->shouldReceive('getAllActive')->once()->andReturn($definitions);
$service = app(NotificationDefinitionService::class);
$service->getAllActive();
$this->assertTrue($this->cache->has('notification.definition.all_active'));
// 히트 — Repository 추가 호출 없음 (mock once())
$service->getAllActive();
}
// ========================================================================
// D-2. NotificationTemplateService
// ========================================================================
/**
* D-2-1: 알림 템플릿 캐시 생성 — 새 키 형식.
*/
#[Test]
public function d_2_1_template_cache_stored_under_new_key(): void
{
$template = new NotificationTemplate(['channel' => 'database']);
$templateRepo = $this->mock(NotificationTemplateRepositoryInterface::class);
$templateRepo->shouldReceive('getActiveByTypeAndChannel')
->with('order.created', 'database')
->once()
->andReturn($template);
$this->mock(NotificationDefinitionRepositoryInterface::class);
$service = app(NotificationTemplateService::class);
$service->resolve('order.created', 'database');
$this->assertTrue($this->cache->has('notification.template.order.created.database'));
$this->assertSame(
'g7:core:notification.template.order.created.database',
$this->cache->resolveKey('notification.template.order.created.database')
);
}
/**
* D-2-2: 알림 템플릿 캐시 히트 — 재호출 시 Repository 미호출.
*/
#[Test]
public function d_2_2_template_cache_hit_skips_repository(): void
{
$template = new NotificationTemplate(['channel' => 'database']);
$templateRepo = $this->mock(NotificationTemplateRepositoryInterface::class);
$templateRepo->shouldReceive('getActiveByTypeAndChannel')->once()->andReturn($template);
$this->mock(NotificationDefinitionRepositoryInterface::class);
$service = app(NotificationTemplateService::class);
$service->resolve('order.created', 'database');
$service->resolve('order.created', 'database');
}
/**
* D-2-3: 템플릿 수정 시 캐시 무효화.
*/
#[Test]
public function d_2_3_update_template_invalidates_cache(): void
{
$this->cache->put('notification.template.order.created.database', 'cached');
$definition = new NotificationDefinition(['type' => 'order.created']);
$template = $this->makeTemplateWithDefinition($definition, 'database');
$templateRepo = $this->mock(NotificationTemplateRepositoryInterface::class);
$templateRepo->shouldReceive('update')->andReturn($template);
$this->mock(NotificationDefinitionRepositoryInterface::class);
$service = app(NotificationTemplateService::class);
$service->updateTemplate($template, ['subject' => 'new']);
$this->assertFalse($this->cache->has('notification.template.order.created.database'));
}
/**
* D-2-4: 템플릿 활성/비활성 토글 시 무효화.
*/
#[Test]
public function d_2_4_toggle_template_active_invalidates_cache(): void
{
$this->cache->put('notification.template.order.created.database', 'cached');
$definition = new NotificationDefinition(['type' => 'order.created']);
$template = $this->makeTemplateWithDefinition($definition, 'database');
$template->is_active = true;
$templateRepo = $this->mock(NotificationTemplateRepositoryInterface::class);
$templateRepo->shouldReceive('update')->andReturn($template);
$this->mock(NotificationDefinitionRepositoryInterface::class);
$service = app(NotificationTemplateService::class);
$service->toggleActive($template);
$this->assertFalse($this->cache->has('notification.template.order.created.database'));
}
/**
* D-2-5: 기본값 복원 시 무효화.
*/
#[Test]
public function d_2_5_reset_to_default_invalidates_cache(): void
{
$this->cache->put('notification.template.order.created.database', 'cached');
$definition = new NotificationDefinition(['type' => 'order.created']);
$template = $this->makeTemplateWithDefinition($definition, 'database');
$templateRepo = $this->mock(NotificationTemplateRepositoryInterface::class);
$templateRepo->shouldReceive('update')->andReturn($template);
$this->mock(NotificationDefinitionRepositoryInterface::class);
$service = app(NotificationTemplateService::class);
$service->resetToDefault($template, ['subject' => 'default', 'body' => 'default']);
$this->assertFalse($this->cache->has('notification.template.order.created.database'));
}
// ------------------------------------------------------------------------
// 헬퍼
// ------------------------------------------------------------------------
/**
* NotificationTemplate 인스턴스에 definition 관계를 직접 주입합니다.
* (Eloquent setRelation 패턴)
*/
private function makeTemplateWithDefinition(NotificationDefinition $definition, string $channel): NotificationTemplate
{
$template = new NotificationTemplate(['channel' => $channel]);
$template->setRelation('definition', $definition);
return $template;
}
}