Files
Gnuboard7/modules/_bundled/sirsoft-page/tests/Feature/Console/PruneTempAttachmentsCommandTest.php
T
HeuJung cfc2caba1b feat(ckeditor5,board,ecommerce,page,core,admin_basic): 미참조 에디터 업로드 이미지 정리 신설 + 업로드 고아 동종 결함 7건 전수 해소
공개 (@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종
2026-08-17 02:44:54 +09:00

192 lines
6.7 KiB
PHP

<?php
namespace Modules\Sirsoft\Page\Tests\Feature\Console;
use App\Extension\Storage\ModuleStorageDriver;
use Modules\Sirsoft\Page\Models\Page;
use Modules\Sirsoft\Page\Models\PageAttachment;
use Modules\Sirsoft\Page\Tests\ModuleTestCase;
use PHPUnit\Framework\Attributes\Test;
/**
* 미연결 임시 페이지 첨부 정리 커맨드 테스트
*
* `temp_key` 가 남고 `page_id` 가 비어 있는 행만 대상이어야 합니다 — 연결된 첨부가
* 섞이면 공개 중인 페이지의 첨부가 사라집니다.
*/
class PruneTempAttachmentsCommandTest extends ModuleTestCase
{
/**
* 임시 첨부 파일과 기록을 함께 만듭니다.
*
* @param array<string, mixed> $attributes 덮어쓸 속성
* @return PageAttachment 생성된 첨부
*/
private function makeAttachment(array $attributes = []): PageAttachment
{
$filename = uniqid('temp-page-').'.png';
$path = $attributes['path'] ?? "temp/tempkey/{$filename}";
(new ModuleStorageDriver('sirsoft-page', 'modules'))->put('attachments', $path, 'bytes');
return PageAttachment::create(array_merge([
'page_id' => null,
'temp_key' => 'tempkey',
'original_filename' => $filename,
'stored_filename' => $filename,
'disk' => 'modules',
'path' => $path,
'mime_type' => 'image/png',
'size' => 5,
'collection' => 'attachments',
'order' => 1,
], $attributes));
}
/**
* @scenario age=past_retention, attachment_state=temp_unlinked
*
* @effects page_temp_prune_deletes_file_and_record
*/
#[Test]
public function stale_temp_attachments_lose_both_file_and_record(): void
{
$attachment = $this->makeAttachment();
$attachment->forceFill(['created_at' => now()->subDays(5)])->saveQuietly();
$this->artisan('sirsoft-page:prune-temp-attachments')->assertSuccessful();
$this->assertFalse(
(new ModuleStorageDriver('sirsoft-page', 'modules'))->exists('attachments', $attachment->path)
);
$this->assertDatabaseMissing('page_attachments', ['id' => $attachment->id]);
}
/**
* @scenario age=within_retention, attachment_state=temp_unlinked
*
* @effects page_temp_prune_keeps_within_retention
*/
#[Test]
public function temp_attachment_within_retention_is_kept(): void
{
$attachment = $this->makeAttachment();
$this->artisan('sirsoft-page:prune-temp-attachments')->assertSuccessful();
$this->assertDatabaseHas('page_attachments', ['id' => $attachment->id]);
}
/**
* @scenario age=past_retention, attachment_state=linked_to_page
*
* @effects page_temp_prune_skips_linked
*/
#[Test]
public function attachment_linked_to_a_page_is_never_pruned(): void
{
$page = Page::create([
'slug' => 'prune-temp-attachment-test-'.uniqid(),
'title' => ['ko' => '연결 첨부 테스트', 'en' => 'Linked Attachment Test'],
'content' => ['ko' => '', 'en' => ''],
'published' => true,
]);
$attachment = $this->makeAttachment([
'page_id' => $page->id,
'temp_key' => null,
'path' => '2026/08/14/'.uniqid('linked-').'.png',
]);
$attachment->forceFill(['created_at' => now()->subDays(90)])->saveQuietly();
$this->artisan('sirsoft-page:prune-temp-attachments')->assertSuccessful();
$this->assertDatabaseHas('page_attachments', ['id' => $attachment->id]);
}
/**
* @effects page_temp_prune_dry_run
*/
#[Test]
public function dry_run_reports_targets_without_deleting(): void
{
$attachment = $this->makeAttachment();
$attachment->forceFill(['created_at' => now()->subDays(5)])->saveQuietly();
$this->artisan('sirsoft-page:prune-temp-attachments --dry-run')
->expectsOutputToContain('[DRY RUN]')
->assertSuccessful();
$this->assertDatabaseHas('page_attachments', ['id' => $attachment->id]);
}
/**
* @effects page_temp_prune_days_guard
*/
#[Test]
public function retention_below_one_day_performs_no_cleanup(): void
{
$attachment = $this->makeAttachment();
$attachment->forceFill(['created_at' => now()->subDays(90)])->saveQuietly();
$this->artisan('sirsoft-page:prune-temp-attachments --days=0')->assertSuccessful();
$this->assertDatabaseHas('page_attachments', ['id' => $attachment->id]);
}
/**
* 파일을 비운 temp_key 디렉토리는 남기지 않는다 (회귀)
*
* 파일만 지우고 디렉토리를 남기면 페이지 작성 폼 세션마다 빈 디렉토리가 쌓여, 정리를
* 돌려도 저장소 흔적은 계속 늘어난다.
*
* @scenario age=past_retention, attachment_state=temp_unlinked
*
* @effects page_temp_prune_removes_empty_directory
*/
#[Test]
public function emptied_temp_directory_is_removed(): void
{
$attachment = $this->makeAttachment(['path' => 'temp/tempkey-empty-dir/'.uniqid('t-').'.png']);
$attachment->forceFill(['created_at' => now()->subDays(5)])->saveQuietly();
$this->artisan('sirsoft-page:prune-temp-attachments')->assertSuccessful();
$storage = new ModuleStorageDriver('sirsoft-page', 'modules');
$this->assertSame(
[],
$storage->files('attachments', 'temp/tempkey-empty-dir'),
'디렉토리 안의 파일은 모두 삭제되어야 한다.'
);
$this->assertDirectoryDoesNotExist(
rtrim($storage->getBasePath('attachments'), '/\\').'/temp/tempkey-empty-dir',
'비워진 temp 디렉토리는 남기지 않는다.'
);
}
/**
* 같은 temp_key 에 처리되지 않은 파일이 남아 있으면 디렉토리를 지우지 않는다.
*
* @scenario age=past_retention, attachment_state=temp_unlinked
*
* @effects page_temp_prune_keeps_directory_with_remaining_files
*/
#[Test]
public function temp_directory_with_remaining_files_is_kept(): void
{
$directory = 'temp/tempkey-partial';
$processed = $this->makeAttachment(['path' => $directory.'/'.uniqid('done-').'.png']);
$processed->forceFill(['created_at' => now()->subDays(5)])->saveQuietly();
// 같은 디렉토리에 이번 회차 대상이 아닌 파일이 남아 있는 상태
$storage = new ModuleStorageDriver('sirsoft-page', 'modules');
$storage->put('attachments', $directory.'/keep.png', 'bytes');
$this->artisan('sirsoft-page:prune-temp-attachments')->assertSuccessful();
$this->assertTrue($storage->exists('attachments', $directory.'/keep.png'));
}
}