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; } }