$slug, 'name' => ['ko' => '쿨다운 테스트 게시판', 'en' => 'Cooldown Test Board'], 'is_active' => true, 'secret_mode' => 'disabled', 'blocked_keywords' => [], 'min_title_length' => 1, 'max_title_length' => 200, 'min_content_length' => 1, 'max_content_length' => 10000, ]; } protected function setUp(): void { parent::setUp(); Cache::flush(); $this->grantUserRolePermissions(['posts.read', 'posts.write']); $this->setGuestPermissions(['posts.read', 'posts.write']); $this->mockSettings = [ 'spam_security' => [ 'post_cooldown_seconds' => 0, 'comment_cooldown_seconds' => 0, 'report_cooldown_seconds' => 0, 'view_count_cache_ttl' => 86400, ], ]; } private function applySettings(): void { // g7_module_settings()는 Config::get("g7_settings.modules.sirsoft-board.*")를 읽음 foreach ($this->mockSettings as $category => $values) { config(["g7_settings.modules.sirsoft-board.{$category}" => $values]); } } private function postUrl(): string { return "/api/modules/sirsoft-board/boards/{$this->board->slug}/posts"; } private function validPostData(): array { return [ 'title' => '쿨다운 테스트 제목', 'content' => '쿨다운 테스트 내용입니다.', ]; } // ========================================== // 쿨다운 비활성 (0초) // ========================================== /** * 쿨다운 0초: 연속 작성 허용 */ public function test_consecutive_posts_allowed_when_cooldown_disabled(): void { $this->mockSettings['spam_security']['post_cooldown_seconds'] = 0; $this->applySettings(); $user = $this->createUser(); $this->actingAs($user)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(201); $this->actingAs($user)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(201); } // ========================================== // 쿨다운 활성 // ========================================== /** * 쿨다운 활성: 첫 번째 작성 성공 */ public function test_first_post_succeeds_when_cooldown_active(): void { $this->mockSettings['spam_security']['post_cooldown_seconds'] = 60; $this->applySettings(); $user = $this->createUser(); $this->actingAs($user)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(201); } /** * 쿨다운 활성: 첫 작성 직후 두 번째 작성 422 */ public function test_second_post_rejected_when_cooldown_active(): void { $this->mockSettings['spam_security']['post_cooldown_seconds'] = 60; $this->applySettings(); $user = $this->createUser(); // 첫 번째 작성 성공 → 쿨다운 캐시 등록 $this->actingAs($user)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(201); // 두 번째 작성 시 422 $this->actingAs($user)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(422); } /** * 쿨다운 활성: 다른 사용자는 영향 없음 (user_id 격리) */ public function test_cooldown_is_isolated_per_user(): void { $this->mockSettings['spam_security']['post_cooldown_seconds'] = 60; $this->applySettings(); $userA = $this->createUser(); $userB = $this->createUser(); // userA 작성 → 쿨다운 등록 $this->actingAs($userA)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(201); // userB는 쿨다운 영향 없이 작성 가능 $this->actingAs($userB)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(201); } /** * 쿨다운 오류 응답에 422 상태코드와 errors 필드가 있다 */ public function test_cooldown_rejection_returns_422_with_errors(): void { $this->mockSettings['spam_security']['post_cooldown_seconds'] = 60; $this->applySettings(); $user = $this->createUser(); $this->actingAs($user)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(201); $this->actingAs($user)->postJson($this->postUrl(), $this->validPostData()) ->assertStatus(422) ->assertJsonStructure(['errors']); } }