Files
Gnuboard7/modules/_bundled/sirsoft-board/tests/BoardTestCase.php
T
2026-04-22 10:43:31 +09:00

279 lines
9.5 KiB
PHP

<?php
namespace Modules\Sirsoft\Board\Tests;
use App\Models\Permission;
use App\Models\Role;
use Illuminate\Support\Facades\DB;
use Modules\Sirsoft\Board\Models\Board;
/**
* 단일 테이블을 사용하는 테스트 베이스 클래스
*
* PostAccessTest, PostGuestTest 등 게시판별 데이터(board_posts, board_comments)를
* 다루어야 하는 테스트에서 사용합니다.
*
* 전략:
* - RefreshDatabase를 오버라이드하여 비활성화 (TRUNCATE 없이 board_id 기반 삭제)
* - setUp에서 해당 게시판의 데이터만 삭제하여 초기화
* - ModuleTestCase의 마이그레이션은 유지
*/
abstract class BoardTestCase extends ModuleTestCase
{
/**
* RefreshDatabase 비활성화
*
* @return void
*/
protected function refreshTestDatabase(): void
{
// 트랜잭션 대신 수동 DELETE 방식으로 데이터 정리
// (DDL implicit commit으로 인해 DatabaseTransactions 사용 불가)
}
/**
* 테스트용 게시판
*/
protected Board $board;
/**
* 테스트 게시판 slug (하위 클래스에서 오버라이드 가능)
*/
protected function getTestBoardSlug(): string
{
// 클래스명 기반으로 고유한 slug 생성 (예: PostGuestTest -> post-guest)
$className = (new \ReflectionClass($this))->getShortName();
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1-$2', str_replace('Test', '', $className)));
}
/**
* 테스트 환경 설정
*/
protected function setUp(): void
{
parent::setUp();
// 테스트 게시판 및 데이터 설정
$this->setupTestBoard();
}
/**
* 테스트 게시판을 설정합니다.
* 게시판이 이미 존재하면 해당 게시판의 데이터만 초기화합니다.
*/
protected function setupTestBoard(): void
{
$slug = $this->getTestBoardSlug();
// 게시판 생성 또는 조회 (updateOrCreate로 매번 기본 속성 보장)
// firstOrCreate는 기존 레코드 속성을 갱신하지 않아 이전 테스트의
// is_active=false 등이 잔류하는 문제가 있음
$this->board = Board::updateOrCreate(
['slug' => $slug],
$this->getDefaultBoardAttributes($slug)
);
// 해당 게시판의 게시글/댓글/첨부파일/신고 데이터만 삭제
DB::table('boards_reports')->where('board_id', $this->board->id)->delete();
DB::table('board_attachments')->where('board_id', $this->board->id)->delete();
DB::table('board_comments')->where('board_id', $this->board->id)->delete();
DB::table('board_posts')->where('board_id', $this->board->id)->delete();
// users 테이블도 초기화 (각 테스트마다 새로운 사용자 생성)
$this->truncateUserTables();
// PermissionMiddleware의 static guest role 캐시 초기화
// DELETE로 roles를 삭제하면 AUTO_INCREMENT가 바뀌므로 캐시된 Role 객체가 stale 됨
$this->resetPermissionMiddlewareCache();
// 기본 비회원 권한 부여 (roles/permissions 시스템 사용)
// truncateUserTables() 이후에 roles가 재생성되므로 여기서 호출
$this->createDefaultRoles();
$this->grantDefaultGuestPermissions();
}
/**
* 사용자 관련 테이블 초기화
*/
protected function truncateUserTables(): void
{
// DELETE(DML) 사용 — TRUNCATE(DDL)는 암묵적 커밋 + prepared statement 무효화 문제 유발
// FK 의존성 역순으로 삭제하여 제약조건 위반 방지
DB::table('role_permissions')->delete();
DB::table('user_roles')->delete();
DB::table('permissions')->delete();
DB::table('roles')->delete();
DB::table('users')->delete();
}
/**
* 기본 게시판 속성을 반환합니다.
* 하위 클래스에서 오버라이드하여 커스터마이징 가능합니다.
*
* @param string $slug 게시판 slug
* @return array 게시판 속성
*/
protected function getDefaultBoardAttributes(string $slug): array
{
return [
'slug' => $slug,
'name' => ['ko' => '테스트 게시판', 'en' => 'Test Board'],
'is_active' => true,
'use_file_upload' => true,
'secret_mode' => 'enabled',
'blocked_keywords' => [],
];
}
/**
* 비회원(guest) 역할에 게시판 권한을 정확히 설정합니다.
*
* 기존 guest 권한을 모두 제거하고, 지정한 권한만 부여합니다.
* guest_permissions 컬럼 대신 role/permission 시스템을 사용합니다.
*
* @param array $permissionKeys 권한 키 배열 (예: ['posts.read', 'posts.write'])
*/
protected function setGuestPermissions(array $permissionKeys): void
{
$guestRole = Role::where('identifier', 'guest')->first();
if (! $guestRole) {
return;
}
$slug = $this->board->slug;
// 이 게시판의 기존 guest 권한 모두 해제
$boardPermIds = $guestRole->permissions()
->where('identifier', 'like', "sirsoft-board.{$slug}.%")
->pluck('permissions.id');
$guestRole->permissions()->detach($boardPermIds);
// 새 권한 부여
foreach ($permissionKeys as $key) {
$perm = Permission::firstOrCreate(
['identifier' => "sirsoft-board.{$slug}.{$key}"],
['name' => ['ko' => $key, 'en' => $key], 'type' => 'user']
);
$guestRole->permissions()->syncWithoutDetaching([$perm->id]);
}
// 미들웨어 캐시 초기화 (eager-loaded permissions 갱신)
$this->resetPermissionMiddlewareCache();
}
/**
* 기본 비회원 권한을 부여합니다.
*
* posts.read, posts.write, attachments.upload 권한을 guest 역할에 부여합니다.
*/
protected function grantDefaultGuestPermissions(): void
{
$this->setGuestPermissions(['posts.read', 'posts.write', 'attachments.upload']);
}
/**
* PermissionMiddleware의 static guest role 캐시를 초기화합니다.
*
* DELETE로 roles 테이블을 비우면 AUTO_INCREMENT가 변경되어
* 캐시된 Role 객체의 ID와 실제 DB ID가 불일치합니다.
*/
protected function resetPermissionMiddlewareCache(): void
{
$reflection = new \ReflectionClass(\App\Http\Middleware\PermissionMiddleware::class);
$property = $reflection->getProperty('guestRoleCache');
$property->setAccessible(true);
$property->setValue(null, null);
}
/**
* user role에 게시판 권한을 부여합니다.
*
* 인증된 사용자(member)가 게시판에 접근하려면 user 역할에
* 해당 게시판 권한이 필요합니다.
*
* @param array $permissionKeys 권한 키 배열 (예: ['posts.read', 'posts.write'])
*/
protected function grantUserRolePermissions(array $permissionKeys = ['posts.read', 'posts.write', 'attachments.upload']): void
{
$userRole = Role::where('identifier', 'user')->first();
if (! $userRole) {
return;
}
$slug = $this->board->slug;
foreach ($permissionKeys as $key) {
$perm = Permission::firstOrCreate(
['identifier' => "sirsoft-board.{$slug}.{$key}"],
['name' => ['ko' => $key, 'en' => $key], 'type' => 'user']
);
$userRole->permissions()->syncWithoutDetaching([$perm->id]);
}
}
/**
* 게시판 설정을 업데이트합니다.
*
* @param array $settings 업데이트할 설정
*/
protected function updateBoardSettings(array $settings): void
{
$this->board->update($settings);
$this->board->refresh();
}
/**
* 테스트 게시글을 직접 DB에 생성합니다.
*
* @param array $attributes 게시글 속성
* @return int 생성된 게시글 ID
*/
protected function createTestPost(array $attributes = []): int
{
$defaults = [
'board_id' => $this->board->id,
'title' => '테스트 게시글',
'content' => '테스트 내용입니다.',
'user_id' => null,
'author_name' => '테스트',
'password' => null,
'ip_address' => '127.0.0.1',
'is_notice' => false,
'is_secret' => false,
'status' => 'published',
'trigger_type' => 'admin',
'view_count' => 0,
'created_at' => now(),
'updated_at' => now(),
];
return DB::table('board_posts')->insertGetId(array_merge($defaults, $attributes));
}
/**
* 테스트 댓글을 직접 DB에 생성합니다.
*
* @param int $postId 게시글 ID
* @param array $attributes 댓글 속성
* @return int 생성된 댓글 ID
*/
protected function createTestComment(int $postId, array $attributes = []): int
{
$defaults = [
'board_id' => $this->board->id,
'post_id' => $postId,
'user_id' => null,
'author_name' => '테스트',
'content' => '테스트 댓글입니다.',
'password' => null,
'ip_address' => '127.0.0.1',
'status' => 'published',
'created_at' => now(),
'updated_at' => now(),
];
return DB::table('board_comments')->insertGetId(array_merge($defaults, $attributes));
}
}