'Category 1', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $forum = Forum::create([ 'name' => 'Forum A', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $response = $this->getJson('/api/forums?parent[exists]=false'); $response->assertOk(); $response->assertJsonCount(1); $response->assertJsonFragment(['id' => $category->id]); $response = $this->getJson('/api/forums?parent[exists]=true'); $response->assertOk(); $response->assertJsonCount(1); $response->assertJsonFragment(['id' => $forum->id]); }); it('filters forums by parent id and type', function (): void { $category = Forum::create([ 'name' => 'Category 2', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $forum = Forum::create([ 'name' => 'Forum B', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $response = $this->getJson("/api/forums?parent=/api/forums/{$category->id}"); $response->assertOk(); $response->assertJsonCount(1); $response->assertJsonFragment(['id' => $forum->id]); $response = $this->getJson('/api/forums?type=category'); $response->assertOk(); $response->assertJsonFragment(['id' => $category->id]); }); it('shows forum with last post data', function (): void { $role = Role::create(['name' => 'ROLE_MEMBER', 'color' => '#00ff00']); $user = User::factory()->create(); $user->roles()->attach($role); $user->load('roles'); $category = Forum::create([ 'name' => 'Category 3', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $forum = Forum::create([ 'name' => 'Forum C', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $thread = Thread::create([ 'forum_id' => $forum->id, 'user_id' => $user->id, 'title' => 'Thread', 'body' => 'Body', ]); $post = Post::create([ 'thread_id' => $thread->id, 'user_id' => $user->id, 'body' => 'Reply', ]); $response = $this->getJson("/api/forums/{$forum->id}"); $response->assertOk(); $response->assertJsonFragment([ 'id' => $forum->id, 'last_post_user_id' => $user->id, ]); $payload = $response->getData(true); expect($payload['last_post_user_group_color'])->toBe('#00ff00'); }); it('creates category and shifts positions', function (): void { Sanctum::actingAs(User::factory()->create()); Forum::create([ 'name' => 'Category A', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $response = $this->postJson('/api/forums', [ 'name' => 'Category B', 'type' => 'category', 'description' => 'Desc', ]); $response->assertStatus(201); $this->assertDatabaseHas('forums', [ 'name' => 'Category A', 'position' => 2, ]); }); it('updates forum parent and description', function (): void { Sanctum::actingAs(User::factory()->create()); $categoryA = Forum::create([ 'name' => 'Category A', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $categoryB = Forum::create([ 'name' => 'Category B', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 2, ]); $forum = Forum::create([ 'name' => 'Forum D', 'description' => null, 'type' => 'forum', 'parent_id' => $categoryA->id, 'position' => 1, ]); $response = $this->patchJson("/api/forums/{$forum->id}", [ 'parent' => "/api/forums/{$categoryB->id}", 'description' => 'Updated', ]); $response->assertOk(); $this->assertDatabaseHas('forums', [ 'id' => $forum->id, 'parent_id' => $categoryB->id, 'description' => 'Updated', ]); }); it('updates forum name and type', function (): void { Sanctum::actingAs(User::factory()->create()); $category = Forum::create([ 'name' => 'Category H', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $forum = Forum::create([ 'name' => 'Forum H', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $response = $this->patchJson("/api/forums/{$forum->id}", [ 'name' => 'Forum H Updated', 'type' => 'forum', ]); $response->assertOk(); $this->assertDatabaseHas('forums', [ 'id' => $forum->id, 'name' => 'Forum H Updated', ]); }); it('rejects forum update without category parent', function (): void { Sanctum::actingAs(User::factory()->create()); $category = Forum::create([ 'name' => 'Category Z', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $forum = Forum::create([ 'name' => 'Forum E', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $response = $this->patchJson("/api/forums/{$forum->id}", [ 'parent' => null, ]); $response->assertStatus(422); $response->assertJsonFragment(['message' => 'Forums must belong to a category.']); }); it('rejects forum update with non-category parent', function (): void { Sanctum::actingAs(User::factory()->create()); $category = Forum::create([ 'name' => 'Category X', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $parent = Forum::create([ 'name' => 'Not Category', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $forum = Forum::create([ 'name' => 'Forum G', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $response = $this->patchJson("/api/forums/{$forum->id}", [ 'parent' => "/api/forums/{$parent->id}", ]); $response->assertStatus(422); $response->assertJsonFragment(['message' => 'Parent must be a category.']); }); it('destroys forum and sets deleted_by', function (): void { $user = User::factory()->create(); Sanctum::actingAs($user); $forum = Forum::create([ 'name' => 'Forum F', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $response = $this->deleteJson("/api/forums/{$forum->id}"); $response->assertStatus(204); $forum->refresh(); expect($forum->deleted_by)->toBe($user->id); }); it('reorders with string parent id', function (): void { Sanctum::actingAs(User::factory()->create()); $parent = Forum::create([ 'name' => 'Cat Parent', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $first = Forum::create([ 'name' => 'Forum 1', 'description' => null, 'type' => 'forum', 'parent_id' => $parent->id, 'position' => 1, ]); $second = Forum::create([ 'name' => 'Forum 2', 'description' => null, 'type' => 'forum', 'parent_id' => $parent->id, 'position' => 2, ]); $response = $this->postJson('/api/forums/reorder', [ 'parentId' => (string) $parent->id, 'orderedIds' => [$second->id, $first->id], ]); $response->assertOk(); $this->assertDatabaseHas('forums', ['id' => $second->id, 'position' => 1]); }); it('reorders with empty parent id string', function (): void { Sanctum::actingAs(User::factory()->create()); $first = Forum::create([ 'name' => 'Cat X', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $second = Forum::create([ 'name' => 'Cat Y', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 2, ]); $response = $this->postJson('/api/forums/reorder', [ 'parentId' => '', 'orderedIds' => [$second->id, $first->id], ]); $response->assertOk(); $this->assertDatabaseHas('forums', ['id' => $second->id, 'position' => 1]); }); it('reorders with parent id null string', function (): void { Sanctum::actingAs(User::factory()->create()); $first = Forum::create([ 'name' => 'Cat N1', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $second = Forum::create([ 'name' => 'Cat N2', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 2, ]); $response = $this->postJson('/api/forums/reorder', [ 'parentId' => 'null', 'orderedIds' => [$second->id, $first->id], ]); $response->assertOk(); $this->assertDatabaseHas('forums', ['id' => $second->id, 'position' => 1]); }); it('creates forum under category and increments position', function (): void { Sanctum::actingAs(User::factory()->create()); $category = Forum::create([ 'name' => 'Category P', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); Forum::create([ 'name' => 'Forum P1', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $response = $this->postJson('/api/forums', [ 'name' => 'Forum P2', 'type' => 'forum', 'parent' => "/api/forums/{$category->id}", ]); $response->assertStatus(201); $this->assertDatabaseHas('forums', [ 'name' => 'Forum P2', 'position' => 2, ]); }); it('rejects forum without category parent', function (): void { Sanctum::actingAs(User::factory()->create()); $response = $this->postJson('/api/forums', [ 'name' => 'Bad Forum', 'type' => 'forum', 'parent' => null, ]); $response->assertStatus(422); $response->assertJsonFragment(['message' => 'Forums must belong to a category.']); }); it('rejects non-category parent', function (): void { Sanctum::actingAs(User::factory()->create()); $category = Forum::create([ 'name' => 'Category', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $parent = Forum::create([ 'name' => 'Not Category', 'description' => null, 'type' => 'forum', 'parent_id' => $category->id, 'position' => 1, ]); $response = $this->postJson('/api/forums', [ 'name' => 'Child Forum', 'type' => 'forum', 'parent' => "/api/forums/{$parent->id}", ]); $response->assertStatus(422); $response->assertJsonFragment(['message' => 'Parent must be a category.']); }); it('reorders positions within parent scope', function (): void { Sanctum::actingAs(User::factory()->create()); $first = Forum::create([ 'name' => 'Cat A', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 1, ]); $second = Forum::create([ 'name' => 'Cat B', 'description' => null, 'type' => 'category', 'parent_id' => null, 'position' => 2, ]); $response = $this->postJson('/api/forums/reorder', [ 'parentId' => null, 'orderedIds' => [$second->id, $first->id], ]); $response->assertOk(); $this->assertDatabaseHas('forums', [ 'id' => $second->id, 'position' => 1, ]); $this->assertDatabaseHas('forums', [ 'id' => $first->id, 'position' => 2, ]); });