A barebones implementation of an atproto PDS in PHP and Slim Framework 4.
pds php atproto
0

Configure Feed

Select the types of activity you want to include in your feed.

test: add missing unit tests

Andrés Ignacio Torres (May 17, 2026, 12:33 AM -0700) d42e1189 d4c034bb

+174
+58
tests/Application/Actions/Pds/XrpcExceptionTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Application\Actions\Pds; 6 + 7 + use App\Application\Actions\Pds\XrpcException; 8 + use Tests\TestCase; 9 + 10 + class XrpcExceptionTest extends TestCase 11 + { 12 + public function testConstructorDefaultsToBadRequestStatus(): void 13 + { 14 + $exception = new XrpcException('InvalidRequest', 'boom'); 15 + 16 + $this->assertSame('InvalidRequest', $exception->getError()); 17 + $this->assertSame('boom', $exception->getMessage()); 18 + $this->assertSame(400, $exception->getStatusCode()); 19 + } 20 + 21 + public function testConstructorAcceptsCustomStatusCode(): void 22 + { 23 + $exception = new XrpcException('AuthRequired', 'missing token', 401); 24 + 25 + $this->assertSame('AuthRequired', $exception->getError()); 26 + $this->assertSame(401, $exception->getStatusCode()); 27 + } 28 + 29 + public function testInvalidRequestUsesInvalidRequestErrorCode(): void 30 + { 31 + $exception = XrpcException::invalidRequest('something is off'); 32 + 33 + $this->assertSame('InvalidRequest', $exception->getError()); 34 + $this->assertSame('something is off', $exception->getMessage()); 35 + $this->assertSame(400, $exception->getStatusCode()); 36 + } 37 + 38 + public function testInvalidRequestSupportsCustomErrorCode(): void 39 + { 40 + $exception = XrpcException::invalidRequest('Handle too short', 'InvalidHandle'); 41 + 42 + $this->assertSame('InvalidHandle', $exception->getError()); 43 + $this->assertSame('Handle too short', $exception->getMessage()); 44 + $this->assertSame(400, $exception->getStatusCode()); 45 + } 46 + 47 + public function testMissingParamFormatsMessage(): void 48 + { 49 + $exception = XrpcException::missingParam('com.atproto.foo.bar', 'baz'); 50 + 51 + $this->assertSame('InvalidRequest', $exception->getError()); 52 + $this->assertSame( 53 + 'Invalid com.atproto.foo.bar params: Missing required key "baz"', 54 + $exception->getMessage() 55 + ); 56 + $this->assertSame(400, $exception->getStatusCode()); 57 + } 58 + }
+116
tests/Infrastructure/Atproto/AppView/GuzzleAppViewClientTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Atproto\AppView; 6 + 7 + use App\Domain\Pds\Atproto\AppView\AppViewException; 8 + use App\Infrastructure\Atproto\AppView\GuzzleAppViewClient; 9 + use GuzzleHttp\Client; 10 + use GuzzleHttp\Exception\ConnectException; 11 + use GuzzleHttp\Handler\MockHandler; 12 + use GuzzleHttp\HandlerStack; 13 + use GuzzleHttp\Middleware; 14 + use GuzzleHttp\Psr7\Request; 15 + use GuzzleHttp\Psr7\Response; 16 + use Tests\TestCase; 17 + 18 + class GuzzleAppViewClientTest extends TestCase 19 + { 20 + /** 21 + * @param list<Response|\Throwable> $queue 22 + * @param list<array{request: \Psr\Http\Message\RequestInterface}> $history 23 + */ 24 + private function makeClient(array $queue, array &$history = []): GuzzleAppViewClient 25 + { 26 + $mock = new MockHandler($queue); 27 + $stack = HandlerStack::create($mock); 28 + $stack->push(Middleware::history($history)); 29 + 30 + $httpClient = new Client([ 31 + 'handler' => $stack, 32 + 'base_uri' => 'https://api.bsky.app/', 33 + ]); 34 + 35 + return new GuzzleAppViewClient($httpClient); 36 + } 37 + 38 + public function testResolveHandleReturnsDidOnSuccess(): void 39 + { 40 + $history = []; 41 + $client = $this->makeClient( 42 + [new Response(200, [], json_encode(['did' => 'did:plc:abc']))], 43 + $history 44 + ); 45 + 46 + $did = $client->resolveHandle('bob.bsky.social'); 47 + 48 + $this->assertSame('did:plc:abc', $did); 49 + $this->assertCount(1, $history); 50 + 51 + /** @var \Psr\Http\Message\RequestInterface $sent */ 52 + $sent = $history[0]['request']; 53 + $this->assertSame('GET', $sent->getMethod()); 54 + $this->assertSame( 55 + 'https://api.bsky.app/xrpc/com.atproto.identity.resolveHandle', 56 + (string) $sent->getUri()->withQuery('') 57 + ); 58 + $this->assertSame('handle=bob.bsky.social', $sent->getUri()->getQuery()); 59 + } 60 + 61 + public function testResolveHandleWrapsNon2xxResponseAsAppViewException(): void 62 + { 63 + $request = new Request('GET', 'xrpc/com.atproto.identity.resolveHandle'); 64 + $client = $this->makeClient([ 65 + new \GuzzleHttp\Exception\BadResponseException( 66 + 'Bad Request', 67 + $request, 68 + new Response(400, [], json_encode(['error' => 'InvalidRequest', 'message' => 'nope'])) 69 + ), 70 + ]); 71 + 72 + $this->expectException(AppViewException::class); 73 + $client->resolveHandle('missing.bsky.social'); 74 + } 75 + 76 + public function testResolveHandleWrapsTransportErrorAsAppViewException(): void 77 + { 78 + $request = new Request('GET', 'xrpc/com.atproto.identity.resolveHandle'); 79 + $client = $this->makeClient([ 80 + new ConnectException('connection refused', $request), 81 + ]); 82 + 83 + $this->expectException(AppViewException::class); 84 + $client->resolveHandle('bob.bsky.social'); 85 + } 86 + 87 + public function testResolveHandleThrowsWhenDidFieldMissing(): void 88 + { 89 + $client = $this->makeClient([ 90 + new Response(200, [], json_encode(['notTheDid' => 'oops'])), 91 + ]); 92 + 93 + $this->expectException(AppViewException::class); 94 + $client->resolveHandle('bob.bsky.social'); 95 + } 96 + 97 + public function testResolveHandleThrowsWhenResponseIsNotJson(): void 98 + { 99 + $client = $this->makeClient([ 100 + new Response(200, [], 'this is not json'), 101 + ]); 102 + 103 + $this->expectException(AppViewException::class); 104 + $client->resolveHandle('bob.bsky.social'); 105 + } 106 + 107 + public function testResolveHandleThrowsWhenDidIsNotAString(): void 108 + { 109 + $client = $this->makeClient([ 110 + new Response(200, [], json_encode(['did' => 123])), 111 + ]); 112 + 113 + $this->expectException(AppViewException::class); 114 + $client->resolveHandle('bob.bsky.social'); 115 + } 116 + }