Files
HeuJung 6bde24bc75 fix(ecommerce): 숫자 설정 문자열 영속으로 인한 주문 생성 실패 해소
관리자 환경설정에서 미입금 자동취소 기한을 저장하면 HTML number 입력의
문자열이 그대로 영속되고, Carbon 3.13 의 strict 타입 경계에서 TypeError 가
발생해 무통장입금·가상계좌 주문이 전면 실패했다(공개 제보). 한 곳만 막으면
다른 저장 경로가 남으므로 저장·조회·소비·모델 네 계층에 각각 방어를 두고,
저장소 전역에서 같은 형태가 더 없음을 audit 룰로 확정했다.

같은 계획서 수행 중 드러난 인접 결함도 함께 해소했다.
- 쿠폰 수정 경로로 (발급일 기준, 일수 NULL) 조합이 저장되어 발급 즉시
 만료된 쿠폰이 조용히 나가던 문제 — 저장·발급 양쪽에서 차단
- 리뷰 작성 기한을 화면과 저장 판정이 서로 다른 기준으로 계산하던 문제
- 결제 플러그인이 PaymentMethodEnum 과 다른 어휘로 지원 결제수단을 선언해
 가상계좌·계좌이체·휴대폰결제에 PG사를 지정할 수 없던 문제

엔진에서는 `_localInit` 적용 여부를 전역 해시로만 판정해 인스턴스별 저장소가
리셋되지 않던 결함(engine-v1.54.5)과, 제거 대상이 없어도 상태 갱신·캐시
무효화가 상시 발생하던 판정 결함(engine-v1.54.6)을 고쳤다.

Playwright MCP 정밀 점검(T1~T11)에서 설정이 빈 문자열로 남은 사이트의
주문서 입금기한 안내가 미치환 플레이스홀더를 노출하는 것을 실측해, 화면
폴백을 서버와 같은 규칙으로 맞췄다.
2026-08-02 18:35:28 +09:00

78 lines
2.4 KiB
PHP

<?php
namespace Modules\Sirsoft\Ecommerce\Tests\Unit\Support;
use Modules\Sirsoft\Ecommerce\Support\ReviewWritePolicy;
use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase;
/**
* 리뷰 작성 기한 정책 SSoT 검증
*
* 같은 규칙이 서비스(저장 가능 여부)와 리소스(화면 표시)에 각각 구현돼 있어
* 조회 방식과 기본값 출처가 서로 달랐다. 특히 서비스 쪽 config 폴백은 모듈 config
* 네임스페이스를 잘못 참조해(`ecommerce.*`) 항상 null 로 해석되고 있었다.
*
* @effects review_deadline_uses_single_policy_source
*/
class ReviewWritePolicyTest extends ModuleTestCase
{
/**
* 모듈 config 네임스페이스가 실제로 해석된다. (기존 폴백은 null 이었다)
*/
public function test_module_config_namespace_resolves(): void
{
$this->assertNull(config('ecommerce.review.write_deadline_days'));
$this->assertNotNull(config('sirsoft-ecommerce.review.write_deadline_days'));
}
/**
* 설정 미지정 시 config 기본값을 사용한다.
*/
public function test_deadline_days_falls_back_to_module_config(): void
{
$this->assertSame(
(int) config('sirsoft-ecommerce.review.write_deadline_days'),
ReviewWritePolicy::deadlineDays()
);
}
/**
* 기한 내 구매확정은 통과한다.
*/
public function test_deadline_not_passed_within_period(): void
{
$this->assertFalse(ReviewWritePolicy::isDeadlinePassed(now()->subDay()));
}
/**
* 기한을 넘긴 구매확정은 차단된다.
*/
public function test_deadline_passed_after_period(): void
{
$days = ReviewWritePolicy::deadlineDays();
$this->assertTrue(ReviewWritePolicy::isDeadlinePassed(now()->subDays($days + 1)));
}
/**
* 미확정(null)은 기한 판정 대상이 아니다.
*/
public function test_null_confirmed_at_is_not_deadline_passed(): void
{
$this->assertFalse(ReviewWritePolicy::isDeadlinePassed(null));
}
/**
* 판정이 인자를 변형하지 않는다. (copy 누락 시 호출자의 시각이 밀린다)
*/
public function test_confirmed_at_is_not_mutated(): void
{
$confirmedAt = now()->subDay();
$snapshot = $confirmedAt->copy();
ReviewWritePolicy::isDeadlinePassed($confirmedAt);
$this->assertTrue($snapshot->equalTo($confirmedAt));
}
}