이슈 후속(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.
208 lines
6.4 KiB
PHP
208 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Jobs;
|
|
|
|
use App\Jobs\GenerateSitemapJob;
|
|
use App\Seo\SitemapManager;
|
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Mockery;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* GenerateSitemapJob 유닛 테스트
|
|
*
|
|
* 잡의 큐 속성과 SitemapManager 위임 동작을 검증합니다.
|
|
* 실제 생성/캐시/last_updated_at 로직은 SitemapManager 단위 테스트에서 검증합니다.
|
|
*/
|
|
class GenerateSitemapJobTest extends TestCase
|
|
{
|
|
protected function tearDown(): void
|
|
{
|
|
Mockery::close();
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* ShouldQueue 인터페이스를 구현하는지 확인합니다.
|
|
*/
|
|
public function test_job_implements_should_queue(): void
|
|
{
|
|
$job = new GenerateSitemapJob;
|
|
|
|
$this->assertInstanceOf(ShouldQueue::class, $job);
|
|
}
|
|
|
|
/**
|
|
* 대용량 생성을 견디는 큐 속성을 확인합니다.
|
|
*/
|
|
public function test_job_has_correct_tries_and_timeout(): void
|
|
{
|
|
$job = new GenerateSitemapJob;
|
|
|
|
$this->assertSame(3, $job->tries);
|
|
$this->assertSame(1800, $job->timeout);
|
|
$this->assertSame(120, $job->backoff);
|
|
}
|
|
|
|
/**
|
|
* 전용 'sitemap' 큐로 라우팅되는지 확인합니다.
|
|
*
|
|
* default 큐와 분리해, 긴 생성 작업이 방송/알림을 막지 않고(실시간 진행상황),
|
|
* 이 큐 워커 1개 제약으로 동시 중복 생성을 차단합니다.
|
|
*/
|
|
public function test_job_routes_to_dedicated_sitemap_queue(): void
|
|
{
|
|
$job = new GenerateSitemapJob;
|
|
|
|
$this->assertSame('sitemap', GenerateSitemapJob::QUEUE);
|
|
$this->assertSame('sitemap', $job->queue);
|
|
}
|
|
|
|
/**
|
|
* ShouldBeUnique 를 구현하여 동시 실행을 막는지 확인합니다.
|
|
*
|
|
* 봇 요청 캐시 미스마다 dispatch 되어도 큐에 한 건만 남아야 합니다.
|
|
*/
|
|
public function test_job_implements_should_be_unique(): void
|
|
{
|
|
$job = new GenerateSitemapJob;
|
|
|
|
$this->assertInstanceOf(ShouldBeUnique::class, $job);
|
|
}
|
|
|
|
/**
|
|
* 유니크 락 키가 인스턴스와 무관하게 고정값인지 확인합니다.
|
|
*
|
|
* 인스턴스마다 키가 다르면 중복 디스패치가 그대로 통과합니다.
|
|
*/
|
|
public function test_unique_id_is_stable_across_instances(): void
|
|
{
|
|
$this->assertSame('seo-sitemap', (new GenerateSitemapJob)->uniqueId());
|
|
$this->assertSame('seo-sitemap', (new GenerateSitemapJob)->uniqueId());
|
|
}
|
|
|
|
/**
|
|
* 유니크 락이 영구 고착되지 않도록 만료 시간이 설정되어 있는지 확인합니다.
|
|
*/
|
|
public function test_unique_lock_expires(): void
|
|
{
|
|
$job = new GenerateSitemapJob;
|
|
|
|
$this->assertSame(900, $job->uniqueFor);
|
|
}
|
|
|
|
/**
|
|
* retryUntil 이 재시도 누적을 30분으로 제한하는지 확인합니다.
|
|
*/
|
|
public function test_retry_until_limits_retry_window(): void
|
|
{
|
|
Carbon::setTestNow(Carbon::parse('2026-07-17 10:00:00'));
|
|
|
|
$job = new GenerateSitemapJob;
|
|
|
|
$this->assertSame(
|
|
'2026-07-17 10:30:00',
|
|
Carbon::instance($job->retryUntil())->format('Y-m-d H:i:s')
|
|
);
|
|
|
|
Carbon::setTestNow();
|
|
}
|
|
|
|
/**
|
|
* handle() 호출 시 SitemapManager::regenerate 에 위임하는지 확인합니다.
|
|
*/
|
|
public function test_handle_delegates_to_sitemap_manager(): void
|
|
{
|
|
$manager = Mockery::mock(SitemapManager::class);
|
|
$manager->shouldReceive('regenerate')
|
|
->once()
|
|
->andReturn([
|
|
'success' => true,
|
|
'status' => 'updated',
|
|
'data' => [
|
|
'last_updated_at' => '2026-04-29T10:00:00+00:00',
|
|
'size_bytes' => 1234,
|
|
'ttl' => 86400,
|
|
],
|
|
]);
|
|
|
|
Log::shouldReceive('info')
|
|
->once()
|
|
->with('[SEO] Sitemap generated and cached', Mockery::on(function ($context) {
|
|
return $context['size'] === 1234 && $context['ttl'] === 86400;
|
|
}));
|
|
|
|
$job = new GenerateSitemapJob;
|
|
$job->handle($manager);
|
|
|
|
$this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount());
|
|
}
|
|
|
|
/**
|
|
* disabled 상태이면 생성을 건너뛰고 예외를 던지지 않는지 확인합니다.
|
|
*/
|
|
public function test_handle_skips_when_disabled(): void
|
|
{
|
|
$manager = Mockery::mock(SitemapManager::class);
|
|
$manager->shouldReceive('regenerate')
|
|
->once()
|
|
->andReturn([
|
|
'success' => false,
|
|
'status' => 'disabled',
|
|
'message' => 'Sitemap 생성이 비활성화되어 있습니다.',
|
|
]);
|
|
|
|
Log::shouldReceive('info')
|
|
->once()
|
|
->with('[SEO] Sitemap generation skipped (disabled)');
|
|
|
|
$job = new GenerateSitemapJob;
|
|
$job->handle($manager);
|
|
|
|
$this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount());
|
|
}
|
|
|
|
/**
|
|
* 실패 응답 시 RuntimeException 을 던져 큐가 재시도하도록 합니다.
|
|
*/
|
|
public function test_handle_throws_on_failure(): void
|
|
{
|
|
$manager = Mockery::mock(SitemapManager::class);
|
|
$manager->shouldReceive('regenerate')
|
|
->once()
|
|
->andReturn([
|
|
'success' => false,
|
|
'status' => 'failed',
|
|
'message' => 'contributor crashed',
|
|
]);
|
|
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('contributor crashed');
|
|
|
|
$job = new GenerateSitemapJob;
|
|
$job->handle($manager);
|
|
}
|
|
|
|
/**
|
|
* failed() 호출 시 에러 로그를 기록하는지 확인합니다.
|
|
*/
|
|
public function test_failed_logs_error(): void
|
|
{
|
|
$exception = new \RuntimeException('Sitemap generation timeout');
|
|
|
|
Log::shouldReceive('error')
|
|
->once()
|
|
->with('[SEO] Sitemap generation failed', Mockery::on(function ($context) {
|
|
return $context['error'] === 'Sitemap generation timeout';
|
|
}));
|
|
|
|
$job = new GenerateSitemapJob;
|
|
$job->failed($exception);
|
|
|
|
$this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount());
|
|
}
|
|
}
|