Files
2026-05-11 11:29:41 +09:00

213 lines
7.4 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests\Feature\User;
// ModuleTestCase 수동 로드 (autoload 전에 로드 필요)
require_once __DIR__.'/../../ModuleTestCase.php';
use Illuminate\Support\Facades\DB;
use Modules\Sirsoft\Board\Tests\BoardTestCase;
/**
* 게시글 이전/다음 네비게이션 API 테스트
*
* GET /api/modules/sirsoft-board/boards/{slug}/posts/{id}/navigation
*
* 검증 항목:
* - 기본 응답 구조 (prev/next)
* - 이전글/다음글 반환
* - 공지글 네비게이션 미제공 (prev/next 모두 null)
* - 첫 번째 글 prev=null, 마지막 글 next=null
* - 존재하지 않는 게시글 → 404
*/
class PostNavigationApiTest extends BoardTestCase
{
protected function getTestBoardSlug(): string
{
return 'post-navigation';
}
protected function setUp(): void
{
parent::setUp();
// user 역할에 게시판 읽기 권한 부여
$this->grantUserRolePermissions(['posts.read']);
}
/**
* navigation API가 prev/next 키를 포함한 올바른 구조로 응답하는지 검증
*/
public function test_navigation_returns_correct_structure(): void
{
// Given: 게시글 3개 (id 순서대로 생성 — board 기본 정렬: id desc)
$id1 = $this->createTestPost(['title' => '첫 번째 글', 'created_at' => now()->subMinutes(3)]);
$id2 = $this->createTestPost(['title' => '두 번째 글', 'created_at' => now()->subMinutes(2)]);
$id3 = $this->createTestPost(['title' => '세 번째 글', 'created_at' => now()->subMinutes(1)]);
// When: 중간 글(id2) navigation 조회
$response = $this->getJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$id2}/navigation"
);
// Then: 200 + prev/next 키 존재
$response->assertStatus(200)
->assertJsonStructure([
'data' => [
'prev',
'next',
],
]);
}
/**
* id DESC 정렬(기본) 기준으로 이전글/다음글이 올바르게 반환되는지 검증
*/
public function test_navigation_returns_correct_prev_and_next(): void
{
// Given: 게시글 3개 (id1 < id2 < id3)
$id1 = $this->createTestPost(['title' => '첫 번째 글']);
$id2 = $this->createTestPost(['title' => '두 번째 글']);
$id3 = $this->createTestPost(['title' => '세 번째 글']);
// 게시판 기본 정렬: id DESC → 목록 순서: id3, id2, id1
// id2 기준: prev(위쪽) = id3, next(아래쪽) = id1
// When
$response = $this->getJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$id2}/navigation"
);
// Then
$response->assertStatus(200);
$data = $response->json('data');
$this->assertNotNull($data['prev'], 'id2의 이전글(id3)이 존재해야 함');
$this->assertEquals($id3, $data['prev']['id']);
$this->assertNotNull($data['next'], 'id2의 다음글(id1)이 존재해야 함');
$this->assertEquals($id1, $data['next']['id']);
}
/**
* 목록의 첫 번째 글(가장 최신 = id가 가장 큰)은 prev가 null인지 검증
*/
public function test_first_post_has_null_prev(): void
{
// Given
$this->createTestPost(['title' => '오래된 글']);
$latestId = $this->createTestPost(['title' => '최신 글']);
// When: 최신 글(목록 맨 위) navigation
$response = $this->getJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$latestId}/navigation"
);
// Then: 위쪽에 더 이상 글이 없으므로 prev = null
$response->assertStatus(200);
$this->assertNull($response->json('data.prev'));
$this->assertNotNull($response->json('data.next'));
}
/**
* 목록의 마지막 글(가장 오래된 = id가 가장 작은)은 next가 null인지 검증
*/
public function test_last_post_has_null_next(): void
{
// Given
$oldestId = $this->createTestPost(['title' => '오래된 글']);
$this->createTestPost(['title' => '최신 글']);
// When: 오래된 글(목록 맨 아래) navigation
$response = $this->getJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$oldestId}/navigation"
);
// Then: 아래쪽에 더 이상 글이 없으므로 next = null
$response->assertStatus(200);
$this->assertNull($response->json('data.next'));
$this->assertNotNull($response->json('data.prev'));
}
/**
* 게시글이 1개뿐일 때 prev/next 모두 null인지 검증
*/
public function test_single_post_has_null_prev_and_next(): void
{
// Given
$id = $this->createTestPost(['title' => '유일한 글']);
// When
$response = $this->getJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$id}/navigation"
);
// Then
$response->assertStatus(200);
$this->assertNull($response->json('data.prev'));
$this->assertNull($response->json('data.next'));
}
/**
* 공지글은 navigation 미제공 — prev/next 모두 null 반환
*/
public function test_notice_post_returns_null_navigation(): void
{
// Given: 공지글 + 일반 게시글 2개
$this->createTestPost(['title' => '일반글 1']);
$noticeId = $this->createTestPost(['title' => '공지글', 'is_notice' => true]);
$this->createTestPost(['title' => '일반글 2']);
// When: 공지글 navigation 조회
$response = $this->getJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$noticeId}/navigation"
);
// Then: 공지글은 네비게이션 미제공
$response->assertStatus(200);
$this->assertNull($response->json('data.prev'));
$this->assertNull($response->json('data.next'));
}
/**
* 존재하지 않는 게시글 ID → 200 + 빈 prev/next (graceful degradation)
*
* 이슈 #269 이후 navigation 컨트롤러는 getAdjacent 실패를 모두 잡아 빈 응답으로 반환.
* 500 을 내지 않도록 한 회귀 방지 정책.
*/
public function test_navigation_with_nonexistent_post_returns_empty(): void
{
// When: 존재하지 않는 게시글 ID
$response = $this->getJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/99999999/navigation"
);
// Then: 200 + prev/next null (graceful degradation)
$response->assertStatus(200);
$response->assertJson(['data' => ['prev' => null, 'next' => null]]);
}
/**
* 비활성화된 게시판의 navigation 조회 시 404 응답
*
* BoardNotFoundException → NotFoundHttpException → 404
*/
public function test_navigation_on_inactive_board_returns_404(): void
{
// Given: 게시글 생성 후 게시판 비활성화
$id = $this->createTestPost(['title' => '게시글']);
$this->board->update(['is_active' => false]);
// When
$response = $this->getJson(
"/api/modules/sirsoft-board/boards/{$this->board->slug}/posts/{$id}/navigation"
);
// Then: BoardNotFoundException → 404
$response->assertStatus(404);
// 이후 테스트를 위해 복구
$this->board->update(['is_active' => true]);
}
}