Files
Gnuboard7/modules/_bundled/sirsoft-ecommerce/tests/Feature/Seo/EcommerceSitemapIndexTest.php
T
HeuJung c77ddee03b fix(core,ecommerce): Windows 확장 업데이트 파일 잠금 해소 + 통화 삭제 영속화
Windows 는 하위 트리에 열린 핸들이 하나라도 있으면 디렉토리 rename 을 막는다.
잠금 프로세스를 찾아 종료하던 기존 대응은 디렉토리 핸들을 감지하지 못했고
사용 중인 편집기를 예고 없이 죽였다. rename 이 막히면 파일 단위 연산으로
폴백해 어떤 프로세스도 건드리지 않고 교체를 끝낸다. 커밋 전 점검에서
pint.json 이 코어 업데이트 대상에 빠져 있던 것도 함께 등재했다.

이커머스는 기본 제공 통화 삭제가 저장 응답에서 즉시 부활했다. 항목 단위
보충 병합이 "소실" 과 "의도적 삭제" 를 구분하지 못한 탓이라, 삭제 의도를
서버가 도출해 저장본에 기록하고 병합이 그 기록을 존중하게 했다. 그 통화로
결제된 과거 주문의 표기가 흔들리지 않도록 소수 자릿수 해석도 스냅샷 우선으로
바꿨다 — 금액은 원래 스냅샷 환율을 써 안전했으나 자릿수만 현재 설정을 봤다.
2026-08-12 10:10:01 +09:00

133 lines
5.6 KiB
PHP

<?php
namespace Modules\Sirsoft\Ecommerce\Tests\Feature\Seo;
use App\Jobs\GenerateSitemapJob;
use App\Models\SitemapUrl;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Config;
use Modules\Sirsoft\Ecommerce\Enums\ProductDisplayStatus;
use Modules\Sirsoft\Ecommerce\Listeners\SeoCategoryCacheListener;
use Modules\Sirsoft\Ecommerce\Listeners\SeoProductCacheListener;
use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase;
/**
* Ecommerce 리스너 사이트맵 증분 색인 테스트 (S4 ⑲)
*
* 검증 목적 (DoD: 공개→append / 비공개→remove):
* - 전시 상품 생성/수정 → 색인(append), 숨김/삭제 → 색인 제거
* - 활성 카테고리 변경 → 색인, 비활성/삭제 → 색인 제거
* - 리스너가 사이트맵 재생성 잡을 디스패치
*/
class EcommerceSitemapIndexTest extends ModuleTestCase
{
protected function setUp(): void
{
parent::setUp();
Bus::fake([GenerateSitemapJob::class]);
}
/**
* 전시 상품 생성 시 사이트맵에 색인(append)되고 잡이 디스패치된다.
*/
public function test_visible_product_create_appends_to_sitemap(): void
{
$product = (object) ['id' => 42, 'display_status' => ProductDisplayStatus::VISIBLE, 'updated_at' => now()];
(new SeoProductCacheListener)->onProductCreate($product);
$row = SitemapUrl::where('resource_type', 'product')->where('resource_id', '42')->first();
$this->assertNotNull($row, '전시 상품은 사이트맵에 색인돼야 합니다.');
$this->assertStringEndsWith('/products/42', $row->loc);
$this->assertSame('sirsoft-ecommerce', $row->contributor);
Bus::assertDispatched(GenerateSitemapJob::class);
}
/**
* 숨김 상품으로 수정 시 사이트맵 색인이 제거(remove)된다.
*/
public function test_hidden_product_update_removes_from_sitemap(): void
{
// 먼저 전시로 색인
(new SeoProductCacheListener)->onProductCreate(
(object) ['id' => 7, 'display_status' => ProductDisplayStatus::VISIBLE, 'updated_at' => now()]
);
$this->assertSame(1, SitemapUrl::where('resource_id', '7')->count());
// 숨김으로 전환
(new SeoProductCacheListener)->onProductUpdate(
(object) ['id' => 7, 'display_status' => ProductDisplayStatus::HIDDEN, 'updated_at' => now()]
);
$this->assertSame(0, SitemapUrl::where('resource_id', '7')->count(), '숨김 상품은 색인에서 제거돼야 합니다.');
}
/**
* 상품 삭제 시 사이트맵 색인이 제거된다.
*/
public function test_product_delete_removes_from_sitemap(): void
{
(new SeoProductCacheListener)->onProductCreate(
(object) ['id' => 9, 'display_status' => ProductDisplayStatus::VISIBLE, 'updated_at' => now()]
);
$this->assertSame(1, SitemapUrl::where('resource_id', '9')->count());
(new SeoProductCacheListener)->onProductDelete((object) ['id' => 9]);
$this->assertSame(0, SitemapUrl::where('resource_type', 'product')->where('resource_id', '9')->count());
}
/**
* 활성 카테고리 변경 시 색인, 비활성 시 색인 제거.
*/
public function test_category_visibility_toggles_index(): void
{
$listener = new SeoCategoryCacheListener;
$listener->onCategoryChange((object) ['id' => 3, 'slug' => 'shoes', 'is_active' => true, 'updated_at' => now()]);
$row = SitemapUrl::where('resource_type', 'category')->where('resource_id', '3')->first();
$this->assertNotNull($row);
$this->assertStringContainsString('/category/shoes', $row->loc);
$listener->onCategoryChange((object) ['id' => 3, 'slug' => 'shoes', 'is_active' => false, 'updated_at' => now()]);
$this->assertSame(0, SitemapUrl::where('resource_type', 'category')->where('resource_id', '3')->count());
}
/**
* 카테고리 삭제(ID 전달) 시 색인이 제거된다.
*/
public function test_category_delete_removes_index(): void
{
$listener = new SeoCategoryCacheListener;
$listener->onCategoryChange((object) ['id' => 4, 'slug' => 'bags', 'is_active' => true, 'updated_at' => now()]);
$this->assertSame(1, SitemapUrl::where('resource_type', 'category')->where('resource_id', '4')->count());
// after_delete 훅은 모델이 아닌 ID(int)를 전달
$listener->onCategoryDelete(4);
$this->assertSame(0, SitemapUrl::where('resource_type', 'category')->where('resource_id', '4')->count());
}
/**
* 'SEO 제공 페이지(상품 상세)' 토글이 OFF 면 전시 상품이어도 색인이 제거된다.
*
* 리스너의 공개상태 판정은 전시 여부 AND 토글을 함께 본다(기여자 getUrlsLazy 와 일치).
*/
public function test_product_detail_toggle_off_removes_index_even_when_visible(): void
{
$listener = new SeoProductCacheListener;
// 토글 ON 상태로 먼저 색인
$listener->onProductCreate((object) ['id' => 50, 'display_status' => ProductDisplayStatus::VISIBLE, 'updated_at' => now()]);
$this->assertSame(1, SitemapUrl::where('resource_id', '50')->count());
// 토글 OFF → 전시 상태여도 색인 제거
Config::set('g7_settings.modules.sirsoft-ecommerce.seo.seo_product_detail', false);
$listener->onProductUpdate((object) ['id' => 50, 'display_status' => ProductDisplayStatus::VISIBLE, 'updated_at' => now()]);
$this->assertSame(0, SitemapUrl::where('resource_id', '50')->count(), 'seo_product_detail OFF 면 전시 상품도 색인에서 제거돼야 합니다.');
}
}