Parse and validate AT Protocol Lexicons with DTO generation for Laravel
at dev 9.9 kB view raw
1<?php 2 3namespace SocialDept\AtpSchema\Tests\Unit\Data; 4 5use Orchestra\Testbench\TestCase; 6use SocialDept\AtpSchema\Data\LexiconDocument; 7use SocialDept\AtpSchema\Exceptions\SchemaValidationException; 8use SocialDept\AtpSchema\Parser\Nsid; 9 10class LexiconDocumentTest extends TestCase 11{ 12 public function test_it_creates_from_array(): void 13 { 14 $data = [ 15 'lexicon' => 1, 16 'id' => 'app.bsky.feed.post', 17 'description' => 'A post record', 18 'defs' => [ 19 'main' => [ 20 'type' => 'record', 21 ], 22 ], 23 ]; 24 25 $doc = LexiconDocument::fromArray($data, 'test.json'); 26 27 $this->assertSame(1, $doc->lexicon); 28 $this->assertInstanceOf(Nsid::class, $doc->id); 29 $this->assertSame('app.bsky.feed.post', $doc->id->toString()); 30 $this->assertSame('A post record', $doc->description); 31 $this->assertArrayHasKey('main', $doc->defs); 32 $this->assertSame('test.json', $doc->source); 33 $this->assertSame($data, $doc->raw); 34 } 35 36 public function test_it_creates_from_json(): void 37 { 38 $json = json_encode([ 39 'lexicon' => 1, 40 'id' => 'app.bsky.feed.post', 41 'description' => 'A post record', 42 'defs' => [ 43 'main' => [ 44 'type' => 'record', 45 ], 46 ], 47 ]); 48 49 $doc = LexiconDocument::fromJson($json, 'test.json'); 50 51 $this->assertSame(1, $doc->lexicon); 52 $this->assertInstanceOf(Nsid::class, $doc->id); 53 $this->assertSame('app.bsky.feed.post', $doc->id->toString()); 54 $this->assertSame('A post record', $doc->description); 55 $this->assertArrayHasKey('main', $doc->defs); 56 $this->assertSame('test.json', $doc->source); 57 } 58 59 public function test_it_creates_from_json_without_source(): void 60 { 61 $json = json_encode([ 62 'lexicon' => 1, 63 'id' => 'app.bsky.feed.post', 64 'defs' => [ 65 'main' => ['type' => 'record'], 66 ], 67 ]); 68 69 $doc = LexiconDocument::fromJson($json); 70 71 $this->assertSame(1, $doc->lexicon); 72 $this->assertSame('app.bsky.feed.post', $doc->id->toString()); 73 $this->assertNull($doc->source); 74 } 75 76 public function test_it_throws_on_invalid_json(): void 77 { 78 $this->expectException(\InvalidArgumentException::class); 79 $this->expectExceptionMessage('Invalid JSON'); 80 81 LexiconDocument::fromJson('{"invalid json'); 82 } 83 84 public function test_it_throws_on_non_array_json(): void 85 { 86 $this->expectException(\InvalidArgumentException::class); 87 $this->expectExceptionMessage('JSON must decode to an array'); 88 89 LexiconDocument::fromJson('"just a string"'); 90 } 91 92 public function test_it_throws_on_json_array_list(): void 93 { 94 $this->expectException(\InvalidArgumentException::class); 95 $this->expectExceptionMessage('JSON must decode to an array'); 96 97 LexiconDocument::fromJson('123'); 98 } 99 100 public function test_it_throws_on_missing_lexicon(): void 101 { 102 $this->expectException(SchemaValidationException::class); 103 $this->expectExceptionMessage('Required field missing in schema unknown: lexicon'); 104 105 LexiconDocument::fromArray([ 106 'id' => 'app.bsky.feed.post', 107 'defs' => [], 108 ]); 109 } 110 111 public function test_it_throws_on_missing_id(): void 112 { 113 $this->expectException(SchemaValidationException::class); 114 $this->expectExceptionMessage('Required field missing in schema unknown: id'); 115 116 LexiconDocument::fromArray([ 117 'lexicon' => 1, 118 'defs' => [], 119 ]); 120 } 121 122 public function test_it_throws_on_missing_defs(): void 123 { 124 $this->expectException(SchemaValidationException::class); 125 $this->expectExceptionMessage('Required field missing in schema app.bsky.feed.post: defs'); 126 127 LexiconDocument::fromArray([ 128 'lexicon' => 1, 129 'id' => 'app.bsky.feed.post', 130 ]); 131 } 132 133 public function test_it_throws_on_invalid_lexicon_version(): void 134 { 135 $this->expectException(SchemaValidationException::class); 136 $this->expectExceptionMessage('Unsupported lexicon version 2 in schema app.bsky.feed.post'); 137 138 LexiconDocument::fromArray([ 139 'lexicon' => 2, 140 'id' => 'app.bsky.feed.post', 141 'defs' => [], 142 ]); 143 } 144 145 public function test_it_gets_definition(): void 146 { 147 $doc = LexiconDocument::fromArray([ 148 'lexicon' => 1, 149 'id' => 'app.bsky.feed.post', 150 'defs' => [ 151 'main' => ['type' => 'record'], 152 'other' => ['type' => 'object'], 153 ], 154 ]); 155 156 $this->assertSame(['type' => 'record'], $doc->getDefinition('main')); 157 $this->assertSame(['type' => 'object'], $doc->getDefinition('other')); 158 $this->assertNull($doc->getDefinition('nonexistent')); 159 } 160 161 public function test_it_checks_definition_exists(): void 162 { 163 $doc = LexiconDocument::fromArray([ 164 'lexicon' => 1, 165 'id' => 'app.bsky.feed.post', 166 'defs' => [ 167 'main' => ['type' => 'record'], 168 ], 169 ]); 170 171 $this->assertTrue($doc->hasDefinition('main')); 172 $this->assertFalse($doc->hasDefinition('nonexistent')); 173 } 174 175 public function test_it_gets_main_definition(): void 176 { 177 $doc = LexiconDocument::fromArray([ 178 'lexicon' => 1, 179 'id' => 'app.bsky.feed.post', 180 'defs' => [ 181 'main' => ['type' => 'record'], 182 ], 183 ]); 184 185 $this->assertSame(['type' => 'record'], $doc->getMainDefinition()); 186 } 187 188 public function test_it_gets_definition_names(): void 189 { 190 $doc = LexiconDocument::fromArray([ 191 'lexicon' => 1, 192 'id' => 'app.bsky.feed.post', 193 'defs' => [ 194 'main' => ['type' => 'record'], 195 'other' => ['type' => 'object'], 196 'another' => ['type' => 'string'], 197 ], 198 ]); 199 200 $names = $doc->getDefinitionNames(); 201 202 $this->assertCount(3, $names); 203 $this->assertContains('main', $names); 204 $this->assertContains('other', $names); 205 $this->assertContains('another', $names); 206 } 207 208 public function test_it_gets_nsid(): void 209 { 210 $doc = LexiconDocument::fromArray([ 211 'lexicon' => 1, 212 'id' => 'app.bsky.feed.post', 213 'defs' => [], 214 ]); 215 216 $this->assertSame('app.bsky.feed.post', $doc->getNsid()); 217 } 218 219 public function test_it_gets_version(): void 220 { 221 $doc = LexiconDocument::fromArray([ 222 'lexicon' => 1, 223 'id' => 'app.bsky.feed.post', 224 'defs' => [], 225 ]); 226 227 $this->assertSame(1, $doc->getVersion()); 228 } 229 230 public function test_it_gets_version_same_as_lexicon_property(): void 231 { 232 $doc = LexiconDocument::fromArray([ 233 'lexicon' => 1, 234 'id' => 'app.bsky.feed.post', 235 'defs' => [], 236 ]); 237 238 $this->assertSame($doc->lexicon, $doc->getVersion()); 239 } 240 241 public function test_it_converts_to_array(): void 242 { 243 $doc = LexiconDocument::fromArray([ 244 'lexicon' => 1, 245 'id' => 'app.bsky.feed.post', 246 'description' => 'A post record', 247 'defs' => [ 248 'main' => ['type' => 'record'], 249 ], 250 ]); 251 252 $array = $doc->toArray(); 253 254 $this->assertSame(1, $array['lexicon']); 255 $this->assertSame('app.bsky.feed.post', $array['id']); 256 $this->assertSame('A post record', $array['description']); 257 $this->assertArrayHasKey('main', $array['defs']); 258 } 259 260 public function test_it_identifies_record_schema(): void 261 { 262 $doc = LexiconDocument::fromArray([ 263 'lexicon' => 1, 264 'id' => 'app.bsky.feed.post', 265 'defs' => [ 266 'main' => ['type' => 'record'], 267 ], 268 ]); 269 270 $this->assertTrue($doc->isRecord()); 271 $this->assertFalse($doc->isQuery()); 272 $this->assertFalse($doc->isProcedure()); 273 $this->assertFalse($doc->isSubscription()); 274 } 275 276 public function test_it_identifies_query_schema(): void 277 { 278 $doc = LexiconDocument::fromArray([ 279 'lexicon' => 1, 280 'id' => 'com.atproto.repo.getRecord', 281 'defs' => [ 282 'main' => ['type' => 'query'], 283 ], 284 ]); 285 286 $this->assertTrue($doc->isQuery()); 287 $this->assertFalse($doc->isRecord()); 288 $this->assertFalse($doc->isProcedure()); 289 $this->assertFalse($doc->isSubscription()); 290 } 291 292 public function test_it_identifies_procedure_schema(): void 293 { 294 $doc = LexiconDocument::fromArray([ 295 'lexicon' => 1, 296 'id' => 'com.atproto.repo.createRecord', 297 'defs' => [ 298 'main' => ['type' => 'procedure'], 299 ], 300 ]); 301 302 $this->assertTrue($doc->isProcedure()); 303 $this->assertFalse($doc->isRecord()); 304 $this->assertFalse($doc->isQuery()); 305 $this->assertFalse($doc->isSubscription()); 306 } 307 308 public function test_it_identifies_subscription_schema(): void 309 { 310 $doc = LexiconDocument::fromArray([ 311 'lexicon' => 1, 312 'id' => 'com.atproto.sync.subscribeRepos', 313 'defs' => [ 314 'main' => ['type' => 'subscription'], 315 ], 316 ]); 317 318 $this->assertTrue($doc->isSubscription()); 319 $this->assertFalse($doc->isRecord()); 320 $this->assertFalse($doc->isQuery()); 321 $this->assertFalse($doc->isProcedure()); 322 } 323}