fix(page): 버전 복원 후 검색엔진 캐시가 즉시 갱신되도록 after_restore 훅 구독
버전 복원 시 발화되는 after_restore 훅을 SeoPageCacheListener가 구독하지 않아, 복원 후에도 검색봇에 복원 전 버전이 계속 노출되던 문제를 수정. 복원은 콘텐츠 변경의 일종이므로 수정/삭제와 동일한 onPageChange 무효화 정책을 재사용.
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
- 페이지를 수정하다가 새 페이지 등록 화면으로 이동했을 때, 직전 페이지의 첨부파일·제목·슬러그와 "사용 가능한 슬러그입니다" 안내가 등록 화면에 그대로 남아 보이던 문제를 수정했습니다.
|
||||
- 새 페이지 등록 화면에서 슬러그를 입력하기 전 주소 미리보기가 "/page/{{slug}}" 처럼 표시되던 문제를 수정했습니다. 이제 입력 전에는 "/page/example" 로 보이고, 입력하면 입력한 주소로 즉시 바뀝니다.
|
||||
- 관리자 화면에서 페이지 첨부파일의 다운로드 / 미리보기가 관리자 전용 주소로 열리도록 수정했습니다. 이전에는 관리자 화면에서도 공개용 주소가 사용되어 비공개 페이지의 첨부파일을 관리자가 열지 못하는 경우가 있었습니다.
|
||||
- 페이지를 수정하거나 삭제한 직후 해당 페이지 상세 화면의 검색엔진용 캐시가 즉시 갱신되도록 수정했습니다. 이전에는 일부 캐시가 갱신되지 않아 변경 전 내용이 검색엔진에 남는 경우가 있었습니다.
|
||||
- 페이지를 수정·삭제하거나 이전 버전으로 복원한 직후 해당 페이지 상세 화면의 검색엔진용 캐시가 즉시 갱신되도록 수정했습니다. 이전에는 일부 캐시가 갱신되지 않아 변경 전 내용이 검색엔진에 남는 경우가 있었습니다.
|
||||
- 페이지 목록에서 여러 페이지를 한 번에 발행 / 미발행할 때 활동 이력에 발행 여부가 기록되지 않던 문제를 수정했습니다. 이제 일괄 처리에서도 페이지별로 발행 / 미발행 기록이 남습니다.
|
||||
- 페이지 관리 목록에서 검색 조건 '전체'로 슬러그를 검색해도 결과가 나오지 않던 문제를 수정했습니다 — '전체'는 제목과 슬러그를 함께 검색하고, '제목'을 고르면 제목만 검색됩니다.
|
||||
- 페이지 관리 목록의 페이지당 표시 개수 기본값이 화면 표시(20건)와 실제 조회(15건) 간에 어긋나던 문제를 수정했습니다 — 기본값을 20건으로 통일했습니다.
|
||||
|
||||
@@ -35,6 +35,11 @@ class SeoPageCacheListener implements HookListenerInterface
|
||||
'method' => 'onPageChange',
|
||||
'priority' => 20,
|
||||
],
|
||||
// 버전 복원도 콘텐츠(title/content/seo_meta) 변경이므로 수정과 동일하게 무효화 (이슈 #424-14)
|
||||
'sirsoft-page.page.after_restore' => [
|
||||
'method' => 'onPageChange',
|
||||
'priority' => 20,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use App\Helpers\PermissionHelper;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Seo\Contracts\SeoCacheManagerInterface;
|
||||
use Modules\Sirsoft\Page\Models\Page;
|
||||
use Modules\Sirsoft\Page\Models\PageAttachment;
|
||||
use Modules\Sirsoft\Page\Models\PageVersion;
|
||||
@@ -1297,6 +1298,102 @@ class PageControllerTest extends FeatureTestCase
|
||||
$this->assertEquals(3, $page->current_version); // 복원 후 버전 증가
|
||||
}
|
||||
|
||||
/**
|
||||
* 버전 복원 시 페이지 상세 SEO 캐시가 무효화되는지 확인 (이슈 #424-14)
|
||||
*
|
||||
* 복원은 title/content/seo_meta 를 이전 버전으로 되돌리므로 수정과 동일하게
|
||||
* 검색봇용 SEO 캐시를 갱신해야 한다. 복원 전 상세 URL 캐시를 적재한 뒤 복원하면
|
||||
* after_restore 훅 → SeoPageCacheListener 가 해당 캐시를 무효화해야 한다.
|
||||
* (회귀 배경: after_restore 미구독으로 복원 후에도 봇에 복원 전 버전이 잔존)
|
||||
*/
|
||||
public function test_restore_version_invalidates_page_detail_seo_cache(): void
|
||||
{
|
||||
$page = Page::factory()->create([
|
||||
'slug' => 'restore-seo-cache',
|
||||
'title' => ['ko' => '현재 제목', 'en' => 'Current Title'],
|
||||
'current_version' => 2,
|
||||
'created_by' => $this->adminUser->id,
|
||||
'updated_by' => $this->adminUser->id,
|
||||
]);
|
||||
|
||||
$version1 = PageVersion::create([
|
||||
'page_id' => $page->id,
|
||||
'version' => 1,
|
||||
'title' => ['ko' => '원래 제목', 'en' => 'Original Title'],
|
||||
'content' => ['ko' => '원래 내용', 'en' => 'Original Content'],
|
||||
'content_mode' => 'html',
|
||||
'created_by' => $this->adminUser->id,
|
||||
]);
|
||||
|
||||
PageVersion::create([
|
||||
'page_id' => $page->id,
|
||||
'version' => 2,
|
||||
'title' => ['ko' => '현재 제목', 'en' => 'Current Title'],
|
||||
'content' => ['ko' => '현재 내용', 'en' => 'Current Content'],
|
||||
'content_mode' => 'html',
|
||||
'created_by' => $this->adminUser->id,
|
||||
]);
|
||||
|
||||
// 복원 전 페이지 상세 URL 캐시 적재 (봇 응답 캐시 시뮬레이션)
|
||||
$cache = app(SeoCacheManagerInterface::class);
|
||||
$cache->put("/page/{$page->slug}", 'ko', '<html>현재 내용</html>');
|
||||
$this->assertContains("/page/{$page->slug}", $cache->getCachedUrls());
|
||||
|
||||
$response = $this->actingAs($this->adminUser)
|
||||
->postJson("/api/modules/sirsoft-page/admin/pages/{$page->id}/versions/{$version1->id}/restore");
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
// 복원 후 상세 캐시가 무효화되어 봇이 새로 렌더한다
|
||||
$this->assertNotContains("/page/{$page->slug}", $cache->getCachedUrls());
|
||||
}
|
||||
|
||||
/**
|
||||
* 버전 복원 후 새 버전이 생성되고 기존 버전 이력이 모두 보존되는지 확인 (이슈 #424-14, 검수항목 3·4)
|
||||
*
|
||||
* 복원은 다음 버전 번호로 새 스냅샷을 만들며, 복원 대상을 포함한 기존 버전 이력을
|
||||
* 삭제하지 않고 그대로 보존해야 한다.
|
||||
*/
|
||||
public function test_restore_version_creates_new_version_and_preserves_history(): void
|
||||
{
|
||||
$page = Page::factory()->create([
|
||||
'slug' => 'restore-history-preserved',
|
||||
'title' => ['ko' => '현재 제목', 'en' => 'Current Title'],
|
||||
'current_version' => 2,
|
||||
'created_by' => $this->adminUser->id,
|
||||
'updated_by' => $this->adminUser->id,
|
||||
]);
|
||||
|
||||
$version1 = PageVersion::create([
|
||||
'page_id' => $page->id,
|
||||
'version' => 1,
|
||||
'title' => ['ko' => '원래 제목', 'en' => 'Original Title'],
|
||||
'content' => ['ko' => '원래 내용', 'en' => 'Original Content'],
|
||||
'content_mode' => 'html',
|
||||
'created_by' => $this->adminUser->id,
|
||||
]);
|
||||
|
||||
PageVersion::create([
|
||||
'page_id' => $page->id,
|
||||
'version' => 2,
|
||||
'title' => ['ko' => '현재 제목', 'en' => 'Current Title'],
|
||||
'content' => ['ko' => '현재 내용', 'en' => 'Current Content'],
|
||||
'content_mode' => 'html',
|
||||
'created_by' => $this->adminUser->id,
|
||||
]);
|
||||
|
||||
$this->actingAs($this->adminUser)
|
||||
->postJson("/api/modules/sirsoft-page/admin/pages/{$page->id}/versions/{$version1->id}/restore")
|
||||
->assertStatus(200);
|
||||
|
||||
// 기존 버전 1·2 는 보존되고, 복원으로 버전 3 이 새로 생성되어 총 3건
|
||||
$versions = PageVersion::where('page_id', $page->id)->orderBy('version')->get();
|
||||
$this->assertCount(3, $versions);
|
||||
$this->assertEquals([1, 2, 3], $versions->pluck('version')->all());
|
||||
// 복원 대상(버전 1) 레코드가 삭제되지 않고 남아 있다
|
||||
$this->assertNotNull(PageVersion::find($version1->id));
|
||||
}
|
||||
|
||||
// ─── 인증/권한 차단 ────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,14 +40,18 @@ class SeoPageCacheListenerTest extends ModuleTestCase
|
||||
$this->assertArrayHasKey('sirsoft-page.page.after_create', $hooks);
|
||||
$this->assertArrayHasKey('sirsoft-page.page.after_update', $hooks);
|
||||
$this->assertArrayHasKey('sirsoft-page.page.after_delete', $hooks);
|
||||
// 버전 복원도 SEO 캐시 무효화가 필요하다 (이슈 #424-14: 복원 후 봇에 복원 전 버전 잔존 회귀 방지)
|
||||
$this->assertArrayHasKey('sirsoft-page.page.after_restore', $hooks);
|
||||
|
||||
$this->assertEquals('onPageChange', $hooks['sirsoft-page.page.after_create']['method']);
|
||||
$this->assertEquals('onPageChange', $hooks['sirsoft-page.page.after_update']['method']);
|
||||
$this->assertEquals('onPageChange', $hooks['sirsoft-page.page.after_delete']['method']);
|
||||
$this->assertEquals('onPageChange', $hooks['sirsoft-page.page.after_restore']['method']);
|
||||
|
||||
$this->assertEquals(20, $hooks['sirsoft-page.page.after_create']['priority']);
|
||||
$this->assertEquals(20, $hooks['sirsoft-page.page.after_update']['priority']);
|
||||
$this->assertEquals(20, $hooks['sirsoft-page.page.after_delete']['priority']);
|
||||
$this->assertEquals(20, $hooks['sirsoft-page.page.after_restore']['priority']);
|
||||
}
|
||||
|
||||
// ─── onPageChange: 캐시 무효화 상태 검증 ──────────────
|
||||
|
||||
+11
-1
@@ -48,11 +48,17 @@ description: |
|
||||
- changes_summary.changed_fields 를
|
||||
$t('sirsoft-page.admin.page.detail.versions.field_labels.<field>') 로 매핑.
|
||||
|
||||
14 (버전 복원 후 SEO 캐시 무효화):
|
||||
- 특정 버전으로 복원하면 title/content/seo_meta 가 이전 버전으로 되돌아가므로, 수정과
|
||||
동일하게 페이지 상세 SEO 캐시가 즉시 갱신돼야 한다.
|
||||
- 회귀: SeoPageCacheListener 가 after_restore 를 미구독해 복원 후에도 봇에 복원 전 버전이
|
||||
잔존 → after_restore 를 onPageChange 로 구독 추가(수정/삭제와 동일 무효화 정책).
|
||||
|
||||
axes:
|
||||
url_context: [public, admin]
|
||||
form_mode: [create, edit]
|
||||
upload_count: [single, multiple]
|
||||
page_change: [create, update, delete]
|
||||
page_change: [create, update, delete, restore]
|
||||
cached_entry: [layout_tagged, url_only]
|
||||
|
||||
effects:
|
||||
@@ -67,6 +73,8 @@ effects:
|
||||
- page_change_invalidates_url_only_cached_detail_without_layout_tag
|
||||
- page_change_invalidates_page_show_layout_cache
|
||||
- page_change_invalidates_home_and_search_and_sitemap_cache
|
||||
- page_restore_invalidates_page_detail_seo_cache
|
||||
- page_restore_creates_new_version_and_preserves_history
|
||||
- page_version_changed_fields_render_localized_labels
|
||||
|
||||
test_files:
|
||||
@@ -83,6 +91,8 @@ notes: |
|
||||
패턴으로 정렬했다. 생성 모드 업로드 순서 연결은 PageAttachmentServiceTest, 수정 모드 reorder 와
|
||||
잔존 버그(라우트 전환 시 key 리마운트)는 admin_page_form 레이아웃 렌더 테스트로 확인.
|
||||
6-1 은 백엔드 Feature 테스트, 12-2 는 리스너 unit 테스트, 13 은 레이아웃 렌더 테스트로 커버.
|
||||
14 는 리스너 unit 테스트(after_restore 구독 단언) + Admin Feature 테스트(복원 엔드포인트 →
|
||||
상세 SEO 캐시 무효화 + 새 버전 생성·이력 보존)로 커버.
|
||||
|
||||
E2E: 페이지 모듈에 Playwright 인프라(playwright.config.ts + page-auth/page-seed fixture +
|
||||
playwright:seed-page stub 커맨드)를 이커머스와 동일 패턴으로 신설했다. spec 3건은
|
||||
|
||||
Reference in New Issue
Block a user