postService = $this->createMock(PostServiceInterface::class); $this->controller = new RssController( $this->postService, self::APP_URL, self::APP_NAME, ); } /** * feed() doit retourner un Content-Type application/rss+xml. */ public function testFeedReturnsRssContentType(): void { $this->postService->method('getRecentPosts')->willReturn([]); $res = $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); $this->assertStringContainsString('application/rss+xml', $res->getHeaderLine('Content-Type')); } /** * feed() doit retourner un XML valide même si aucun article n'existe. */ public function testFeedReturnsValidXmlWhenNoPostsExist(): void { $this->postService->method('getRecentPosts')->willReturn([]); $res = $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); $body = (string) $res->getBody(); $xml = simplexml_load_string($body); $this->assertNotFalse($xml, 'Le corps de la réponse doit être du XML valide'); $this->assertSame('2.0', (string) $xml['version']); } /** * feed() doit inclure les balises channel obligatoires (title, link, description). */ public function testFeedIncludesRequiredChannelElements(): void { $this->postService->method('getRecentPosts')->willReturn([]); $res = $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); $xml = simplexml_load_string((string) $res->getBody()); $this->assertNotFalse($xml); $channel = $xml->channel; $this->assertSame(self::APP_NAME, (string) $channel->title); $this->assertNotEmpty((string) $channel->link); $this->assertNotEmpty((string) $channel->description); } /** * feed() doit inclure un item par article avec title, link et guid. */ public function testFeedIncludesOneItemPerPost(): void { $post = new Post(1, 'Titre test', 'Contenu de test', 'titre-test', 1, 'alice'); $this->postService->method('getRecentPosts')->willReturn([$post]); $res = $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); $xml = simplexml_load_string((string) $res->getBody()); $this->assertNotFalse($xml); $items = $xml->channel->item; $this->assertCount(1, $items); $this->assertSame('Titre test', (string) $items[0]->title); $this->assertStringContainsString('titre-test', (string) $items[0]->link); $this->assertSame((string) $items[0]->link, (string) $items[0]->guid); } /** * feed() doit tronquer le contenu à 300 caractères dans la description. */ public function testFeedTruncatesLongContentTo300Chars(): void { $longContent = str_repeat('a', 400); $post = new Post(1, 'Titre', $longContent, 'titre', 1); $this->postService->method('getRecentPosts')->willReturn([$post]); $res = $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); $xml = simplexml_load_string((string) $res->getBody()); $this->assertNotFalse($xml); $description = (string) $xml->channel->item[0]->description; // 300 caractères + '…' = 301 octets UTF-8 pour le contenu visible $this->assertLessThanOrEqual(302, mb_strlen($description)); $this->assertStringEndsWith('…', $description); } /** * feed() doit appeler getRecentPosts() avec la limite de 20 articles. */ public function testFeedRequestsTwentyRecentPosts(): void { $this->postService->expects($this->once()) ->method('getRecentPosts') ->with(20) ->willReturn([]); $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); } /** * feed() doit inclure la balise author si l'article a un auteur. */ public function testFeedIncludesAuthorWhenPresent(): void { $post = new Post(1, 'Titre', 'Contenu', 'titre', 1, 'alice'); $this->postService->method('getRecentPosts')->willReturn([$post]); $res = $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); $xml = simplexml_load_string((string) $res->getBody()); $this->assertNotFalse($xml); $this->assertSame('alice', (string) $xml->channel->item[0]->author); } /** * feed() ne doit pas inclure la balise author si l'auteur est null. */ public function testFeedOmitsAuthorWhenNull(): void { $post = new Post(1, 'Titre', 'Contenu', 'titre', null, null); $this->postService->method('getRecentPosts')->willReturn([$post]); $res = $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); $xml = simplexml_load_string((string) $res->getBody()); $this->assertNotFalse($xml); $this->assertCount(0, $xml->channel->item[0]->author); } /** * feed() doit construire les URLs des articles en utilisant APP_URL. */ public function testFeedBuildsPostUrlsWithAppUrl(): void { $post = new Post(1, 'Titre', 'Contenu', 'mon-slug', 1); $this->postService->method('getRecentPosts')->willReturn([$post]); $res = $this->controller->feed($this->makeGet('/rss.xml'), $this->makeResponse()); $xml = simplexml_load_string((string) $res->getBody()); $this->assertNotFalse($xml); $this->assertStringStartsWith(self::APP_URL, (string) $xml->channel->item[0]->link); } }