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