Files
Gnuboard7/modules/_bundled/sirsoft-board/tests/ModuleTestCase.php
T
2026-04-20 20:37:49 +09:00

260 lines
8.6 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Modules\Sirsoft\Board\Models\Board;
use Tests\TestCase;
/**
* Board 모듈 테스트 베이스 클래스
*
* 모든 Board 모듈 테스트는 이 클래스를 상속받아야 합니다.
* 모듈 오토로드, ServiceProvider 등록, 마이그레이션, 라우트 등록을 자동으로 처리합니다.
*
* 성능 최적화:
* - 각 테스트 메서드는 DatabaseTransactions로 트랜잭션 롤백만 수행 (빠름)
* - board_posts/board_comments/board_attachments 단일 테이블 사용 (DDL 불필요)
*/
abstract class ModuleTestCase extends TestCase
{
use DatabaseTransactions;
/**
* 모듈 루트 경로를 반환합니다.
*
* __DIR__을 기반으로 동적 해석하여 _bundled/활성 디렉토리 모두에서 동작합니다.
*
* @return string 모듈 루트 절대 경로
*/
protected function getModuleBasePath(): string
{
// __DIR__ = {module_root}/tests/ → dirname = {module_root}
return dirname(__DIR__);
}
/**
* 마이그레이션 완료 플래그 (클래스당 한 번만 실행)
*/
protected static bool $migrated = false;
/**
* 테스트용 공유 게시판 slug (클래스 내 모든 테스트가 공유)
*/
protected static string $sharedBoardSlug = '';
/**
* 테스트 환경 설정
*/
protected function setUp(): void
{
parent::setUp();
// 모듈 오토로드 등록 (테스트 환경)
$this->registerModuleAutoload();
// 모듈 ServiceProvider 등록 (Repository 바인딩)
$this->app->register(\Modules\Sirsoft\Board\Providers\BoardServiceProvider::class);
// 모듈 마이그레이션 실행 (boards 테이블 등)
$this->runModuleMigrationIfNeeded();
// 모듈을 활성화 상태로 등록 (테스트 환경)
$this->registerModuleAsActive();
// ModuleManager 메모리 맵에 sirsoft-board 로드 (테스트 환경)
// CoreServiceProvider::boot()에서 loadModules()가 호출되지만,
// 테스트 컨테이너에서 ModuleManager 싱글톤이 비어있는 경우를 대비해 명시적으로 재로드
$this->app->make(\App\Extension\ModuleManager::class)->loadModules();
// 모듈 라우트를 수동으로 등록
$this->registerModuleRoutes();
// 기본 역할 생성
$this->createDefaultRoles();
}
/**
* 모듈 마이그레이션 실행 (필요한 경우에만)
*
* 코어 테이블이 없으면 먼저 코어 마이그레이션을 실행하고,
* 그 후 모듈 마이그레이션을 실행합니다.
* static $migrated 플래그로 프로세스당 한 번만 실행합니다.
*/
protected function runModuleMigrationIfNeeded(): void
{
if (static::$migrated) {
return;
}
// 코어 마이그레이션 먼저 실행 (users, roles, modules 등 의존 테이블 생성)
// Board 모듈의 FK는 users 테이블을 참조하므로 코어 테이블이 먼저 필요
if (! Schema::hasTable('users') || ! Schema::hasTable('roles') || ! Schema::hasTable('modules')) {
$this->artisan('migrate');
}
// 모듈 마이그레이션 실행 (코어 테이블 생성 후)
if (! Schema::hasTable('boards')) {
$this->artisan('migrate', [
'--path' => $this->getModuleBasePath() . '/database/migrations',
'--realpath' => true,
]);
}
static::$migrated = true;
}
/**
* 모듈을 활성화 상태로 등록합니다.
*/
protected function registerModuleAsActive(): void
{
// 이미 등록되어 있으면 스킵
if (\App\Models\Module::where('identifier', 'sirsoft-board')->exists()) {
return;
}
\App\Models\Module::create([
'identifier' => 'sirsoft-board',
'vendor' => 'sirsoft',
'name' => ['ko' => '게시판', 'en' => 'Board'],
'status' => \App\Enums\ExtensionStatus::Active->value,
'version' => '1.0.0',
'config' => [],
]);
}
/**
* 모듈 오토로드를 등록합니다.
*/
protected function registerModuleAutoload(): void
{
$moduleBasePath = $this->getModuleBasePath() . '/src/';
spl_autoload_register(function ($class) use ($moduleBasePath) {
$prefix = 'Modules\\Sirsoft\\Board\\';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relativeClass = substr($class, $len);
$file = $moduleBasePath.str_replace('\\', '/', $relativeClass).'.php';
if (file_exists($file)) {
require $file;
}
});
}
/**
* 모듈 라우트를 등록합니다.
*/
protected function registerModuleRoutes(): void
{
$apiRoutesFile = $this->getModuleBasePath() . '/src/routes/api.php';
if (file_exists($apiRoutesFile)) {
\Illuminate\Support\Facades\Route::prefix('api/modules/sirsoft-board')
->name('api.modules.sirsoft-board.')
->middleware('api')
->group($apiRoutesFile);
}
}
/**
* 기본 역할들을 생성합니다.
*/
protected function createDefaultRoles(): void
{
\App\Models\Role::firstOrCreate(
['identifier' => 'admin'],
['name' => ['ko' => '관리자', 'en' => 'Administrator']]
);
\App\Models\Role::firstOrCreate(
['identifier' => 'user'],
['name' => ['ko' => '일반 사용자', 'en' => 'User']]
);
\App\Models\Role::firstOrCreate(
['identifier' => 'guest'],
['name' => ['ko' => '비회원', 'en' => 'Guest']]
);
}
/**
* 관리자 역할을 가진 사용자를 생성합니다.
*
* @param array $permissions 추가 권한 목록
* @return \App\Models\User
*/
protected function createAdminUser(array $permissions = []): \App\Models\User
{
$adminRole = \App\Models\Role::where('identifier', 'admin')->first();
$user = \App\Models\User::factory()->create();
$user->roles()->attach($adminRole->id);
// 추가 권한이 있으면 생성 및 할당
if (! empty($permissions)) {
foreach ($permissions as $permissionIdentifier) {
$permission = \App\Models\Permission::firstOrCreate(
['identifier' => $permissionIdentifier],
[
'name' => ['ko' => $permissionIdentifier, 'en' => $permissionIdentifier],
'type' => 'admin',
]
);
$adminRole->permissions()->syncWithoutDetaching([$permission->id]);
}
}
return $user;
}
/**
* 일반 사용자를 생성합니다.
*
* @return \App\Models\User
*/
protected function createUser(): \App\Models\User
{
$userRole = \App\Models\Role::where('identifier', 'user')->first();
$user = \App\Models\User::factory()->create();
$user->roles()->attach($userRole->id);
return $user;
}
/**
* 게시판 ID에 대한 파티션이 존재하도록 보장합니다.
*
* board_posts/board_comments/board_attachments는 LIST 파티션 테이블이므로
* 게시판 ID에 해당하는 파티션이 없으면 INSERT 시 오류가 발생합니다.
* 파티션이 이미 존재하면 무시합니다.
*
* @param int $boardId 파티션을 추가할 게시판 ID
*/
protected function ensureBoardPartitions(int $boardId): void
{
$prefix = DB::getTablePrefix();
$tables = ['board_posts', 'board_comments', 'board_attachments'];
foreach ($tables as $table) {
try {
DB::statement(
"ALTER TABLE {$prefix}{$table} ADD PARTITION (PARTITION p{$boardId} VALUES IN ({$boardId}))"
);
} catch (\Exception $e) {
// 파티션 이미 존재하거나, 파티셔닝되지 않은 테이블(파티션 제거 후 환경)인 경우 무시
if (! str_contains($e->getMessage(), 'Duplicate partition name')
&& ! str_contains($e->getMessage(), 'not partitioned')) {
throw $e;
}
}
}
}
}