Files
Gnuboard7/tests/Unit/Services/LanguagePack/LanguagePackServiceParityTest.php
T
2026-05-11 11:29:41 +09:00

241 lines
8.0 KiB
PHP

<?php
namespace Tests\Unit\Services\LanguagePack;
use App\Enums\LanguagePackScope;
use App\Enums\LanguagePackStatus;
use App\Extension\HookManager;
use App\Models\LanguagePack;
use App\Services\LanguagePack\LanguagePackRegistry;
use App\Services\LanguagePackService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\File;
use RuntimeException;
use Tests\TestCase;
/**
* 언어팩 운영 풀 패리티 (확장 관리 동등 수준) 회귀 테스트.
*
* 검증 범위:
* - 동시성 가드: status=Updating 인 팩에 대해 activate/deactivate/uninstall/performUpdate 차단
* - 라이프사이클 훅: installed / updated / uninstalled / activated / deactivated
* - 번들 소스 설치: lang-packs/_bundled/{identifier} 디렉토리에서 (재)설치
*
* 모듈/플러그인/템플릿 관리 플로우와 동일한 안전 장치/확장성을 보장합니다.
*/
class LanguagePackServiceParityTest extends TestCase
{
use RefreshDatabase;
private LanguagePackService $service;
private LanguagePackRegistry $registry;
/**
* 테스트 픽스처 초기화.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
$this->service = $this->app->make(LanguagePackService::class);
$this->registry = $this->app->make(LanguagePackRegistry::class);
}
/**
* status 가 Updating 인 팩의 activate 진입을 차단합니다.
*
* @return void
*/
public function test_activate_blocks_when_status_is_updating(): void
{
$pack = $this->makePack('sirsoft', 'ja', LanguagePackStatus::Updating->value);
$this->expectException(RuntimeException::class);
$this->service->activate($pack);
}
/**
* status 가 Updating 인 팩의 deactivate 진입을 차단합니다.
*
* @return void
*/
public function test_deactivate_blocks_when_status_is_updating(): void
{
// deactivate 는 isActive() 검사가 먼저이므로 Active 로 만든 후 Updating 으로 직접 변경
$pack = $this->makePack('sirsoft', 'ja', LanguagePackStatus::Active->value);
$pack->status = LanguagePackStatus::Updating->value;
$pack->save();
$this->expectException(RuntimeException::class);
$this->service->deactivate($pack);
}
/**
* status 가 Updating 인 팩의 uninstall 진입을 차단합니다.
*
* @return void
*/
public function test_uninstall_blocks_when_status_is_updating(): void
{
$pack = $this->makePack('sirsoft', 'ja', LanguagePackStatus::Updating->value);
$this->expectException(RuntimeException::class);
$this->service->uninstall($pack);
}
/**
* activate 시 `core.language_packs.activated` 훅을 발행합니다.
*
* @return void
*/
public function test_activate_dispatches_activated_hook(): void
{
$captured = [];
HookManager::addAction('core.language_packs.activated', function ($pack) use (&$captured) {
$captured[] = $pack->identifier;
});
$pack = $this->makePack('sirsoft', 'ko', LanguagePackStatus::Inactive->value);
$this->service->activate($pack);
$this->assertContains($pack->identifier, $captured);
}
/**
* deactivate 시 `core.language_packs.deactivated` 훅을 발행합니다.
*
* @return void
*/
public function test_deactivate_dispatches_deactivated_hook(): void
{
$captured = [];
HookManager::addAction('core.language_packs.deactivated', function ($pack) use (&$captured) {
$captured[] = $pack->identifier;
});
$pack = $this->makePack('sirsoft', 'ko', LanguagePackStatus::Active->value);
$this->service->deactivate($pack);
$this->assertContains($pack->identifier, $captured);
}
/**
* uninstall 시 `core.language_packs.uninstalled` 훅을 발행합니다.
*
* @return void
*/
public function test_uninstall_dispatches_uninstalled_hook(): void
{
$captured = [];
HookManager::addAction('core.language_packs.uninstalled', function ($pack) use (&$captured) {
$captured[] = $pack->identifier;
});
$pack = $this->makePack('sirsoft', 'ko', LanguagePackStatus::Inactive->value);
$this->service->uninstall($pack);
$this->assertContains($pack->identifier, $captured);
}
/**
* `lang-packs/_bundled/{identifier}` 가 없으면 RuntimeException 으로 차단합니다.
*
* @return void
*/
public function test_install_from_bundled_throws_when_directory_missing(): void
{
$missing = 'nonexistent-bundle-'.uniqid();
$this->expectException(RuntimeException::class);
$this->service->installFromBundled($missing);
}
/**
* `lang-packs/_bundled/{identifier}` 디렉토리에서 설치하면 활성 디렉토리로 복사 + DB 등록.
*
* @return void
*/
public function test_install_from_bundled_copies_to_active_and_registers(): void
{
// identifier 네이밍: {vendor}-{scope}-{locale} for core (LanguagePackManifestValidator 규칙)
$vendor = 'parity'.substr((string) hexdec(uniqid()), -4);
$identifier = $vendor.'-core-ja';
$bundlePath = base_path('lang-packs/_bundled/'.$identifier);
File::ensureDirectoryExists($bundlePath);
File::put($bundlePath.'/language-pack.json', json_encode([
'identifier' => $identifier,
'namespace' => $vendor,
'vendor' => $vendor,
'name' => ['ko' => 'Parity Bundled Pack', 'en' => 'Parity Bundled Pack'],
'scope' => 'core',
'locale' => 'ja',
'locale_name' => 'Japanese',
'locale_native_name' => '日本語',
'version' => '1.0.0',
]));
File::ensureDirectoryExists($bundlePath.'/lang/ja');
File::put($bundlePath.'/lang/ja/messages.json', json_encode(['hello' => 'こんにちは']));
try {
$pack = $this->service->installFromBundled($identifier, false);
$this->assertSame($identifier, $pack->identifier);
$this->assertSame('bundled', $pack->source_type);
$this->assertTrue(File::isDirectory(base_path('lang-packs/'.$identifier)));
} finally {
File::deleteDirectory($bundlePath);
File::deleteDirectory(base_path('lang-packs/'.$identifier));
}
}
/**
* assertNotInProgress 헬퍼는 Updating 외 상태에 대해서는 통과합니다.
*
* @return void
*/
public function test_assert_not_in_progress_passes_for_non_updating_states(): void
{
foreach ([
LanguagePackStatus::Active,
LanguagePackStatus::Inactive,
LanguagePackStatus::Installed,
LanguagePackStatus::Error,
] as $status) {
$pack = $this->makePack('sirsoft', 'ko-'.$status->value, $status->value);
$this->service->assertNotInProgress($pack); // throws if blocked
$pack->delete();
}
$this->assertTrue(true);
}
/**
* 테스트 언어팩 1건을 DB 에 직접 생성합니다.
*
* @param string $vendor 벤더
* @param string $locale 로케일
* @param string $status 상태
* @return LanguagePack 생성된 언어팩
*/
private function makePack(string $vendor, string $locale, string $status = LanguagePackStatus::Installed->value): LanguagePack
{
return LanguagePack::query()->create([
'identifier' => sprintf('%s-core-%s-%s', $vendor, $locale, uniqid()),
'vendor' => $vendor,
'scope' => LanguagePackScope::Core->value,
'target_identifier' => null,
'locale' => $locale,
'locale_name' => strtoupper($locale),
'locale_native_name' => $locale,
'text_direction' => 'ltr',
'version' => '1.0.0',
'status' => $status,
'is_protected' => false,
'manifest' => [],
]);
}
}