Files
Gnuboard7/modules/_bundled/sirsoft-page/tests/Feature/Admin/PageAttachmentControllerTest.php
T
chym1217 b98a536162 fix(page): 페이지 첨부 썸네일·다운로드 미노출 수정
썸네일(<img>)·다운로드(<a>)는 브라우저 직접 GET 이라 토큰을 실을 수 없는데
커밋 d48c684 가 첨부 URL 을 auth:sanctum admin 라우트로 바꿔 401 회귀가 났다.
게시판·이커머스 표준으로 정렬한다.

- 썸네일(preview): 공개 hash 서빙으로 단일화 (발행/미발행 무관 200)
- 다운로드: 공개 hash 라우트 + 권한 게이트(발행=누구나 / 미발행=pages.read 관리자만)
- 프론트 다운로드를 downloadAttachment 핸들러(토큰 fetch→blob)로 전환
- 다운로드 아이콘 arrow-down-tray(FA 미존재) → download
- 사용자 페이지에도 이미지 첨부 썸네일 추가 (관리자 상세와 일관)
- admin 첨부 preview/download 데드 라우트·메서드 제거
- iteration 안 정적 id 를 att_idx 동적 id 로 교정 (위생 정리)
2026-07-06 09:37:06 +09:00

282 lines
9.6 KiB
PHP

<?php
namespace Modules\Sirsoft\Page\Tests\Feature\Admin;
// FeatureTestCase를 수동으로 require (autoload 전에 로드 필요)
require_once __DIR__.'/../../FeatureTestCase.php';
use App\Contracts\Extension\StorageInterface;
use App\Models\User;
use Illuminate\Http\UploadedFile;
use Mockery;
use Modules\Sirsoft\Page\Models\Page;
use Modules\Sirsoft\Page\Models\PageAttachment;
use Modules\Sirsoft\Page\Tests\FeatureTestCase;
/**
* 관리자 첨부파일 API 테스트
*
* PageAttachmentController의 업로드, 삭제, 순서 변경, 다운로드, 미리보기를 검증합니다.
*/
class PageAttachmentControllerTest extends FeatureTestCase
{
protected User $adminUser;
/**
* 테스트 환경 설정
*/
protected function setUp(): void
{
parent::setUp();
$this->adminUser = $this->createAdminUser([
'sirsoft-page.pages.read',
'sirsoft-page.pages.create',
'sirsoft-page.pages.update',
'sirsoft-page.pages.delete',
]);
// StorageInterface를 모킹 (파일 시스템 의존 제거)
$storageMock = Mockery::mock(StorageInterface::class);
$storageMock->shouldReceive('put')->andReturn(true);
$storageMock->shouldReceive('get')->andReturn('file content');
$storageMock->shouldReceive('exists')->andReturn(true);
$storageMock->shouldReceive('delete')->andReturn(true);
$storageMock->shouldReceive('deleteDirectory')->andReturn(true);
$storageMock->shouldReceive('getDisk')->andReturn('local');
$storageMock->shouldReceive('url')->andReturn('/storage/test.pdf');
$storageMock->shouldReceive('response')->andReturn(null);
$this->app->instance(StorageInterface::class, $storageMock);
}
/**
* 테스트 정리
*/
protected function tearDown(): void
{
Page::where('slug', 'like', 'test-%')->forceDelete();
Mockery::close();
parent::tearDown();
}
// ─── 업로드 (upload) ───────────────────────────────
/**
* 첨부파일을 업로드할 수 있는지 확인
*/
public function test_admin_can_upload_attachment_to_existing_page(): void
{
$page = Page::factory()->create([
'slug' => 'test-attach-upload',
'created_by' => $this->adminUser->id,
'updated_by' => $this->adminUser->id,
]);
$file = UploadedFile::fake()->create('document.pdf', 500, 'application/pdf');
$response = $this->actingAs($this->adminUser)
->postJson('/api/modules/sirsoft-page/admin/attachments', [
'file' => $file,
'page_id' => $page->id,
]);
$response->assertStatus(201)
->assertJsonPath('success', true)
->assertJsonStructure([
'data' => [
'data' => [
'id', 'hash', 'original_filename', 'mime_type', 'size',
],
],
]);
$this->assertEquals('document.pdf', $response->json('data.data.original_filename'));
}
/**
* temp_key로 임시 업로드할 수 있는지 확인
*/
public function test_admin_can_upload_attachment_with_temp_key(): void
{
$file = UploadedFile::fake()->create('report.pdf', 300, 'application/pdf');
$response = $this->actingAs($this->adminUser)
->postJson('/api/modules/sirsoft-page/admin/attachments', [
'file' => $file,
'temp_key' => 'test-temp-key-123',
]);
$response->assertStatus(201);
$this->assertDatabaseHas('page_attachments', [
'temp_key' => 'test-temp-key-123',
'original_filename' => 'report.pdf',
]);
}
/**
* 파일 없이 업로드 시 422를 반환하는지 확인
*/
public function test_upload_without_file_returns_422(): void
{
$response = $this->actingAs($this->adminUser)
->postJson('/api/modules/sirsoft-page/admin/attachments', []);
$response->assertStatus(422)
->assertJsonValidationErrors(['file']);
}
/**
* 허용되지 않는 파일 형식 업로드 시 422를 반환하는지 확인
*/
public function test_upload_disallowed_file_type_returns_422(): void
{
$file = UploadedFile::fake()->create('script.exe', 100, 'application/x-msdownload');
$response = $this->actingAs($this->adminUser)
->postJson('/api/modules/sirsoft-page/admin/attachments', [
'file' => $file,
'temp_key' => 'test-bad-type',
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['file']);
}
// ─── 삭제 (destroy) ────────────────────────────────
/**
* 첨부파일을 삭제할 수 있는지 확인
*/
public function test_admin_can_delete_attachment(): void
{
$page = Page::factory()->create([
'slug' => 'test-attach-delete',
'created_by' => $this->adminUser->id,
'updated_by' => $this->adminUser->id,
]);
$attachment = PageAttachment::create([
'page_id' => $page->id,
'original_filename' => 'delete-me.pdf',
'stored_filename' => 'stored-delete.pdf',
'disk' => 'local',
'path' => 'test/delete-me.pdf',
'mime_type' => 'application/pdf',
'size' => 1024,
'collection' => 'attachments',
'order' => 1,
'created_by' => $this->adminUser->id,
]);
$response = $this->actingAs($this->adminUser)
->deleteJson("/api/modules/sirsoft-page/admin/attachments/{$attachment->id}");
$response->assertStatus(200)
->assertJsonPath('success', true);
$this->assertDatabaseMissing('page_attachments', ['id' => $attachment->id]);
}
/**
* 존재하지 않는 첨부파일 삭제 시 404를 반환하는지 확인
*/
public function test_deleting_nonexistent_attachment_returns_404(): void
{
$response = $this->actingAs($this->adminUser)
->deleteJson('/api/modules/sirsoft-page/admin/attachments/99999');
$response->assertStatus(404);
}
// ─── 순서 변경 (reorder) ───────────────────────────
/**
* 첨부파일 순서를 변경할 수 있는지 확인
*/
public function test_admin_can_reorder_attachments(): void
{
$page = Page::factory()->create([
'slug' => 'test-attach-reorder',
'created_by' => $this->adminUser->id,
'updated_by' => $this->adminUser->id,
]);
$attachment1 = PageAttachment::create([
'page_id' => $page->id,
'original_filename' => 'first.pdf',
'stored_filename' => 'stored-first.pdf',
'disk' => 'local',
'path' => 'test/first.pdf',
'mime_type' => 'application/pdf',
'size' => 1024,
'collection' => 'attachments',
'order' => 1,
'created_by' => $this->adminUser->id,
]);
$attachment2 = PageAttachment::create([
'page_id' => $page->id,
'original_filename' => 'second.pdf',
'stored_filename' => 'stored-second.pdf',
'disk' => 'local',
'path' => 'test/second.pdf',
'mime_type' => 'application/pdf',
'size' => 2048,
'collection' => 'attachments',
'order' => 2,
'created_by' => $this->adminUser->id,
]);
$response = $this->actingAs($this->adminUser)
->patchJson('/api/modules/sirsoft-page/admin/attachments/reorder', [
'order' => [
['id' => $attachment1->id, 'order' => 2],
['id' => $attachment2->id, 'order' => 1],
],
]);
$response->assertStatus(200)
->assertJsonPath('success', true);
$attachment1->refresh();
$attachment2->refresh();
$this->assertEquals(2, $attachment1->order);
$this->assertEquals(1, $attachment2->order);
}
/**
* 빈 order 배열로 순서 변경 시 422를 반환하는지 확인
*/
public function test_reorder_with_empty_order_returns_422(): void
{
$response = $this->actingAs($this->adminUser)
->patchJson('/api/modules/sirsoft-page/admin/attachments/reorder', [
'order' => [],
]);
$response->assertStatus(422)
->assertJsonValidationErrors(['order']);
}
// 첨부 다운로드/미리보기는 공개 hash 라우트(pages/attachment/*)로 단일화되어
// 관리자 전용 preview/download 라우트가 제거됨. 해당 서빙 경로 테스트는
// PublicPageControllerTest 의 preview/download 매트릭스로 이관.
// ─── 인증 차단 ─────────────────────────────────────
/**
* 미인증 사용자가 첨부파일을 업로드할 수 없는지 확인
*/
public function test_unauthenticated_user_cannot_upload_attachment(): void
{
$file = UploadedFile::fake()->create('test.pdf', 100, 'application/pdf');
$response = $this->postJson('/api/modules/sirsoft-page/admin/attachments', [
'file' => $file,
'temp_key' => 'test-unauth',
]);
$response->assertStatus(401);
}
}