462 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			462 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?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 Lewisdale\Webmentions\Gateways\WebmentionGatewayInterface;
 | 
						|
use Lewisdale\Webmentions\Models\MentionType;
 | 
						|
use PHPUnit\Framework\Attributes\TestWith;
 | 
						|
use PHPUnit\Framework\TestCase;
 | 
						|
use Symfony\Contracts\HttpClient\HttpClientInterface;
 | 
						|
use Symfony\Contracts\HttpClient\ResponseInterface;
 | 
						|
 | 
						|
class EndpointTest extends TestCase
 | 
						|
{
 | 
						|
    private function objectContains(string $key, mixed $expected)
 | 
						|
    {
 | 
						|
        return $this->callback(function (object $obj) use ($expected, $key) {
 | 
						|
            $val = $obj->$key;
 | 
						|
            $type = gettype($val);
 | 
						|
 | 
						|
            return match ($type) {
 | 
						|
                "object" => $val == $expected,
 | 
						|
                "array" => count(array_diff($val, $expected)) === 0,
 | 
						|
                default => $val === $expected
 | 
						|
            };
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    #[TestWith(["https://my.url.com", true])]
 | 
						|
    #[TestWith(["my.url.com", false])]
 | 
						|
    public function testValidatesUrls(string $url, bool $expected)
 | 
						|
    {
 | 
						|
        $endpoint = new Endpoint($this->createMock(HttpClientInterface::class), $this->createMock(WebmentionGatewayInterface::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), $this->createMock(WebmentionGatewayInterface::class));
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testThrowsInvalidTargetExceptionIfTheTargetUrlIsNotOnCurrentSite()
 | 
						|
    {
 | 
						|
        $this->expectException(InvalidTargetException::class);
 | 
						|
        $endpoint = new Endpoint($this->createMock(HttpClientInterface::class), $this->createMock(WebmentionGatewayInterface::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, $this->createMock(WebmentionGatewayInterface::class));
 | 
						|
        $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, $this->createMock(WebmentionGatewayInterface::class));
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testItShouldParseAWebmentionAsALikeIfItHasTheCorrectMicroFormat()
 | 
						|
    {
 | 
						|
        $source = "https://my-valid-source-url.com";
 | 
						|
        $target = "https://lewisdale.dev/post/a-post-page";
 | 
						|
 | 
						|
        $content = <<<XML
 | 
						|
        <html>
 | 
						|
            <head>
 | 
						|
            </head>
 | 
						|
            <body>
 | 
						|
                <article>
 | 
						|
                    <h1>Some content</h1>
 | 
						|
                    <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
 | 
						|
                    <p>I'm liking <a href="$target" class="u-like-of">this post</a>.</p>
 | 
						|
                </article>
 | 
						|
            </body>
 | 
						|
        </html>
 | 
						|
        XML;
 | 
						|
 | 
						|
        $mockClient = $this->createMock(HttpClientInterface::class);
 | 
						|
        $mockResponse = $this->createMock(ResponseInterface::class);
 | 
						|
        $mockGateway = $this->createMock(WebmentionGatewayInterface::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);
 | 
						|
 | 
						|
        $mockGateway->expects($this->once())
 | 
						|
            ->method('save')
 | 
						|
            ->with($this->objectContains('type', MentionType::Like));
 | 
						|
 | 
						|
        $endpoint = new Endpoint($mockClient, $mockGateway);
 | 
						|
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testItShouldParseAWebmentionAsAMentionIfItHasNoMicroformat()
 | 
						|
    {
 | 
						|
        $source = "https://my-valid-source-url.com";
 | 
						|
        $target = "https://lewisdale.dev/post/a-post-page";
 | 
						|
 | 
						|
        $content = <<<XML
 | 
						|
        <html>
 | 
						|
            <head>
 | 
						|
            </head>
 | 
						|
            <body>
 | 
						|
                <article class="h-entry">
 | 
						|
                    <h1>Some content</h1>
 | 
						|
                    <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
 | 
						|
                    <p>I'm writing about <a href="$target" class="my-link">this post</a>.</p>                
 | 
						|
                </article>
 | 
						|
            </body>
 | 
						|
        </html>
 | 
						|
        XML;
 | 
						|
 | 
						|
        $mockClient = $this->createMock(HttpClientInterface::class);
 | 
						|
        $mockResponse = $this->createMock(ResponseInterface::class);
 | 
						|
        $mockGateway = $this->createMock(WebmentionGatewayInterface::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);
 | 
						|
 | 
						|
        $mockGateway->expects($this->once())
 | 
						|
            ->method('save')
 | 
						|
            ->with($this->objectContains('type', MentionType::Mention));
 | 
						|
 | 
						|
        $endpoint = new Endpoint($mockClient, $mockGateway);
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testItShouldParseAWebmentionAsAReplyIfItHasTheCorrectMicroFormat()
 | 
						|
    {
 | 
						|
        $source = "https://my-valid-source-url.com";
 | 
						|
        $target = "https://lewisdale.dev/post/a-post-page";
 | 
						|
 | 
						|
        $content = <<<XML
 | 
						|
        <html>
 | 
						|
            <head>
 | 
						|
            </head>
 | 
						|
            <body>
 | 
						|
                <article class="h-entry">
 | 
						|
                    <h1>Some content</h1>
 | 
						|
                    <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
 | 
						|
                    <p>I'm writing about <a href="$target" class="u-in-reply-to">this post</a>.</p>
 | 
						|
                </article>
 | 
						|
            </body>
 | 
						|
        </html>
 | 
						|
        XML;
 | 
						|
 | 
						|
        $mockClient = $this->createMock(HttpClientInterface::class);
 | 
						|
        $mockResponse = $this->createMock(ResponseInterface::class);
 | 
						|
        $mockGateway = $this->createMock(WebmentionGatewayInterface::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);
 | 
						|
 | 
						|
        $mockGateway->expects($this->once())
 | 
						|
            ->method('save')
 | 
						|
            ->with($this->objectContains('type', MentionType::Reply));
 | 
						|
 | 
						|
        $endpoint = new Endpoint($mockClient, $mockGateway);
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testItShouldParseAWebmentionAsARepost()
 | 
						|
    {
 | 
						|
        $source = "https://my-valid-source-url.com";
 | 
						|
        $target = "https://lewisdale.dev/post/a-post-page";
 | 
						|
 | 
						|
        $content = <<<XML
 | 
						|
        <html>
 | 
						|
            <head>
 | 
						|
            </head>
 | 
						|
            <body>
 | 
						|
                <article="h-entry">
 | 
						|
                    <h1>Some content</h1>
 | 
						|
                    <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
 | 
						|
                    <p>I'm writing about <a href="$target" class="u-repost-of">this post</a>.</p>
 | 
						|
                </article>
 | 
						|
            </body>
 | 
						|
        </html>
 | 
						|
        XML;
 | 
						|
 | 
						|
        $mockClient = $this->createMock(HttpClientInterface::class);
 | 
						|
        $mockResponse = $this->createMock(ResponseInterface::class);
 | 
						|
        $mockGateway = $this->createMock(WebmentionGatewayInterface::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);
 | 
						|
 | 
						|
        $mockGateway->expects($this->once())
 | 
						|
            ->method('save')
 | 
						|
            ->with($this->objectContains('type', MentionType::Repost));
 | 
						|
 | 
						|
        $endpoint = new Endpoint($mockClient, $mockGateway);
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testItShouldParseARepostsContent()
 | 
						|
    {
 | 
						|
        $source = "https://my-valid-source-url.com";
 | 
						|
        $target = "https://lewisdale.dev/post/a-post-page";
 | 
						|
 | 
						|
        $content = <<<XML
 | 
						|
        <html>
 | 
						|
            <head>
 | 
						|
            </head>
 | 
						|
            <body>
 | 
						|
                <article="h-entry">
 | 
						|
                    <h1>Some content</h1>
 | 
						|
                    <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
 | 
						|
                    <p>I'm writing about <a href="$target" class="u-repost-of">this post</a>.</p>
 | 
						|
                </article>
 | 
						|
            </body>
 | 
						|
        </html>
 | 
						|
        XML;
 | 
						|
 | 
						|
        $mockClient = $this->createMock(HttpClientInterface::class);
 | 
						|
        $mockResponse = $this->createMock(ResponseInterface::class);
 | 
						|
        $mockGateway = $this->createMock(WebmentionGatewayInterface::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);
 | 
						|
 | 
						|
        $mockGateway->expects($this->once())
 | 
						|
            ->method('save')
 | 
						|
            ->with($this->objectContains('content', "Reposted this post"));
 | 
						|
 | 
						|
        $endpoint = new Endpoint($mockClient, $mockGateway);
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testItShouldParseALikeContent()
 | 
						|
    {
 | 
						|
        $source = "https://my-valid-source-url.com";
 | 
						|
        $target = "https://lewisdale.dev/post/a-post-page";
 | 
						|
 | 
						|
        $content = <<<HTML
 | 
						|
        <html>
 | 
						|
            <head>
 | 
						|
            </head>
 | 
						|
            <body>
 | 
						|
                <span class="h-entry">
 | 
						|
                    <a class="u-like-of" href="$target">A Cool Post</a><a class="u-url" href="/"></a>
 | 
						|
                </span>
 | 
						|
            </body>
 | 
						|
        </html>
 | 
						|
        HTML;
 | 
						|
 | 
						|
        $mockClient = $this->createMock(HttpClientInterface::class);
 | 
						|
        $mockResponse = $this->createMock(ResponseInterface::class);
 | 
						|
        $mockGateway = $this->createMock(WebmentionGatewayInterface::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);
 | 
						|
 | 
						|
        $mockGateway->expects($this->once())
 | 
						|
            ->method('save')
 | 
						|
            ->with($this->objectContains('content', "Liked this post"));
 | 
						|
 | 
						|
        $endpoint = new Endpoint($mockClient, $mockGateway);
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testItShouldParseAReplyContent()
 | 
						|
    {
 | 
						|
        $source = "https://my-valid-source-url.com";
 | 
						|
        $target = "https://lewisdale.dev/post/a-post-page";
 | 
						|
 | 
						|
        $content = <<<HTML
 | 
						|
        <html>
 | 
						|
            <head>
 | 
						|
            </head>
 | 
						|
            <body>
 | 
						|
                <article class="h-entry">
 | 
						|
                    <a class="u-in-reply-to" rel="in-reply-to" href="$target">@post</a>: That's a great idea!<a class="u-url" href="/"></a>
 | 
						|
                </article>
 | 
						|
            </body>
 | 
						|
        </html>
 | 
						|
        HTML;
 | 
						|
 | 
						|
        $mockClient = $this->createMock(HttpClientInterface::class);
 | 
						|
        $mockResponse = $this->createMock(ResponseInterface::class);
 | 
						|
        $mockGateway = $this->createMock(WebmentionGatewayInterface::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);
 | 
						|
 | 
						|
        $mockGateway->expects($this->once())
 | 
						|
            ->method('save')
 | 
						|
            ->with($this->objectContains('content', "@post: That's a great idea!"));
 | 
						|
 | 
						|
        $endpoint = new Endpoint($mockClient, $mockGateway);
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
 | 
						|
    public function testItShouldParseAnAuthorCardWithANameUrlAndPhoto()
 | 
						|
    {
 | 
						|
        $source = "https://my-valid-source-url.com";
 | 
						|
        $target = "https://lewisdale.dev/post/a-post-page";
 | 
						|
 | 
						|
        $content = <<<HTML
 | 
						|
        <html>
 | 
						|
            <head>
 | 
						|
            </head>
 | 
						|
            <body>
 | 
						|
                <article class="h-entry">
 | 
						|
                    <a class="u-in-reply-to" rel="in-reply-to" href="$target">@post</a>: That's a great idea!<a class="u-url" href="/"></a>
 | 
						|
                    <div class="h-card">
 | 
						|
                        <p class="p-name">Anne Author</p> who can be found at <a class="u-url" href="https://my-blog.com">my-blog.com</a>.
 | 
						|
                        <img src="https://dummyimage.com/100x100/fff/aaa" class="u-photo" alt="My profile picture" />
 | 
						|
                    </div>
 | 
						|
                </article>
 | 
						|
            </body>
 | 
						|
        </html>
 | 
						|
        HTML;
 | 
						|
 | 
						|
        $mockClient = $this->createMock(HttpClientInterface::class);
 | 
						|
        $mockResponse = $this->createMock(ResponseInterface::class);
 | 
						|
        $mockGateway = $this->createMock(WebmentionGatewayInterface::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);
 | 
						|
 | 
						|
        $expected = new \Lewisdale\Webmentions\Models\Author(
 | 
						|
            null,
 | 
						|
            "Anne Author",
 | 
						|
            "https://my-blog.com",
 | 
						|
            "https://dummyimage.com/100x100/fff/aaa"
 | 
						|
        );
 | 
						|
        $mockGateway->expects($this->once())
 | 
						|
            ->method('save')
 | 
						|
            ->with($this->objectContains('author', $expected));
 | 
						|
 | 
						|
        $endpoint = new Endpoint($mockClient, $mockGateway);
 | 
						|
        $endpoint->receiveWebmention($source, $target);
 | 
						|
    }
 | 
						|
} |