A lax Web news feed parser
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

208 lines
7.2 KiB

<?php
/** @license MIT
* Copyright 2018 J. King
* See LICENSE and AUTHORS files for details */
declare(strict_types=1);
namespace JKingWeb\Lax\TestCase\JSON;
/* Test format is as follows:
Each test is a YAML map with the following keys:
- `input`: The test input, as a string or directly as a JSON Feed structure
- `output`: The result of the parsing upon success; described in more detail below
- `exception`: The exception ID thrown upon failure
- `type`: An HTTP Content-Type (with or without parameters) for the document
- `doc_url`: A fictitious URL where a newsfeed might be located, used for relative URL resolution
The 'input' key along with either 'output' or 'exception' are required for all tests.
The test output is necessarily mangled due to the limits of YAML:
- Any field which should be an absolute URL should be written as a string,
which will be transformed accordingly. Relative URLs should be represented
as a sequence with the relative part first, followed by the base that should
be applied to it
- Any collections should be represented as sequences of maps, which will
all be transformed accordingly
- Rich text can either be supplied as a string (which will yield a Text object
with plain-text content) or as a map with any of the properties of the
Text class listed
The transformations as performed by the `makeFeed` and `makeEntry` methods
of the abstract test case.
*/
use JKingWeb\Lax\Date;
use JKingWeb\Lax\Feed;
use JKingWeb\Lax\Entry;
use JKingWeb\Lax\Text;
use JKingWeb\Lax\Url;
use JKingWeb\Lax\Parser\Exception;
use JKingWeb\Lax\Parser\JSON\Feed as Parser;
use JKingWeb\Lax\Person\Person;
use JKingWeb\Lax\Category\Category;
use JKingWeb\Lax\Enclosure\Enclosure;
use JKingWeb\Lax\Person\Collection as PersonCollection;
use JKingWeb\Lax\Category\Collection as CategoryCollection;
use JKingWeb\Lax\Enclosure\Collection as EnclosureCollection;
use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Parser as YamlParser;
/**
* @covers JKingWeb\Lax\Parser\Construct<extended>
* @covers JKingWeb\Lax\Parser\JSON\Feed<extended>
* @covers JKingWeb\Lax\Parser\JSON\Entry<extended>
*/
class JSONTest extends \PHPUnit\Framework\TestCase {
/** @dataProvider provideJSONFeedVersion1 */
public function testJSONFeedVersion1($input, string $type, ?string $url, $output): void {
if (is_object($input)) {
$input = json_encode($input);
} elseif (!is_string($input)) {
throw new \Exception("Test input is invalid");
}
$p = new Parser($input, $type, $url);
if ($output instanceof \Exception) {
$this->expectExceptionObject($output);
$p->parse(new Feed);
} else {
$act = $p->parse(new Feed);
$exp = $this->makeFeed($output);
$this->assertEquals($exp, $act);
}
}
public function provideJSONFeedVersion1(): iterable {
foreach (new \GlobIterator(__DIR__."/*.yaml", \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::KEY_AS_FILENAME) as $file => $path) {
foreach ((new YamlParser)->parseFile($path, Yaml::PARSE_OBJECT_FOR_MAP) as $description => $test) {
if (isset($test->exception)) {
$test->output = new Exception((string) $test->exception);
}
yield "$file: {$description}" => [
$test->input,
$test->type ?? "",
$test->doc_url ?? null,
$test->output,
];
}
}
}
protected function makeFeed(\stdClass $output): Feed {
$f = new Feed;
foreach ($output as $k => $v) {
if (in_array($k, ["title", "summary"])) {
$f->$k = $this->makeText($v);
} elseif ($k === "people") {
$c = new PersonCollection;
foreach ($v as $m) {
$c[] = $this->makePerson($m);
}
$f->$k = $c;
} elseif ($k === "entries") {
$c = [];
foreach ($v as $m) {
$c[] = $this->makeEntry($m);
}
$f->$k = $c;
} elseif (in_array($k, ["meta", "sched"])) {
foreach ($v as $kk => $vv) {
if ($kk === "url") {
$f->$k->$kk = $this->makeUrl($vv);
} else {
$f->$k->$kk = $vv;
}
}
} elseif (in_array($k, ["url", "link", "icon", "image"])) {
$f->$k = $this->makeUrl($v);
} else {
$f->$k = $v;
}
}
return $f;
}
protected function makeEntry(\stdClass $entry): Entry {
$e = new Entry;
foreach ($entry as $k => $v) {
if (in_array($k, ["link", "relatedLink", "banner"])) {
$e->$k = $this->makeUrl($v);
} elseif (in_array($k, ["dateCreated", "dateModified"])) {
$e->$k = new Date($v, new \DateTimeZone("UTC"));
} elseif (in_array($k, ["title", "summary", "content"])) {
$e->$k = $this->makeText($v);
} elseif ($k === "people") {
$c = new PersonCollection;
foreach ($v as $m) {
$c[] = $this->makePerson($m);
}
$e->$k = $c;
} elseif ($k === "enclosures") {
$c = new EnclosureCollection;
foreach ($v as $m) {
$c[] = $this->makeEnclosure($m);
}
$e->$k = $c;
} elseif ($k === "categories") {
$c = new CategoryCollection;
foreach ($v as $m) {
$o = new Category;
foreach ($m as $kk => $vv) {
$o->$kk = $vv;
}
$c[] = $o;
}
$e->$k = $c;
} else {
$e->$k = $v;
}
}
return $e;
}
protected function makeText($data): Text {
if (is_string($data)) {
return new Text($data);
}
$out = new Text;
foreach ($data as $k => $v) {
$out->$k = $v;
}
return $out;
}
protected function makePerson(\stdClass $person): Person {
$p = new Person;
foreach ($person as $k => $v) {
if (in_array($k, ["url", "avatar"])) {
$p->$k = $this->makeUrl($v);
} else {
$p->$k = $v;
}
}
return $p;
}
protected function makeEnclosure(\stdClass $enclosure): Enclosure {
$e = new Enclosure;
foreach ($enclosure as $k => $v) {
if ($k === "urli") {
$e->$k = $this->makeUrl($v);
} else {
$e->$k = $v;
}
}
return $e;
}
protected function makeUrl($url): ?Url {
if (is_array($url)) {
return new Url($url[0] ?? "", ($url[1] ?? null) ? new Url($url[1]) : null);
} else {
return new Url($url);
}
}
}