xref: /dokuwiki/_test/tests/Feed/FeedParserTest.php (revision 7930587300104cf1bc6f40d322f168238a9fdfdc)
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