Merge pull request '[MTM] Dependency updates' (#158) from develop into main
Reviewed-on: #158
This commit is contained in:
commit
dfa1192e64
21 changed files with 2015 additions and 2289 deletions
9
app/Exceptions/AuthorshipParserException.php
Normal file
9
app/Exceptions/AuthorshipParserException.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class AuthorshipParserException extends Exception {}
|
||||
9
app/Exceptions/InvalidMentionException.php
Normal file
9
app/Exceptions/InvalidMentionException.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidMentionException extends Exception {}
|
||||
|
|
@ -4,7 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Exceptions\AuthorshipParserException;
|
||||
use App\Models\Like;
|
||||
use App\Services\WebMention\Authorship;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
|
@ -12,8 +14,6 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Jonnybarnes\WebmentionsParser\Authorship;
|
||||
use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException;
|
||||
|
||||
class ProcessLike implements ShouldQueue
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Exceptions\InvalidMentionException;
|
||||
use App\Exceptions\RemoteContentNotFoundException;
|
||||
use App\Models\Note;
|
||||
use App\Models\WebMention;
|
||||
use App\Services\WebMention\Parser;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Jonnybarnes\WebmentionsParser\Exceptions\InvalidMentionException;
|
||||
use Jonnybarnes\WebmentionsParser\Parser;
|
||||
use Mf2;
|
||||
|
||||
class ProcessWebMention implements ShouldQueue
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Exceptions\AuthorshipParserException;
|
||||
use App\Services\WebMention\Authorship;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
|
|
@ -12,8 +14,6 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Jonnybarnes\WebmentionsParser\Authorship;
|
||||
use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException;
|
||||
|
||||
class SaveProfileImage implements ShouldQueue
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\WebMention\Authorship;
|
||||
use App\Traits\FilterHtml;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
||||
|
|
@ -13,7 +14,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Jonnybarnes\WebmentionsParser\Authorship;
|
||||
|
||||
#[Table('webmentions')]
|
||||
#[Guarded(['id'])]
|
||||
|
|
|
|||
152
app/Services/WebMention/Authorship.php
Normal file
152
app/Services/WebMention/Authorship.php
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\WebMention;
|
||||
|
||||
use App\Exceptions\AuthorshipParserException;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
use function Mf2\parse;
|
||||
|
||||
/**
|
||||
* Determine the author of a post from its parsed microformats, following
|
||||
* the IndieWeb authorship algorithm: https://indieweb.org/authorship
|
||||
*
|
||||
* Originally from the jonnybarnes/webmentions-parser package.
|
||||
*/
|
||||
class Authorship
|
||||
{
|
||||
/**
|
||||
* Find the author of the first h-entry (or h-feed) in the microformats.
|
||||
*
|
||||
* Returns the author's h-card, or their name as a string if that's all
|
||||
* we have, or false if the author can't be determined.
|
||||
*
|
||||
* @throws AuthorshipParserException
|
||||
*/
|
||||
public function findAuthor(array $mf, bool $permalink = true): array|string|false
|
||||
{
|
||||
$hEntry = null;
|
||||
$hFeed = null;
|
||||
foreach ($mf['items'] ?? [] as $item) {
|
||||
foreach ($item['type'] ?? [] as $type) {
|
||||
if ($type === 'h-entry') {
|
||||
$hEntry = $item;
|
||||
} elseif ($type === 'h-feed') {
|
||||
$hFeed = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($hEntry === null && $hFeed === null) {
|
||||
throw new AuthorshipParserException('No h-entry or h-feed found');
|
||||
}
|
||||
|
||||
// If the h-entry has an author property use that
|
||||
$author = $hEntry['properties']['author'] ?? null;
|
||||
|
||||
// A parent h-feed with an h-card takes precedence
|
||||
foreach ($hFeed['children'] ?? [] as $child) {
|
||||
if (($child['type'][0] ?? null) === 'h-card') {
|
||||
$author = $child;
|
||||
}
|
||||
}
|
||||
|
||||
$authorPage = null;
|
||||
|
||||
if ($author !== null) {
|
||||
// Flatten a single string entry
|
||||
if (is_array($author) && count($author) === 1 && is_string($author[0] ?? null)) {
|
||||
$author = $author[0];
|
||||
}
|
||||
|
||||
// A URL author is the author-page, anything else is the author itself
|
||||
if (is_string($author) && filter_var($author, FILTER_VALIDATE_URL)) {
|
||||
$authorPage = $author;
|
||||
} else {
|
||||
return $this->normalise($author);
|
||||
}
|
||||
}
|
||||
|
||||
// If no author-page and h-entry is a permalink then use the rel-author link
|
||||
if ($authorPage === null && $permalink && isset($mf['rels']['author'])) {
|
||||
$authorPage = is_array($mf['rels']['author']) ? $mf['rels']['author'][0] : $mf['rels']['author'];
|
||||
}
|
||||
|
||||
if ($authorPage === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->findAuthorOnPage($authorPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the author-page and look for a representative h-card on it.
|
||||
*
|
||||
* @throws AuthorshipParserException
|
||||
*/
|
||||
private function findAuthorOnPage(string $authorPage): array|false
|
||||
{
|
||||
try {
|
||||
$response = Http::get($authorPage);
|
||||
} catch (ConnectionException) {
|
||||
throw new AuthorshipParserException('Unable to connect to the author’s page');
|
||||
}
|
||||
|
||||
if ($response->failed()) {
|
||||
throw new AuthorshipParserException('Unable to get the content from the author’s page');
|
||||
}
|
||||
|
||||
$authorMf2 = parse($response->body(), $authorPage);
|
||||
$hCards = array_filter(
|
||||
$authorMf2['items'] ?? [],
|
||||
fn (array $item): bool => in_array('h-card', $item['type'] ?? [], true)
|
||||
);
|
||||
|
||||
// Use the first h-card where url == uid == author-page
|
||||
foreach ($hCards as $hCard) {
|
||||
if (
|
||||
in_array($authorPage, $hCard['properties']['url'] ?? [], true)
|
||||
&& in_array($authorPage, $hCard['properties']['uid'] ?? [], true)
|
||||
) {
|
||||
return $this->normalise($hCard);
|
||||
}
|
||||
}
|
||||
|
||||
// Else the first h-card with a url that matches a rel-me link on the page
|
||||
$relMeLinks = $authorMf2['rels']['me'] ?? [];
|
||||
foreach ($hCards as $hCard) {
|
||||
foreach ($hCard['properties']['url'] ?? [] as $url) {
|
||||
if (in_array($url, $relMeLinks, true)) {
|
||||
return $this->normalise($hCard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Else the first h-card with url == author-page
|
||||
foreach ($hCards as $hCard) {
|
||||
if (in_array($authorPage, $hCard['properties']['url'] ?? [], true)) {
|
||||
return $this->normalise($hCard);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function normalise(array|string $author): array|string
|
||||
{
|
||||
if (is_array($author) && array_key_exists(0, $author)) {
|
||||
$author = $author[0];
|
||||
}
|
||||
|
||||
if (is_string($author)) {
|
||||
return $author;
|
||||
}
|
||||
|
||||
unset($author['value'], $author['children']);
|
||||
|
||||
return $author;
|
||||
}
|
||||
}
|
||||
86
app/Services/WebMention/Parser.php
Normal file
86
app/Services/WebMention/Parser.php
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\WebMention;
|
||||
|
||||
use App\Exceptions\InvalidMentionException;
|
||||
|
||||
/**
|
||||
* Inspect the parsed microformats of a webmention source.
|
||||
*
|
||||
* Originally from the jonnybarnes/webmentions-parser package.
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
/**
|
||||
* Return the type of mention.
|
||||
*
|
||||
* @throws InvalidMentionException when the mention type can't be determined
|
||||
*/
|
||||
public function getMentionType(array $microformats): string
|
||||
{
|
||||
foreach (['in-reply-to', 'like-of', 'repost-of'] as $type) {
|
||||
if ($this->arrayKeyExistsRecursive($type, $microformats)) {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidMentionException;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a reply is to the intended target.
|
||||
*/
|
||||
public function checkInReplyTo(array $microformats, string $target): bool
|
||||
{
|
||||
foreach ($microformats['items'] ?? [] as $item) {
|
||||
$inReplyTo = $item['properties']['in-reply-to'] ?? null;
|
||||
if ($inReplyTo === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The reply context may be an h-cite rather than a plain URL
|
||||
if (is_array($inReplyTo[0]) && ($inReplyTo[0]['properties']['url'][0] ?? null) === $target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($target, $inReplyTo, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the microformats contain a like of the target.
|
||||
*/
|
||||
public function checkLikeOf(array $microformats, string $target): bool
|
||||
{
|
||||
return in_array($target, $microformats['items'][0]['properties']['like-of'] ?? [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the microformats contain a repost of the target.
|
||||
*/
|
||||
public function checkRepostOf(array $microformats, string $target): bool
|
||||
{
|
||||
return in_array($target, $microformats['items'][0]['properties']['repost-of'] ?? [], true);
|
||||
}
|
||||
|
||||
private function arrayKeyExistsRecursive(string $needle, array $haystack): bool
|
||||
{
|
||||
if (array_key_exists($needle, $haystack)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($haystack as $value) {
|
||||
if (is_array($value) && $this->arrayKeyExistsRecursive($needle, $value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -14,13 +14,13 @@
|
|||
"ext-pgsql": "*",
|
||||
"ext-sodium": "*",
|
||||
"cviebrock/eloquent-sluggable": "^13.0",
|
||||
"guzzlehttp/psr7": "^3.0",
|
||||
"indieauth/client": "^1.1",
|
||||
"intervention/image": "^4.0",
|
||||
"jonnybarnes/indieweb": "~0.2",
|
||||
"jonnybarnes/webmentions-parser": "~0.5",
|
||||
"laravel/framework": "^13.0",
|
||||
"laravel/horizon": "^5.0",
|
||||
"laravel/scout": "^10.1",
|
||||
"laravel/scout": "^11.0",
|
||||
"laravel/tinker": "^3.0",
|
||||
"league/commonmark": "^2.0",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
|
|
|
|||
2236
composer.lock
generated
2236
composer.lock
generated
File diff suppressed because it is too large
Load diff
1532
package-lock.json
generated
1532
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,8 +13,8 @@
|
|||
"globals": "^17.3.0",
|
||||
"lightningcss": "^1.29.3",
|
||||
"lightningcss-cli": "^1.29.3",
|
||||
"stylelint": "^17.3.0",
|
||||
"stylelint-config-standard": "^40.0.0"
|
||||
"stylelint": "^18.0.0",
|
||||
"stylelint-config-standard": "^41.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"eslint": "eslint resources/js/*.js",
|
||||
|
|
|
|||
2
public/assets/js/is-land.min.js
vendored
2
public/assets/js/is-land.min.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
|
@ -22,6 +22,8 @@
|
|||
<p>
|
||||
A tool to parse incoming webmentions to a site, including
|
||||
determining the author of the source webmention.
|
||||
This library is now archived and no longer maintained; its
|
||||
code now lives directly in this site.
|
||||
</p>
|
||||
</div>
|
||||
@stop
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ namespace Tests\Feature;
|
|||
|
||||
use App\Jobs\ProcessLike;
|
||||
use App\Models\Like;
|
||||
use App\Services\WebMention\Authorship;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Jonnybarnes\WebmentionsParser\Authorship;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
use Tests\TestToken;
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ use App\Jobs\ProcessWebMention;
|
|||
use App\Jobs\SaveProfileImage;
|
||||
use App\Models\Note;
|
||||
use App\Models\WebMention;
|
||||
use App\Services\WebMention\Parser;
|
||||
use Illuminate\FileSystem\FileSystem;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Jonnybarnes\WebmentionsParser\Parser;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Tests\Unit\Jobs;
|
||||
|
||||
use App\Exceptions\AuthorshipParserException;
|
||||
use App\Jobs\SaveProfileImage;
|
||||
use App\Services\WebMention\Authorship;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Jonnybarnes\WebmentionsParser\Authorship;
|
||||
use Jonnybarnes\WebmentionsParser\Exceptions\AuthorshipParserException;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
|
|
|||
167
tests/Unit/Services/WebMention/AuthorshipTest.php
Normal file
167
tests/Unit/Services/WebMention/AuthorshipTest.php
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Services\WebMention;
|
||||
|
||||
use App\Exceptions\AuthorshipParserException;
|
||||
use App\Services\WebMention\Authorship;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AuthorshipTest extends TestCase
|
||||
{
|
||||
private const string AUTHOR_PAGE = 'https://fred.example/';
|
||||
|
||||
private function entryBy(mixed $author, array $rels = []): array
|
||||
{
|
||||
return [
|
||||
'items' => [[
|
||||
'type' => ['h-entry'],
|
||||
'properties' => ['content' => ['A post'], 'author' => [$author]],
|
||||
]],
|
||||
'rels' => $rels,
|
||||
];
|
||||
}
|
||||
|
||||
private function hCard(string $name, array $extraProperties = []): array
|
||||
{
|
||||
return [
|
||||
'type' => ['h-card'],
|
||||
'properties' => array_merge(['name' => [$name], 'url' => [self::AUTHOR_PAGE]], $extraProperties),
|
||||
'value' => $name,
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_returns_an_embedded_author_h_card(): void
|
||||
{
|
||||
$author = (new Authorship)->findAuthor($this->entryBy($this->hCard('Fred Bloggs')));
|
||||
|
||||
$this->assertSame('Fred Bloggs', $author['properties']['name'][0]);
|
||||
$this->assertArrayNotHasKey('value', $author);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_returns_a_plain_author_name(): void
|
||||
{
|
||||
$this->assertSame('Fred Bloggs', (new Authorship)->findAuthor($this->entryBy('Fred Bloggs')));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_uses_an_h_card_in_the_parent_h_feed(): void
|
||||
{
|
||||
$mf = ['items' => [[
|
||||
'type' => ['h-feed'],
|
||||
'properties' => [],
|
||||
'children' => [
|
||||
['type' => ['h-entry'], 'properties' => ['content' => ['A post']]],
|
||||
$this->hCard('Feed Author'),
|
||||
],
|
||||
]], 'rels' => []];
|
||||
|
||||
$author = (new Authorship)->findAuthor($mf);
|
||||
|
||||
$this->assertSame('Feed Author', $author['properties']['name'][0]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_fetches_a_url_author_and_uses_the_h_card_whose_url_and_uid_match(): void
|
||||
{
|
||||
Http::fake([self::AUTHOR_PAGE => Http::response(<<<'HTML'
|
||||
<div class="h-card"><a class="p-name u-url" href="https://fred.example/">Not the uid one</a></div>
|
||||
<div class="h-card"><a class="p-name u-url u-uid" href="https://fred.example/">Fred Bloggs</a></div>
|
||||
HTML)]);
|
||||
|
||||
$author = (new Authorship)->findAuthor($this->entryBy(self::AUTHOR_PAGE));
|
||||
|
||||
$this->assertSame('Fred Bloggs', $author['properties']['name'][0]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_fetches_a_url_author_and_uses_the_h_card_matching_a_rel_me_link(): void
|
||||
{
|
||||
Http::fake([self::AUTHOR_PAGE => Http::response(<<<'HTML'
|
||||
<link rel="me" href="https://social.example/@fred">
|
||||
<div class="h-card"><span class="p-name">Someone Else</span><a class="u-url" href="https://elsewhere.example/"></a></div>
|
||||
<div class="h-card"><span class="p-name">Fred Bloggs</span><a class="u-url" href="https://social.example/@fred"></a></div>
|
||||
HTML)]);
|
||||
|
||||
$author = (new Authorship)->findAuthor($this->entryBy(self::AUTHOR_PAGE));
|
||||
|
||||
$this->assertSame('Fred Bloggs', $author['properties']['name'][0]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_fetches_a_url_author_and_uses_the_h_card_whose_url_is_the_author_page(): void
|
||||
{
|
||||
Http::fake([self::AUTHOR_PAGE => Http::response(<<<'HTML'
|
||||
<div class="h-card"><a class="p-name u-url" href="https://elsewhere.example/">Someone Else</a></div>
|
||||
<div class="h-card"><a class="p-name u-url" href="https://fred.example/">Fred Bloggs</a></div>
|
||||
HTML)]);
|
||||
|
||||
$author = (new Authorship)->findAuthor($this->entryBy(self::AUTHOR_PAGE));
|
||||
|
||||
$this->assertSame('Fred Bloggs', $author['properties']['name'][0]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_falls_back_to_the_rel_author_link(): void
|
||||
{
|
||||
Http::fake([self::AUTHOR_PAGE => Http::response(
|
||||
'<div class="h-card"><a class="p-name u-url" href="https://fred.example/">Fred Bloggs</a></div>'
|
||||
)]);
|
||||
$mf = ['items' => [['type' => ['h-entry'], 'properties' => ['content' => ['A post']]]], 'rels' => ['author' => [self::AUTHOR_PAGE]]];
|
||||
|
||||
$author = (new Authorship)->findAuthor($mf);
|
||||
|
||||
$this->assertSame('Fred Bloggs', $author['properties']['name'][0]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_ignores_the_rel_author_link_when_not_a_permalink(): void
|
||||
{
|
||||
Http::fake();
|
||||
$mf = ['items' => [['type' => ['h-entry'], 'properties' => ['content' => ['A post']]]], 'rels' => ['author' => [self::AUTHOR_PAGE]]];
|
||||
|
||||
$this->assertFalse((new Authorship)->findAuthor($mf, permalink: false));
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_returns_false_when_no_author_can_be_found(): void
|
||||
{
|
||||
Http::fake([self::AUTHOR_PAGE => Http::response('<p>No h-card here</p>')]);
|
||||
|
||||
$this->assertFalse((new Authorship)->findAuthor($this->entryBy(self::AUTHOR_PAGE)));
|
||||
$this->assertFalse((new Authorship)->findAuthor(['items' => [['type' => ['h-entry'], 'properties' => []]], 'rels' => []]));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_throws_without_an_h_entry_or_h_feed(): void
|
||||
{
|
||||
$this->expectException(AuthorshipParserException::class);
|
||||
|
||||
(new Authorship)->findAuthor(['items' => [$this->hCard('Fred Bloggs')], 'rels' => []]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_throws_when_the_author_page_cannot_be_fetched(): void
|
||||
{
|
||||
Http::fake([self::AUTHOR_PAGE => Http::response('Not found', 404)]);
|
||||
|
||||
$this->expectException(AuthorshipParserException::class);
|
||||
|
||||
(new Authorship)->findAuthor($this->entryBy(self::AUTHOR_PAGE));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function one_instance_can_be_reused_without_leaking_state(): void
|
||||
{
|
||||
$authorship = new Authorship;
|
||||
|
||||
$this->assertSame('Fred Bloggs', $authorship->findAuthor($this->entryBy('Fred Bloggs')));
|
||||
$this->assertFalse($authorship->findAuthor(['items' => [['type' => ['h-entry'], 'properties' => []]], 'rels' => []]));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue