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 for new classes

Andrés Ignacio Torres (May 18, 2026, 12:39 PM -0700) b7ddba92 0ad90847

+2189
+58
tests/Application/Actions/ActionErrorTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Application\Actions; 6 + 7 + use App\Application\Actions\ActionError; 8 + use Tests\TestCase; 9 + 10 + class ActionErrorTest extends TestCase 11 + { 12 + public function testConstructorAndGetters(): void 13 + { 14 + $error = new ActionError(ActionError::BAD_REQUEST, 'Invalid input'); 15 + 16 + $this->assertSame(ActionError::BAD_REQUEST, $error->getType()); 17 + $this->assertSame('Invalid input', $error->getDescription()); 18 + } 19 + 20 + public function testDescriptionDefaultsToNull(): void 21 + { 22 + $error = new ActionError(ActionError::SERVER_ERROR); 23 + 24 + $this->assertNull($error->getDescription()); 25 + } 26 + 27 + public function testSettersAreFluentAndUpdateValues(): void 28 + { 29 + $error = new ActionError(ActionError::SERVER_ERROR); 30 + 31 + $result = $error 32 + ->setType(ActionError::VALIDATION_ERROR) 33 + ->setDescription('Bad field'); 34 + 35 + $this->assertSame($error, $result); 36 + $this->assertSame(ActionError::VALIDATION_ERROR, $error->getType()); 37 + $this->assertSame('Bad field', $error->getDescription()); 38 + } 39 + 40 + public function testSetDescriptionAcceptsNull(): void 41 + { 42 + $error = new ActionError(ActionError::SERVER_ERROR, 'oops'); 43 + 44 + $error->setDescription(); 45 + 46 + $this->assertNull($error->getDescription()); 47 + } 48 + 49 + public function testJsonSerialize(): void 50 + { 51 + $error = new ActionError(ActionError::NOT_ALLOWED, 'nope'); 52 + 53 + $this->assertSame( 54 + ['type' => ActionError::NOT_ALLOWED, 'description' => 'nope'], 55 + $error->jsonSerialize() 56 + ); 57 + } 58 + }
+70
tests/Application/Actions/ActionPayloadTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Application\Actions; 6 + 7 + use App\Application\Actions\ActionError; 8 + use App\Application\Actions\ActionPayload; 9 + use JsonSerializable; 10 + use Tests\TestCase; 11 + 12 + class ActionPayloadTest extends TestCase 13 + { 14 + public function testDefaults(): void 15 + { 16 + $payload = new ActionPayload(); 17 + 18 + $this->assertSame(200, $payload->getStatusCode()); 19 + $this->assertNull($payload->getData()); 20 + $this->assertNull($payload->getError()); 21 + } 22 + 23 + public function testGettersWithAllFields(): void 24 + { 25 + $error = new ActionError(ActionError::BAD_REQUEST, 'bad'); 26 + $data = ['hello' => 'world']; 27 + 28 + $payload = new ActionPayload(400, $data, $error); 29 + 30 + $this->assertSame(400, $payload->getStatusCode()); 31 + $this->assertSame($data, $payload->getData()); 32 + $this->assertSame($error, $payload->getError()); 33 + } 34 + 35 + public function testJsonSerializeWithArrayDataIncludesStatusAndData(): void 36 + { 37 + $payload = new ActionPayload(201, ['id' => 1]); 38 + 39 + $this->assertSame( 40 + ['statusCode' => 201, 'data' => ['id' => 1]], 41 + $payload->jsonSerialize() 42 + ); 43 + } 44 + 45 + public function testJsonSerializeOmitsNullDataAndIncludesError(): void 46 + { 47 + $error = new ActionError(ActionError::SERVER_ERROR, 'kaboom'); 48 + $payload = new ActionPayload(500, null, $error); 49 + 50 + $this->assertSame( 51 + ['statusCode' => 500, 'error' => $error], 52 + $payload->jsonSerialize() 53 + ); 54 + } 55 + 56 + public function testJsonSerializeDelegatesToJsonSerializableData(): void 57 + { 58 + $data = new class implements JsonSerializable { 59 + /** @return array<string, string> */ 60 + public function jsonSerialize(): array 61 + { 62 + return ['delegated' => 'yes']; 63 + } 64 + }; 65 + 66 + $payload = new ActionPayload(200, $data); 67 + 68 + $this->assertSame(['delegated' => 'yes'], $payload->jsonSerialize()); 69 + } 70 + }
+36
tests/Application/Settings/SettingsTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Application\Settings; 6 + 7 + use App\Application\Settings\Settings; 8 + use Tests\TestCase; 9 + 10 + class SettingsTest extends TestCase 11 + { 12 + public function testGetWithoutKeyReturnsAllSettings(): void 13 + { 14 + $values = ['foo' => 'bar', 'baz' => 42]; 15 + $settings = new Settings($values); 16 + 17 + $this->assertSame($values, $settings->get()); 18 + } 19 + 20 + public function testGetWithKeyReturnsValue(): void 21 + { 22 + $settings = new Settings(['foo' => 'bar', 'baz' => 42]); 23 + 24 + $this->assertSame('bar', $settings->get('foo')); 25 + $this->assertSame(42, $settings->get('baz')); 26 + } 27 + 28 + public function testGetSupportsNestedAndNullValues(): void 29 + { 30 + $nested = ['a' => ['b' => 'c']]; 31 + $settings = new Settings(['nested' => $nested, 'nullable' => null]); 32 + 33 + $this->assertSame($nested, $settings->get('nested')); 34 + $this->assertNull($settings->get('nullable')); 35 + } 36 + }
+63
tests/Domain/Account/AppPassword/AppPasswordTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Account\AppPassword; 6 + 7 + use App\Domain\Account\AppPassword\AppPassword; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class AppPasswordTest extends TestCase 12 + { 13 + public function testGettersWithAllFields(): void 14 + { 15 + $createdAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $appPassword = new AppPassword( 17 + did: 'did:web:alice.pds.test', 18 + name: 'phone', 19 + passwordScrypt: 'hash', 20 + createdAt: $createdAt, 21 + privileged: true, 22 + ); 23 + 24 + $this->assertSame('did:web:alice.pds.test', $appPassword->getDid()); 25 + $this->assertSame('phone', $appPassword->getName()); 26 + $this->assertSame('hash', $appPassword->getPasswordScrypt()); 27 + $this->assertEquals($createdAt, $appPassword->getCreatedAt()); 28 + $this->assertTrue($appPassword->isPrivileged()); 29 + } 30 + 31 + public function testPrivilegedDefaultsToFalse(): void 32 + { 33 + $appPassword = new AppPassword( 34 + did: 'did:web:alice.pds.test', 35 + name: 'phone', 36 + passwordScrypt: 'hash', 37 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 38 + ); 39 + 40 + $this->assertFalse($appPassword->isPrivileged()); 41 + } 42 + 43 + public function testJsonSerializeOmitsPasswordScryptAndFormatsDate(): void 44 + { 45 + $appPassword = new AppPassword( 46 + did: 'did:web:alice.pds.test', 47 + name: 'phone', 48 + passwordScrypt: 'secret', 49 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 50 + privileged: false, 51 + ); 52 + 53 + $json = json_decode((string) json_encode($appPassword), true); 54 + 55 + $this->assertSame([ 56 + 'did' => 'did:web:alice.pds.test', 57 + 'name' => 'phone', 58 + 'createdAt' => '2026-01-01T00:00:00+00:00', 59 + 'privileged' => false, 60 + ], $json); 61 + $this->assertArrayNotHasKey('passwordScrypt', $json); 62 + } 63 + }
+56
tests/Domain/Account/EmailToken/EmailTokenTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Account\EmailToken; 6 + 7 + use App\Domain\Account\EmailToken\EmailToken; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class EmailTokenTest extends TestCase 12 + { 13 + public function testGetters(): void 14 + { 15 + $requestedAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $token = new EmailToken( 17 + purpose: EmailToken::PURPOSE_CONFIRM_EMAIL, 18 + did: 'did:web:alice.pds.test', 19 + token: 'abc-123', 20 + requestedAt: $requestedAt, 21 + ); 22 + 23 + $this->assertSame(EmailToken::PURPOSE_CONFIRM_EMAIL, $token->getPurpose()); 24 + $this->assertSame('did:web:alice.pds.test', $token->getDid()); 25 + $this->assertSame('abc-123', $token->getToken()); 26 + $this->assertEquals($requestedAt, $token->getRequestedAt()); 27 + } 28 + 29 + public function testJsonSerializeOmitsTokenAndFormatsDate(): void 30 + { 31 + $token = new EmailToken( 32 + purpose: EmailToken::PURPOSE_RESET_PASSWORD, 33 + did: 'did:web:alice.pds.test', 34 + token: 'secret-token', 35 + requestedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 36 + ); 37 + 38 + $json = json_decode((string) json_encode($token), true); 39 + 40 + $this->assertSame([ 41 + 'purpose' => EmailToken::PURPOSE_RESET_PASSWORD, 42 + 'did' => 'did:web:alice.pds.test', 43 + 'requestedAt' => '2026-01-01T00:00:00+00:00', 44 + ], $json); 45 + $this->assertArrayNotHasKey('token', $json); 46 + } 47 + 48 + public function testPurposeConstantsHaveExpectedValues(): void 49 + { 50 + $this->assertSame('confirm_email', EmailToken::PURPOSE_CONFIRM_EMAIL); 51 + $this->assertSame('update_email', EmailToken::PURPOSE_UPDATE_EMAIL); 52 + $this->assertSame('reset_password', EmailToken::PURPOSE_RESET_PASSWORD); 53 + $this->assertSame('delete_account', EmailToken::PURPOSE_DELETE_ACCOUNT); 54 + $this->assertSame('plc_operation', EmailToken::PURPOSE_PLC_OPERATION); 55 + } 56 + }
+55
tests/Domain/Account/InviteCode/InviteCodeTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Account\InviteCode; 6 + 7 + use App\Domain\Account\InviteCode\InviteCode; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class InviteCodeTest extends TestCase 12 + { 13 + public function testGetters(): void 14 + { 15 + $createdAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $code = new InviteCode( 17 + code: 'pds-test-abc123', 18 + availableUses: 5, 19 + disabled: false, 20 + forAccount: 'did:web:alice.pds.test', 21 + createdBy: 'admin', 22 + createdAt: $createdAt, 23 + ); 24 + 25 + $this->assertSame('pds-test-abc123', $code->getCode()); 26 + $this->assertSame(5, $code->getAvailableUses()); 27 + $this->assertFalse($code->isDisabled()); 28 + $this->assertSame('did:web:alice.pds.test', $code->getForAccount()); 29 + $this->assertSame('admin', $code->getCreatedBy()); 30 + $this->assertEquals($createdAt, $code->getCreatedAt()); 31 + } 32 + 33 + public function testJsonSerialize(): void 34 + { 35 + $code = new InviteCode( 36 + code: 'pds-test-abc123', 37 + availableUses: 3, 38 + disabled: true, 39 + forAccount: 'did:web:alice.pds.test', 40 + createdBy: 'admin', 41 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 42 + ); 43 + 44 + $json = json_decode((string) json_encode($code), true); 45 + 46 + $this->assertSame([ 47 + 'code' => 'pds-test-abc123', 48 + 'availableUses' => 3, 49 + 'disabled' => true, 50 + 'forAccount' => 'did:web:alice.pds.test', 51 + 'createdBy' => 'admin', 52 + 'createdAt' => '2026-01-01T00:00:00+00:00', 53 + ], $json); 54 + } 55 + }
+43
tests/Domain/Account/InviteCode/InviteCodeUseTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Account\InviteCode; 6 + 7 + use App\Domain\Account\InviteCode\InviteCodeUse; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class InviteCodeUseTest extends TestCase 12 + { 13 + public function testGetters(): void 14 + { 15 + $usedAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $use = new InviteCodeUse( 17 + code: 'pds-test-abc123', 18 + usedBy: 'did:web:alice.pds.test', 19 + usedAt: $usedAt, 20 + ); 21 + 22 + $this->assertSame('pds-test-abc123', $use->getCode()); 23 + $this->assertSame('did:web:alice.pds.test', $use->getUsedBy()); 24 + $this->assertEquals($usedAt, $use->getUsedAt()); 25 + } 26 + 27 + public function testJsonSerialize(): void 28 + { 29 + $use = new InviteCodeUse( 30 + code: 'pds-test-abc123', 31 + usedBy: 'did:web:alice.pds.test', 32 + usedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 33 + ); 34 + 35 + $json = json_decode((string) json_encode($use), true); 36 + 37 + $this->assertSame([ 38 + 'code' => 'pds-test-abc123', 39 + 'usedBy' => 'did:web:alice.pds.test', 40 + 'usedAt' => '2026-01-01T00:00:00+00:00', 41 + ], $json); 42 + } 43 + }
+61
tests/Domain/Account/RefreshToken/RefreshTokenTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Account\RefreshToken; 6 + 7 + use App\Domain\Account\RefreshToken\RefreshToken; 8 + use Tests\TestCase; 9 + 10 + class RefreshTokenTest extends TestCase 11 + { 12 + public function testGettersWithAllFields(): void 13 + { 14 + $token = new RefreshToken( 15 + id: 'rt-1', 16 + did: 'did:web:alice.pds.test', 17 + expiresAt: '2026-02-01T00:00:00Z', 18 + appPasswordName: 'phone', 19 + nextId: 'rt-2', 20 + ); 21 + 22 + $this->assertSame('rt-1', $token->getId()); 23 + $this->assertSame('did:web:alice.pds.test', $token->getDid()); 24 + $this->assertSame('2026-02-01T00:00:00Z', $token->getExpiresAt()); 25 + $this->assertSame('phone', $token->getAppPasswordName()); 26 + $this->assertSame('rt-2', $token->getNextId()); 27 + } 28 + 29 + public function testNullableFields(): void 30 + { 31 + $token = new RefreshToken( 32 + id: 'rt-1', 33 + did: 'did:web:alice.pds.test', 34 + expiresAt: '2026-02-01T00:00:00Z', 35 + appPasswordName: null, 36 + nextId: null, 37 + ); 38 + 39 + $this->assertNull($token->getAppPasswordName()); 40 + $this->assertNull($token->getNextId()); 41 + } 42 + 43 + public function testJsonSerialize(): void 44 + { 45 + $token = new RefreshToken( 46 + id: 'rt-1', 47 + did: 'did:web:alice.pds.test', 48 + expiresAt: '2026-02-01T00:00:00Z', 49 + appPasswordName: 'phone', 50 + nextId: 'rt-2', 51 + ); 52 + 53 + $this->assertSame([ 54 + 'id' => 'rt-1', 55 + 'did' => 'did:web:alice.pds.test', 56 + 'expiresAt' => '2026-02-01T00:00:00Z', 57 + 'appPasswordName' => 'phone', 58 + 'nextId' => 'rt-2', 59 + ], $token->jsonSerialize()); 60 + } 61 + }
+67
tests/Domain/Blob/BlobTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Blob; 6 + 7 + use App\Domain\Blob\Blob; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class BlobTest extends TestCase 12 + { 13 + public function testGettersWithAllFields(): void 14 + { 15 + $createdAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $blob = new Blob( 17 + cid: 'bafy-blob', 18 + mimeType: 'image/png', 19 + size: 2048, 20 + tempKey: 'tmp-1', 21 + createdAt: $createdAt, 22 + takedownRef: 'tk-1', 23 + ); 24 + 25 + $this->assertSame('bafy-blob', $blob->getCid()); 26 + $this->assertSame('image/png', $blob->getMimeType()); 27 + $this->assertSame(2048, $blob->getSize()); 28 + $this->assertSame('tmp-1', $blob->getTempKey()); 29 + $this->assertEquals($createdAt, $blob->getCreatedAt()); 30 + $this->assertSame('tk-1', $blob->getTakedownRef()); 31 + } 32 + 33 + public function testDefaultTakedownRefIsNullAndTempKeyMayBeNull(): void 34 + { 35 + $blob = new Blob( 36 + cid: 'bafy-blob', 37 + mimeType: 'image/png', 38 + size: 2048, 39 + tempKey: null, 40 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 41 + ); 42 + 43 + $this->assertNull($blob->getTempKey()); 44 + $this->assertNull($blob->getTakedownRef()); 45 + } 46 + 47 + public function testJsonSerialize(): void 48 + { 49 + $blob = new Blob( 50 + cid: 'bafy-blob', 51 + mimeType: 'image/png', 52 + size: 2048, 53 + tempKey: 'tmp-1', 54 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 55 + takedownRef: null, 56 + ); 57 + 58 + $this->assertSame([ 59 + 'cid' => 'bafy-blob', 60 + 'mimeType' => 'image/png', 61 + 'size' => 2048, 62 + 'tempKey' => 'tmp-1', 63 + 'createdAt' => '2026-01-01T00:00:00+00:00', 64 + 'takedownRef' => null, 65 + ], $blob->jsonSerialize()); 66 + } 67 + }
+43
tests/Domain/Did/DidDocEntryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Did; 6 + 7 + use App\Domain\Did\DidDocEntry; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class DidDocEntryTest extends TestCase 12 + { 13 + public function testGetters(): void 14 + { 15 + $updatedAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $doc = ['id' => 'did:plc:alice', 'verificationMethod' => []]; 17 + 18 + $entry = new DidDocEntry( 19 + did: 'did:plc:alice', 20 + doc: $doc, 21 + updatedAt: $updatedAt, 22 + ); 23 + 24 + $this->assertSame('did:plc:alice', $entry->getDid()); 25 + $this->assertSame($doc, $entry->getDoc()); 26 + $this->assertEquals($updatedAt, $entry->getUpdatedAt()); 27 + } 28 + 29 + public function testJsonSerialize(): void 30 + { 31 + $entry = new DidDocEntry( 32 + did: 'did:plc:alice', 33 + doc: ['id' => 'did:plc:alice'], 34 + updatedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 35 + ); 36 + 37 + $this->assertSame([ 38 + 'did' => 'did:plc:alice', 39 + 'doc' => ['id' => 'did:plc:alice'], 40 + 'updatedAt' => '2026-01-01T00:00:00+00:00', 41 + ], $entry->jsonSerialize()); 42 + } 43 + }
+92
tests/Domain/Lexicon/LexiconEntryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Lexicon; 6 + 7 + use App\Domain\Lexicon\LexiconEntry; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class LexiconEntryTest extends TestCase 12 + { 13 + public function testGettersWithAllFields(): void 14 + { 15 + $createdAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $updatedAt = new DateTimeImmutable('2026-01-02T00:00:00Z'); 17 + $lastSucceededAt = new DateTimeImmutable('2026-01-02T01:00:00Z'); 18 + $lex = ['lexicon' => 1, 'defs' => []]; 19 + 20 + $entry = new LexiconEntry( 21 + nsid: 'com.example.foo', 22 + createdAt: $createdAt, 23 + updatedAt: $updatedAt, 24 + lastSucceededAt: $lastSucceededAt, 25 + uri: 'https://example.com/foo.json', 26 + lexicon: $lex, 27 + ); 28 + 29 + $this->assertSame('com.example.foo', $entry->getNsid()); 30 + $this->assertEquals($createdAt, $entry->getCreatedAt()); 31 + $this->assertEquals($updatedAt, $entry->getUpdatedAt()); 32 + $this->assertEquals($lastSucceededAt, $entry->getLastSucceededAt()); 33 + $this->assertSame('https://example.com/foo.json', $entry->getUri()); 34 + $this->assertSame($lex, $entry->getLexicon()); 35 + } 36 + 37 + public function testNullableFields(): void 38 + { 39 + $entry = new LexiconEntry( 40 + nsid: 'com.example.foo', 41 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 42 + updatedAt: new DateTimeImmutable('2026-01-02T00:00:00Z'), 43 + lastSucceededAt: null, 44 + uri: null, 45 + lexicon: null, 46 + ); 47 + 48 + $this->assertNull($entry->getLastSucceededAt()); 49 + $this->assertNull($entry->getUri()); 50 + $this->assertNull($entry->getLexicon()); 51 + } 52 + 53 + public function testJsonSerializeOmitsLexicon(): void 54 + { 55 + $entry = new LexiconEntry( 56 + nsid: 'com.example.foo', 57 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 58 + updatedAt: new DateTimeImmutable('2026-01-02T00:00:00Z'), 59 + lastSucceededAt: new DateTimeImmutable('2026-01-02T01:00:00Z'), 60 + uri: 'https://example.com/foo.json', 61 + lexicon: ['lexicon' => 1], 62 + ); 63 + 64 + $json = json_decode((string) json_encode($entry), true); 65 + 66 + $this->assertSame([ 67 + 'nsid' => 'com.example.foo', 68 + 'createdAt' => '2026-01-01T00:00:00+00:00', 69 + 'updatedAt' => '2026-01-02T00:00:00+00:00', 70 + 'lastSucceededAt' => '2026-01-02T01:00:00+00:00', 71 + 'uri' => 'https://example.com/foo.json', 72 + ], $json); 73 + $this->assertArrayNotHasKey('lexicon', $json); 74 + } 75 + 76 + public function testJsonSerializeWithNullLastSucceededAt(): void 77 + { 78 + $entry = new LexiconEntry( 79 + nsid: 'com.example.foo', 80 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 81 + updatedAt: new DateTimeImmutable('2026-01-02T00:00:00Z'), 82 + lastSucceededAt: null, 83 + uri: null, 84 + lexicon: null, 85 + ); 86 + 87 + $payload = $entry->jsonSerialize(); 88 + 89 + $this->assertNull($payload['lastSucceededAt']); 90 + $this->assertNull($payload['uri']); 91 + } 92 + }
+46
tests/Domain/OAuth/AccountDeviceTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\OAuth; 6 + 7 + use App\Domain\OAuth\AccountDevice; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class AccountDeviceTest extends TestCase 12 + { 13 + public function testGetters(): void 14 + { 15 + $createdAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $updatedAt = new DateTimeImmutable('2026-01-02T00:00:00Z'); 17 + $device = new AccountDevice( 18 + did: 'did:plc:alice', 19 + deviceId: 'dev-1', 20 + createdAt: $createdAt, 21 + updatedAt: $updatedAt, 22 + ); 23 + 24 + $this->assertSame('did:plc:alice', $device->getDid()); 25 + $this->assertSame('dev-1', $device->getDeviceId()); 26 + $this->assertEquals($createdAt, $device->getCreatedAt()); 27 + $this->assertEquals($updatedAt, $device->getUpdatedAt()); 28 + } 29 + 30 + public function testJsonSerialize(): void 31 + { 32 + $device = new AccountDevice( 33 + did: 'did:plc:alice', 34 + deviceId: 'dev-1', 35 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 36 + updatedAt: new DateTimeImmutable('2026-01-02T00:00:00Z'), 37 + ); 38 + 39 + $this->assertSame([ 40 + 'did' => 'did:plc:alice', 41 + 'deviceId' => 'dev-1', 42 + 'createdAt' => '2026-01-01T00:00:00+00:00', 43 + 'updatedAt' => '2026-01-02T00:00:00+00:00', 44 + ], $device->jsonSerialize()); 45 + } 46 + }
+86
tests/Domain/OAuth/AuthorizationRequestTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\OAuth; 6 + 7 + use App\Domain\OAuth\AuthorizationRequest; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class AuthorizationRequestTest extends TestCase 12 + { 13 + public function testGettersWithAllFields(): void 14 + { 15 + $expiresAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $clientAuth = ['method' => 'none']; 17 + $params = ['scope' => 'atproto']; 18 + 19 + $req = new AuthorizationRequest( 20 + id: 'req-1', 21 + did: 'did:plc:alice', 22 + deviceId: 'dev-1', 23 + clientId: 'https://app.test/client.json', 24 + clientAuth: $clientAuth, 25 + parameters: $params, 26 + expiresAt: $expiresAt, 27 + code: 'auth-code-1', 28 + ); 29 + 30 + $this->assertSame('req-1', $req->getId()); 31 + $this->assertSame('did:plc:alice', $req->getDid()); 32 + $this->assertSame('dev-1', $req->getDeviceId()); 33 + $this->assertSame('https://app.test/client.json', $req->getClientId()); 34 + $this->assertSame($clientAuth, $req->getClientAuth()); 35 + $this->assertSame($params, $req->getParameters()); 36 + $this->assertEquals($expiresAt, $req->getExpiresAt()); 37 + $this->assertSame('auth-code-1', $req->getCode()); 38 + } 39 + 40 + public function testNullableFields(): void 41 + { 42 + $req = new AuthorizationRequest( 43 + id: 'req-1', 44 + did: null, 45 + deviceId: null, 46 + clientId: 'https://app.test/client.json', 47 + clientAuth: null, 48 + parameters: [], 49 + expiresAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 50 + code: null, 51 + ); 52 + 53 + $this->assertNull($req->getDid()); 54 + $this->assertNull($req->getDeviceId()); 55 + $this->assertNull($req->getClientAuth()); 56 + $this->assertSame([], $req->getParameters()); 57 + $this->assertNull($req->getCode()); 58 + } 59 + 60 + public function testJsonSerializeOmitsClientAuthAndParameters(): void 61 + { 62 + $req = new AuthorizationRequest( 63 + id: 'req-1', 64 + did: 'did:plc:alice', 65 + deviceId: 'dev-1', 66 + clientId: 'https://app.test/client.json', 67 + clientAuth: ['method' => 'secret'], 68 + parameters: ['scope' => 'atproto'], 69 + expiresAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 70 + code: 'auth-code-1', 71 + ); 72 + 73 + $json = json_decode((string) json_encode($req), true); 74 + 75 + $this->assertSame([ 76 + 'id' => 'req-1', 77 + 'did' => 'did:plc:alice', 78 + 'deviceId' => 'dev-1', 79 + 'clientId' => 'https://app.test/client.json', 80 + 'expiresAt' => '2026-01-01T00:00:00+00:00', 81 + 'code' => 'auth-code-1', 82 + ], $json); 83 + $this->assertArrayNotHasKey('clientAuth', $json); 84 + $this->assertArrayNotHasKey('parameters', $json); 85 + } 86 + }
+54
tests/Domain/OAuth/AuthorizedClientTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\OAuth; 6 + 7 + use App\Domain\OAuth\AuthorizedClient; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class AuthorizedClientTest extends TestCase 12 + { 13 + public function testGetters(): void 14 + { 15 + $createdAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $updatedAt = new DateTimeImmutable('2026-01-02T00:00:00Z'); 17 + $data = ['scopes' => ['atproto']]; 18 + 19 + $client = new AuthorizedClient( 20 + did: 'did:plc:alice', 21 + clientId: 'https://app.test/client.json', 22 + createdAt: $createdAt, 23 + updatedAt: $updatedAt, 24 + data: $data, 25 + ); 26 + 27 + $this->assertSame('did:plc:alice', $client->getDid()); 28 + $this->assertSame('https://app.test/client.json', $client->getClientId()); 29 + $this->assertEquals($createdAt, $client->getCreatedAt()); 30 + $this->assertEquals($updatedAt, $client->getUpdatedAt()); 31 + $this->assertSame($data, $client->getData()); 32 + } 33 + 34 + public function testJsonSerializeOmitsData(): void 35 + { 36 + $client = new AuthorizedClient( 37 + did: 'did:plc:alice', 38 + clientId: 'https://app.test/client.json', 39 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 40 + updatedAt: new DateTimeImmutable('2026-01-02T00:00:00Z'), 41 + data: ['secret' => 'shh'], 42 + ); 43 + 44 + $json = json_decode((string) json_encode($client), true); 45 + 46 + $this->assertSame([ 47 + 'did' => 'did:plc:alice', 48 + 'clientId' => 'https://app.test/client.json', 49 + 'createdAt' => '2026-01-01T00:00:00+00:00', 50 + 'updatedAt' => '2026-01-02T00:00:00+00:00', 51 + ], $json); 52 + $this->assertArrayNotHasKey('data', $json); 53 + } 54 + }
+62
tests/Domain/OAuth/DeviceTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\OAuth; 6 + 7 + use App\Domain\OAuth\Device; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class DeviceTest extends TestCase 12 + { 13 + public function testGettersWithAllFields(): void 14 + { 15 + $lastSeenAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $device = new Device( 17 + id: 'dev-1', 18 + sessionId: 'sess-1', 19 + userAgent: 'Mozilla/5.0', 20 + ipAddress: '127.0.0.1', 21 + lastSeenAt: $lastSeenAt, 22 + ); 23 + 24 + $this->assertSame('dev-1', $device->getId()); 25 + $this->assertSame('sess-1', $device->getSessionId()); 26 + $this->assertSame('Mozilla/5.0', $device->getUserAgent()); 27 + $this->assertSame('127.0.0.1', $device->getIpAddress()); 28 + $this->assertEquals($lastSeenAt, $device->getLastSeenAt()); 29 + } 30 + 31 + public function testUserAgentCanBeNull(): void 32 + { 33 + $device = new Device( 34 + id: 'dev-1', 35 + sessionId: 'sess-1', 36 + userAgent: null, 37 + ipAddress: '127.0.0.1', 38 + lastSeenAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 39 + ); 40 + 41 + $this->assertNull($device->getUserAgent()); 42 + } 43 + 44 + public function testJsonSerialize(): void 45 + { 46 + $device = new Device( 47 + id: 'dev-1', 48 + sessionId: 'sess-1', 49 + userAgent: 'Mozilla/5.0', 50 + ipAddress: '127.0.0.1', 51 + lastSeenAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 52 + ); 53 + 54 + $this->assertSame([ 55 + 'id' => 'dev-1', 56 + 'sessionId' => 'sess-1', 57 + 'userAgent' => 'Mozilla/5.0', 58 + 'ipAddress' => '127.0.0.1', 59 + 'lastSeenAt' => '2026-01-01T00:00:00+00:00', 60 + ], $device->jsonSerialize()); 61 + } 62 + }
+101
tests/Domain/OAuth/OAuthTokenTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\OAuth; 6 + 7 + use App\Domain\OAuth\OAuthToken; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class OAuthTokenTest extends TestCase 12 + { 13 + private function makeToken(): OAuthToken 14 + { 15 + return new OAuthToken( 16 + id: 42, 17 + did: 'did:plc:alice', 18 + tokenId: 'tok-1', 19 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 20 + updatedAt: new DateTimeImmutable('2026-01-02T00:00:00Z'), 21 + expiresAt: new DateTimeImmutable('2026-02-01T00:00:00Z'), 22 + clientId: 'https://app.test/client.json', 23 + clientAuth: ['method' => 'none'], 24 + deviceId: 'dev-1', 25 + parameters: ['scope' => 'atproto'], 26 + details: ['extra' => true], 27 + code: 'auth-code', 28 + currentRefreshToken: 'refresh-1', 29 + scope: 'atproto', 30 + ); 31 + } 32 + 33 + public function testGetters(): void 34 + { 35 + $token = $this->makeToken(); 36 + 37 + $this->assertSame(42, $token->getId()); 38 + $this->assertSame('did:plc:alice', $token->getDid()); 39 + $this->assertSame('tok-1', $token->getTokenId()); 40 + $this->assertEquals(new DateTimeImmutable('2026-01-01T00:00:00Z'), $token->getCreatedAt()); 41 + $this->assertEquals(new DateTimeImmutable('2026-01-02T00:00:00Z'), $token->getUpdatedAt()); 42 + $this->assertEquals(new DateTimeImmutable('2026-02-01T00:00:00Z'), $token->getExpiresAt()); 43 + $this->assertSame('https://app.test/client.json', $token->getClientId()); 44 + $this->assertSame(['method' => 'none'], $token->getClientAuth()); 45 + $this->assertSame('dev-1', $token->getDeviceId()); 46 + $this->assertSame(['scope' => 'atproto'], $token->getParameters()); 47 + $this->assertSame(['extra' => true], $token->getDetails()); 48 + $this->assertSame('auth-code', $token->getCode()); 49 + $this->assertSame('refresh-1', $token->getCurrentRefreshToken()); 50 + $this->assertSame('atproto', $token->getScope()); 51 + } 52 + 53 + public function testNullableFields(): void 54 + { 55 + $token = new OAuthToken( 56 + id: 1, 57 + did: 'did:plc:alice', 58 + tokenId: 'tok-1', 59 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 60 + updatedAt: new DateTimeImmutable('2026-01-02T00:00:00Z'), 61 + expiresAt: new DateTimeImmutable('2026-02-01T00:00:00Z'), 62 + clientId: 'https://app.test/client.json', 63 + clientAuth: [], 64 + deviceId: null, 65 + parameters: [], 66 + details: null, 67 + code: null, 68 + currentRefreshToken: null, 69 + scope: null, 70 + ); 71 + 72 + $this->assertNull($token->getDeviceId()); 73 + $this->assertNull($token->getDetails()); 74 + $this->assertNull($token->getCode()); 75 + $this->assertNull($token->getCurrentRefreshToken()); 76 + $this->assertNull($token->getScope()); 77 + } 78 + 79 + public function testJsonSerializeOmitsSensitiveJsonColumns(): void 80 + { 81 + $token = $this->makeToken(); 82 + $json = json_decode((string) json_encode($token), true); 83 + 84 + $this->assertSame([ 85 + 'id' => 42, 86 + 'did' => 'did:plc:alice', 87 + 'tokenId' => 'tok-1', 88 + 'createdAt' => '2026-01-01T00:00:00+00:00', 89 + 'updatedAt' => '2026-01-02T00:00:00+00:00', 90 + 'expiresAt' => '2026-02-01T00:00:00+00:00', 91 + 'clientId' => 'https://app.test/client.json', 92 + 'deviceId' => 'dev-1', 93 + 'code' => 'auth-code', 94 + 'currentRefreshToken' => 'refresh-1', 95 + 'scope' => 'atproto', 96 + ], $json); 97 + $this->assertArrayNotHasKey('clientAuth', $json); 98 + $this->assertArrayNotHasKey('parameters', $json); 99 + $this->assertArrayNotHasKey('details', $json); 100 + } 101 + }
+29
tests/Domain/OAuth/UsedRefreshTokenTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\OAuth; 6 + 7 + use App\Domain\OAuth\UsedRefreshToken; 8 + use Tests\TestCase; 9 + 10 + class UsedRefreshTokenTest extends TestCase 11 + { 12 + public function testGetters(): void 13 + { 14 + $used = new UsedRefreshToken(tokenId: 7, refreshToken: 'refresh-xyz'); 15 + 16 + $this->assertSame(7, $used->getTokenId()); 17 + $this->assertSame('refresh-xyz', $used->getRefreshToken()); 18 + } 19 + 20 + public function testJsonSerialize(): void 21 + { 22 + $used = new UsedRefreshToken(tokenId: 7, refreshToken: 'refresh-xyz'); 23 + 24 + $this->assertSame( 25 + ['tokenId' => 7, 'refreshToken' => 'refresh-xyz'], 26 + $used->jsonSerialize() 27 + ); 28 + } 29 + }
+26
tests/Domain/Pds/Atproto/Identity/ResolveHandleResponseTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Pds\Atproto\Identity; 6 + 7 + use App\Domain\Pds\Atproto\Identity\ResolveHandleResponse; 8 + use Tests\TestCase; 9 + 10 + class ResolveHandleResponseTest extends TestCase 11 + { 12 + public function testGetDid(): void 13 + { 14 + $response = new ResolveHandleResponse('did:plc:alice'); 15 + 16 + $this->assertSame('did:plc:alice', $response->getDid()); 17 + } 18 + 19 + public function testJsonSerialize(): void 20 + { 21 + $response = new ResolveHandleResponse('did:plc:alice'); 22 + 23 + $this->assertSame(['did' => 'did:plc:alice'], $response->jsonSerialize()); 24 + $this->assertSame('{"did":"did:plc:alice"}', (string) json_encode($response)); 25 + } 26 + }
+39
tests/Domain/Preference/AccountPrefTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Preference; 6 + 7 + use App\Domain\Preference\AccountPref; 8 + use Tests\TestCase; 9 + 10 + class AccountPrefTest extends TestCase 11 + { 12 + public function testGetters(): void 13 + { 14 + $pref = new AccountPref( 15 + id: 1, 16 + name: 'app.bsky.actor.defs#savedFeedsPref', 17 + valueJson: '{"items":[]}', 18 + ); 19 + 20 + $this->assertSame(1, $pref->getId()); 21 + $this->assertSame('app.bsky.actor.defs#savedFeedsPref', $pref->getName()); 22 + $this->assertSame('{"items":[]}', $pref->getValueJson()); 23 + } 24 + 25 + public function testJsonSerialize(): void 26 + { 27 + $pref = new AccountPref( 28 + id: 1, 29 + name: 'app.bsky.actor.defs#savedFeedsPref', 30 + valueJson: '{"items":[]}', 31 + ); 32 + 33 + $this->assertSame([ 34 + 'id' => 1, 35 + 'name' => 'app.bsky.actor.defs#savedFeedsPref', 36 + 'valueJson' => '{"items":[]}', 37 + ], $pref->jsonSerialize()); 38 + } 39 + }
+39
tests/Domain/Record/BacklinkTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Record; 6 + 7 + use App\Domain\Record\Backlink; 8 + use Tests\TestCase; 9 + 10 + class BacklinkTest extends TestCase 11 + { 12 + public function testGetters(): void 13 + { 14 + $backlink = new Backlink( 15 + uri: 'at://did:plc:alice/app.bsky.feed.like/abc', 16 + path: 'subject.uri', 17 + linkTo: 'at://did:plc:bob/app.bsky.feed.post/xyz', 18 + ); 19 + 20 + $this->assertSame('at://did:plc:alice/app.bsky.feed.like/abc', $backlink->getUri()); 21 + $this->assertSame('subject.uri', $backlink->getPath()); 22 + $this->assertSame('at://did:plc:bob/app.bsky.feed.post/xyz', $backlink->getLinkTo()); 23 + } 24 + 25 + public function testJsonSerialize(): void 26 + { 27 + $backlink = new Backlink( 28 + uri: 'at://did:plc:alice/app.bsky.feed.like/abc', 29 + path: 'subject.uri', 30 + linkTo: 'at://did:plc:bob/app.bsky.feed.post/xyz', 31 + ); 32 + 33 + $this->assertSame([ 34 + 'uri' => 'at://did:plc:alice/app.bsky.feed.like/abc', 35 + 'path' => 'subject.uri', 36 + 'linkTo' => 'at://did:plc:bob/app.bsky.feed.post/xyz', 37 + ], $backlink->jsonSerialize()); 38 + } 39 + }
+35
tests/Domain/Record/RecordBlobTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Record; 6 + 7 + use App\Domain\Record\RecordBlob; 8 + use Tests\TestCase; 9 + 10 + class RecordBlobTest extends TestCase 11 + { 12 + public function testGetters(): void 13 + { 14 + $rb = new RecordBlob( 15 + blobCid: 'bafy-blob', 16 + recordUri: 'at://did:plc:alice/app.bsky.feed.post/3k', 17 + ); 18 + 19 + $this->assertSame('bafy-blob', $rb->getBlobCid()); 20 + $this->assertSame('at://did:plc:alice/app.bsky.feed.post/3k', $rb->getRecordUri()); 21 + } 22 + 23 + public function testJsonSerialize(): void 24 + { 25 + $rb = new RecordBlob( 26 + blobCid: 'bafy-blob', 27 + recordUri: 'at://did:plc:alice/app.bsky.feed.post/3k', 28 + ); 29 + 30 + $this->assertSame([ 31 + 'blobCid' => 'bafy-blob', 32 + 'recordUri' => 'at://did:plc:alice/app.bsky.feed.post/3k', 33 + ], $rb->jsonSerialize()); 34 + } 35 + }
+71
tests/Domain/Record/RecordTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Record; 6 + 7 + use App\Domain\Record\Record; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class RecordTest extends TestCase 12 + { 13 + public function testGettersWithAllFields(): void 14 + { 15 + $indexedAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $record = new Record( 17 + uri: 'at://did:plc:alice/app.bsky.feed.post/3k', 18 + cid: 'bafy-record', 19 + collection: 'app.bsky.feed.post', 20 + rkey: '3k', 21 + repoRev: 'rev-1', 22 + indexedAt: $indexedAt, 23 + takedownRef: 'tk-1', 24 + ); 25 + 26 + $this->assertSame('at://did:plc:alice/app.bsky.feed.post/3k', $record->getUri()); 27 + $this->assertSame('bafy-record', $record->getCid()); 28 + $this->assertSame('app.bsky.feed.post', $record->getCollection()); 29 + $this->assertSame('3k', $record->getRkey()); 30 + $this->assertSame('rev-1', $record->getRepoRev()); 31 + $this->assertEquals($indexedAt, $record->getIndexedAt()); 32 + $this->assertSame('tk-1', $record->getTakedownRef()); 33 + } 34 + 35 + public function testTakedownRefDefaultsToNull(): void 36 + { 37 + $record = new Record( 38 + uri: 'at://did:plc:alice/app.bsky.feed.post/3k', 39 + cid: 'bafy-record', 40 + collection: 'app.bsky.feed.post', 41 + rkey: '3k', 42 + repoRev: 'rev-1', 43 + indexedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 44 + ); 45 + 46 + $this->assertNull($record->getTakedownRef()); 47 + } 48 + 49 + public function testJsonSerialize(): void 50 + { 51 + $record = new Record( 52 + uri: 'at://did:plc:alice/app.bsky.feed.post/3k', 53 + cid: 'bafy-record', 54 + collection: 'app.bsky.feed.post', 55 + rkey: '3k', 56 + repoRev: 'rev-1', 57 + indexedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 58 + takedownRef: null, 59 + ); 60 + 61 + $this->assertSame([ 62 + 'uri' => 'at://did:plc:alice/app.bsky.feed.post/3k', 63 + 'cid' => 'bafy-record', 64 + 'collection' => 'app.bsky.feed.post', 65 + 'rkey' => '3k', 66 + 'repoRev' => 'rev-1', 67 + 'indexedAt' => '2026-01-01T00:00:00+00:00', 68 + 'takedownRef' => null, 69 + ], $record->jsonSerialize()); 70 + } 71 + }
+26
tests/Domain/Repo/CborBytesTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Repo; 6 + 7 + use App\Domain\Repo\CborBytes; 8 + use Tests\TestCase; 9 + 10 + class CborBytesTest extends TestCase 11 + { 12 + public function testGetBytesReturnsConstructorValue(): void 13 + { 14 + $raw = "\x00\x01\x02hello"; 15 + $wrapped = new CborBytes($raw); 16 + 17 + $this->assertSame($raw, $wrapped->getBytes()); 18 + } 19 + 20 + public function testSupportsEmptyByteString(): void 21 + { 22 + $wrapped = new CborBytes(''); 23 + 24 + $this->assertSame('', $wrapped->getBytes()); 25 + } 26 + }
+21
tests/Domain/Repo/CidLinkTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Repo; 6 + 7 + use App\Domain\Repo\CidLink; 8 + use Tests\TestCase; 9 + 10 + class CidLinkTest extends TestCase 11 + { 12 + public function testGetCidReturnsConstructorValue(): void 13 + { 14 + $link = new CidLink('bafyreigh2akiscaildc3o3vhbw5dvz25ftpqhmkfymf2e3glr3nzxqvxha'); 15 + 16 + $this->assertSame( 17 + 'bafyreigh2akiscaildc3o3vhbw5dvz25ftpqhmkfymf2e3glr3nzxqvxha', 18 + $link->getCid() 19 + ); 20 + } 21 + }
+45
tests/Domain/Repo/RepoBlockTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Repo; 6 + 7 + use App\Domain\Repo\RepoBlock; 8 + use Tests\TestCase; 9 + 10 + class RepoBlockTest extends TestCase 11 + { 12 + public function testGetters(): void 13 + { 14 + $block = new RepoBlock( 15 + cid: 'bafy-cid', 16 + repoRev: 'rev-1', 17 + size: 128, 18 + content: "\x00binary\xffbytes", 19 + ); 20 + 21 + $this->assertSame('bafy-cid', $block->getCid()); 22 + $this->assertSame('rev-1', $block->getRepoRev()); 23 + $this->assertSame(128, $block->getSize()); 24 + $this->assertSame("\x00binary\xffbytes", $block->getContent()); 25 + } 26 + 27 + public function testJsonSerializeOmitsContent(): void 28 + { 29 + $block = new RepoBlock( 30 + cid: 'bafy-cid', 31 + repoRev: 'rev-1', 32 + size: 128, 33 + content: "\x00binary\xffbytes", 34 + ); 35 + 36 + $payload = $block->jsonSerialize(); 37 + 38 + $this->assertSame([ 39 + 'cid' => 'bafy-cid', 40 + 'repoRev' => 'rev-1', 41 + 'size' => 128, 42 + ], $payload); 43 + $this->assertArrayNotHasKey('content', $payload); 44 + } 45 + }
+45
tests/Domain/Repo/RepoRootTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Repo; 6 + 7 + use App\Domain\Repo\RepoRoot; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class RepoRootTest extends TestCase 12 + { 13 + public function testGetters(): void 14 + { 15 + $indexedAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $root = new RepoRoot( 17 + did: 'did:plc:alice', 18 + cid: 'bafy-root', 19 + rev: 'rev-1', 20 + indexedAt: $indexedAt, 21 + ); 22 + 23 + $this->assertSame('did:plc:alice', $root->getDid()); 24 + $this->assertSame('bafy-root', $root->getCid()); 25 + $this->assertSame('rev-1', $root->getRev()); 26 + $this->assertEquals($indexedAt, $root->getIndexedAt()); 27 + } 28 + 29 + public function testJsonSerialize(): void 30 + { 31 + $root = new RepoRoot( 32 + did: 'did:plc:alice', 33 + cid: 'bafy-root', 34 + rev: 'rev-1', 35 + indexedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 36 + ); 37 + 38 + $this->assertSame([ 39 + 'did' => 'did:plc:alice', 40 + 'cid' => 'bafy-root', 41 + 'rev' => 'rev-1', 42 + 'indexedAt' => '2026-01-01T00:00:00+00:00', 43 + ], $root->jsonSerialize()); 44 + } 45 + }
+77
tests/Domain/Sequencer/RepoSeqEventTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Domain\Sequencer; 6 + 7 + use App\Domain\Sequencer\RepoSeqEvent; 8 + use DateTimeImmutable; 9 + use Tests\TestCase; 10 + 11 + class RepoSeqEventTest extends TestCase 12 + { 13 + public function testGettersWithAllFields(): void 14 + { 15 + $sequencedAt = new DateTimeImmutable('2026-01-01T00:00:00Z'); 16 + $event = new RepoSeqEvent( 17 + seq: 42, 18 + did: 'did:plc:alice', 19 + eventType: RepoSeqEvent::TYPE_APPEND, 20 + event: "\x00cbor", 21 + sequencedAt: $sequencedAt, 22 + invalidated: true, 23 + ); 24 + 25 + $this->assertSame(42, $event->getSeq()); 26 + $this->assertSame('did:plc:alice', $event->getDid()); 27 + $this->assertSame(RepoSeqEvent::TYPE_APPEND, $event->getEventType()); 28 + $this->assertSame("\x00cbor", $event->getEvent()); 29 + $this->assertEquals($sequencedAt, $event->getSequencedAt()); 30 + $this->assertTrue($event->isInvalidated()); 31 + } 32 + 33 + public function testInvalidatedDefaultsToFalse(): void 34 + { 35 + $event = new RepoSeqEvent( 36 + seq: 1, 37 + did: 'did:plc:alice', 38 + eventType: RepoSeqEvent::TYPE_SYNC, 39 + event: '', 40 + sequencedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 41 + ); 42 + 43 + $this->assertFalse($event->isInvalidated()); 44 + } 45 + 46 + public function testJsonSerializeOmitsEventBytes(): void 47 + { 48 + $event = new RepoSeqEvent( 49 + seq: 42, 50 + did: 'did:plc:alice', 51 + eventType: RepoSeqEvent::TYPE_APPEND, 52 + event: "\x00binary", 53 + sequencedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 54 + invalidated: false, 55 + ); 56 + 57 + $json = json_decode((string) json_encode($event), true); 58 + 59 + $this->assertSame([ 60 + 'seq' => 42, 61 + 'did' => 'did:plc:alice', 62 + 'eventType' => RepoSeqEvent::TYPE_APPEND, 63 + 'sequencedAt' => '2026-01-01T00:00:00+00:00', 64 + 'invalidated' => false, 65 + ], $json); 66 + $this->assertArrayNotHasKey('event', $json); 67 + } 68 + 69 + public function testTypeConstants(): void 70 + { 71 + $this->assertSame('append', RepoSeqEvent::TYPE_APPEND); 72 + $this->assertSame('sync', RepoSeqEvent::TYPE_SYNC); 73 + $this->assertSame('identity', RepoSeqEvent::TYPE_IDENTITY); 74 + $this->assertSame('account', RepoSeqEvent::TYPE_ACCOUNT); 75 + $this->assertSame('tombstone', RepoSeqEvent::TYPE_TOMBSTONE); 76 + } 77 + }
+80
tests/Infrastructure/Persistence/Lexicon/InMemoryLexiconRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\Lexicon; 6 + 7 + use App\Domain\Lexicon\LexiconEntry; 8 + use App\Domain\Lexicon\LexiconEntryNotFoundException; 9 + use App\Infrastructure\Persistence\Lexicon\InMemoryLexiconRepository; 10 + use DateTimeImmutable; 11 + use Tests\TestCase; 12 + 13 + class InMemoryLexiconRepositoryTest extends TestCase 14 + { 15 + private function makeEntry(string $nsid = 'com.example.foo'): LexiconEntry 16 + { 17 + return new LexiconEntry( 18 + nsid: $nsid, 19 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 20 + updatedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 21 + lastSucceededAt: null, 22 + uri: null, 23 + lexicon: null, 24 + ); 25 + } 26 + 27 + public function testFindByNsid(): void 28 + { 29 + $entry = $this->makeEntry(); 30 + $repo = new InMemoryLexiconRepository([$entry]); 31 + 32 + $this->assertSame($entry, $repo->findByNsid('com.example.foo')); 33 + } 34 + 35 + public function testFindByNsidThrowsWhenMissing(): void 36 + { 37 + $repo = new InMemoryLexiconRepository(); 38 + 39 + $this->expectException(LexiconEntryNotFoundException::class); 40 + $repo->findByNsid('nope'); 41 + } 42 + 43 + public function testFindAll(): void 44 + { 45 + $repo = new InMemoryLexiconRepository([ 46 + $this->makeEntry('com.example.a'), 47 + $this->makeEntry('com.example.b'), 48 + ]); 49 + 50 + $this->assertCount(2, $repo->findAll()); 51 + } 52 + 53 + public function testSaveInsertsAndUpdates(): void 54 + { 55 + $repo = new InMemoryLexiconRepository(); 56 + $repo->save($this->makeEntry('com.example.a')); 57 + $this->assertCount(1, $repo->findAll()); 58 + 59 + $updated = new LexiconEntry( 60 + nsid: 'com.example.a', 61 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 62 + updatedAt: new DateTimeImmutable('2026-02-01T00:00:00Z'), 63 + lastSucceededAt: null, 64 + uri: 'https://example.com/a.json', 65 + lexicon: null, 66 + ); 67 + $repo->save($updated); 68 + 69 + $this->assertCount(1, $repo->findAll()); 70 + $this->assertSame('https://example.com/a.json', $repo->findByNsid('com.example.a')->getUri()); 71 + } 72 + 73 + public function testDeleteByNsid(): void 74 + { 75 + $repo = new InMemoryLexiconRepository([$this->makeEntry()]); 76 + $repo->deleteByNsid('com.example.foo'); 77 + 78 + $this->assertCount(0, $repo->findAll()); 79 + } 80 + }
+88
tests/Infrastructure/Persistence/OAuth/InMemoryAccountDeviceRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\OAuth; 6 + 7 + use App\Domain\OAuth\AccountDevice; 8 + use App\Domain\OAuth\AccountDeviceNotFoundException; 9 + use App\Infrastructure\Persistence\OAuth\InMemoryAccountDeviceRepository; 10 + use DateTimeImmutable; 11 + use Tests\TestCase; 12 + 13 + class InMemoryAccountDeviceRepositoryTest extends TestCase 14 + { 15 + private function makeEntry(string $did = 'did:plc:alice', string $deviceId = 'dev-1'): AccountDevice 16 + { 17 + return new AccountDevice( 18 + did: $did, 19 + deviceId: $deviceId, 20 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 21 + updatedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 22 + ); 23 + } 24 + 25 + public function testFindByDidAndDeviceId(): void 26 + { 27 + $entry = $this->makeEntry(); 28 + $repo = new InMemoryAccountDeviceRepository([$entry]); 29 + 30 + $this->assertSame($entry, $repo->findByDidAndDeviceId('did:plc:alice', 'dev-1')); 31 + } 32 + 33 + public function testFindByDidAndDeviceIdThrowsWhenMissing(): void 34 + { 35 + $repo = new InMemoryAccountDeviceRepository(); 36 + 37 + $this->expectException(AccountDeviceNotFoundException::class); 38 + $repo->findByDidAndDeviceId('did:plc:alice', 'dev-1'); 39 + } 40 + 41 + public function testFindAllForDid(): void 42 + { 43 + $repo = new InMemoryAccountDeviceRepository([ 44 + $this->makeEntry('did:plc:alice', 'dev-1'), 45 + $this->makeEntry('did:plc:alice', 'dev-2'), 46 + $this->makeEntry('did:plc:bob', 'dev-3'), 47 + ]); 48 + 49 + $this->assertCount(2, $repo->findAllForDid('did:plc:alice')); 50 + $this->assertCount(1, $repo->findAllForDid('did:plc:bob')); 51 + $this->assertCount(0, $repo->findAllForDid('did:plc:carol')); 52 + } 53 + 54 + public function testSaveInsertsNewAndUpdatesExisting(): void 55 + { 56 + $repo = new InMemoryAccountDeviceRepository(); 57 + $repo->save($this->makeEntry()); 58 + $this->assertCount(1, $repo->findAllForDid('did:plc:alice')); 59 + 60 + $updated = new AccountDevice( 61 + did: 'did:plc:alice', 62 + deviceId: 'dev-1', 63 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 64 + updatedAt: new DateTimeImmutable('2026-02-01T00:00:00Z'), 65 + ); 66 + $repo->save($updated); 67 + 68 + $this->assertCount(1, $repo->findAllForDid('did:plc:alice')); 69 + $this->assertEquals( 70 + new DateTimeImmutable('2026-02-01T00:00:00Z'), 71 + $repo->findByDidAndDeviceId('did:plc:alice', 'dev-1')->getUpdatedAt(), 72 + ); 73 + } 74 + 75 + public function testDeleteByDidAndDeviceId(): void 76 + { 77 + $repo = new InMemoryAccountDeviceRepository([ 78 + $this->makeEntry('did:plc:alice', 'dev-1'), 79 + $this->makeEntry('did:plc:alice', 'dev-2'), 80 + ]); 81 + 82 + $repo->deleteByDidAndDeviceId('did:plc:alice', 'dev-1'); 83 + 84 + $this->assertCount(1, $repo->findAllForDid('did:plc:alice')); 85 + $this->expectException(AccountDeviceNotFoundException::class); 86 + $repo->findByDidAndDeviceId('did:plc:alice', 'dev-1'); 87 + } 88 + }
+88
tests/Infrastructure/Persistence/OAuth/InMemoryAuthorizationRequestRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\OAuth; 6 + 7 + use App\Domain\OAuth\AuthorizationRequest; 8 + use App\Domain\OAuth\AuthorizationRequestNotFoundException; 9 + use App\Infrastructure\Persistence\OAuth\InMemoryAuthorizationRequestRepository; 10 + use DateTimeImmutable; 11 + use Tests\TestCase; 12 + 13 + class InMemoryAuthorizationRequestRepositoryTest extends TestCase 14 + { 15 + private function makeRequest( 16 + string $id = 'req-1', 17 + ?string $code = 'auth-code', 18 + ?DateTimeImmutable $expiresAt = null, 19 + ): AuthorizationRequest { 20 + return new AuthorizationRequest( 21 + id: $id, 22 + did: 'did:plc:alice', 23 + deviceId: 'dev-1', 24 + clientId: 'https://app.test/client.json', 25 + clientAuth: null, 26 + parameters: [], 27 + expiresAt: $expiresAt ?? new DateTimeImmutable('+1 hour'), 28 + code: $code, 29 + ); 30 + } 31 + 32 + public function testFindByIdAndFindByCode(): void 33 + { 34 + $req = $this->makeRequest(); 35 + $repo = new InMemoryAuthorizationRequestRepository([$req]); 36 + 37 + $this->assertSame($req, $repo->findById('req-1')); 38 + $this->assertSame($req, $repo->findByCode('auth-code')); 39 + } 40 + 41 + public function testFindByIdThrowsWhenMissing(): void 42 + { 43 + $repo = new InMemoryAuthorizationRequestRepository(); 44 + 45 + $this->expectException(AuthorizationRequestNotFoundException::class); 46 + $repo->findById('nope'); 47 + } 48 + 49 + public function testFindByCodeThrowsWhenMissing(): void 50 + { 51 + $repo = new InMemoryAuthorizationRequestRepository([$this->makeRequest(code: null)]); 52 + 53 + $this->expectException(AuthorizationRequestNotFoundException::class); 54 + $repo->findByCode('nope'); 55 + } 56 + 57 + public function testSaveInsertsAndUpdates(): void 58 + { 59 + $repo = new InMemoryAuthorizationRequestRepository(); 60 + $repo->save($this->makeRequest(id: 'req-1', code: 'a')); 61 + $repo->save($this->makeRequest(id: 'req-1', code: 'b')); 62 + 63 + $this->assertSame('b', $repo->findById('req-1')->getCode()); 64 + } 65 + 66 + public function testDeleteById(): void 67 + { 68 + $repo = new InMemoryAuthorizationRequestRepository([$this->makeRequest()]); 69 + $repo->deleteById('req-1'); 70 + 71 + $this->expectException(AuthorizationRequestNotFoundException::class); 72 + $repo->findById('req-1'); 73 + } 74 + 75 + public function testDeleteExpiredRemovesPastEntriesOnly(): void 76 + { 77 + $past = $this->makeRequest(id: 'expired', expiresAt: new DateTimeImmutable('-1 hour')); 78 + $future = $this->makeRequest(id: 'live', expiresAt: new DateTimeImmutable('+1 hour')); 79 + $repo = new InMemoryAuthorizationRequestRepository([$past, $future]); 80 + 81 + $removed = $repo->deleteExpired(); 82 + 83 + $this->assertSame(1, $removed); 84 + $this->assertSame($future, $repo->findById('live')); 85 + $this->expectException(AuthorizationRequestNotFoundException::class); 86 + $repo->findById('expired'); 87 + } 88 + }
+103
tests/Infrastructure/Persistence/OAuth/InMemoryAuthorizedClientRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\OAuth; 6 + 7 + use App\Domain\OAuth\AuthorizedClient; 8 + use App\Domain\OAuth\AuthorizedClientNotFoundException; 9 + use App\Infrastructure\Persistence\OAuth\InMemoryAuthorizedClientRepository; 10 + use DateTimeImmutable; 11 + use Tests\TestCase; 12 + 13 + class InMemoryAuthorizedClientRepositoryTest extends TestCase 14 + { 15 + private function makeEntry( 16 + string $did = 'did:plc:alice', 17 + string $clientId = 'https://app.test/client.json', 18 + ): AuthorizedClient { 19 + return new AuthorizedClient( 20 + did: $did, 21 + clientId: $clientId, 22 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 23 + updatedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 24 + data: [], 25 + ); 26 + } 27 + 28 + public function testFindByDidAndClientId(): void 29 + { 30 + $entry = $this->makeEntry(); 31 + $repo = new InMemoryAuthorizedClientRepository([$entry]); 32 + 33 + $this->assertSame($entry, $repo->findByDidAndClientId('did:plc:alice', 'https://app.test/client.json')); 34 + } 35 + 36 + public function testFindByDidAndClientIdThrowsWhenMissing(): void 37 + { 38 + $repo = new InMemoryAuthorizedClientRepository(); 39 + 40 + $this->expectException(AuthorizedClientNotFoundException::class); 41 + $repo->findByDidAndClientId('did:plc:alice', 'nope'); 42 + } 43 + 44 + public function testFindAllForDid(): void 45 + { 46 + $repo = new InMemoryAuthorizedClientRepository([ 47 + $this->makeEntry('did:plc:alice', 'c1'), 48 + $this->makeEntry('did:plc:alice', 'c2'), 49 + $this->makeEntry('did:plc:bob', 'c3'), 50 + ]); 51 + 52 + $this->assertCount(2, $repo->findAllForDid('did:plc:alice')); 53 + $this->assertCount(1, $repo->findAllForDid('did:plc:bob')); 54 + } 55 + 56 + public function testSaveInsertsAndUpdates(): void 57 + { 58 + $repo = new InMemoryAuthorizedClientRepository(); 59 + $repo->save($this->makeEntry()); 60 + $this->assertCount(1, $repo->findAllForDid('did:plc:alice')); 61 + 62 + $updated = new AuthorizedClient( 63 + did: 'did:plc:alice', 64 + clientId: 'https://app.test/client.json', 65 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 66 + updatedAt: new DateTimeImmutable('2026-02-01T00:00:00Z'), 67 + data: ['new' => true], 68 + ); 69 + $repo->save($updated); 70 + 71 + $this->assertCount(1, $repo->findAllForDid('did:plc:alice')); 72 + $this->assertSame( 73 + ['new' => true], 74 + $repo->findByDidAndClientId('did:plc:alice', 'https://app.test/client.json')->getData() 75 + ); 76 + } 77 + 78 + public function testDeleteByDidAndClientId(): void 79 + { 80 + $repo = new InMemoryAuthorizedClientRepository([ 81 + $this->makeEntry('did:plc:alice', 'c1'), 82 + $this->makeEntry('did:plc:alice', 'c2'), 83 + ]); 84 + 85 + $repo->deleteByDidAndClientId('did:plc:alice', 'c1'); 86 + 87 + $this->assertCount(1, $repo->findAllForDid('did:plc:alice')); 88 + } 89 + 90 + public function testDeleteAllForDid(): void 91 + { 92 + $repo = new InMemoryAuthorizedClientRepository([ 93 + $this->makeEntry('did:plc:alice', 'c1'), 94 + $this->makeEntry('did:plc:alice', 'c2'), 95 + $this->makeEntry('did:plc:bob', 'c3'), 96 + ]); 97 + 98 + $repo->deleteAllForDid('did:plc:alice'); 99 + 100 + $this->assertCount(0, $repo->findAllForDid('did:plc:alice')); 101 + $this->assertCount(1, $repo->findAllForDid('did:plc:bob')); 102 + } 103 + }
+67
tests/Infrastructure/Persistence/OAuth/InMemoryDeviceRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\OAuth; 6 + 7 + use App\Domain\OAuth\Device; 8 + use App\Domain\OAuth\DeviceNotFoundException; 9 + use App\Infrastructure\Persistence\OAuth\InMemoryDeviceRepository; 10 + use DateTimeImmutable; 11 + use Tests\TestCase; 12 + 13 + class InMemoryDeviceRepositoryTest extends TestCase 14 + { 15 + private function makeDevice(string $id = 'dev-1'): Device 16 + { 17 + return new Device( 18 + id: $id, 19 + sessionId: 'sess-' . $id, 20 + userAgent: null, 21 + ipAddress: '127.0.0.1', 22 + lastSeenAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 23 + ); 24 + } 25 + 26 + public function testFindById(): void 27 + { 28 + $device = $this->makeDevice(); 29 + $repo = new InMemoryDeviceRepository([$device]); 30 + 31 + $this->assertSame($device, $repo->findById('dev-1')); 32 + } 33 + 34 + public function testFindByIdThrowsWhenMissing(): void 35 + { 36 + $repo = new InMemoryDeviceRepository(); 37 + 38 + $this->expectException(DeviceNotFoundException::class); 39 + $repo->findById('nope'); 40 + } 41 + 42 + public function testSaveInsertsAndUpdates(): void 43 + { 44 + $repo = new InMemoryDeviceRepository(); 45 + $repo->save($this->makeDevice()); 46 + 47 + $updated = new Device( 48 + id: 'dev-1', 49 + sessionId: 'sess-updated', 50 + userAgent: 'Mozilla/5.0', 51 + ipAddress: '10.0.0.1', 52 + lastSeenAt: new DateTimeImmutable('2026-02-01T00:00:00Z'), 53 + ); 54 + $repo->save($updated); 55 + 56 + $this->assertSame('sess-updated', $repo->findById('dev-1')->getSessionId()); 57 + } 58 + 59 + public function testDeleteById(): void 60 + { 61 + $repo = new InMemoryDeviceRepository([$this->makeDevice()]); 62 + $repo->deleteById('dev-1'); 63 + 64 + $this->expectException(DeviceNotFoundException::class); 65 + $repo->findById('dev-1'); 66 + } 67 + }
+128
tests/Infrastructure/Persistence/OAuth/InMemoryOAuthTokenRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\OAuth; 6 + 7 + use App\Domain\OAuth\OAuthToken; 8 + use App\Domain\OAuth\OAuthTokenNotFoundException; 9 + use App\Infrastructure\Persistence\OAuth\InMemoryOAuthTokenRepository; 10 + use DateTimeImmutable; 11 + use Tests\TestCase; 12 + 13 + class InMemoryOAuthTokenRepositoryTest extends TestCase 14 + { 15 + private function makeToken( 16 + int $id = 1, 17 + string $tokenId = 'tok-1', 18 + string $did = 'did:plc:alice', 19 + ?string $code = 'code-1', 20 + ?string $refresh = 'refresh-1', 21 + ): OAuthToken { 22 + return new OAuthToken( 23 + id: $id, 24 + did: $did, 25 + tokenId: $tokenId, 26 + createdAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 27 + updatedAt: new DateTimeImmutable('2026-01-01T00:00:00Z'), 28 + expiresAt: new DateTimeImmutable('2026-02-01T00:00:00Z'), 29 + clientId: 'https://app.test/client.json', 30 + clientAuth: [], 31 + deviceId: null, 32 + parameters: [], 33 + details: null, 34 + code: $code, 35 + currentRefreshToken: $refresh, 36 + scope: null, 37 + ); 38 + } 39 + 40 + public function testFindByTokenId(): void 41 + { 42 + $token = $this->makeToken(); 43 + $repo = new InMemoryOAuthTokenRepository([$token]); 44 + 45 + $this->assertSame($token, $repo->findByTokenId('tok-1')); 46 + } 47 + 48 + public function testFindByTokenIdThrowsWhenMissing(): void 49 + { 50 + $repo = new InMemoryOAuthTokenRepository(); 51 + 52 + $this->expectException(OAuthTokenNotFoundException::class); 53 + $repo->findByTokenId('nope'); 54 + } 55 + 56 + public function testFindByCode(): void 57 + { 58 + $repo = new InMemoryOAuthTokenRepository([$this->makeToken()]); 59 + 60 + $this->assertSame('tok-1', $repo->findByCode('code-1')->getTokenId()); 61 + } 62 + 63 + public function testFindByCodeThrowsWhenMissing(): void 64 + { 65 + $repo = new InMemoryOAuthTokenRepository([$this->makeToken(code: null)]); 66 + 67 + $this->expectException(OAuthTokenNotFoundException::class); 68 + $repo->findByCode('nope'); 69 + } 70 + 71 + public function testFindByRefreshToken(): void 72 + { 73 + $repo = new InMemoryOAuthTokenRepository([$this->makeToken()]); 74 + 75 + $this->assertSame('tok-1', $repo->findByRefreshToken('refresh-1')->getTokenId()); 76 + } 77 + 78 + public function testFindByRefreshTokenThrowsWhenMissing(): void 79 + { 80 + $repo = new InMemoryOAuthTokenRepository([$this->makeToken(refresh: null)]); 81 + 82 + $this->expectException(OAuthTokenNotFoundException::class); 83 + $repo->findByRefreshToken('nope'); 84 + } 85 + 86 + public function testFindAllForDid(): void 87 + { 88 + $repo = new InMemoryOAuthTokenRepository([ 89 + $this->makeToken(id: 1, tokenId: 't1', did: 'did:plc:alice', code: 'c1', refresh: 'r1'), 90 + $this->makeToken(id: 2, tokenId: 't2', did: 'did:plc:alice', code: 'c2', refresh: 'r2'), 91 + $this->makeToken(id: 3, tokenId: 't3', did: 'did:plc:bob', code: 'c3', refresh: 'r3'), 92 + ]); 93 + 94 + $this->assertCount(2, $repo->findAllForDid('did:plc:alice')); 95 + $this->assertCount(1, $repo->findAllForDid('did:plc:bob')); 96 + } 97 + 98 + public function testSaveInsertsAndUpdates(): void 99 + { 100 + $repo = new InMemoryOAuthTokenRepository(); 101 + $repo->save($this->makeToken()); 102 + $repo->save($this->makeToken(code: 'updated')); 103 + 104 + $this->assertSame('updated', $repo->findByTokenId('tok-1')->getCode()); 105 + } 106 + 107 + public function testDeleteByTokenId(): void 108 + { 109 + $repo = new InMemoryOAuthTokenRepository([$this->makeToken()]); 110 + $repo->deleteByTokenId('tok-1'); 111 + 112 + $this->expectException(OAuthTokenNotFoundException::class); 113 + $repo->findByTokenId('tok-1'); 114 + } 115 + 116 + public function testDeleteAllForDid(): void 117 + { 118 + $repo = new InMemoryOAuthTokenRepository([ 119 + $this->makeToken(id: 1, tokenId: 't1', did: 'did:plc:alice', code: 'c1', refresh: 'r1'), 120 + $this->makeToken(id: 2, tokenId: 't2', did: 'did:plc:bob', code: 'c2', refresh: 'r2'), 121 + ]); 122 + 123 + $repo->deleteAllForDid('did:plc:alice'); 124 + 125 + $this->assertCount(0, $repo->findAllForDid('did:plc:alice')); 126 + $this->assertCount(1, $repo->findAllForDid('did:plc:bob')); 127 + } 128 + }
+58
tests/Infrastructure/Persistence/OAuth/InMemoryUsedRefreshTokenRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\OAuth; 6 + 7 + use App\Domain\OAuth\UsedRefreshToken; 8 + use App\Infrastructure\Persistence\OAuth\InMemoryUsedRefreshTokenRepository; 9 + use Tests\TestCase; 10 + 11 + class InMemoryUsedRefreshTokenRepositoryTest extends TestCase 12 + { 13 + public function testExistsReturnsTrueForSeededTokens(): void 14 + { 15 + $repo = new InMemoryUsedRefreshTokenRepository([ 16 + new UsedRefreshToken(1, 'refresh-a'), 17 + ]); 18 + 19 + $this->assertTrue($repo->exists('refresh-a')); 20 + $this->assertFalse($repo->exists('refresh-b')); 21 + } 22 + 23 + public function testSaveMakesTokenExist(): void 24 + { 25 + $repo = new InMemoryUsedRefreshTokenRepository(); 26 + $repo->save(new UsedRefreshToken(1, 'refresh-a')); 27 + 28 + $this->assertTrue($repo->exists('refresh-a')); 29 + } 30 + 31 + public function testSaveOverwritesPriorEntryForSameRefreshTokenString(): void 32 + { 33 + $repo = new InMemoryUsedRefreshTokenRepository([ 34 + new UsedRefreshToken(1, 'refresh-a'), 35 + ]); 36 + $repo->save(new UsedRefreshToken(2, 'refresh-a')); 37 + 38 + $this->assertTrue($repo->exists('refresh-a')); 39 + // deleting by the old tokenId should not remove the rebound entry 40 + $repo->deleteAllForTokenId(1); 41 + $this->assertTrue($repo->exists('refresh-a')); 42 + } 43 + 44 + public function testDeleteAllForTokenId(): void 45 + { 46 + $repo = new InMemoryUsedRefreshTokenRepository([ 47 + new UsedRefreshToken(1, 'refresh-a'), 48 + new UsedRefreshToken(1, 'refresh-b'), 49 + new UsedRefreshToken(2, 'refresh-c'), 50 + ]); 51 + 52 + $repo->deleteAllForTokenId(1); 53 + 54 + $this->assertFalse($repo->exists('refresh-a')); 55 + $this->assertFalse($repo->exists('refresh-b')); 56 + $this->assertTrue($repo->exists('refresh-c')); 57 + } 58 + }
+69
tests/Infrastructure/Persistence/Record/InMemoryBacklinkRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\Record; 6 + 7 + use App\Domain\Record\Backlink; 8 + use App\Infrastructure\Persistence\Record\InMemoryBacklinkRepository; 9 + use Tests\TestCase; 10 + 11 + class InMemoryBacklinkRepositoryTest extends TestCase 12 + { 13 + public function testFindByUriAndLinkTo(): void 14 + { 15 + $a = new Backlink('at://alice/like/1', 'subject.uri', 'at://bob/post/1'); 16 + $b = new Backlink('at://alice/like/2', 'subject.uri', 'at://bob/post/1'); 17 + $c = new Backlink('at://alice/like/3', 'subject.uri', 'at://carol/post/9'); 18 + $repo = new InMemoryBacklinkRepository([$a, $b, $c]); 19 + 20 + $this->assertCount(1, $repo->findByUri('at://alice/like/1')); 21 + $this->assertCount(2, $repo->findByLinkTo('at://bob/post/1')); 22 + $this->assertCount(0, $repo->findByUri('nope')); 23 + } 24 + 25 + public function testSaveIsIdempotentForDuplicateTriple(): void 26 + { 27 + $repo = new InMemoryBacklinkRepository(); 28 + $bl = new Backlink('at://alice/like/1', 'subject.uri', 'at://bob/post/1'); 29 + 30 + $repo->save($bl); 31 + $repo->save($bl); 32 + $repo->save(new Backlink('at://alice/like/1', 'subject.uri', 'at://bob/post/1')); 33 + 34 + $this->assertCount(1, $repo->findByUri('at://alice/like/1')); 35 + } 36 + 37 + public function testSaveAddsWhenPathOrLinkToDiffers(): void 38 + { 39 + $repo = new InMemoryBacklinkRepository(); 40 + $repo->save(new Backlink('at://alice/like/1', 'subject.uri', 'at://bob/post/1')); 41 + $repo->save(new Backlink('at://alice/like/1', 'reply.parent.uri', 'at://bob/post/1')); 42 + 43 + $this->assertCount(2, $repo->findByUri('at://alice/like/1')); 44 + } 45 + 46 + public function testDeleteByUri(): void 47 + { 48 + $repo = new InMemoryBacklinkRepository([ 49 + new Backlink('at://alice/like/1', 'subject.uri', 'at://bob/post/1'), 50 + new Backlink('at://alice/like/2', 'subject.uri', 'at://bob/post/1'), 51 + ]); 52 + 53 + $repo->deleteByUri('at://alice/like/1'); 54 + 55 + $this->assertCount(0, $repo->findByUri('at://alice/like/1')); 56 + $this->assertCount(1, $repo->findByLinkTo('at://bob/post/1')); 57 + } 58 + 59 + public function testDeleteAll(): void 60 + { 61 + $repo = new InMemoryBacklinkRepository([ 62 + new Backlink('at://alice/like/1', 'subject.uri', 'at://bob/post/1'), 63 + ]); 64 + 65 + $repo->deleteAll(); 66 + 67 + $this->assertCount(0, $repo->findByLinkTo('at://bob/post/1')); 68 + } 69 + }
+62
tests/Infrastructure/Persistence/Record/InMemoryRecordBlobRepositoryTest.php
··· 1 + <?php 2 + 3 + declare(strict_types=1); 4 + 5 + namespace Tests\Infrastructure\Persistence\Record; 6 + 7 + use App\Domain\Record\RecordBlob; 8 + use App\Infrastructure\Persistence\Record\InMemoryRecordBlobRepository; 9 + use Tests\TestCase; 10 + 11 + class InMemoryRecordBlobRepositoryTest extends TestCase 12 + { 13 + public function testFindByBlobCidAndRecordUri(): void 14 + { 15 + $repo = new InMemoryRecordBlobRepository([ 16 + new RecordBlob('blob-1', 'at://alice/post/1'), 17 + new RecordBlob('blob-2', 'at://alice/post/1'), 18 + new RecordBlob('blob-1', 'at://alice/post/2'), 19 + ]); 20 + 21 + $this->assertCount(2, $repo->findByBlobCid('blob-1')); 22 + $this->assertCount(2, $repo->findByRecordUri('at://alice/post/1')); 23 + $this->assertCount(0, $repo->findByBlobCid('nope')); 24 + } 25 + 26 + public function testSaveIsIdempotent(): void 27 + { 28 + $repo = new InMemoryRecordBlobRepository(); 29 + $repo->save(new RecordBlob('blob-1', 'at://alice/post/1')); 30 + $repo->save(new RecordBlob('blob-1', 'at://alice/post/1')); 31 + 32 + $this->assertCount(1, $repo->findByBlobCid('blob-1')); 33 + } 34 + 35 + public function testDeleteByRecordUri(): void 36 + { 37 + $repo = new InMemoryRecordBlobRepository([ 38 + new RecordBlob('blob-1', 'at://alice/post/1'), 39 + new RecordBlob('blob-2', 'at://alice/post/1'), 40 + new RecordBlob('blob-1', 'at://alice/post/2'), 41 + ]); 42 + 43 + $repo->deleteByRecordUri('at://alice/post/1'); 44 + 45 + $this->assertCount(0, $repo->findByRecordUri('at://alice/post/1')); 46 + $this->assertCount(1, $repo->findByRecordUri('at://alice/post/2')); 47 + } 48 + 49 + public function testDeleteByBlobCid(): void 50 + { 51 + $repo = new InMemoryRecordBlobRepository([ 52 + new RecordBlob('blob-1', 'at://alice/post/1'), 53 + new RecordBlob('blob-1', 'at://alice/post/2'), 54 + new RecordBlob('blob-2', 'at://alice/post/3'), 55 + ]); 56 + 57 + $repo->deleteByBlobCid('blob-1'); 58 + 59 + $this->assertCount(0, $repo->findByBlobCid('blob-1')); 60 + $this->assertCount(1, $repo->findByBlobCid('blob-2')); 61 + } 62 + }