Files
Gnuboard7/modules/_bundled/sirsoft-ecommerce/tests/Unit/Providers/GuestOrderTokenMiddlewareRegistrationTest.php
HeuJung e21bff678f fix(core,ecommerce,board,basic,pay_kginicis,pay_nhnkcp,pay_nicepayments): 7.0.6 회귀 3건·잠복 결함 3건 해소 + 통화 하드코딩 전수 제거
7.0.6 사이클에서 유입된 회귀 3건을 출시 전에 닫는다. 추가옵션 저장소의
반환 타입 선언이 실제 반환(중첩 맵)과 어긋나 추가옵션 있는 상품의 담기·
바로구매가 죽었고, 검색 엔진이 결과 소비 시점에 SELECT 를 재작성해 정렬이
참조하던 스코어 별칭을 지웠으며, 미들웨어 targets 가 늘어난 라우트를
따라가지 못해 비회원 현금영수증이 전면 불능이었다. 셋 다 정적 검사의
사각지대였으므로 룰 2건과 계약 테스트를 함께 세워 재발을 막는다.

같은 유형의 잠복 결함 3건(빈 반환의 타입 불일치 2건, 없는 메서드 호출 1건)도
호출 경로가 생기는 순간 fatal 이므로 함께 닫았다.

브라우저 검수 중 드러난 선행 결함 5건을 이어서 수정한다. 엔진이 event 키로
선언한 액션을 React prop 으로 연결하지 않아 오류 없이 조용히 무반응이었고,
연결한 뒤에는 커스텀 컴포넌트의 이벤트 payload 가 유실됐다. 비회원 주문상세
응답에 현금영수증 키가 없어 발급 결과에 도달할 수 없었으며, 입금확인과
현금영수증 발급이 base 통화 금액을 써서 결제 통화가 다른 주문에서 각각
저장 실패와 잘못된 증빙을 냈다.

마지막으로 통화 단위를 코드·문구에서 걷어내 설정으로 이관한다. 다국어
문구의 원/円, 프론트의 KRW 고정표와 리터럴 폴백 173건, 서버 포맷의 통화
코드 리터럴이 대상이며, 기본 통화가 원이 아닌 상점에서 값은 맞고 단위만
틀린 금액이 예외 없이 나가던 문제였다. 통화 판정을 설정 단일 출처로 모으고
규칙 3건을 정적 검사로 고정했다.
2026-08-10 09:07:46 +09:00

173 lines
6.9 KiB
PHP

<?php
declare(strict_types=1);
namespace Modules\Sirsoft\Ecommerce\Tests\Unit\Providers;
use App\Contracts\Extension\ExtensionMiddlewareRegistryInterface;
use App\Extension\ExtensionMiddlewareRegistry;
use Illuminate\Support\Facades\Route;
use Modules\Sirsoft\Ecommerce\Http\Middleware\VerifyGuestOrderToken;
use Modules\Sirsoft\Ecommerce\Module;
use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase;
/**
* VerifyGuestOrderToken 이 비회원 주문 액션 라우트 전체에 부착되는지 검증.
*
* 회귀 배경: targets 에 라우트명을 개별 나열하고 있었는데 현금영수증 라우트 2건
* (`guest.orders.cash-receipt.show` / `.issue`) 이 추가될 때 targets 가 갱신되지 않아
* 미들웨어가 부착되지 않았다. 그 결과 `$request->attributes->set('guest_order', ...)` 가
* 실행되지 않아 FormRequest 가 404 를 던지고 비회원 현금영수증 기능이 전면 불능이었다.
*
* 이 테스트의 핵심은 `test_all_guest_order_action_routes_are_token_protected` 다 —
* 라우트 테이블에서 대상을 파생시키므로, 앞으로 `guest/orders/` 밑에 라우트를 추가하고
* targets 를 갱신하지 않으면 손대지 않아도 자동으로 red 가 된다. 개별 케이스 나열은
* 이번 결함을 재현하지 못한다(나열한 것만 검사하므로 누락된 라우트를 영원히 못 본다).
*/
class GuestOrderTokenMiddlewareRegistrationTest extends ModuleTestCase
{
/**
* 비회원 주문 라우트명 공통 접두사 (모듈 로더가 붙이는 `api.modules.{id}.` 포함).
*/
private const GUEST_ORDER_PREFIX = 'api.modules.sirsoft-ecommerce.guest.orders.';
/**
* 토큰 미들웨어에서 의도적으로 제외되는 라우트.
*
* `guest.orders.verify` 는 조회 토큰을 **발급**하는 엔드포인트다. 즉 호출 시점에
* 아직 토큰이 없으므로 토큰 검증 미들웨어를 붙이면 최초 인증 요청이 404 로 막혀
* 비회원 주문 조회가 통째로 죽는다. 요청 빈도 제한은 `throttle:20,1` 과
* `GuestOrderAuthService` 의 실패 잠금이 담당한다.
*
* @var list<string>
*/
private const EXEMPT = [
self::GUEST_ORDER_PREFIX.'verify',
];
protected function setUp(): void
{
parent::setUp();
// 인덱스가 태그 캐시라 케이스 간 오염을 막으려면 앞뒤로 비운다.
ExtensionMiddlewareRegistry::flush();
}
protected function tearDown(): void
{
ExtensionMiddlewareRegistry::flush();
parent::tearDown();
}
/**
* 현금영수증 조회·발급 라우트에 토큰 미들웨어가 부착되는지 (직접 회귀 테스트).
*/
public function test_gate_resolves_token_middleware_for_cash_receipt_routes(): void
{
$registry = $this->app->make(ExtensionMiddlewareRegistryInterface::class);
foreach (['cash-receipt.show', 'cash-receipt.issue'] as $suffix) {
$routeName = self::GUEST_ORDER_PREFIX.$suffix;
$matched = $registry->resolveForRoute(
$routeName,
'api/modules/sirsoft-ecommerce/guest/orders/{orderNumber}/cash-receipt',
'api',
'after_core',
);
$this->assertContains(
VerifyGuestOrderToken::class,
$matched,
"{$routeName} 에 VerifyGuestOrderToken 이 부착되어야 합니다. 미부착이면 guest_order 속성이 "
.'설정되지 않아 FormRequest 가 404 를 던지고 현금영수증 기능이 전면 불능이 됩니다.',
);
}
}
/**
* 비회원 주문 라우트 전수가 `보호 ∪ 명시적 면제` 로 완전 분류되는지.
*
* 라우트 테이블에서 모집단을 파생시키므로 새 라우트가 늘면 자동으로 검사 대상이 된다.
*/
public function test_all_guest_order_action_routes_are_token_protected(): void
{
$registry = $this->app->make(ExtensionMiddlewareRegistryInterface::class);
$guestRoutes = [];
foreach (Route::getRoutes() as $route) {
$name = $route->getName();
if (is_string($name) && str_starts_with($name, self::GUEST_ORDER_PREFIX)) {
$guestRoutes[$name] = $route->uri();
}
}
$this->assertNotEmpty(
$guestRoutes,
'비회원 주문 라우트가 하나도 수집되지 않았습니다. 접두사('.self::GUEST_ORDER_PREFIX
.')가 바뀌었다면 이 테스트는 아무것도 검사하지 못하는 상태이므로 접두사를 함께 고쳐야 합니다.',
);
$unprotected = [];
foreach ($guestRoutes as $name => $uri) {
if (in_array($name, self::EXEMPT, true)) {
continue;
}
$matched = $registry->resolveForRoute($name, $uri, 'api', 'after_core');
if (! in_array(VerifyGuestOrderToken::class, $matched, true)) {
$unprotected[] = $name;
}
}
$this->assertSame(
[],
$unprotected,
'토큰 미검증 상태로 열려 있는 비회원 주문 라우트가 있습니다: '.implode(', ', $unprotected)
.'. Module::getMiddleware() 의 VerifyGuestOrderToken targets 를 갱신하거나, '
.'보호하면 안 되는 라우트라면 이 테스트의 EXEMPT 에 사유와 함께 등재하십시오.',
);
}
/**
* 토큰 발급 라우트(`verify`)는 미부착이어야 한다.
*
* targets 를 `guest.orders.*` 로 뭉뚱그리면 이 라우트까지 삼켜 비회원 조회가 통째로 막힌다.
*/
public function test_verify_route_is_not_token_protected(): void
{
$registry = $this->app->make(ExtensionMiddlewareRegistryInterface::class);
$matched = $registry->resolveForRoute(
self::GUEST_ORDER_PREFIX.'verify',
'api/modules/sirsoft-ecommerce/guest/orders/verify',
'api',
'after_core',
);
$this->assertNotContains(
VerifyGuestOrderToken::class,
$matched,
'guest.orders.verify 는 토큰을 발급하는 엔드포인트라 토큰 검증 미들웨어가 붙으면 '
.'최초 인증 요청이 404 로 막힙니다. targets 를 guest.orders.* 로 넓히지 마십시오.',
);
}
/**
* 선언 계약: api 그룹 전용.
*/
public function test_module_declares_token_middleware_for_api_group_only(): void
{
$declaration = collect((new Module)->getMiddleware())
->firstWhere('class', VerifyGuestOrderToken::class);
$this->assertNotNull(
$declaration,
'Module::getMiddleware() 가 VerifyGuestOrderToken 을 선언해야 합니다.',
);
$this->assertSame(['api'], $declaration['groups']);
}
}