2017-05-21 21:16:32 +00:00
|
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace JKingWeb\Arsse;
|
|
|
|
Use Phake;
|
|
|
|
|
|
|
|
|
|
|
|
class TestFeed extends \PHPUnit\Framework\TestCase {
|
|
|
|
use Test\Tools;
|
|
|
|
|
2017-05-22 03:26:36 +00:00
|
|
|
protected static $host = "http://localhost:8000/";
|
|
|
|
protected $base = "";
|
|
|
|
|
2017-05-21 21:16:32 +00:00
|
|
|
function setUp() {
|
2017-05-22 14:02:36 +00:00
|
|
|
if(!@file_get_contents(self::$host."IsUp")) {
|
2017-05-22 03:26:36 +00:00
|
|
|
$this->markTestSkipped("Test Web server is not accepting requests");
|
2017-05-27 22:15:52 +00:00
|
|
|
} else if(!extension_loaded('curl')) {
|
|
|
|
$this->markTestSkipped("Feed tests are only accurate with curl enabled.");
|
2017-05-22 03:26:36 +00:00
|
|
|
}
|
|
|
|
$this->base = self::$host."Feed/";
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->clearData();
|
|
|
|
Data::$conf = new Conf();
|
|
|
|
}
|
|
|
|
|
2017-05-27 22:15:52 +00:00
|
|
|
function testHandle400() {
|
|
|
|
$this->assertException("unsupportedFeedFormat", "Feed");
|
|
|
|
new Feed(null, $this->base."Fetching/Error?code=400");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testHandle401() {
|
|
|
|
$this->assertException("unauthorized", "Feed");
|
|
|
|
new Feed(null, $this->base."Fetching/Error?code=401");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testHandle403() {
|
|
|
|
$this->assertException("forbidden", "Feed");
|
|
|
|
new Feed(null, $this->base."Fetching/Error?code=403");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testHandle404() {
|
|
|
|
$this->assertException("invalidUrl", "Feed");
|
|
|
|
new Feed(null, $this->base."Fetching/Error?code=404");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testHandle500() {
|
|
|
|
$this->assertException("unsupportedFeedFormat", "Feed");
|
|
|
|
new Feed(null, $this->base."Fetching/Error?code=500");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testHandleARedirectLoop() {
|
|
|
|
$this->assertException("maxRedirect", "Feed");
|
|
|
|
new Feed(null, $this->base."Fetching/EndlessLoop?i=0");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testHandleATimeout() {
|
|
|
|
Data::$conf->fetchTimeout = 1;
|
|
|
|
$this->assertException("timeout", "Feed");
|
|
|
|
new Feed(null, $this->base."Fetching/Timeout");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testHandleAnOverlyLargeFeed() {
|
|
|
|
Data::$conf->fetchSizeLimit = 512;
|
|
|
|
$this->assertException("maxSize", "Feed");
|
|
|
|
new Feed(null, $this->base."Fetching/TooLarge");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testHandleACertificateError() {
|
|
|
|
$this->assertException("invalidCertificate", "Feed");
|
|
|
|
new Feed(null, "https://localhost:8000/");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testParseAFeed() {
|
|
|
|
// test that various properties are set on the feed and on items
|
|
|
|
$f = new Feed(null, $this->base."Parsing/Valid");
|
|
|
|
$this->assertTrue(isset($f->lastModified));
|
|
|
|
$this->assertTrue(isset($f->nextFetch));
|
|
|
|
// check ID preference cascade
|
|
|
|
$h0 = "0a4f0e3768c8a5e9d8d9a16545ae4ff5b097f6dac3ad49555a94a7cace68ba73"; // hash of Atom ID
|
|
|
|
$h1 = "a135beced0236b723d12f845ff20ec22d4fc3afe1130012618f027170d57cb4e"; // hash of RSS2 GUID
|
|
|
|
$h2 = "205e986f4f8b3acfa281227beadb14f5e8c32c8dae4737f888c94c0df49c56f8"; // hash of Dublin Core identifier
|
|
|
|
$this->assertSame($h0, $f->data->items[0]->id);
|
|
|
|
$this->assertSame($h1, $f->data->items[1]->id);
|
|
|
|
$this->assertSame($h2, $f->data->items[2]->id);
|
|
|
|
// check null hashes
|
|
|
|
$h3 = "6287ba30f534e404e68356237e809683e311285d8b9f47d046ac58784eece052"; // URL hash
|
|
|
|
$h4 = "6cbb5d2dcb11610a99eb3f633dc246690c0acf33327bf7534f95542caa8f27c4"; // title hash
|
|
|
|
$h5 = "2b7c57ffa9adde92ccd1884fa1153a5bcd3211e48d99e27be5414cb078e6891c"; // content/enclosure hash
|
|
|
|
$this->assertNotEquals("", $f->data->items[3]->urlTitleHash);
|
|
|
|
$this->assertSame($h3, $f->data->items[3]->urlContentHash);
|
|
|
|
$this->assertSame("", $f->data->items[3]->titleContentHash);
|
|
|
|
$this->assertNotEquals("", $f->data->items[4]->urlTitleHash);
|
|
|
|
$this->assertSame("", $f->data->items[4]->urlContentHash);
|
|
|
|
$this->assertSame($h4, $f->data->items[4]->titleContentHash);
|
|
|
|
$this->assertSame("", $f->data->items[5]->urlTitleHash);
|
|
|
|
$this->assertNotEquals("", $f->data->items[5]->urlContentHash);
|
|
|
|
$this->assertNotEquals("", $f->data->items[5]->titleContentHash);
|
|
|
|
// check null IDs
|
|
|
|
$this->assertSame("", $f->data->items[3]->id);
|
|
|
|
$this->assertSame("", $f->data->items[4]->id);
|
|
|
|
$this->assertSame("", $f->data->items[5]->id);
|
|
|
|
}
|
|
|
|
|
|
|
|
function testParseEntityExpansionAttack() {
|
|
|
|
$this->assertException("xmlEntity", "Feed");
|
|
|
|
new Feed(null, $this->base."Parsing/XEEAttack");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testParseExternalEntityAttack() {
|
|
|
|
$this->assertException("xmlEntity", "Feed");
|
|
|
|
new Feed(null, $this->base."Parsing/XXEAttack");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testParseAnUnsupportedFeed() {
|
|
|
|
$this->assertException("unsupportedFeedFormat", "Feed");
|
|
|
|
new Feed(null, $this->base."Parsing/Unsupported");
|
|
|
|
}
|
|
|
|
|
|
|
|
function testParseAMalformedFeed() {
|
|
|
|
$this->assertException("malformedXml", "Feed");
|
|
|
|
new Feed(null, $this->base."Parsing/Malformed");
|
|
|
|
}
|
|
|
|
|
2017-05-24 00:39:29 +00:00
|
|
|
function testDeduplicateFeedItems() {
|
2017-05-26 21:55:51 +00:00
|
|
|
// duplicates with dates lead to the newest match being kept
|
2017-05-24 00:39:29 +00:00
|
|
|
$t = strtotime("2002-05-19T15:21:36Z");
|
|
|
|
$f = new Feed(null, $this->base."Deduplication/Permalink-Dates");
|
|
|
|
$this->assertCount(2, $f->newItems);
|
|
|
|
$this->assertTime($t, $f->newItems[0]->updatedDate);
|
|
|
|
$f = new Feed(null, $this->base."Deduplication/ID-Dates");
|
|
|
|
$this->assertCount(2, $f->newItems);
|
|
|
|
$this->assertTime($t, $f->newItems[0]->updatedDate);
|
2017-05-24 02:15:57 +00:00
|
|
|
$f = new Feed(null, $this->base."Deduplication/IdenticalHashes");
|
|
|
|
$this->assertCount(2, $f->newItems);
|
|
|
|
$this->assertTime($t, $f->newItems[0]->updatedDate);
|
2017-05-26 21:55:51 +00:00
|
|
|
$f = new Feed(null, $this->base."Deduplication/Hashes-Dates1"); // content differs
|
|
|
|
$this->assertCount(2, $f->newItems);
|
|
|
|
$this->assertTime($t, $f->newItems[0]->updatedDate);
|
|
|
|
$f = new Feed(null, $this->base."Deduplication/Hashes-Dates2"); // title differs
|
|
|
|
$this->assertCount(2, $f->newItems);
|
|
|
|
$this->assertTime($t, $f->newItems[0]->updatedDate);
|
|
|
|
$f = new Feed(null, $this->base."Deduplication/Hashes-Dates3"); // URL differs
|
|
|
|
$this->assertCount(2, $f->newItems);
|
|
|
|
$this->assertTime($t, $f->newItems[0]->updatedDate);
|
|
|
|
// duplicates without dates lead to the topmost entry being kept
|
|
|
|
$f = new Feed(null, $this->base."Deduplication/Hashes");
|
|
|
|
$this->assertCount(2, $f->newItems);
|
|
|
|
$this->assertSame("http://example.com/1", $f->newItems[0]->url);
|
2017-05-24 00:39:29 +00:00
|
|
|
}
|
|
|
|
|
2017-05-22 17:01:38 +00:00
|
|
|
function testHandleCacheHeadersOn304() {
|
|
|
|
// upon 304, the client should re-use the caching header values it supplied the server
|
2017-05-22 14:02:36 +00:00
|
|
|
$t = time();
|
|
|
|
$e = "78567a";
|
|
|
|
$f = new Feed(null, $this->base."Caching/304Random", $this->dateTransform($t, "http"), $e);
|
|
|
|
$this->assertTime($t, $f->lastModified);
|
|
|
|
$this->assertSame($e, $f->resource->getETag());
|
|
|
|
$f = new Feed(null, $this->base."Caching/304ETagOnly", $this->dateTransform($t, "http"), $e);
|
|
|
|
$this->assertTime($t, $f->lastModified);
|
|
|
|
$this->assertSame($e, $f->resource->getETag());
|
|
|
|
$f = new Feed(null, $this->base."Caching/304LastModOnly", $this->dateTransform($t, "http"), $e);
|
|
|
|
$this->assertTime($t, $f->lastModified);
|
|
|
|
$this->assertSame($e, $f->resource->getETag());
|
|
|
|
$f = new Feed(null, $this->base."Caching/304None", $this->dateTransform($t, "http"), $e);
|
|
|
|
$this->assertTime($t, $f->lastModified);
|
|
|
|
$this->assertSame($e, $f->resource->getETag());
|
|
|
|
}
|
2017-05-22 17:01:38 +00:00
|
|
|
|
|
|
|
function testHandleCacheHeadersOn200() {
|
|
|
|
// these tests should trust the server-returned time, even in cases of obviously incorrect results
|
|
|
|
$t = time() - 2000;
|
|
|
|
$f = new Feed(null, $this->base."Caching/200Past");
|
|
|
|
$this->assertTime($t, $f->lastModified);
|
|
|
|
$this->assertNotEmpty($f->resource->getETag());
|
|
|
|
$t = time() - 2000;
|
|
|
|
$f = new Feed(null, $this->base."Caching/200Past", $this->dateTransform(time(), "http"));
|
|
|
|
$this->assertTime($t, $f->lastModified);
|
|
|
|
$this->assertNotEmpty($f->resource->getETag());
|
|
|
|
$t = time() + 2000;
|
|
|
|
$f = new Feed(null, $this->base."Caching/200Future");
|
|
|
|
$this->assertTime($t, $f->lastModified);
|
|
|
|
$this->assertNotEmpty($f->resource->getETag());
|
|
|
|
// these tests have no HTTP headers and rely on article dates
|
|
|
|
$t = strtotime("2002-05-19T15:21:36Z");
|
2017-05-22 21:06:01 +00:00
|
|
|
$f = new Feed(null, $this->base."Caching/200PubDateOnly");
|
2017-05-22 17:01:38 +00:00
|
|
|
$this->assertTime($t, $f->lastModified);
|
2017-05-22 21:06:01 +00:00
|
|
|
$f = new Feed(null, $this->base."Caching/200UpdateDate");
|
2017-05-22 17:01:38 +00:00
|
|
|
$this->assertTime($t, $f->lastModified);
|
2017-05-22 21:06:01 +00:00
|
|
|
$f = new Feed(null, $this->base."Caching/200Multiple");
|
2017-05-22 17:01:38 +00:00
|
|
|
$this->assertTime($t, $f->lastModified);
|
2017-05-22 21:06:01 +00:00
|
|
|
// this test has no dates at all and should report the current time
|
|
|
|
$t = time();
|
|
|
|
$f = new Feed(null, $this->base."Caching/200None");
|
|
|
|
$this->assertTime($t, $f->lastModified);
|
2017-05-22 17:01:38 +00:00
|
|
|
}
|
2017-05-22 14:02:36 +00:00
|
|
|
|
2017-05-21 23:51:03 +00:00
|
|
|
function testComputeNextFetchOnError() {
|
|
|
|
for($a = 0; $a < 100; $a++) {
|
|
|
|
if($a < 3) {
|
|
|
|
$this->assertTime("now + 5 minutes", Feed::nextFetchOnError($a));
|
|
|
|
} else if($a < 15) {
|
|
|
|
$this->assertTime("now + 3 hours", Feed::nextFetchOnError($a));
|
|
|
|
} else {
|
|
|
|
$this->assertTime("now + 1 day", Feed::nextFetchOnError($a));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-21 21:16:32 +00:00
|
|
|
function testComputeNextFetchFrom304() {
|
|
|
|
// if less than half an hour, check in 15 minutes
|
|
|
|
$t = strtotime("now");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 15 minutes");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
$t = strtotime("now - 29 minutes");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 15 minutes");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
// if less than an hour, check in 30 minutes
|
|
|
|
$t = strtotime("now - 30 minutes");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 30 minutes");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
$t = strtotime("now - 59 minutes");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 30 minutes");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
// if less than three hours, check in an hour
|
|
|
|
$t = strtotime("now - 1 hour");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 1 hour");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
$t = strtotime("now - 2 hours 59 minutes");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 1 hour");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
// if more than 36 hours, check in 24 hours
|
|
|
|
$t = strtotime("now - 36 hours");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 1 day");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
$t = strtotime("now - 2 years");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 1 day");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
// otherwise check in three hours
|
2017-05-21 23:51:03 +00:00
|
|
|
$t = strtotime("now - 3 hours");
|
2017-05-21 21:16:32 +00:00
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 3 hours");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
$t = strtotime("now - 35 hours");
|
|
|
|
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
|
2017-05-22 03:26:36 +00:00
|
|
|
$exp = strtotime("now + 3 hours");
|
2017-05-21 21:16:32 +00:00
|
|
|
$this->assertTime($exp, $f->nextFetch);
|
|
|
|
}
|
|
|
|
}
|