Resolve AT Protocol DIDs, handles, and schemas with intelligent caching for Laravel
at main 5.5 kB view raw
1<?php 2 3namespace SocialDept\AtpResolver\Tests\Unit; 4 5use PHPUnit\Framework\TestCase; 6use SocialDept\AtpResolver\Contracts\CacheStore; 7use SocialDept\AtpResolver\Contracts\DidResolver; 8use SocialDept\AtpResolver\Contracts\HandleResolver; 9use SocialDept\AtpResolver\Data\DidDocument; 10use SocialDept\AtpResolver\Resolver; 11 12class ResolverTest extends TestCase 13{ 14 public function test_it_can_resolve_pds_from_did(): void 15 { 16 $didResolver = $this->createMock(DidResolver::class); 17 $handleResolver = $this->createMock(HandleResolver::class); 18 $cache = $this->createMock(CacheStore::class); 19 20 $didDocument = DidDocument::fromArray([ 21 'id' => 'did:plc:abc123', 22 'service' => [ 23 [ 24 'type' => 'AtprotoPersonalDataServer', 25 'serviceEndpoint' => 'https://pds.example.com', 26 ], 27 ], 28 ]); 29 30 $didResolver->expects($this->once()) 31 ->method('resolve') 32 ->with('did:plc:abc123') 33 ->willReturn($didDocument); 34 35 $cache->method('has')->willReturn(false); 36 37 // Expect multiple cache puts (DID document + PDS endpoint) 38 $cache->expects($this->exactly(2)) 39 ->method('put') 40 ->willReturnCallback(function ($key, $value, $ttl) { 41 $this->assertContains($key, ['did:did:plc:abc123', 'pds:did:plc:abc123']); 42 43 return null; 44 }); 45 46 $beacon = new Resolver($didResolver, $handleResolver, $cache); 47 $pds = $beacon->resolvePds('did:plc:abc123'); 48 49 $this->assertSame('https://pds.example.com', $pds); 50 } 51 52 public function test_it_can_resolve_pds_from_handle(): void 53 { 54 $didResolver = $this->createMock(DidResolver::class); 55 $handleResolver = $this->createMock(HandleResolver::class); 56 $cache = $this->createMock(CacheStore::class); 57 58 $handleResolver->expects($this->once()) 59 ->method('resolve') 60 ->with('user.bsky.social') 61 ->willReturn('did:plc:abc123'); 62 63 $didDocument = DidDocument::fromArray([ 64 'id' => 'did:plc:abc123', 65 'service' => [ 66 [ 67 'type' => 'AtprotoPersonalDataServer', 68 'serviceEndpoint' => 'https://pds.example.com', 69 ], 70 ], 71 ]); 72 73 $didResolver->expects($this->once()) 74 ->method('resolve') 75 ->with('did:plc:abc123') 76 ->willReturn($didDocument); 77 78 $cache->method('has')->willReturn(false); 79 80 // Expect multiple cache puts (handle + DID document + PDS endpoint) 81 $cache->expects($this->exactly(3)) 82 ->method('put') 83 ->willReturnCallback(function ($key, $value, $ttl) { 84 $this->assertContains($key, ['handle:user.bsky.social', 'did:did:plc:abc123', 'pds:user.bsky.social']); 85 86 return null; 87 }); 88 89 $beacon = new Resolver($didResolver, $handleResolver, $cache); 90 $pds = $beacon->resolvePds('user.bsky.social'); 91 92 $this->assertSame('https://pds.example.com', $pds); 93 } 94 95 public function test_it_returns_null_when_no_pds_endpoint(): void 96 { 97 $didResolver = $this->createMock(DidResolver::class); 98 $handleResolver = $this->createMock(HandleResolver::class); 99 $cache = $this->createMock(CacheStore::class); 100 101 $didDocument = DidDocument::fromArray([ 102 'id' => 'did:plc:abc123', 103 'service' => [], 104 ]); 105 106 $didResolver->expects($this->once()) 107 ->method('resolve') 108 ->with('did:plc:abc123') 109 ->willReturn($didDocument); 110 111 $cache->method('has')->willReturn(false); 112 113 // DID document is still cached, but PDS endpoint is not (since it's null) 114 $cache->expects($this->once()) 115 ->method('put') 116 ->with('did:did:plc:abc123', $didDocument, $this->anything()); 117 118 $beacon = new Resolver($didResolver, $handleResolver, $cache); 119 $pds = $beacon->resolvePds('did:plc:abc123'); 120 121 $this->assertNull($pds); 122 } 123 124 public function test_it_uses_cached_pds_endpoint(): void 125 { 126 $didResolver = $this->createMock(DidResolver::class); 127 $handleResolver = $this->createMock(HandleResolver::class); 128 $cache = $this->createMock(CacheStore::class); 129 130 $cache->expects($this->once()) 131 ->method('has') 132 ->with('pds:did:plc:abc123') 133 ->willReturn(true); 134 135 $cache->expects($this->once()) 136 ->method('get') 137 ->with('pds:did:plc:abc123') 138 ->willReturn('https://cached-pds.example.com'); 139 140 $didResolver->expects($this->never())->method('resolve'); 141 142 $beacon = new Resolver($didResolver, $handleResolver, $cache); 143 $pds = $beacon->resolvePds('did:plc:abc123'); 144 145 $this->assertSame('https://cached-pds.example.com', $pds); 146 } 147 148 public function test_it_can_clear_pds_cache(): void 149 { 150 $didResolver = $this->createMock(DidResolver::class); 151 $handleResolver = $this->createMock(HandleResolver::class); 152 $cache = $this->createMock(CacheStore::class); 153 154 $cache->expects($this->once()) 155 ->method('forget') 156 ->with('pds:did:plc:abc123'); 157 158 $beacon = new Resolver($didResolver, $handleResolver, $cache); 159 $beacon->clearPdsCache('did:plc:abc123'); 160 } 161}