'Category', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $forum = Forum::create([ 'name' => 'Forum', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); return Thread::create([ 'forum_id' => $forum->id, 'user_id' => null, 'title' => 'Thread Title', 'body' => 'Thread Body', ]); } it('creates a post in a thread', function (): void { $user = User::factory()->create(); Sanctum::actingAs($user); $thread = makeThread(); $response = $this->postJson('/api/posts', [ 'body' => 'First reply', 'thread' => "/api/threads/{$thread->id}", ]); $response->assertStatus(201); $response->assertJsonFragment([ 'body' => 'First reply', 'thread' => "/api/threads/{$thread->id}", ]); $this->assertDatabaseHas('posts', [ 'thread_id' => $thread->id, 'user_id' => $user->id, 'body' => 'First reply', ]); }); it('validates required fields when creating posts', function (): void { $user = User::factory()->create(); Sanctum::actingAs($user); $response = $this->postJson('/api/posts', []); $response->assertStatus(422); $response->assertJsonValidationErrors(['body', 'thread']); }); it('enforces post update permissions', function (): void { $thread = makeThread(); $owner = User::factory()->create(); $other = User::factory()->create(); $post = Post::create([ 'thread_id' => $thread->id, 'user_id' => $owner->id, 'body' => 'Original body', ]); Sanctum::actingAs($other); $response = $this->patchJson("/api/posts/{$post->id}", [ 'body' => 'Hacked body', ]); $response->assertStatus(403); Sanctum::actingAs($owner); $response = $this->patchJson("/api/posts/{$post->id}", [ 'body' => 'Owner update', ]); $response->assertOk(); $this->assertDatabaseHas('posts', [ 'id' => $post->id, 'body' => 'Owner update', ]); $admin = User::factory()->create(); $role = Role::create(['name' => 'ROLE_ADMIN', 'color' => '#111111']); $admin->roles()->attach($role); Sanctum::actingAs($admin); $response = $this->patchJson("/api/posts/{$post->id}", [ 'body' => 'Admin update', ]); $response->assertOk(); $this->assertDatabaseHas('posts', [ 'id' => $post->id, 'body' => 'Admin update', ]); }); it('requires authentication to update a post', function (): void { $thread = makeThread(); $post = Post::create([ 'thread_id' => $thread->id, 'user_id' => null, 'body' => 'Original body', ]); $response = $this->patchJson("/api/posts/{$post->id}", [ 'body' => 'Updated body', ]); $response->assertStatus(401); }); it('deletes a post and tracks deleted_by', function (): void { $thread = makeThread(); $user = User::factory()->create(); $post = Post::create([ 'thread_id' => $thread->id, 'user_id' => $user->id, 'body' => 'To be deleted', ]); Sanctum::actingAs($user); $response = $this->deleteJson("/api/posts/{$post->id}"); $response->assertStatus(204); $this->assertSoftDeleted('posts', [ 'id' => $post->id, ]); $this->assertDatabaseHas('posts', [ 'id' => $post->id, 'deleted_by' => $user->id, ]); }); it('filters posts by thread', function (): void { $threadA = makeThread(); $threadB = makeThread(); $postA = Post::create([ 'thread_id' => $threadA->id, 'user_id' => null, 'body' => 'Post A', ]); Post::create([ 'thread_id' => $threadB->id, 'user_id' => null, 'body' => 'Post B', ]); $response = $this->getJson("/api/posts?thread=/api/threads/{$threadA->id}"); $response->assertOk(); $response->assertJsonCount(1); $response->assertJsonFragment([ 'id' => $postA->id, 'body' => 'Post A', ]); }); it('allows users to thank and unthank posts', function (): void { $thread = makeThread(); $author = User::factory()->create(); $thanker = User::factory()->create(); $post = Post::create([ 'thread_id' => $thread->id, 'user_id' => $author->id, 'body' => 'Helpful answer', ]); Sanctum::actingAs($thanker); $response = $this->postJson("/api/posts/{$post->id}/thanks"); $response->assertStatus(201); $this->assertDatabaseHas('post_thanks', [ 'post_id' => $post->id, 'user_id' => $thanker->id, ]); $response = $this->deleteJson("/api/posts/{$post->id}/thanks"); $response->assertStatus(204); $this->assertDatabaseMissing('post_thanks', [ 'post_id' => $post->id, 'user_id' => $thanker->id, ]); });