Some tests & implementation around verifying and parsing received webmentions
This commit is contained in:
parent
83ec00c8a0
commit
38885beb74
@ -2,19 +2,39 @@
|
||||
|
||||
namespace Lewisdale\Webmentions;
|
||||
|
||||
use League\Uri\Exceptions\SyntaxError;
|
||||
use Lewisdale\Webmentions\Exceptions\InvalidTargetException;
|
||||
use Lewisdale\Webmentions\Exceptions\InvalidUrlException;
|
||||
use Lewisdale\Webmentions\Gateways\WebmentionGatewayInterface;
|
||||
use Lewisdale\Webmentions\Models\Webmention;
|
||||
use Symfony\Component\HttpClient\HttpClient;
|
||||
use League\Uri\Uri;
|
||||
use Lewisdale\Webmentions\Exceptions\SourceNotFoundException;
|
||||
use Lewisdale\Webmentions\Exceptions\TargetNotMentionedException;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
class Endpoint {
|
||||
private readonly WebmentionGatewayInterface $gateway;
|
||||
|
||||
public function validateUrl(string $url) : bool {
|
||||
return !!filter_var($url, FILTER_VALIDATE_URL);
|
||||
}
|
||||
function __construct(
|
||||
private readonly HttpClientInterface $httpClient
|
||||
)
|
||||
{}
|
||||
|
||||
public function validateUrl(string $url) : bool {
|
||||
try {
|
||||
$uri = Uri::createFromString($url);
|
||||
$scheme = $uri->getScheme();
|
||||
$schemeValid = in_array($scheme, ["http", "https"]);
|
||||
|
||||
return $schemeValid && !!filter_var($url, FILTER_VALIDATE_URL);
|
||||
|
||||
} catch (SyntaxError $e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public function receiveWebmention(string $source, string $target) : void
|
||||
{
|
||||
// Validate that both source and target are actual domains
|
||||
@ -29,21 +49,29 @@ class Endpoint {
|
||||
}
|
||||
|
||||
// Parse content from the source
|
||||
$client = HttpClient::create();
|
||||
$response = $client->request('GET', $source);
|
||||
$response = $this->httpClient->request('GET', $source);
|
||||
|
||||
if ($response->getStatusCode() === 200)
|
||||
if ($response->getStatusCode() < 400)
|
||||
{
|
||||
$content = $this->parseContent($response->getContent());
|
||||
$content = $this->parseContent($response->getContent(), $target);
|
||||
$author = $this->parseAuthor($response->getContent());
|
||||
|
||||
$webmention = new Webmention(null, $target, $source, $content, $author);
|
||||
$this->gateway->save($webmention);
|
||||
} else {
|
||||
throw new SourceNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
private function parseContent(string $content) : ?string
|
||||
private function parseContent(string $content, string $target) : ?string
|
||||
{
|
||||
$body = new Crawler($content);
|
||||
$anchors = $body->filter('a[href="'. $target . '"]');
|
||||
|
||||
if (!$anchors->count()) {
|
||||
throw new TargetNotMentionedException();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
8
src/Exceptions/SourceNotFoundException.php
Normal file
8
src/Exceptions/SourceNotFoundException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lewisdale\Webmentions\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class SourceNotFoundException extends Exception {}
|
||||
?>
|
8
src/Exceptions/TargetNotMentionedException.php
Normal file
8
src/Exceptions/TargetNotMentionedException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Lewisdale\Webmentions\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class TargetNotMentionedException extends Exception {}
|
||||
?>
|
@ -1,16 +1,105 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use Lewisdale\Webmentions\Endpoint;
|
||||
use Lewisdale\Webmentions\Exceptions\InvalidTargetException;
|
||||
use Lewisdale\Webmentions\Exceptions\InvalidUrlException;
|
||||
use Lewisdale\Webmentions\Exceptions\SourceNotFoundException;
|
||||
use Lewisdale\Webmentions\Exceptions\TargetNotMentionedException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\Attributes\TestWith;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
class EndpointTest extends TestCase {
|
||||
#[TestWith(["https://my.url.com", true])]
|
||||
#[TestWith(["my.url.com", false])]
|
||||
public function testValidatesUrls(string $url, bool $expected)
|
||||
{
|
||||
$endpoint = new Endpoint();
|
||||
$endpoint = new Endpoint($this->createMock(HttpClientInterface::class));
|
||||
$this->assertEquals($expected, $endpoint->validateUrl($url), "Expected $url");
|
||||
}
|
||||
|
||||
#[TestWith(["https://my-valid-source.url", "htt://my-invalid-target.com"])]
|
||||
#[TestWith(["my-invalid-source", "https://my-valid-target.com"])]
|
||||
#[TestWith(["http:///an-invalid-source", "an-invalid-target"])]
|
||||
public function testThrowsInvalidUrlExceptionIfTheUrlIsInvalid(string $source, string $target)
|
||||
{
|
||||
$this->expectException(InvalidUrlException::class);
|
||||
$endpoint = new Endpoint($this->createMock(HttpClientInterface::class));
|
||||
$endpoint->receiveWebmention($source, $target);
|
||||
}
|
||||
|
||||
public function testThrowsInvalidTargetExceptionIfTheTargetUrlIsNotOnCurrentSite()
|
||||
{
|
||||
$this->expectException(InvalidTargetException::class);
|
||||
$endpoint = new Endpoint($this->createMock(HttpClientInterface::class));
|
||||
$endpoint->receiveWebmention("https://a-valid-source.url", "https://not-my-site.com");
|
||||
}
|
||||
|
||||
#[TestWith([404])]
|
||||
#[TestWith([401])]
|
||||
#[TestWith([500])]
|
||||
#[TestWith([501])]
|
||||
#[TestWith([502])]
|
||||
#[TestWith([503])]
|
||||
#[TestWith([500])]
|
||||
public function testItShouldThrowASourceNotFoundExceptionIfTheSourceUrlIsUnreachable(int $statusCode)
|
||||
{
|
||||
$source = "https://my-valid-source-url.com";
|
||||
$target = "https://lewisdale.dev/post/a-post-page";
|
||||
|
||||
$mockClient = $this->createMock(HttpClientInterface::class);
|
||||
$mockResponse = $this->createMock(ResponseInterface::class);
|
||||
|
||||
$mockClient->expects($this->once())
|
||||
->method('request')
|
||||
->with($this->identicalTo('GET'), $this->identicalTo($source))
|
||||
->will($this->returnValue($mockResponse));
|
||||
|
||||
$mockResponse->method('getStatusCode')
|
||||
->will($this->returnValue($statusCode));
|
||||
|
||||
$this->expectException(SourceNotFoundException::class);
|
||||
|
||||
$endpoint = new Endpoint($mockClient);
|
||||
$endpoint->receiveWebmention($source, $target);
|
||||
}
|
||||
|
||||
public function testItShouldThrowATargetNotMentionedErrorIfTheSourceDoesNotMentionTheTarget()
|
||||
{
|
||||
$source = "https://my-valid-source-url.com";
|
||||
$target = "https://lewisdale.dev/post/a-post-page";
|
||||
|
||||
$content = <<<XML
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Some content</h1>
|
||||
<p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
|
||||
<p>But it does not mention the target.</p>
|
||||
</body>
|
||||
</html>
|
||||
XML;
|
||||
|
||||
$mockClient = $this->createMock(HttpClientInterface::class);
|
||||
$mockResponse = $this->createMock(ResponseInterface::class);
|
||||
|
||||
$mockClient->expects($this->once())
|
||||
->method('request')
|
||||
->with($this->identicalTo('GET'), $this->identicalTo($source))
|
||||
->will($this->returnValue($mockResponse));
|
||||
|
||||
$mockResponse->method('getStatusCode')
|
||||
->will($this->returnValue(200));
|
||||
|
||||
$mockResponse->method('getContent')
|
||||
->willReturn($content);
|
||||
|
||||
$this->expectException(TargetNotMentionedException::class);
|
||||
|
||||
$endpoint = new Endpoint($mockClient);
|
||||
$endpoint->receiveWebmention($source, $target);
|
||||
}
|
||||
}
|
||||
?>
|
Loading…
Reference in New Issue
Block a user