Files
Gnuboard7/tests/Feature/Template/LayoutEditorCacheInvalidationTest.php
HeuJung 0b6f329249 fix(core): 레이아웃 버전 변경량 계산의 메모리 초과와 편집기 확장 편집·재로드·충돌 안내·버전 기록 결함 수정
- 버전 이력 변경량(+N/-N 줄) 계산이 변경 영역 (줄 수)² 크기의 LCS 표를 만들어
 큰 공통 레이아웃의 첫 편집기 저장이 PHP 기본 메모리 한도(128M) 서버에서
 500 으로 끝났다. 두 행 DP 로 길이만 구하도록 바꿔 메모리가 줄 수에 비례하고
 표시 숫자는 종전과 같다(참조 구현 동치·메모리 상한 회귀 테스트)
- 확장 편집 모드 저장이 overlay 확장의 injections 를 비우던 결함: 서버가 주입
 노드 출처 메타에 injection 순번을 싣고, 편집기는 그 순번(없으면 원본 노드 id)
 으로 되돌리며, 되돌릴 수 없는 노드가 있으면 저장하지 않고 안내한다
- 서빙 캐시 키를 서버 현재 확장 캐시 버전으로만 조립해 저장·복원 두 번째부터
 재로드·「최신 불러오기」가 옛 내용을 받던 결함 수정(요청 v 는 HTTP 캐시 우회용)
- 409 충돌 안내가 errors 아래의 버전을 읽지 못해 「최신 버전: -1」 로 표시되던
 결함을 세 저장 경로 공용 판독으로 수정
- 버전 저장 시 저장자를 기록하고, 복원 시에도 잠금 번호를 올린다
- 회귀 테스트(PHPUnit·Vitest), 트러블슈팅 사례 32~34, 규정·API 문서, ja 언어팩,
 편집기 번들 재빌드, 이력 문서 동반
2026-09-09 12:19:12 +09:00

126 lines
5.4 KiB
PHP

<?php
namespace Tests\Feature\Template;
use App\Contracts\Repositories\LayoutRepositoryInterface;
use App\Enums\ExtensionStatus;
use App\Extension\Cache\CoreCacheDriver;
use App\Extension\Traits\InvalidatesLayoutCache;
use App\Models\Template;
use App\Models\TemplateLayout;
use App\Services\ExtensionStaticCacheService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
/**
* 레이아웃 편집기(`with_source_meta=1`) 캐시 무효화 회귀
*
* 배경: `PublicLayoutController::serve()` 는 일반 응답과 레이아웃 편집기 응답
* (`with_source_meta=1`)을 **별도 캐시 키**(`.meta` 접미사)로 저장한다. 템플릿/
* 레이아웃 상태 변화(refresh-layout / activate / deactivate / uninstall 등) 시
* 공통으로 거치는 `InvalidatesLayoutCache::forgetLayoutCacheKeys()` 가 일반 키만
* 삭제하고 `.meta` 키를 누락하면, 사용자 페이지는 갱신되지만 **편집기 캔버스만
* stale 캐시**(옛 레이아웃)를 받는다( 재현 — 사용자는 복원, 편집기는
* 미복원). 레이아웃 저장 경로(`LayoutService::clearPublicServingCache`)는 이미 두
* 키를 지우므로, 본 테스트는 상태 변화 공통 지점이 두 키를 모두 무효화함을 잠근다.
*/
class LayoutEditorCacheInvalidationTest extends TestCase
{
use RefreshDatabase;
/** trait 를 노출하는 익명 테스트 더블 */
private function makeInvalidator(): object
{
return new class
{
use InvalidatesLayoutCache;
public LayoutRepositoryInterface $layoutRepository;
public function __construct()
{
$this->layoutRepository = app(LayoutRepositoryInterface::class);
}
/** protected 메서드 노출 */
public function invalidate(int $templateId, string $identifier): void
{
$this->invalidateTemplateLayoutCache($templateId, $identifier);
}
};
}
private function coreCache(): CoreCacheDriver
{
return new CoreCacheDriver(config('cache.default', 'array'));
}
#[Test]
public function 레이아웃_캐시_무효화는_일반키와_편집기meta키를_모두_삭제한다(): void
{
$template = Template::factory()->create([
'identifier' => 'sirsoft-basic',
'type' => 'user',
'status' => ExtensionStatus::Active->value,
]);
TemplateLayout::create([
'template_id' => $template->id,
'name' => 'home',
'content' => ['meta' => ['title' => 'Home'], 'components' => []],
]);
$cache = $this->coreCache();
// 트레이트 게터와 같은 근거 — 원시 키 읽기는 키 부재 시 0 을 돌려주어 실제 무효화 키(`.v{time}`)와
// 어긋난 `.v0` 키를 만든다(트레이트는 부재 시 재생성). 무효화 대상 키는 게터가 SSoT 다.
$version = ExtensionStaticCacheService::getExtensionCacheVersion();
// PublicLayoutController::serve() 가 쓰는 두 캐시 키 — 일반 + 편집기(.meta)
$generalKey = "layout.sirsoft-basic.home.v{$version}";
$metaKey = "layout.sirsoft-basic.home.v{$version}.meta";
$cache->put($generalKey, ['cached' => 'general'], 3600);
$cache->put($metaKey, ['cached' => 'meta'], 3600);
// 사전 조건: 두 키 모두 캐시에 존재
$this->assertNotNull($cache->get($generalKey), '사전: 일반 키 캐시 존재');
$this->assertNotNull($cache->get($metaKey), '사전: 편집기 .meta 키 캐시 존재');
// 상태 변화 공통 지점 — 레이아웃 캐시 무효화
$this->makeInvalidator()->invalidate($template->id, 'sirsoft-basic');
// 두 키 모두 무효화되어야 함 (.meta 누락이면 편집기 stale 회귀)
$this->assertNull($cache->get($generalKey), '일반 키가 무효화됨');
$this->assertNull($cache->get($metaKey), '편집기.meta 키도 무효화됨');
}
#[Test]
public function 템플릿_레이아웃_refresh_후_편집기meta_캐시가_stale로_남지_않는다(): void
{
$template = Template::factory()->create([
'identifier' => 'sirsoft-basic',
'type' => 'user',
'status' => ExtensionStatus::Active->value,
]);
TemplateLayout::create([
'template_id' => $template->id,
'name' => 'home',
'content' => ['meta' => ['title' => 'Home'], 'components' => []],
]);
$cache = $this->coreCache();
// 트레이트 게터와 같은 근거 — 원시 키 읽기는 키 부재 시 0 을 돌려주어 실제 무효화 키(`.v{time}`)와
// 어긋난 `.v0` 키를 만든다(트레이트는 부재 시 재생성). 무효화 대상 키는 게터가 SSoT 다.
$version = ExtensionStaticCacheService::getExtensionCacheVersion();
$metaKey = "layout.sirsoft-basic.home.v{$version}.meta";
$cache->put($metaKey, ['stale' => 'editor-old-content'], 3600);
// 상태 변화 공통 지점 호출 → 편집기 .meta 키가 stale 로 남으면 실패
$this->makeInvalidator()->invalidate($template->id, 'sirsoft-basic');
$this->assertNull(
$cache->get($metaKey),
'refresh/상태변화 후 편집기 .meta 캐시가 stale 로 잔존하면 편집기 캔버스가 옛 레이아웃을 표시한다',
);
}
}