now = new DateTimeImmutable( '2026-05-06T12:00:00', new DateTimeZone('UTC'), ); $this->postRepo = new FakePostRepository; $this->useCase = new SetFeaturedPost($this->postRepo); } private function seedPost(): Post { return $this->postRepo->create(new CreatePostDto( userId: 1, title: 'A', body: 'B', createdAt: $this->now, )); } public function test_non_admin_throws_forbidden(): void { $post = $this->seedPost(); $this->expectException(ForbiddenException::class); $this->useCase->execute(new SetFeaturedPostRequest( postId: $post->getId(), slot: 1, requesterIsAdmin: false, )); } public function test_invalid_slot_throws_bad_request(): void { $post = $this->seedPost(); $this->expectException(BadRequestException::class); $this->useCase->execute(new SetFeaturedPostRequest( postId: $post->getId(), slot: 3, requesterIsAdmin: true, )); } public function test_zero_post_id_throws_bad_request(): void { $this->expectException(BadRequestException::class); $this->useCase->execute(new SetFeaturedPostRequest( postId: 0, slot: 1, requesterIsAdmin: true, )); } public function test_unknown_post_throws_domain_exception(): void { $this->expectException(DomainException::class); $this->useCase->execute(new SetFeaturedPostRequest( postId: 999, slot: 1, requesterIsAdmin: true, )); } public function test_admin_assigns_slot(): void { $post = $this->seedPost(); $this->useCase->execute(new SetFeaturedPostRequest( postId: $post->getId(), slot: 1, requesterIsAdmin: true, )); $reloaded = $this->postRepo->find($post->getId()); $this->assertSame(1, $reloaded->getFeatureSlot()); } public function test_assigning_same_slot_evicts_previous_post(): void { $first = $this->seedPost(); $second = $this->seedPost(); $this->useCase->execute(new SetFeaturedPostRequest( postId: $first->getId(), slot: 1, requesterIsAdmin: true, )); $this->useCase->execute(new SetFeaturedPostRequest( postId: $second->getId(), slot: 1, requesterIsAdmin: true, )); $this->assertNull( $this->postRepo->find($first->getId())->getFeatureSlot(), ); $this->assertSame( 1, $this->postRepo->find($second->getId())->getFeatureSlot(), ); } public function test_two_posts_can_occupy_separate_slots(): void { $first = $this->seedPost(); $second = $this->seedPost(); $this->useCase->execute(new SetFeaturedPostRequest( postId: $first->getId(), slot: 1, requesterIsAdmin: true, )); $this->useCase->execute(new SetFeaturedPostRequest( postId: $second->getId(), slot: 2, requesterIsAdmin: true, )); $featured = $this->postRepo->findFeatured(); $this->assertCount(2, $featured); $this->assertSame(1, $featured[0]->getFeatureSlot()); $this->assertSame(2, $featured[1]->getFeatureSlot()); } public function test_moving_post_to_other_slot_clears_old_slot(): void { $post = $this->seedPost(); $this->useCase->execute(new SetFeaturedPostRequest( postId: $post->getId(), slot: 1, requesterIsAdmin: true, )); $this->useCase->execute(new SetFeaturedPostRequest( postId: $post->getId(), slot: 2, requesterIsAdmin: true, )); $this->assertNull( $this->postRepo->findByFeatureSlot(1), ); $this->assertSame( $post->getId(), $this->postRepo->findByFeatureSlot(2)->getId(), ); } }