Files
2026-05-12 17:58:34 +09:00

212 lines
9.2 KiB
PHP

<?php
namespace Modules\Sirsoft\Ecommerce\Tests\Feature\LanguagePack;
use App\Enums\LanguagePackScope;
use App\Enums\LanguagePackSourceType;
use App\Enums\LanguagePackStatus;
use App\Extension\HookManager;
use App\Models\LanguagePack;
use App\Services\LanguagePack\LanguagePackRegistry;
use Illuminate\Support\Facades\DB;
use Modules\Sirsoft\Ecommerce\Database\Seeders\ClaimReasonSeeder;
use Modules\Sirsoft\Ecommerce\Database\Seeders\ShippingCarrierSeeder;
use Modules\Sirsoft\Ecommerce\Database\Seeders\ShippingTypeSeeder;
use Modules\Sirsoft\Ecommerce\Tests\ModuleTestCase;
use Symfony\Component\Console\Output\BufferedOutput;
/**
* 이커머스 모듈 — 언어팩 활성/비활성 시 entity 시더 자동 재실행 회귀 테스트.
*
* 검증 시나리오:
* 1. ja 모듈 언어팩 활성 → shipping_types.name 에 ja 키 자동 주입 (ShippingTypeSeeder)
* 2. ja 활성 → shipping_carriers.name 에 ja 자동 주입 (ShippingCarrierSeeder)
* 3. ja 활성 → claim_reasons.name 에 ja 자동 주입 (ClaimReasonSeeder)
* 4. user_overrides=['name.ko'] 인 row 는 ko 보존, ja 자동 추가
*
* 실제 훅 체인 (HookManager::doAction → RunSeedersOnLanguagePackLifecycle 리스너 → Artisan::call)
* 으로 검증하며 mock 미사용.
*/
class EcommerceLanguagePackSeederTriggerTest extends ModuleTestCase
{
protected function setUp(): void
{
parent::setUp();
// 본 테스트는 활성 hook 체인이 module:seed 를 호출하는 경로를 검증한다.
// SeedModuleCommand 는 base_path('modules/{id}/database/seeders') 활성 디렉토리를
// is_dir() 로 확인하므로, 활성 디렉토리가 부재한 환경에서는 시더가 silent skip 된다.
// 활성 디렉토리가 없으면 본 테스트의 production-like 시나리오를 재현할 수 없으므로 skip.
$activeModuleSeederDir = base_path('modules/sirsoft-ecommerce/database/seeders');
if (! is_dir($activeModuleSeederDir)) {
$this->markTestSkipped(
'sirsoft-ecommerce 모듈이 활성 디렉토리에 설치되지 않은 환경 — '
.'본 테스트는 module:seed → entity 시더 → ja 자동 동기 production 경로를 검증하므로 '
.'php artisan module:install sirsoft-ecommerce 후 재실행 (또는 CI 환경에서 자동 통과)'
);
}
// module:seed 가 outer transaction 외부에서 commit 한 데이터 정리
DB::table('language_packs')
->where('target_identifier', 'sirsoft-ecommerce')
->where('locale', 'ja')
->delete();
DB::table('ecommerce_shipping_types')->where('id', '>', 0)->delete();
DB::table('ecommerce_shipping_carriers')->where('id', '>', 0)->delete();
DB::table('ecommerce_claim_reasons')->where('id', '>', 0)->delete();
// 본 테스트는 lang pack 활성 hook 이 LanguagePack::resolveDirectory() →
// base_path('lang-packs/{identifier}/seed/*.json') 을 읽어 ja 키를 주입하는 경로를 검증한다.
// 활성 디렉토리가 부재한 dev 환경(사용자가 language-pack:install 미실행) 에서도 테스트가 동작하도록,
// _bundled 원본을 활성 경로에 복사한다 (RefreshDatabase 가 파일시스템에 영향을 주지 않으므로 영구).
$bundledDir = base_path('lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja');
$activeDir = base_path('lang-packs/g7-module-sirsoft-ecommerce-ja');
if (\Illuminate\Support\Facades\File::isDirectory($bundledDir)
&& ! \Illuminate\Support\Facades\File::isDirectory($activeDir)) {
\Illuminate\Support\Facades\File::copyDirectory($bundledDir, $activeDir);
}
app()->forgetInstance(LanguagePackRegistry::class);
}
public function test_module_lang_pack_activation_triggers_shipping_types_seeder(): void
{
$this->ensureSeeded();
$pack = $this->createEcommerceJaLanguagePack(LanguagePackStatus::Inactive);
$pack->update([
'status' => LanguagePackStatus::Active->value,
'activated_at' => now(),
]);
$this->refreshLanguagePackRegistry();
HookManager::doAction('core.language_packs.activated', $pack->fresh());
$this->assertJaKeyPresentInTable('ecommerce_shipping_types');
}
public function test_module_lang_pack_activation_triggers_shipping_carriers_seeder(): void
{
$this->ensureSeeded();
$pack = $this->createEcommerceJaLanguagePack(LanguagePackStatus::Inactive);
$pack->update([
'status' => LanguagePackStatus::Active->value,
'activated_at' => now(),
]);
$this->refreshLanguagePackRegistry();
HookManager::doAction('core.language_packs.activated', $pack->fresh());
$this->assertJaKeyPresentInTable('ecommerce_shipping_carriers');
}
public function test_module_lang_pack_activation_triggers_claim_reasons_seeder(): void
{
$this->ensureSeeded();
$pack = $this->createEcommerceJaLanguagePack(LanguagePackStatus::Inactive);
$pack->update([
'status' => LanguagePackStatus::Active->value,
'activated_at' => now(),
]);
$this->refreshLanguagePackRegistry();
HookManager::doAction('core.language_packs.activated', $pack->fresh());
$this->assertJaKeyPresentInTable('ecommerce_claim_reasons');
}
public function test_user_overridden_ko_is_preserved_while_ja_auto_synced(): void
{
$this->ensureSeeded();
DB::table('ecommerce_shipping_types')
->where('code', 'parcel')
->update([
'name' => json_encode(['ko' => '사용자 수정 ko', 'en' => 'Parcel']),
'user_overrides' => json_encode(['name.ko']),
]);
$pack = $this->createEcommerceJaLanguagePack(LanguagePackStatus::Inactive);
$pack->update([
'status' => LanguagePackStatus::Active->value,
'activated_at' => now(),
]);
$this->refreshLanguagePackRegistry();
HookManager::doAction('core.language_packs.activated', $pack->fresh());
$row = DB::table('ecommerce_shipping_types')->where('code', 'parcel')->first(['name']);
$name = json_decode($row->name, true);
$this->assertEquals('사용자 수정 ko', $name['ko'] ?? null, 'ko 사용자 수정 보존');
$this->assertNotNull($name['ja'] ?? null, 'ja 자동 동기화');
}
private function ensureSeeded(): void
{
// RefreshDatabase 환경 — 시더 클래스 직접 호출 (outer transaction 안에서)
$this->runSeeder(ShippingTypeSeeder::class);
$this->runSeeder(ShippingCarrierSeeder::class);
$this->runSeeder(ClaimReasonSeeder::class);
}
private function runSeeder(string $seederClass): void
{
$seeder = app($seederClass);
// Seeder->run() 안에서 $this->command->info() 호출 가능하도록 dummy command 주입
$command = new \Illuminate\Console\Command();
$command->setOutput(new \Illuminate\Console\OutputStyle(
new \Symfony\Component\Console\Input\ArrayInput([]),
new BufferedOutput(),
));
$seeder->setCommand($command);
$seeder->run();
}
private function assertJaKeyPresentInTable(string $table): void
{
$rows = DB::table($table)->get(['id', 'name']);
$this->assertNotEmpty($rows, "{$table} 시더가 row 를 생성해야 함");
foreach ($rows as $row) {
$name = json_decode($row->name, true) ?: [];
$this->assertArrayHasKey('ja', $name, "{$table} row#{$row->id} 의 name 에 ja 키 누락");
}
}
private function createEcommerceJaLanguagePack(LanguagePackStatus $status): LanguagePack
{
return LanguagePack::create([
'identifier' => 'g7-module-sirsoft-ecommerce-ja',
'vendor' => 'g7',
'name' => 'Ecommerce JA Language Pack',
'version' => '1.0.0-beta.1',
'scope' => LanguagePackScope::Module->value,
'target_identifier' => 'sirsoft-ecommerce',
'locale' => 'ja',
'locale_name' => 'Japanese',
'locale_native_name' => '日本語',
'text_direction' => 'ltr',
'is_protected' => false,
'manifest' => [
'identifier' => 'g7-module-sirsoft-ecommerce-ja',
'scope' => 'module',
'target_identifier' => 'sirsoft-ecommerce',
'locale' => 'ja',
],
'status' => $status->value,
'source_type' => LanguagePackSourceType::Bundled->value,
'installed_at' => now(),
'activated_at' => $status === LanguagePackStatus::Active ? now() : null,
]);
}
private function refreshLanguagePackRegistry(): void
{
app()->forgetInstance(LanguagePackRegistry::class);
$registry = app(LanguagePackRegistry::class);
config([
'app.supported_locales' => $registry->getActiveCoreLocales(),
'app.locale_names' => $registry->getLocaleNames(),
'app.translatable_locales' => $registry->getActiveCoreLocales(),
]);
}
}