Files
Gnuboard7/tests/Unit/Seo/SeoCacheRegeneratorTest.php
2026-04-01 10:30:52 +09:00

221 lines
7.0 KiB
PHP

<?php
namespace Tests\Unit\Seo;
use App\Seo\Contracts\SeoCacheManagerInterface;
use App\Seo\Contracts\SeoRendererInterface;
use App\Seo\SeoCacheRegenerator;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Mockery;
use Tests\TestCase;
/**
* SeoCacheRegenerator 테스트
*
* 단건 SEO 캐시 재생성 서비스의 동작을 검증합니다.
* - 렌더링 성공 시 putWithLayout 호출
* - 렌더링 실패(null) 시 캐시 저장 스킵
* - 예외 발생 시 graceful 처리
* - 다국어 로케일별 재생성
*/
class SeoCacheRegeneratorTest extends TestCase
{
private SeoRendererInterface $rendererMock;
private SeoCacheManagerInterface $cacheMock;
private SeoCacheRegenerator $regenerator;
protected function setUp(): void
{
parent::setUp();
$this->rendererMock = Mockery::mock(SeoRendererInterface::class);
$this->cacheMock = Mockery::mock(SeoCacheManagerInterface::class);
$this->regenerator = new SeoCacheRegenerator(
$this->rendererMock,
$this->cacheMock,
);
}
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
// ─── 기본 재생성 ──────────────────────────────────────
/**
* 렌더링 성공 시 캐시에 저장되는지 확인
*/
public function test_render_and_cache_stores_html_on_success(): void
{
config(['app.locale' => 'ko']);
config(['app.supported_locales' => ['ko']]);
$this->rendererMock->shouldReceive('render')
->once()
->with(Mockery::on(function (Request $request) {
return $request->getPathInfo() === '/shop/products/1';
}))
->andReturnUsing(function (Request $request) {
$request->attributes->set('seo_layout_name', 'shop/show');
return '<html>Product 1</html>';
});
$this->cacheMock->shouldReceive('putWithLayout')
->once()
->with('/shop/products/1', 'ko', '<html>Product 1</html>', 'shop/show');
Log::shouldReceive('debug')->atLeast()->once();
$result = $this->regenerator->renderAndCache('/shop/products/1');
$this->assertTrue($result);
}
/**
* 렌더링 결과가 null이면 캐시 저장을 건너뛰는지 확인
*/
public function test_render_and_cache_skips_when_render_returns_null(): void
{
config(['app.locale' => 'ko']);
config(['app.supported_locales' => ['ko']]);
$this->rendererMock->shouldReceive('render')
->once()
->andReturn(null);
$this->cacheMock->shouldNotReceive('putWithLayout');
$result = $this->regenerator->renderAndCache('/nonexistent');
$this->assertFalse($result);
}
// ─── 다국어 재생성 ──────────────────────────────────────
/**
* 지원 로케일별로 각각 렌더링 + 캐시 저장이 수행되는지 확인
*/
public function test_render_and_cache_iterates_supported_locales(): void
{
config(['app.locale' => 'ko']);
config(['app.supported_locales' => ['ko', 'en']]);
$this->rendererMock->shouldReceive('render')
->twice()
->andReturnUsing(function (Request $request) {
$locale = app()->getLocale();
$request->attributes->set('seo_layout_name', 'shop/show');
return "<html>Product ({$locale})</html>";
});
$this->cacheMock->shouldReceive('putWithLayout')
->once()
->with('/shop/products/1', 'ko', Mockery::type('string'), 'shop/show');
$this->cacheMock->shouldReceive('putWithLayout')
->once()
->with('/shop/products/1', 'en', Mockery::type('string'), 'shop/show');
Log::shouldReceive('debug')->atLeast()->once();
$result = $this->regenerator->renderAndCache('/shop/products/1');
$this->assertTrue($result);
}
// ─── 예외 처리 ──────────────────────────────────────
/**
* 렌더링 중 예외 발생 시 graceful하게 처리되는지 확인
*/
public function test_render_and_cache_handles_exceptions_gracefully(): void
{
config(['app.locale' => 'ko']);
config(['app.supported_locales' => ['ko']]);
$this->rendererMock->shouldReceive('render')
->once()
->andThrow(new \RuntimeException('Renderer failed'));
$this->cacheMock->shouldNotReceive('putWithLayout');
Log::shouldReceive('warning')
->once()
->with('[SEO] Cache regeneration failed', Mockery::on(function ($context) {
return $context['url'] === '/test'
&& $context['error'] === 'Renderer failed';
}));
$result = $this->regenerator->renderAndCache('/test');
$this->assertFalse($result);
}
/**
* 일부 로케일만 실패해도 나머지는 정상 처리되는지 확인
*/
public function test_render_and_cache_continues_on_partial_locale_failure(): void
{
config(['app.locale' => 'ko']);
config(['app.supported_locales' => ['ko', 'en']]);
$callCount = 0;
$this->rendererMock->shouldReceive('render')
->twice()
->andReturnUsing(function (Request $request) use (&$callCount) {
$callCount++;
if ($callCount === 1) {
throw new \RuntimeException('ko render failed');
}
$request->attributes->set('seo_layout_name', 'shop/show');
return '<html>EN</html>';
});
$this->cacheMock->shouldReceive('putWithLayout')
->once()
->with('/shop/products/1', 'en', '<html>EN</html>', 'shop/show');
Log::shouldReceive('warning')->once();
Log::shouldReceive('debug')->atLeast()->once();
$result = $this->regenerator->renderAndCache('/shop/products/1');
$this->assertTrue($result);
}
/**
* 로케일이 렌더링 후 원래 값으로 복원되는지 확인
*/
public function test_render_and_cache_restores_locale(): void
{
config(['app.locale' => 'ko']);
config(['app.supported_locales' => ['en']]);
app()->setLocale('ko');
$this->rendererMock->shouldReceive('render')
->once()
->andReturnUsing(function (Request $request) {
$request->attributes->set('seo_layout_name', 'test');
return '<html>EN</html>';
});
$this->cacheMock->shouldReceive('putWithLayout')->once();
Log::shouldReceive('debug')->atLeast()->once();
$this->regenerator->renderAndCache('/test');
$this->assertEquals('ko', app()->getLocale());
}
}