대용량 사이트에서 사이트맵 생성이 메모리 부족으로 반복 실패하던 문제(공개 )의 1단계로, 전체 URL을 메모리에 적재하던 구조를 스트리밍 분할 생성으로 교체한다. 사이트맵 스트리밍 코어: - SitemapWriter — 자식 파일 1개 분량만 버퍼링하고 임계 도달 시 flush. StorageInterface가 append를 제공하지 않아(put이 전체 덮어쓰기) 택한 구조이며, 최대 메모리가 "자식 파일 1개 크기"로 유계가 된다. URL 수(50,000)와 바이트(45MB) 두 임계로 분할해 sitemaps.org 프로토콜을 지킨다. - 커밋은 _tmp 전량 기록 → live 재배치 → manifest 마지막 기록 순. manifest 존재가 커밋 완료 신호다. 기존 manifest를 먼저 지우지 않아 스왑 중 봇에게 503이 나가지 않는다. - SitemapFileStore — 읽기측 + 파일 경로 SSoT. SitemapXmlRenderer — escape 단일 출처. 데이터 접근 계층 정리: - contributor 3종이 쿼리를 직접 조립하던 것을 Repository 위임으로 전환. MySQL 버퍼드 쿼리에서 cursor는 결과셋 전체를 적재하므로 lazyById를 쓴다. - SeoCacheStatsService → SeoCacheStatRepository 신설 (집계 4쿼리 → 1쿼리). - audit 룰 service-direct-data-access의 사각 2건 수정 — 확장 디렉토리 정규식이 상대경로를 매칭하지 못해 *Contributor처럼 접미사 없는 클래스가 전부 미검출이었다. SEO 캐시 설정 정합화: - SEO 설정의 캐시 3키가 cache 카테고리 형제 키에 항상 져서 운영자 입력이 조용히 무시되고 있었다(설정을 cache 카테고리로 이관하며 SEO 탭 옛 칸을 미제거). 고급 탭을 기준값으로 두되 SEO 탭에 지정이 있으면 오버라이드하도록 SeoCacheSettings 단일 출처로 정리. "지정 여부"는 null로 판정하므로 기본값을 비웠다. - 기존 설치는 Upgrade_7_1_0이 이행 — 옛 기본값과 같으면 미설정으로 비우고, 다르면 운영자 의도로 보아 보존한다. 코어 버전 bump와 루트 CHANGELOG는 계획서 결정(D20)에 따라 마감 단계에서 일괄 수행한다.
163 lines
5.6 KiB
PHP
163 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Seo;
|
|
|
|
use App\Seo\SeoCacheSettings;
|
|
use Illuminate\Support\Facades\Config;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* SeoCacheSettings 단위 테스트 (결정 D19)
|
|
*
|
|
* 우선순위 규칙:
|
|
* 고급 탭(`cache.*`) = 메인 / SEO 탭(`seo.*`)에 별도 지정이 있으면 오버라이드.
|
|
* "별도 지정" 판정은 null 여부 — null 이면 미설정으로 보고 고급 탭 값을 따른다.
|
|
*
|
|
* 회귀 배경: 과거에는 `cache.*` 가 **항상** 이겨 SEO 탭 입력이 조용히 무시되었다.
|
|
* 이 저장소의 `seo.json` 에 `cache_ttl=3600` 이 저장돼 있었으나 실제로는
|
|
* `cache.seo_ttl=7200` 으로 동작하던 것이 실증 사례다.
|
|
*/
|
|
class SeoCacheSettingsTest extends TestCase
|
|
{
|
|
/**
|
|
* 테스트 초기화 - 두 카테고리를 모두 미설정 상태로 비웁니다.
|
|
*/
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
Config::set('g7_settings.core.seo', []);
|
|
Config::set('g7_settings.core.cache', []);
|
|
}
|
|
|
|
/**
|
|
* pageCacheTtl: SEO 탭이 미설정이면 고급 탭 값을 따른다
|
|
*/
|
|
public function test_page_cache_ttl_follows_advanced_tab_when_seo_is_unset(): void
|
|
{
|
|
Config::set('g7_settings.core.cache.seo_ttl', 7200);
|
|
Config::set('g7_settings.core.seo.cache_ttl', null);
|
|
|
|
$this->assertSame(7200, SeoCacheSettings::pageCacheTtl());
|
|
}
|
|
|
|
/**
|
|
* pageCacheTtl: SEO 탭에 값이 있으면 고급 탭을 오버라이드한다 (회귀 — 과거엔 무시됨)
|
|
*/
|
|
public function test_page_cache_ttl_seo_override_wins_over_advanced_tab(): void
|
|
{
|
|
Config::set('g7_settings.core.cache.seo_ttl', 7200);
|
|
Config::set('g7_settings.core.seo.cache_ttl', 3600);
|
|
|
|
$this->assertSame(
|
|
3600,
|
|
SeoCacheSettings::pageCacheTtl(),
|
|
'SEO 탭에 지정한 값이 고급 탭보다 우선해야 합니다.'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* pageCacheTtl: 양쪽 다 미설정이면 최종 기본값
|
|
*/
|
|
public function test_page_cache_ttl_falls_back_to_default_when_both_unset(): void
|
|
{
|
|
$this->assertSame(7200, SeoCacheSettings::pageCacheTtl());
|
|
}
|
|
|
|
/**
|
|
* pageCacheEnabled: SEO 탭이 미설정이면 고급 탭 값을 따른다
|
|
*/
|
|
public function test_page_cache_enabled_follows_advanced_tab_when_seo_is_unset(): void
|
|
{
|
|
Config::set('g7_settings.core.cache.seo_enabled', false);
|
|
Config::set('g7_settings.core.seo.cache_enabled', null);
|
|
|
|
$this->assertFalse(SeoCacheSettings::pageCacheEnabled());
|
|
}
|
|
|
|
/**
|
|
* pageCacheEnabled: SEO 탭의 false 는 미설정이 아니라 오버라이드다
|
|
*
|
|
* null 이 아닌 falsy 값(false)을 "미설정" 으로 잘못 판정하면, 운영자가 SEO 캐시를
|
|
* 끄려 해도 고급 탭의 true 가 이겨 캐시가 계속 켜진다.
|
|
*/
|
|
public function test_page_cache_enabled_treats_false_as_explicit_override(): void
|
|
{
|
|
Config::set('g7_settings.core.cache.seo_enabled', true);
|
|
Config::set('g7_settings.core.seo.cache_enabled', false);
|
|
|
|
$this->assertFalse(
|
|
SeoCacheSettings::pageCacheEnabled(),
|
|
'false 는 미설정이 아니라 명시적 오버라이드여야 합니다.'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* pageCacheEnabled: SEO 탭의 true 도 오버라이드로 동작한다
|
|
*/
|
|
public function test_page_cache_enabled_true_overrides_disabled_advanced_tab(): void
|
|
{
|
|
Config::set('g7_settings.core.cache.seo_enabled', false);
|
|
Config::set('g7_settings.core.seo.cache_enabled', true);
|
|
|
|
$this->assertTrue(SeoCacheSettings::pageCacheEnabled());
|
|
}
|
|
|
|
/**
|
|
* sitemapCacheTtl: SEO 탭이 미설정이면 고급 탭 값을 따른다
|
|
*/
|
|
public function test_sitemap_cache_ttl_follows_advanced_tab_when_seo_is_unset(): void
|
|
{
|
|
Config::set('g7_settings.core.cache.seo_sitemap_ttl', 86400);
|
|
Config::set('g7_settings.core.seo.sitemap_cache_ttl', null);
|
|
|
|
$this->assertSame(86400, SeoCacheSettings::sitemapCacheTtl());
|
|
}
|
|
|
|
/**
|
|
* sitemapCacheTtl: SEO 탭에 값이 있으면 고급 탭을 오버라이드한다 (회귀)
|
|
*/
|
|
public function test_sitemap_cache_ttl_seo_override_wins_over_advanced_tab(): void
|
|
{
|
|
Config::set('g7_settings.core.cache.seo_sitemap_ttl', 86400);
|
|
Config::set('g7_settings.core.seo.sitemap_cache_ttl', 3600);
|
|
|
|
$this->assertSame(3600, SeoCacheSettings::sitemapCacheTtl());
|
|
}
|
|
|
|
/**
|
|
* sitemapCacheTtl: 양쪽 다 미설정이면 최종 기본값
|
|
*/
|
|
public function test_sitemap_cache_ttl_falls_back_to_default_when_both_unset(): void
|
|
{
|
|
$this->assertSame(86400, SeoCacheSettings::sitemapCacheTtl());
|
|
}
|
|
|
|
/**
|
|
* 각 영역은 서로 독립이다 — SEO 페이지 캐시 지정이 sitemap 에 새지 않는다
|
|
*/
|
|
public function test_domains_are_independent(): void
|
|
{
|
|
Config::set('g7_settings.core.cache.seo_ttl', 7200);
|
|
Config::set('g7_settings.core.cache.seo_sitemap_ttl', 86400);
|
|
Config::set('g7_settings.core.seo.cache_ttl', 60);
|
|
|
|
$this->assertSame(60, SeoCacheSettings::pageCacheTtl());
|
|
$this->assertSame(
|
|
86400,
|
|
SeoCacheSettings::sitemapCacheTtl(),
|
|
'SEO 페이지 캐시 오버라이드가 sitemap 캐시에 영향을 주면 안 됩니다.'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 문자열로 저장된 값도 정수로 정규화된다 (폼 입력은 문자열로 올 수 있음)
|
|
*/
|
|
public function test_string_values_are_normalized_to_int(): void
|
|
{
|
|
Config::set('g7_settings.core.seo.sitemap_cache_ttl', '3600');
|
|
|
|
$this->assertSame(3600, SeoCacheSettings::sitemapCacheTtl());
|
|
}
|
|
}
|