Files
Gnuboard7/tests/Feature/Seo/SitemapRegenerateNotificationTest.php
T
HeuJung 124433148c feat(seo): 사이트맵 재생성 알림·폴링 폴백 + 코어 7.0.6 확정
이슈 후속(S7) 마감 + 릴리스 버전 확정.

기능(S7):
- 사이트맵 전체 재생성 완료/실패 시 실행 관리자에게 알림(수동 실행 한정).
 스케줄러·증분·봇 재생성은 알림 없음(triggered_by 부재 시 skip).
- 최종 실패 전용 알림 + 재생성 전용 큐 + WS 진행상황 payload 봉투 정정.
- Reverb OFF 폴링 폴백 경로 수정(실브라우저 폴링에서만 드러난 결함).

버전(D28 — 정정, D20 번복):
- 릴리스를 7.0.5→7.0.6(patch)로 확정. 7.1.0(minor)은 다른 브랜치에서 별도 진행.
- config/app.php + .env + README + INSTALL + CHANGELOG + 모듈 3 requires 를 7.0.6 로.
- D19 SEO 캐시 이행 업그레이드 스텝을 Upgrade_7_0_6 로 리네임.
 이유: 스텝 버전 게이팅이 version<=toVersion 이라, 7.1.0 이름이면
 7.0.5→7.0.6 업그레이드에서 이행이 실행되지 않는다.

검증: composer test-smoke OK(2·29) · 재생성 알림/업그레이드 이행/진행상황 테스트 green ·
version-sync/version-bump(new-bump)/extension-require green · audit 0 error.
2026-07-19 18:57:51 +09:00

137 lines
5.1 KiB
PHP

<?php
namespace Tests\Feature\Seo;
use App\Extension\HookListenerRegistrar;
use App\Extension\HookManager;
use App\Listeners\NotificationHookListener;
use App\Listeners\SeoNotificationDataListener;
use App\Models\User;
use App\Notifications\GenericNotification;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
/**
* 사이트맵 재생성 알림 통합 테스트.
*
* 관리자 수동 재생성(triggered_by 존재) 완료/실패 시 실행한 관리자에게만 알림이 발송되고,
* 스케줄러/증분/봇 재생성(triggered_by null)은 발송되지 않는지 end-to-end 로 검증합니다.
*/
class SitemapRegenerateNotificationTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
// 알림 정의(sitemap_regenerated / _failed) + 동적 훅 구독 + extract_data 필터 등록
$this->artisan('db:seed', ['--class' => 'NotificationDefinitionSeeder']);
app(NotificationHookListener::class)->registerDynamicHooks();
HookListenerRegistrar::register(SeoNotificationDataListener::class, 'core');
}
private function completionResult(): array
{
return [
'success' => true,
'status' => 'updated',
'data' => ['url_count' => 720, 'child_count' => 1],
];
}
public function test_manual_completion_notifies_only_triggering_admin(): void
{
$admin = User::factory()->create();
$other = User::factory()->create();
Notification::fake();
HookManager::doAction('core.seo.sitemap.after_regenerate', $this->completionResult(), $admin->id);
Notification::assertSentTo($admin, GenericNotification::class);
Notification::assertNotSentTo($other, GenericNotification::class);
}
public function test_non_manual_completion_notifies_nobody(): void
{
User::factory()->create();
Notification::fake();
// triggered_by null = 스케줄러/증분/봇 → skip
HookManager::doAction('core.seo.sitemap.after_regenerate', $this->completionResult(), null);
Notification::assertNothingSent();
}
public function test_manual_final_failure_notifies_triggering_admin(): void
{
$admin = User::factory()->create();
Notification::fake();
// 최종 실패(재시도 소진) 훅 = Job::failed() 가 발화
HookManager::doAction('core.seo.sitemap.regenerate_failed_final', [
'success' => false,
'status' => 'failed',
'message' => '디스크 용량 부족',
], $admin->id);
Notification::assertSentTo($admin, GenericNotification::class);
}
public function test_per_attempt_failure_hook_does_not_notify(): void
{
$admin = User::factory()->create();
Notification::fake();
// 매 시도마다 발화하는 per-attempt 훅은 알림을 유발하지 않는다(최종 실패 훅만 구독).
// → 재시도 중 실패가 반복돼도, 결국 성공해도 실패 알림이 중복/오발송되지 않음.
HookManager::doAction('core.seo.sitemap.after_regenerate_failed', [
'success' => false,
'status' => 'failed',
'message' => '일시적 오류',
], $admin->id);
Notification::assertNothingSent();
}
public function test_job_failed_notifies_triggering_admin_via_final_hook(): void
{
$admin = User::factory()->create();
Notification::fake();
// Job::failed() (재시도 소진) 가 최종 실패 훅을 발화 → 유발 관리자에게 알림
(new \App\Jobs\GenerateSitemapJob(\App\Enums\SitemapGenerationMode::Full, $admin->id))
->failed(new \RuntimeException('boom'));
Notification::assertSentTo($admin, GenericNotification::class);
}
public function test_job_failed_without_trigger_user_notifies_nobody(): void
{
User::factory()->create();
Notification::fake();
// 스케줄러/증분 잡(triggeredBy null)의 최종 실패는 알림 없음
(new \App\Jobs\GenerateSitemapJob(\App\Enums\SitemapGenerationMode::Auto, null))
->failed(new \RuntimeException('boom'));
Notification::assertNothingSent();
}
public function test_default_active_channel_is_database_only(): void
{
$definition = \App\Models\NotificationDefinition::where('type', 'sitemap_regenerated')->first();
$this->assertNotNull($definition);
$this->assertSame(['database'], $definition->channels);
// mail 템플릿은 존재하되 비활성, database 템플릿은 활성
$mail = $definition->templates()->where('channel', 'mail')->first();
$database = $definition->templates()->where('channel', 'database')->first();
$this->assertNotNull($mail, 'mail 템플릿 행은 존재해야 한다(운영자가 필요 시 활성화)');
$this->assertFalse((bool) $mail->is_active, 'mail 템플릿은 기본 비활성');
$this->assertTrue((bool) $database->is_active, 'database 템플릿은 기본 활성');
}
}