공개 (@Tuwasduliebst 제보) 대응. - ckeditor5: 미참조 이미지 정리 엔진 신설 — 참조 판정(hash + 파일명 2토큰, filter_reference_sources 훅으로 확장 콘텐츠 등록) + 정리 커맨드(기본 꺼짐 옵트인, 3중 게이트) + 업로드 관리 화면(권한 분리·참조 배지·개별/선택 삭제) + 기설치본 설정 백필 upgrade step - board: 임시 첨부 GC(상시) + 소프트삭제 첨부 영구 정리(옵트인) + 설정 UI, 저장 payload 누락(F4)·빈 temp 디렉토리 잔존(F5) 수정 - ecommerce: 임시 상품 이미지 GC + 카테고리 삭제 시 이미지 파일 미삭제(S4) 수정 - page: 임시 첨부 GC - 코어: 고아 첨부 정리 커맨드(옵트인, site_logo 보호) + 사이트 로고 교체 시 구 로고 재편입 차단(S6) + DB 백업 미구현 501 정직화(B3) + 플러그인 관리자 메뉴 설치·업데이트 자동 동기화(F1·F7) - 동반: 플러그인 설정 검증 오류 필드명 라벨화(F3), Toggle 노드 type 정정 수평 전개(marketing 1.0.2 bump·gdpr·ecommerce), ja 언어팩 4종, API 문서, hooks.md, 시나리오 매니페스트 8종, Playwright E2E 4종
189 lines
6.9 KiB
PHP
189 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace Plugins\Sirsoft\Ckeditor5\Tests;
|
|
|
|
use App\Contracts\Extension\StorageInterface;
|
|
use App\Enums\PermissionType;
|
|
use App\Extension\HookManager;
|
|
use App\Extension\Storage\PluginStorageDriver;
|
|
use App\Models\Permission;
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Plugins\Sirsoft\Ckeditor5\Http\Controllers\Admin\ImageUploadAdminController;
|
|
use Plugins\Sirsoft\Ckeditor5\Http\Controllers\ImageServeController;
|
|
use Plugins\Sirsoft\Ckeditor5\Http\Controllers\ImageUploadController;
|
|
use Plugins\Sirsoft\Ckeditor5\Repositories\Contracts\ImageReferenceSourceRepositoryInterface;
|
|
use Plugins\Sirsoft\Ckeditor5\Repositories\Contracts\ImageUploadRepositoryInterface;
|
|
use Plugins\Sirsoft\Ckeditor5\Repositories\ImageReferenceSourceRepository;
|
|
use Plugins\Sirsoft\Ckeditor5\Repositories\ImageUploadRepository;
|
|
use Plugins\Sirsoft\Ckeditor5\Services\ImageCleanupService;
|
|
use Plugins\Sirsoft\Ckeditor5\Services\ImageServeService;
|
|
use Plugins\Sirsoft\Ckeditor5\Services\ImageUploadService;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* CKEditor5 플러그인 테스트 베이스 클래스
|
|
*
|
|
* 모든 CKEditor5 플러그인 테스트는 이 클래스를 상속받아야 합니다.
|
|
* 코어 + 플러그인 마이그레이션 및 라우트를 자동으로 처리합니다.
|
|
*/
|
|
abstract class PluginTestCase extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
/**
|
|
* 테스트 환경 설정
|
|
*
|
|
* 플러그인 라우트와 바인딩을 테스트 환경에서 직접 등록합니다.
|
|
*/
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Repository 바인딩
|
|
$this->app->bind(
|
|
ImageUploadRepositoryInterface::class,
|
|
ImageUploadRepository::class
|
|
);
|
|
|
|
$this->app->bind(
|
|
ImageReferenceSourceRepositoryInterface::class,
|
|
ImageReferenceSourceRepository::class
|
|
);
|
|
|
|
// StorageInterface 바인딩
|
|
$this->app->when([ImageUploadService::class, ImageServeService::class, ImageCleanupService::class])
|
|
->needs(StorageInterface::class)
|
|
->give(fn () => new PluginStorageDriver('sirsoft-ckeditor5', 'plugins'));
|
|
|
|
// HookManager 상태 스냅샷 (tearDown 에서 복원하여 테스트 간 훅 격리)
|
|
$this->snapshotHookManager();
|
|
|
|
// 라우트 등록
|
|
Route::prefix('api/plugins/sirsoft-ckeditor5')
|
|
->middleware('api')
|
|
->group(function () {
|
|
Route::post('upload', [ImageUploadController::class, 'upload'])
|
|
->name('api.sirsoft-ckeditor5.upload');
|
|
|
|
Route::get('images/{hash}', [ImageServeController::class, 'serve'])
|
|
->name('api.sirsoft-ckeditor5.images.serve');
|
|
|
|
// 업로드 관리 (관리자) — 실제 라우트 파일과 동일한 미들웨어 체인을 유지해
|
|
// 권한 경계(403)까지 테스트가 실제로 밟게 한다.
|
|
Route::prefix('admin')->name('admin.')->middleware('auth:sanctum')->group(function () {
|
|
Route::get('uploads', [ImageUploadAdminController::class, 'index'])
|
|
->middleware('permission:admin,sirsoft-ckeditor5.uploads.read')
|
|
->name('uploads.index');
|
|
|
|
Route::post('uploads/bulk-delete', [ImageUploadAdminController::class, 'bulkDestroy'])
|
|
->middleware('permission:admin,sirsoft-ckeditor5.uploads.delete')
|
|
->name('uploads.bulk-delete');
|
|
|
|
Route::delete('uploads/{id}', [ImageUploadAdminController::class, 'destroy'])
|
|
->whereNumber('id')
|
|
->middleware('permission:admin,sirsoft-ckeditor5.uploads.delete')
|
|
->name('uploads.destroy');
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* HookManager static state 스냅샷 — tearDown 에서 복원하여 테스트 간 훅 격리 보장.
|
|
*
|
|
* @var array{hooks: array, filters: array, dispatching: array}|null
|
|
*/
|
|
private ?array $hookSnapshot = null;
|
|
|
|
/**
|
|
* tearDown 에 HookManager 상태 복원.
|
|
*/
|
|
protected function tearDown(): void
|
|
{
|
|
$this->restoreHookManager();
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* HookManager static $hooks / $filters / $dispatching 를 스냅샷.
|
|
*/
|
|
private function snapshotHookManager(): void
|
|
{
|
|
$ref = new \ReflectionClass(HookManager::class);
|
|
$this->hookSnapshot = [
|
|
'hooks' => $ref->getProperty('hooks')->getValue(),
|
|
'filters' => $ref->getProperty('filters')->getValue(),
|
|
'dispatching' => $ref->getProperty('dispatching')->getValue(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 스냅샷 시점으로 HookManager 복원.
|
|
*/
|
|
private function restoreHookManager(): void
|
|
{
|
|
if ($this->hookSnapshot === null) {
|
|
return;
|
|
}
|
|
|
|
$ref = new \ReflectionClass(HookManager::class);
|
|
$ref->getProperty('hooks')->setValue(null, $this->hookSnapshot['hooks']);
|
|
$ref->getProperty('filters')->setValue(null, $this->hookSnapshot['filters']);
|
|
$ref->getProperty('dispatching')->setValue(null, $this->hookSnapshot['dispatching']);
|
|
|
|
$this->hookSnapshot = null;
|
|
}
|
|
|
|
/**
|
|
* 관리자 권한을 가진 사용자를 생성합니다.
|
|
*
|
|
* @return User
|
|
*/
|
|
protected function createAdminUser(): User
|
|
{
|
|
$adminRole = Role::firstOrCreate(
|
|
['identifier' => 'admin'],
|
|
['name' => ['ko' => '관리자', 'en' => 'Admin'], 'description' => ['ko' => '관리자', 'en' => 'Admin']]
|
|
);
|
|
|
|
$permission = Permission::firstOrCreate(
|
|
['identifier' => 'admin.access'],
|
|
['name' => ['ko' => '관리자 접근', 'en' => 'Admin Access'], 'type' => PermissionType::Admin]
|
|
);
|
|
|
|
$adminRole->permissions()->syncWithoutDetaching([$permission->id]);
|
|
|
|
$user = User::factory()->create();
|
|
$user->roles()->attach($adminRole->id);
|
|
|
|
return $user;
|
|
}
|
|
|
|
/**
|
|
* 마이그레이션 경로를 반환합니다.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function migrateFreshUsing(): array
|
|
{
|
|
// 모든 번들 확장 migrations 포함 — Plugin suite 전체 실행 시 스키마 보장
|
|
$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,
|
|
];
|
|
}
|
|
}
|