Files
Gnuboard7/modules/_bundled/sirsoft-board/tests/Feature/FormRequestValidationTest.php
T
HeuJung 3ebbda5d86 feat(board,ecommerce,pay,verification,templates,core): 문의 답변 사슬·답글 삭제 정책·검증 계약 + 실패 사유 표면화 전수
공개 (답변 중복 등록)/(문의 삭제 시 답변 잔존) 수정과 동형 결함 전수 해소.

- 문의 답변 단일성: 1차(피벗 is_answered) + 2차(게시판 리스너 duplicate 마커) 이중 방어,
 경합 경로에서도 사유(reply_already_exists 422)가 보존되도록 훅 반환 계약 확장
- 질문·답변·피벗 생명주기 대칭: 삭제/복원/영구삭제 훅 연동 + 고아 데이터 백필 업그레이드 스텝
- 게시판별 답글 삭제 정책(cascade/block) 신설, 정렬 화이트리스트·per_page 게이트
 (전량 조회 소비처 200/999 를 수용하는 상한 999 — 조였다가 관리자 화면 2곳 422 회귀 즉시 정정)
- 결제/본인인증 플러그인 검증 계약: FormRequest 15종 승격 + 컨트롤러 catch 정합.
 거래 조회 tid 상한은 저장 컬럼(varchar 100) 기준 — 해외 CBT TID(40자) 수용
- onError 토스트 고정 문구 전수 해소(67건/50파일/15확장) — 서버 실패 사유를 그대로 표면화,
 change-password 오변수(response→error) 교정
- 코어 플러그인 설정 검증 안내의 항목 표시명이 스키마 라벨(ko/en) 폴백에 덮여 요청 로케일
 (ja) 라벨을 잃던 회귀 정정 — 번역 등록이 있으면 그것을 우선 (7.0.7 미출시 기능 자체 결함)
- 사전 결함 정리(Icon 등가표 27·darkmode 2·validated 통일), API 문서·CHANGELOG·ja 팩 동기
- develop(60652fdc0) 리베이스: getTable 정비와 병합 — 양측 의도 보존 (충돌 18파일)
2026-08-17 17:04:44 +09:00

422 lines
13 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests\Feature;
require_once __DIR__.'/../ModuleTestCase.php';
use Illuminate\Support\Facades\Hash;
use Modules\Sirsoft\Board\Tests\BoardTestCase;
/**
* FormRequest 검증 규칙 테스트
*
* 검증 목적:
* StorePostRequest:
* - title 누락 → 422
* - content 누락 → 422
* - title 최소 길이 미만 → 422
* - content_mode 유효하지 않은 값 → 422
* - 정상 데이터 → 201
*
* StoreCommentRequest:
* - content 누락 → 422
* - post_id 누락 → 422
* - 비회원이 author_name 누락 → 422
* - 비회원이 password 누락 → 422
* - 비회원 정상 데이터 → 201
*
* StoreReportRequest:
* - reason_type 누락 → 422
* - reason_detail 누락 → 422
* - reason_type 유효하지 않은 값 → 422
* - 정상 데이터 → 201
*
* UpdatePostRequest:
* - title 최소 길이 미만 → 422
* - password 배열 주입 → 422
*
* DestroyCommentRequest:
* - password 배열 주입 → 422
* - password 최소 길이(4) 미만 → 422
* - 회원 본인 댓글 password 없이 삭제 → 200
*
* DestroyPostRequest:
* - verification_token 배열 주입 → 422
* - 비회원 글 올바른 password 로 삭제 → 200
*
* @group board
* @group feature
* @group formrequest
*/
class FormRequestValidationTest extends BoardTestCase
{
protected function getTestBoardSlug(): string
{
return 'form-request-validation';
}
protected function getDefaultBoardAttributes(string $slug): array
{
return [
'slug' => $slug,
'name' => ['ko' => 'FormRequest 검증 테스트 게시판', 'en' => 'FormRequest Validation Test Board'],
'is_active' => true,
'secret_mode' => 'disabled',
'blocked_keywords' => [],
'use_comment' => true,
'use_report' => true,
'min_title_length' => 2,
'max_title_length' => 200,
'min_content_length' => 5,
'max_content_length' => 10000,
'min_comment_length' => 2,
'max_comment_length' => 1000,
];
}
protected function setUp(): void
{
parent::setUp();
$this->setGuestPermissions(['posts.read', 'posts.write', 'comments.write', 'attachments.upload']);
$this->grantUserRolePermissions(['posts.read', 'posts.write', 'comments.write', 'attachments.upload']);
}
private function postUrl(): string
{
return "/api/modules/sirsoft-board/boards/{$this->board->slug}/posts";
}
private function commentUrl(int $postId): string
{
return "/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}/comments";
}
private function reportUrl(int $postId): string
{
return "/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}/reports";
}
// ==========================================
// StorePostRequest
// ==========================================
/**
* title 누락 → 422
*/
public function test_store_post_fails_without_title(): void
{
$user = $this->createUser();
$this->actingAs($user)->postJson($this->postUrl(), [
'content' => '정상 내용입니다.',
])->assertStatus(422);
}
/**
* content 누락 → 422
*/
public function test_store_post_fails_without_content(): void
{
$user = $this->createUser();
$this->actingAs($user)->postJson($this->postUrl(), [
'title' => '정상 제목',
])->assertStatus(422);
}
/**
* title 최소 길이 미만 (1자) → 422
*/
public function test_store_post_fails_with_title_too_short(): void
{
$user = $this->createUser();
$this->actingAs($user)->postJson($this->postUrl(), [
'title' => '가', // min_title_length=2 미만
'content' => '정상적인 내용입니다.',
])->assertStatus(422);
}
/**
* content_mode 유효하지 않은 값 → 422
*/
public function test_store_post_fails_with_invalid_content_mode(): void
{
$user = $this->createUser();
$this->actingAs($user)->postJson($this->postUrl(), [
'title' => '정상 제목',
'content' => '정상적인 내용입니다.',
'content_mode' => 'markdown', // text|html 만 허용
])->assertStatus(422);
}
/**
* 정상 데이터 → 201
*/
public function test_store_post_succeeds_with_valid_data(): void
{
$user = $this->createUser();
$this->actingAs($user)->postJson($this->postUrl(), [
'title' => '정상 게시글 제목',
'content' => '정상적인 게시글 내용입니다.',
])->assertStatus(201);
}
// ==========================================
// StoreCommentRequest
// ==========================================
/**
* content 누락 → 422
*/
public function test_store_comment_fails_without_content(): void
{
$user = $this->createUser();
$postId = $this->createTestPost();
$this->actingAs($user)->postJson($this->commentUrl($postId), [
'post_id' => $postId,
])->assertStatus(422);
}
/**
* post_id 누락 → 422
*/
public function test_store_comment_fails_without_post_id(): void
{
$user = $this->createUser();
$this->actingAs($user)->postJson($this->commentUrl(1), [
'content' => '댓글 내용입니다.',
])->assertStatus(422);
}
/**
* 비회원이 author_name 누락 → 422
*/
public function test_store_comment_guest_fails_without_author_name(): void
{
$postId = $this->createTestPost();
$this->postJson($this->commentUrl($postId), [
'content' => '댓글 내용입니다.',
'post_id' => $postId,
'password' => 'pass1234',
// author_name 누락
])->assertStatus(422);
}
/**
* 비회원이 password 누락 → 422
*/
public function test_store_comment_guest_fails_without_password(): void
{
$postId = $this->createTestPost();
$this->postJson($this->commentUrl($postId), [
'content' => '댓글 내용입니다.',
'post_id' => $postId,
'author_name' => '비회원',
// password 누락
])->assertStatus(422);
}
/**
* 비회원 정상 댓글 데이터 → 201
*/
public function test_store_comment_guest_succeeds_with_valid_data(): void
{
$postId = $this->createTestPost();
$this->postJson($this->commentUrl($postId), [
'content' => '정상적인 댓글 내용입니다.',
'post_id' => $postId,
'author_name' => '비회원',
'password' => 'pass1234',
])->assertStatus(201);
}
// ==========================================
// StoreReportRequest
// ==========================================
/**
* reason_type 누락 → 422
*/
public function test_store_report_fails_without_reason_type(): void
{
$author = $this->createUser();
$reporter = $this->createUser();
$postId = $this->createTestPost(['user_id' => $author->id]);
$this->actingAs($reporter)->postJson($this->reportUrl($postId), [
'reason_detail' => '신고 상세 내용입니다.',
])->assertStatus(422);
}
/**
* reason_detail 누락 → 422
*/
public function test_store_report_fails_without_reason_detail(): void
{
$author = $this->createUser();
$reporter = $this->createUser();
$postId = $this->createTestPost(['user_id' => $author->id]);
$this->actingAs($reporter)->postJson($this->reportUrl($postId), [
'reason_type' => 'spam',
])->assertStatus(422);
}
/**
* reason_type 유효하지 않은 값 → 422
*/
public function test_store_report_fails_with_invalid_reason_type(): void
{
$author = $this->createUser();
$reporter = $this->createUser();
$postId = $this->createTestPost(['user_id' => $author->id]);
$this->actingAs($reporter)->postJson($this->reportUrl($postId), [
'reason_type' => 'not_a_valid_type',
'reason_detail' => '상세 내용입니다.',
])->assertStatus(422);
}
/**
* 정상 신고 데이터 → 201
*/
public function test_store_report_succeeds_with_valid_data(): void
{
$author = $this->createUser();
$reporter = $this->createUser();
$postId = $this->createTestPost(['user_id' => $author->id]);
$this->actingAs($reporter)->postJson($this->reportUrl($postId), [
'reason_type' => 'spam',
'reason_detail' => '스팸 게시글입니다.',
])->assertStatus(201);
}
// ==========================================
// UpdatePostRequest
// ==========================================
/**
* title 최소 길이 미만으로 수정 → 422
*/
public function test_update_post_fails_with_title_too_short(): void
{
$user = $this->createUser();
$postId = $this->createTestPost(['user_id' => $user->id]);
$this->actingAs($user)->putJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}",
['title' => '가'] // min_title_length=2 미만
)->assertStatus(422);
}
/**
* 게시글 수정에 password 배열 주입 → 422 (string 규칙 차단)
*/
public function test_post_update_rejects_array_password(): void
{
$user = $this->createUser();
$postId = $this->createTestPost(['user_id' => $user->id]);
$this->actingAs($user)->putJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$postId}",
[
'title' => '정상 제목',
'password' => ['x'], // 배열 주입
]
)->assertStatus(422);
}
// ==========================================
// DestroyCommentRequest
// ==========================================
/**
* 댓글 삭제에 password 배열 주입 → 422 (string 규칙 차단)
*/
public function test_comment_destroy_rejects_array_password(): void
{
$postId = $this->createTestPost();
$commentId = $this->createTestComment($postId, [
'password' => Hash::make('pass1234'),
]);
$this->deleteJson(
$this->commentUrl($postId)."/{$commentId}",
['password' => ['x']] // 배열 주입
)->assertStatus(422);
}
/**
* 댓글 삭제에 최소 길이(4) 미만 password → 422
*/
public function test_comment_destroy_rejects_too_short_password(): void
{
$postId = $this->createTestPost();
$commentId = $this->createTestComment($postId, [
'password' => Hash::make('pass1234'),
]);
$this->deleteJson(
$this->commentUrl($postId)."/{$commentId}",
['password' => '123'] // min:4 미만
)->assertStatus(422);
}
/**
* 회원 본인 댓글은 password 없이 삭제 가능 → 200
* (password 는 nullable — 비회원 소유권 확인용으로만 요구)
*/
public function test_member_comment_destroy_without_password_succeeds(): void
{
$user = $this->createUser();
$postId = $this->createTestPost();
$commentId = $this->createTestComment($postId, [
'user_id' => $user->id,
'author_name' => $user->name,
]);
$this->actingAs($user)
->deleteJson($this->commentUrl($postId)."/{$commentId}")
->assertStatus(200);
}
// ==========================================
// DestroyPostRequest
// ==========================================
/**
* 게시글 삭제에 verification_token 배열 주입 → 422 (string 규칙 차단)
*/
public function test_post_destroy_rejects_array_verification_token(): void
{
$postId = $this->createTestPost([
'user_id' => null,
'password' => Hash::make('pass1234'),
]);
$this->deleteJson(
$this->postUrl()."/{$postId}",
['verification_token' => ['x']] // 배열 주입
)->assertStatus(422);
}
/**
* 비회원 글은 올바른 password 로 삭제 가능 → 200
*/
public function test_guest_post_destroy_with_valid_password_succeeds(): void
{
$postId = $this->createTestPost([
'user_id' => null,
'password' => Hash::make('pass1234'),
]);
$this->deleteJson(
$this->postUrl()."/{$postId}",
['password' => 'pass1234']
)->assertStatus(200);
}
}