Browse Source

Start on tests for Feed

- Makes use of PHP's internal Web server to deliver expected responses from a real server
- Windows batch file can be used to run tests (Linux and Mac test runners to come later)
- Added PHPUnit to dev dependencies
microsub
J. King 7 years ago
parent
commit
590abaf0ef
  1. 3
      composer.json
  2. 1131
      composer.lock
  3. 2
      lib/Db/AbstractStatement.php
  4. 44
      lib/Misc/DateFormatter.php
  5. 63
      tests/Feed/TestFeed.php
  6. 4
      tests/docroot/Feed/NextFetch/NotModified.php
  7. 8
      tests/lib/Tools.php
  8. 3
      tests/phpunit.xml
  9. 66
      tests/server.php
  10. 10
      tests/test.bat

3
composer.json

@ -29,7 +29,8 @@
},
"require-dev": {
"mikey179/vfsStream": "^1.6.4",
"phake/phake": "^2.3.2"
"phake/phake": "^2.3.2",
"phpunit/phpunit": "^6.0.5"
},
"autoload": {
"psr-4": {

1131
composer.lock

File diff suppressed because it is too large

2
lib/Db/AbstractStatement.php

@ -95,6 +95,6 @@ abstract class AbstractStatement implements Statement {
$time = (int) $date;
}
// ISO 8601 with space in the middle instead of T.
return date($this->dateFormat($part), $time);
return gmdate($this->dateFormat($part), $time);
}
}

44
lib/Misc/DateFormatter.php

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace JKingWeb\Arsse\Misc;
trait DateFormatter {
protected function dateTransform($date, string $format = "iso8601", bool $local = false) {
$date = $this->dateNormalize($date);
$format = strtolower($format);
if($format=="unix") return $date;
switch ($format) {
case 'http': $f = "D, d M Y H:i:s \G\M\T"; break;
case 'iso8601': $f = \DateTime::ATOM; break;
case 'sql': $f = "Y-m-d H:i:s"; break;
case 'date': $f = "Y-m-d"; break;
case 'time': $f = "H:i:s"; break;
default: $f = \DateTime::ATOM; break;
}
if($local) {
return date($f, $date);
} else {
return gmdate($f, $date);
}
}
protected function dateNormalize($date) {
// convert input to a Unix timestamp
if($date instanceof \DateTimeInterface) {
$time = $date->getTimestamp();
} else if(is_numeric($date)) {
$time = (int) $date;
} else if($date===null) {
return null;
} else if(is_string($date)) {
$time = strtotime($date);
if($time===false) return null;
} else if (is_bool($date)) {
return null;
} else {
$time = (int) $date;
}
return $time;
}
}

63
tests/Feed/TestFeed.php

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace JKingWeb\Arsse;
Use Phake;
class TestFeed extends \PHPUnit\Framework\TestCase {
use Test\Tools;
protected $base = "http://localhost:8000/Feed/";
function time(string $t): string {
return gmdate("D, d M Y H:i:s \G\M\T", strtotime($t));
}
function setUp() {
$this->clearData();
Data::$conf = new Conf();
}
function testComputeNextFetchFrom304() {
// if less than half an hour, check in 15 minutes
$exp = strtotime("now + 15 minutes");
$t = strtotime("now");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$this->assertTime($exp, $f->nextFetch);
$t = strtotime("now - 29 minutes");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$this->assertTime($exp, $f->nextFetch);
// if less than an hour, check in 30 minutes
$exp = strtotime("now + 30 minutes");
$t = strtotime("now - 30 minutes");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$this->assertTime($exp, $f->nextFetch);
$t = strtotime("now - 59 minutes");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$this->assertTime($exp, $f->nextFetch);
// if less than three hours, check in an hour
$exp = strtotime("now + 1 hour");
$t = strtotime("now - 1 hour");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$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"));
$this->assertTime($exp, $f->nextFetch);
// if more than 36 hours, check in 24 hours
$exp = strtotime("now + 1 day");
$t = strtotime("now - 36 hours");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$this->assertTime($exp, $f->nextFetch);
$t = strtotime("now - 2 years");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$this->assertTime($exp, $f->nextFetch);
// otherwise check in three hours
$exp = strtotime("now + 3 hours");
$t = strtotime("now - 6 hours");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$this->assertTime($exp, $f->nextFetch);
$t = strtotime("now - 35 hours");
$f = new Feed(null, $this->base."NextFetch/NotModified?t=$t", $this->dateTransform($t, "http"));
$this->assertTime($exp, $f->nextFetch);
}
}

4
tests/docroot/Feed/NextFetch/NotModified.php

@ -0,0 +1,4 @@
<?php return [
'code' => 304,
'lastMod' => (int) $_GET['t'],
];

8
tests/lib/Tools.php

@ -5,6 +5,8 @@ use JKingWeb\Arsse\Exception;
use JKingWeb\Arsse\Data;
trait Tools {
use \JKingWeb\Arsse\Misc\DateFormatter;
function assertException(string $msg, string $prefix = "", string $type = "Exception") {
$class = \JKingWeb\Arsse\NS_BASE . ($prefix !== "" ? str_replace("/", "\\", $prefix) . "\\" : "") . $type;
$msgID = ($prefix !== "" ? $prefix . "/" : "") . $type. ".$msg";
@ -17,6 +19,12 @@ trait Tools {
$this->expectExceptionCode($code);
}
function assertTime($exp, $test) {
$exp = $this->dateTransform($exp);
$test = $this->dateTransform($test);
$this->assertSame($exp, $test);
}
function clearData(bool $loadLang = true): bool {
$r = new \ReflectionClass(\JKingWeb\Arsse\Data::class);
$props = array_keys($r->getStaticProperties());

3
tests/phpunit.xml

@ -43,5 +43,8 @@
<file>REST/NextCloudNews/TestNCNVersionDiscovery.php</file>
<file>REST/NextCloudNews/TestNCNV1_2.php</file>
</testsuite>
<testsuite name="Feed parser">
<file>Feed/TestFeed.php</file>
</testsuite>
</testsuites>
</phpunit>

66
tests/server.php

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace JKingWeb\Arsse;
require_once __DIR__."/../bootstrap.php";
/*
This is a so-called router for the the internal PHP Web server:
<http://php.net/manual/en/features.commandline.webserver.php>
It is used to test feed parsing in a controlled environment,
answering specific requests used in tests with the data required
to pass the test.
The parameters of the responses are kept in separate files,
which include the following data:
- Response content
- Response code
- Content type
- Whether to send cache headers
- Last modified
- Any other headers
*/
$defaults = [ // default values for response
'code' => 200,
'content' => "",
'mime' => "application/octet-stream",
'lastMod' => time(),
'cache' => true,
'fields' => [],
];
$url = explode("?",$_SERVER['REQUEST_URI'])[0];
$base = BASE."tests".\DIRECTORY_SEPARATOR."docroot";
$test = $base.str_replace("/",\DIRECTORY_SEPARATOR,$url).".php";
if(!file_exists($test)) {
$response = [
'code' => 499,
'content' => "Test '$test' missing.",
'mime' => "application/octet-stream",
'lastMod' => time(),
'cache' => true,
'fields' => [],
];
} else {
$response = array_merge($defaults, (include $test));
}
// set the response code
http_response_code((int) $response['code']);
// if the response has a body, set the content type and (possibly) the ETag.
if(strlen($response['content'])) {
header("Content-Type: ".$response['mime']);
if($response['cache']) header("ETag: ".md5($response['content']));
}
// if caching is enabled, set the last-modified date
if($response['cache']) header("Last-Modified: ".gmdate("D, d M Y H:i:s \G\M\T", $response['lastMod']));
// set any other specified fields verbatim
foreach($response['fields'] as $h) {
header($h);
}
// send the content
echo $response['content'];

10
tests/test.bat

@ -0,0 +1,10 @@
@echo off
setlocal
set base=%~dp0
start php -S localhost:8000 "%base%\server.php"
php "%base%\..\vendor\phpunit\phpunit\phpunit" -c "%base%\phpunit.xml"
timeout /nobreak /t 1 >nul
for /f "usebackq tokens=5" %%a in (`netstat -aon ^| find "LISTENING" ^| find ":8000"`) do (
taskkill /pid %%a >nul
goto :eof
)
Loading…
Cancel
Save