1<?php 2 3namespace dokuwiki\test\Feed; 4 5use dokuwiki\Feed\FeedParser; 6use dokuwiki\Feed\FeedParserFile; 7use dokuwiki\HTTP\DokuHTTPClient; 8 9/** 10 * Tests for fetching and parsing remote feeds 11 */ 12class FeedParserTest extends \DokuWikiTest 13{ 14 protected const FEED = <<<XML 15<?xml version="1.0" encoding="UTF-8"?> 16<rss version="2.0"><channel> 17<title>Example Feed</title> 18<item><title>First Item</title><link>http://example.com/1</link><description>One</description></item> 19<item><title>Second Item</title><link>http://example.com/2</link><description>Two</description></item> 20</channel></rss> 21XML; 22 23 /** 24 * Builds a FeedParserFile that fetches through a mocked DokuHTTPClient. 25 * 26 * FeedParserFile creates its client in initHTTPClient() and maps the 27 * DokuHTTPClient fields onto the SimplePie response in its constructor, so we stub 28 * the factory method and then run the real constructor against the canned response. 29 * 30 * @return FeedParserFile 31 */ 32 protected function fetchedFile($status, $error, $body, $headers = ['content-type' => 'text/xml']) 33 { 34 $http = $this->createMock(DokuHTTPClient::class); 35 $http->method('sendRequest')->willReturnCallback( 36 function () use ($http, $status, $error, $body, $headers) { 37 $http->status = $status; 38 $http->error = $error; 39 $http->resp_body = $body; 40 $http->resp_headers = $headers; 41 return $status >= 200 && $status < 300; 42 } 43 ); 44 45 $file = $this->getMockBuilder(FeedParserFile::class) 46 ->onlyMethods(['initHTTPClient']) 47 ->disableOriginalConstructor() 48 ->getMock(); 49 $file->method('initHTTPClient')->willReturn($http); 50 $file->__construct('http://example.com/feed.xml'); 51 52 return $file; 53 } 54 55 /** 56 * On success DokuHTTPClient reports an empty string error and exposes the status 57 * separately. SimplePie's FileClient rejects any response whose error is non-null 58 * while the status code is zero, so the fetched file has to carry a real status code 59 * and a null error. Regression test for feed aggregation breaking after the 60 * SimplePie 1.9 upgrade. 61 */ 62 public function testSuccessfulResponseIsAcceptedBySimplePie() 63 { 64 $file = $this->fetchedFile(200, '', self::FEED); 65 66 $this->assertSame(200, $file->get_status_code()); 67 $this->assertNull($file->error); 68 $this->assertNotEmpty($file->get_body_content()); 69 } 70 71 /** 72 * A genuine connection failure must stay distinguishable from a success 73 */ 74 public function testFailedResponseKeepsStatusAndError() 75 { 76 $file = $this->fetchedFile(-100, 'Could not connect to server', ''); 77 78 $this->assertSame(-100, $file->get_status_code()); 79 $this->assertSame('Could not connect to server', $file->error); 80 } 81 82 /** 83 * The fetched body is parsed into feed items 84 */ 85 public function testFeedItemsAreParsed() 86 { 87 $feed = new FeedParser(); 88 $feed->enable_order_by_date(false); 89 $feed->set_feed_url('http://example.com/feed.xml'); 90 $feed->set_file($this->fetchedFile(200, '', self::FEED)); 91 $rc = $feed->init(); 92 93 $this->assertTrue($rc); 94 $this->assertSame(2, $feed->get_item_quantity()); 95 $this->assertSame('First Item', $feed->get_item(0)->get_title()); 96 $this->assertSame('http://example.com/1', $feed->get_item(0)->get_permalink()); 97 $this->assertSame('Second Item', $feed->get_item(1)->get_title()); 98 } 99} 100