관리자 환경설정에서 미입금 자동취소 기한을 저장하면 HTML number 입력의 문자열이 그대로 영속되고, Carbon 3.13 의 strict 타입 경계에서 TypeError 가 발생해 무통장입금·가상계좌 주문이 전면 실패했다(공개 제보). 한 곳만 막으면 다른 저장 경로가 남으므로 저장·조회·소비·모델 네 계층에 각각 방어를 두고, 저장소 전역에서 같은 형태가 더 없음을 audit 룰로 확정했다. 같은 계획서 수행 중 드러난 인접 결함도 함께 해소했다. - 쿠폰 수정 경로로 (발급일 기준, 일수 NULL) 조합이 저장되어 발급 즉시 만료된 쿠폰이 조용히 나가던 문제 — 저장·발급 양쪽에서 차단 - 리뷰 작성 기한을 화면과 저장 판정이 서로 다른 기준으로 계산하던 문제 - 결제 플러그인이 PaymentMethodEnum 과 다른 어휘로 지원 결제수단을 선언해 가상계좌·계좌이체·휴대폰결제에 PG사를 지정할 수 없던 문제 엔진에서는 `_localInit` 적용 여부를 전역 해시로만 판정해 인스턴스별 저장소가 리셋되지 않던 결함(engine-v1.54.5)과, 제거 대상이 없어도 상태 갱신·캐시 무효화가 상시 발생하던 판정 결함(engine-v1.54.6)을 고쳤다. Playwright MCP 정밀 점검(T1~T11)에서 설정이 빈 문자열로 남은 사이트의 주문서 입금기한 안내가 미치환 플레이스홀더를 노출하는 것을 실측해, 화면 폴백을 서버와 같은 규칙으로 맞췄다.
221 lines
9.6 KiB
PHP
221 lines
9.6 KiB
PHP
<?php
|
|
|
|
namespace Plugins\Sirsoft\VerificationKginicis\Tests;
|
|
|
|
use App\Extension\HookListenerRegistrar;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Plugins\Sirsoft\VerificationKginicis\Listeners\AssertNoDuplicateInicisIdentity;
|
|
use Plugins\Sirsoft\VerificationKginicis\Listeners\CleanInicisRecordOnUserDelete;
|
|
use Plugins\Sirsoft\VerificationKginicis\Listeners\CleanInicisRecordOnUserWithdraw;
|
|
use Plugins\Sirsoft\VerificationKginicis\Listeners\CompleteInicisRecordAfterRegister;
|
|
use Plugins\Sirsoft\VerificationKginicis\Listeners\RegisterInicisProviderListener;
|
|
use Plugins\Sirsoft\VerificationKginicis\Listeners\ValidateInicisSettingsListener;
|
|
use Plugins\Sirsoft\VerificationKginicis\Providers\InicisVerificationServiceProvider;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* KG이니시스 본인인증 플러그인 테스트 베이스.
|
|
*
|
|
* RefreshDatabase 로 매 테스트마다 DB 초기화 + plugin migration 포함.
|
|
* ServiceProvider + plugin route 도 setUp 시점에 등록 — 코어 plugin 시스템이
|
|
* 테스트 환경에서는 자동 부팅되지 않으므로 수동 등록 필수.
|
|
*/
|
|
abstract class PluginTestCase extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/** 테스트 격리용 임시 plugins 스토리지 루트 (setUp 에서 생성, tearDown 에서 제거). */
|
|
private ?string $isolatedPluginsRoot = null;
|
|
|
|
protected function migrateFreshUsing(): array
|
|
{
|
|
// 모든 번들 확장 migrations 포함 — 여러 확장 스위트를 한 프로세스에서 함께 돌릴 때
|
|
// 가장 먼저 실행된 TestCase 가 스키마를 확정하므로, 자기 확장만 넘기면 뒤따르는
|
|
// 확장의 테이블이 생성되지 않는다 (troubleshooting-backend.md 사례 21).
|
|
$paths = ['database/migrations'];
|
|
foreach (glob(base_path('modules/_bundled/*/database/migrations'), GLOB_ONLYDIR) as $p) {
|
|
$paths[] = str_replace(base_path().DIRECTORY_SEPARATOR, '', $p);
|
|
}
|
|
foreach (glob(base_path('plugins/_bundled/*/database/migrations'), GLOB_ONLYDIR) as $p) {
|
|
$paths[] = str_replace(base_path().DIRECTORY_SEPARATOR, '', $p);
|
|
}
|
|
|
|
return [
|
|
'--drop-views' => $this->shouldDropViews(),
|
|
'--drop-types' => $this->shouldDropTypes(),
|
|
'--seed' => false,
|
|
'--path' => $paths,
|
|
];
|
|
}
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->isolatePluginStorage();
|
|
|
|
$this->registerPluginAutoload();
|
|
|
|
// 플러그인 lang 네임스페이스 등록 — 실 환경에서는 코어 TranslationServiceProvider::boot()
|
|
// 의 loadPluginTranslations() 가 수행하나 테스트 환경에서는 미등록이므로 코어와 동일한
|
|
// 경로로 register namespace 한다 (translator->getLoader()->addNamespace 직접 호출).
|
|
// ServiceProvider boot(__()) 와 검증 attribute 해석이 가능하도록 register 보다 먼저 등록.
|
|
$translator = $this->app['translator'];
|
|
$translator->getLoader()->addNamespace(
|
|
'sirsoft-verification_kginicis',
|
|
base_path('plugins/sirsoft-verification_kginicis/lang'),
|
|
);
|
|
|
|
// 부팅 과정에서 plugin 네임스페이스 + 코어 validation 그룹이 namespace 등록/파일 준비 전에
|
|
// 빈 값으로 캐시(loaded)되어 원본 키가 노출되므로 무효화한다. 무효화로 실제 파일을 다시
|
|
// 읽도록 한 뒤 ServiceProvider 를 register 하여 boot 의 Lang::addLines(validation.attributes)
|
|
// 가 채워진 validation 그룹 위에 얹히도록 순서를 보장한다.
|
|
$loadedProp = new \ReflectionProperty($translator, 'loaded');
|
|
$loadedProp->setAccessible(true);
|
|
$loaded = $loadedProp->getValue($translator);
|
|
unset($loaded['sirsoft-verification_kginicis']);
|
|
unset($loaded['*']['validation']);
|
|
$loadedProp->setValue($translator, $loaded);
|
|
|
|
// validation 그룹을 실제 파일에서 먼저 로드시켜 캐시를 채운다.
|
|
$translator->get('validation.required');
|
|
|
|
$this->app->register(InicisVerificationServiceProvider::class);
|
|
|
|
// ServiceProvider 의 validation.attributes 등록은 booted 콜백에서 수행되는데, 테스트
|
|
// 환경의 캐시 무효화/등록 순서와 어긋날 수 있으므로 무효화 이후 시점에 동일 라벨을
|
|
// 재적용한다. (실 환경에서는 booted 콜백이 plugin lang 준비 후 유효하므로 본 보정은 테스트 전용)
|
|
foreach (['ko', 'en'] as $locale) {
|
|
$translator->addLines([
|
|
'validation.attributes.live_mid' => __('sirsoft-verification_kginicis::messages.settings.live_mid_attribute', [], $locale),
|
|
'validation.attributes.live_api_key' => __('sirsoft-verification_kginicis::messages.settings.live_api_key_attribute', [], $locale),
|
|
], $locale);
|
|
}
|
|
|
|
$this->registerPluginRoutes();
|
|
$this->registerPluginHookListeners();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$this->restorePluginStorage();
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* 플러그인 스토리지('plugins' 디스크)를 테스트 전용 임시 디렉토리로 격리한다.
|
|
*
|
|
* 설정 저장 테스트(PluginSettingsService::save)는 코어 'plugins' 디스크
|
|
* (root = storage_path('app/plugins'))에 setting.json 을 쓴다. 격리하지 않으면
|
|
* 테스트가 실제 로컬 런타임 설정 파일을 덮어써 라이브 모드/자격증명이 오염된다
|
|
* (RefreshDatabase 는 DB 만 롤백하고 파일시스템은 되돌리지 않음). 디스크 root 를
|
|
* 임시 경로로 바꾸고 resolved 인스턴스를 purge 하여 실제 파일을 원천적으로 못
|
|
* 건드리게 한다.
|
|
*/
|
|
private function isolatePluginStorage(): void
|
|
{
|
|
// Laravel 이 테스트용 쓰기 공간으로 보장하는 storage/framework/testing 하위를 사용한다.
|
|
// sys_get_temp_dir() 은 CI/컨테이너/open_basedir 제약 환경에서 위치가 다르거나
|
|
// 쓰기 불가일 수 있어 프로젝트 내부의 격리 경로를 쓴다 (Laravel 규약 준수).
|
|
$this->isolatedPluginsRoot = storage_path(
|
|
'framework/testing/plugin-storage-'.uniqid('', true)
|
|
);
|
|
|
|
File::ensureDirectoryExists($this->isolatedPluginsRoot);
|
|
|
|
config(['filesystems.disks.plugins.root' => $this->isolatedPluginsRoot]);
|
|
|
|
// 이미 resolve 된 'plugins' 디스크 인스턴스를 폐기해 새 root 로 재생성되게 한다.
|
|
Storage::forgetDisk('plugins');
|
|
}
|
|
|
|
/**
|
|
* 격리 임시 디렉토리를 제거한다 (테스트 간 잔여 파일 격리).
|
|
*/
|
|
private function restorePluginStorage(): void
|
|
{
|
|
if ($this->isolatedPluginsRoot !== null && is_dir($this->isolatedPluginsRoot)) {
|
|
File::deleteDirectory($this->isolatedPluginsRoot);
|
|
}
|
|
|
|
$this->isolatedPluginsRoot = null;
|
|
|
|
Storage::forgetDisk('plugins');
|
|
}
|
|
|
|
/**
|
|
* Plugin 의 hook listener 들을 코어 HookManager 에 등록.
|
|
*
|
|
* 실 환경: 코어 PluginManager 가 부팅 시 HookListenerRegistrar 를 호출.
|
|
* 테스트 환경: plugin 이 미설치 상태이므로 수동 등록 필수.
|
|
*/
|
|
protected function registerPluginHookListeners(): void
|
|
{
|
|
HookListenerRegistrar::register(
|
|
RegisterInicisProviderListener::class,
|
|
'plugin:sirsoft-verification_kginicis',
|
|
);
|
|
HookListenerRegistrar::register(
|
|
CompleteInicisRecordAfterRegister::class,
|
|
'plugin:sirsoft-verification_kginicis',
|
|
);
|
|
HookListenerRegistrar::register(
|
|
CleanInicisRecordOnUserWithdraw::class,
|
|
'plugin:sirsoft-verification_kginicis',
|
|
);
|
|
HookListenerRegistrar::register(
|
|
CleanInicisRecordOnUserDelete::class,
|
|
'plugin:sirsoft-verification_kginicis',
|
|
);
|
|
HookListenerRegistrar::register(
|
|
AssertNoDuplicateInicisIdentity::class,
|
|
'plugin:sirsoft-verification_kginicis',
|
|
);
|
|
HookListenerRegistrar::register(
|
|
ValidateInicisSettingsListener::class,
|
|
'plugin:sirsoft-verification_kginicis',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 활성 디렉토리(plugins/sirsoft-verification_kginicis/src/) PSR-4 자동 로드.
|
|
*/
|
|
protected function registerPluginAutoload(): void
|
|
{
|
|
$base = base_path('plugins/sirsoft-verification_kginicis/src/');
|
|
|
|
spl_autoload_register(function (string $class) use ($base): void {
|
|
$prefix = 'Plugins\\Sirsoft\\VerificationKginicis\\';
|
|
$len = strlen($prefix);
|
|
if (strncmp($prefix, $class, $len) !== 0) {
|
|
return;
|
|
}
|
|
$file = $base.str_replace('\\', '/', substr($class, $len)).'.php';
|
|
if (file_exists($file)) {
|
|
require $file;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Plugin 라우트 등록 — 코어 PluginManager 의 자동 prefix 흉내.
|
|
*
|
|
* 실 환경: `/plugins/sirsoft-verification_kginicis/{path}` + 이름 prefix `web.plugins.sirsoft-verification_kginicis.`
|
|
*/
|
|
protected function registerPluginRoutes(): void
|
|
{
|
|
$webRoutesFile = base_path('plugins/sirsoft-verification_kginicis/src/routes/web.php');
|
|
|
|
if (file_exists($webRoutesFile)) {
|
|
Route::prefix('plugins/sirsoft-verification_kginicis')
|
|
->name('web.plugins.sirsoft-verification_kginicis.')
|
|
->middleware('web')
|
|
->group($webRoutesFile);
|
|
}
|
|
}
|
|
}
|