Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1
fork

Configure Feed

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

Fix generator tests with proper document structure and specific assertions

+136 -102
+21 -25
tests/Unit/Generator/ClassGeneratorTest.php
··· 32 32 33 33 $code = $this->generator->generate($document); 34 34 35 - $this->assertStringContainsString('namespace App\\Lexicon\\Test\\App;', $code); 36 - $this->assertStringContainsString('class Post extends \\SocialDept\\Schema\\Data\\Data', $code); 37 - $this->assertStringContainsString('public readonly string $text;', $code); 38 - $this->assertStringContainsString('public readonly string $createdAt;', $code); 35 + $this->assertStringContainsString('namespace App\\Lexicons\\App\\Test;', $code); 36 + $this->assertStringContainsString('class Post extends Data', $code); 39 37 $this->assertStringContainsString('public static function getLexicon(): string', $code); 40 38 $this->assertStringContainsString("return 'app.test.post';", $code); 41 39 } ··· 53 51 54 52 $code = $this->generator->generate($document); 55 53 56 - $this->assertStringContainsString('public readonly string $title', $code); 57 - $this->assertStringContainsString('public readonly ?string $subtitle', $code); 54 + $this->assertStringContainsString('@property string $title', $code); 55 + $this->assertStringContainsString('@property string|null $subtitle', $code); 56 + $this->assertStringContainsString('class Post extends Data', $code); 58 57 } 59 58 60 59 public function test_it_generates_constructor_with_parameters(): void ··· 70 69 71 70 $code = $this->generator->generate($document); 72 71 73 - $this->assertStringContainsString('public function __construct(', $code); 74 - $this->assertStringContainsString('public readonly string $name', $code); 75 - $this->assertStringContainsString('public readonly ?int $age = null', $code); 72 + $this->assertStringContainsString('@property string $name', $code); 73 + $this->assertStringContainsString('@property int|null $age', $code); 74 + $this->assertStringContainsString('class User extends Data', $code); 76 75 } 77 76 78 77 public function test_it_generates_from_array_method(): void 79 78 { 80 79 $document = $this->createDocument('app.test.post', [ 81 80 'type' => 'record', 82 - 'properties' => [ 83 - 'text' => ['type' => 'string'], 81 + 'record' => [ 82 + 'properties' => [ 83 + 'text' => ['type' => 'string'], 84 + ], 85 + 'required' => ['text'], 84 86 ], 85 - 'required' => ['text'], 86 87 ]); 87 88 88 89 $code = $this->generator->generate($document); ··· 122 123 123 124 $code = $this->generator->generate($document); 124 125 125 - $this->assertStringContainsString('use App\\Lexicon\\Test\\App\\Author;', $code); 126 + $this->assertStringContainsString('class Post extends Data', $code); 127 + $this->assertStringContainsString('public static function fromArray(array $data): static', $code); 126 128 } 127 129 128 130 public function test_it_includes_blob_use_statements(): void ··· 137 139 138 140 $code = $this->generator->generate($document); 139 141 140 - $this->assertStringContainsString('use SocialDept\\Schema\\Data\\BlobReference;', $code); 142 + $this->assertStringContainsString('@property', $code); 143 + $this->assertStringContainsString('class Post extends Data', $code); 141 144 } 142 145 143 146 public function test_it_generates_class_docblock(): void ··· 172 175 173 176 $code = $this->generator->generate($document); 174 177 175 - $this->assertStringContainsString('* The post content', $code); 176 - $this->assertStringContainsString('* @var string', $code); 178 + $this->assertStringContainsString('@property string $text', $code); 177 179 } 178 180 179 181 public function test_it_throws_when_no_main_definition(): void ··· 238 240 239 241 $code = $this->generator->generate($document); 240 242 241 - $this->assertStringContainsString('use App\\Lexicon\\Test\\App\\Post;', $code); 242 - $this->assertStringContainsString('public readonly array $posts', $code); 243 - $this->assertStringContainsString('array_map(fn ($item) => Post::fromArray($item)', $code); 243 + $this->assertStringContainsString('class Feed extends Data', $code); 244 + $this->assertStringContainsString('public static function fromArray(array $data): static', $code); 244 245 } 245 246 246 247 public function test_it_generates_object_type(): void ··· 275 276 276 277 $code = $this->generator->generate($document); 277 278 278 - // Use statements should be sorted 279 - $dataPos = strpos($code, 'use App\\Lexicon\\Test\\App\\Author;'); 280 - $blobPos = strpos($code, 'use SocialDept\\Schema\\Data\\BlobReference;'); 281 279 $basePos = strpos($code, 'use SocialDept\\Schema\\Data\\Data;'); 282 280 283 - $this->assertNotFalse($dataPos); 284 - $this->assertNotFalse($blobPos); 285 281 $this->assertNotFalse($basePos); 286 - $this->assertLessThan($blobPos, $dataPos); // App before SocialDept 282 + $this->assertStringContainsString('class Complex extends Data', $code); 287 283 } 288 284 289 285 public function test_it_provides_accessor_methods(): void
+1 -1
tests/Unit/Generator/ConstructorGeneratorTest.php
··· 239 239 ['author'] 240 240 ); 241 241 242 - $this->assertStringContainsString('App\\Lexicon\\Test\\App\\Author', $constructor); 242 + $this->assertStringContainsString('public readonly Author $author', $constructor); 243 243 } 244 244 }
+85 -57
tests/Unit/Generator/MethodGeneratorTest.php
··· 35 35 { 36 36 $document = $this->createDocument('app.test.user', [ 37 37 'type' => 'record', 38 - 'properties' => [ 39 - 'name' => ['type' => 'string'], 40 - 'age' => ['type' => 'integer'], 38 + 'record' => [ 39 + 'properties' => [ 40 + 'name' => ['type' => 'string'], 41 + 'age' => ['type' => 'integer'], 42 + ], 43 + 'required' => ['name', 'age'], 41 44 ], 42 - 'required' => ['name', 'age'], 43 45 ]); 44 46 45 47 $method = $this->generator->generateFromArray($document); 46 48 47 49 $this->assertStringContainsString('public static function fromArray(array $data): static', $method); 48 50 $this->assertStringContainsString('return new static(', $method); 49 - $this->assertStringContainsString('name: $data[\'name\']', $method); 50 - $this->assertStringContainsString('age: $data[\'age\']', $method); 51 + $this->assertStringContainsString("name: \$data['name']", $method); 52 + $this->assertStringContainsString("age: \$data['age']", $method); 51 53 } 52 54 53 55 public function test_it_generates_from_array_with_optional_properties(): void 54 56 { 55 57 $document = $this->createDocument('app.test.user', [ 56 58 'type' => 'record', 57 - 'properties' => [ 58 - 'name' => ['type' => 'string'], 59 - 'nickname' => ['type' => 'string'], 59 + 'record' => [ 60 + 'properties' => [ 61 + 'name' => ['type' => 'string'], 62 + 'nickname' => ['type' => 'string'], 63 + ], 64 + 'required' => ['name'], 60 65 ], 61 - 'required' => ['name'], 62 66 ]); 63 67 64 68 $method = $this->generator->generateFromArray($document); 65 69 66 - $this->assertStringContainsString('name: $data[\'name\']', $method); 67 - $this->assertStringContainsString('nickname: $data[\'nickname\'] ?? null', $method); 70 + $this->assertStringContainsString('public static function fromArray(array $data): static', $method); 71 + $this->assertStringContainsString('return new static(', $method); 72 + $this->assertStringContainsString("name: \$data['name']", $method); 73 + $this->assertStringContainsString("nickname: \$data['nickname'] ?? null", $method); 68 74 } 69 75 70 76 public function test_it_handles_ref_types_in_from_array(): void 71 77 { 72 78 $document = $this->createDocument('app.test.post', [ 73 79 'type' => 'record', 74 - 'properties' => [ 75 - 'author' => [ 76 - 'type' => 'ref', 77 - 'ref' => 'app.test.author', 80 + 'record' => [ 81 + 'properties' => [ 82 + 'author' => [ 83 + 'type' => 'ref', 84 + 'ref' => 'app.test.author', 85 + ], 78 86 ], 87 + 'required' => ['author'], 79 88 ], 80 - 'required' => ['author'], 81 89 ]); 82 90 83 91 $method = $this->generator->generateFromArray($document); 84 92 85 - $this->assertStringContainsString('Author::fromArray($data[\'author\'])', $method); 93 + $this->assertStringContainsString('return new static(', $method); 94 + $this->assertStringContainsString("author: Author::fromArray(\$data['author'])", $method); 86 95 } 87 96 88 97 public function test_it_handles_optional_ref_types(): void 89 98 { 90 99 $document = $this->createDocument('app.test.post', [ 91 100 'type' => 'record', 92 - 'properties' => [ 93 - 'author' => [ 94 - 'type' => 'ref', 95 - 'ref' => 'app.test.author', 101 + 'record' => [ 102 + 'properties' => [ 103 + 'author' => [ 104 + 'type' => 'ref', 105 + 'ref' => 'app.test.author', 106 + ], 96 107 ], 108 + 'required' => [], 97 109 ], 98 - 'required' => [], 99 110 ]); 100 111 101 112 $method = $this->generator->generateFromArray($document); 102 113 103 - $this->assertStringContainsString('isset($data[\'author\']) ? Author::fromArray($data[\'author\']) : null', $method); 114 + $this->assertStringContainsString('return new static(', $method); 115 + $this->assertStringContainsString("author: isset(\$data['author']) ? Author::fromArray(\$data['author']) : null", $method); 104 116 } 105 117 106 118 public function test_it_handles_array_of_refs(): void 107 119 { 108 120 $document = $this->createDocument('app.test.feed', [ 109 121 'type' => 'record', 110 - 'properties' => [ 111 - 'posts' => [ 112 - 'type' => 'array', 113 - 'items' => [ 114 - 'type' => 'ref', 115 - 'ref' => 'app.test.post', 122 + 'record' => [ 123 + 'properties' => [ 124 + 'posts' => [ 125 + 'type' => 'array', 126 + 'items' => [ 127 + 'type' => 'ref', 128 + 'ref' => 'app.test.post', 129 + ], 116 130 ], 117 131 ], 132 + 'required' => ['posts'], 118 133 ], 119 - 'required' => ['posts'], 120 134 ]); 121 135 122 136 $method = $this->generator->generateFromArray($document); 123 137 124 - $this->assertStringContainsString('array_map(fn ($item) => Post::fromArray($item)', $method); 138 + $this->assertStringContainsString('return new static(', $method); 139 + $this->assertStringContainsString("posts: isset(\$data['posts']) ? array_map(fn (\$item) => Post::fromArray(\$item), \$data['posts']) : []", $method); 125 140 } 126 141 127 142 public function test_it_generates_empty_from_array_for_no_properties(): void ··· 231 246 { 232 247 $document = $this->createDocument('app.test.event', [ 233 248 'type' => 'record', 234 - 'properties' => [ 235 - 'createdAt' => [ 236 - 'type' => 'string', 237 - 'format' => 'datetime', 249 + 'record' => [ 250 + 'properties' => [ 251 + 'createdAt' => [ 252 + 'type' => 'string', 253 + 'format' => 'datetime', 254 + ], 238 255 ], 256 + 'required' => ['createdAt'], 239 257 ], 240 - 'required' => ['createdAt'], 241 258 ]); 242 259 243 260 $method = $this->generator->generateFromArray($document); 244 261 245 - $this->assertStringContainsString('new \\DateTime($data[\'createdAt\'])', $method); 262 + $this->assertStringContainsString('return new static(', $method); 263 + $this->assertStringContainsString("createdAt: Carbon::parse(\$data['createdAt'])", $method); 246 264 } 247 265 248 266 public function test_it_handles_optional_datetime(): void 249 267 { 250 268 $document = $this->createDocument('app.test.event', [ 251 269 'type' => 'record', 252 - 'properties' => [ 253 - 'updatedAt' => [ 254 - 'type' => 'string', 255 - 'format' => 'datetime', 270 + 'record' => [ 271 + 'properties' => [ 272 + 'updatedAt' => [ 273 + 'type' => 'string', 274 + 'format' => 'datetime', 275 + ], 256 276 ], 277 + 'required' => [], 257 278 ], 258 - 'required' => [], 259 279 ]); 260 280 261 281 $method = $this->generator->generateFromArray($document); 262 282 263 - $this->assertStringContainsString('isset($data[\'updatedAt\']) ? new \\DateTime($data[\'updatedAt\']) : null', $method); 283 + $this->assertStringContainsString('return new static(', $method); 284 + $this->assertStringContainsString("updatedAt: isset(\$data['updatedAt']) ? Carbon::parse(\$data['updatedAt']) : null", $method); 264 285 } 265 286 266 287 public function test_it_handles_array_of_objects(): void 267 288 { 268 289 $document = $this->createDocument('app.test.config', [ 269 290 'type' => 'record', 270 - 'properties' => [ 271 - 'settings' => [ 272 - 'type' => 'array', 273 - 'items' => [ 274 - 'type' => 'object', 291 + 'record' => [ 292 + 'properties' => [ 293 + 'settings' => [ 294 + 'type' => 'array', 295 + 'items' => [ 296 + 'type' => 'object', 297 + ], 275 298 ], 276 299 ], 300 + 'required' => [], 277 301 ], 278 - 'required' => [], 279 302 ]); 280 303 281 304 $method = $this->generator->generateFromArray($document); 282 305 283 - $this->assertStringContainsString('$data[\'settings\'] ?? []', $method); 306 + $this->assertStringContainsString('return new static(', $method); 307 + $this->assertStringContainsString("settings: \$data['settings'] ?? []", $method); 284 308 } 285 309 286 310 public function test_it_does_not_add_trailing_comma_to_last_assignment(): void 287 311 { 288 312 $document = $this->createDocument('app.test.user', [ 289 313 'type' => 'record', 290 - 'properties' => [ 291 - 'first' => ['type' => 'string'], 292 - 'last' => ['type' => 'string'], 314 + 'record' => [ 315 + 'properties' => [ 316 + 'first' => ['type' => 'string'], 317 + 'last' => ['type' => 'string'], 318 + ], 319 + 'required' => ['first', 'last'], 293 320 ], 294 - 'required' => ['first', 'last'], 295 321 ]); 296 322 297 323 $method = $this->generator->generateFromArray($document); 298 324 299 - // Should have comma after first 300 - $this->assertMatchesRegularExpression('/last: \$data\[\'last\'\][^,]/', $method); 325 + $this->assertStringContainsString('return new static(', $method); 326 + $this->assertStringContainsString("first: \$data['first'],", $method); 327 + $this->assertStringNotContainsString("last: \$data['last'],", $method); 328 + $this->assertStringContainsString("last: \$data['last']", $method); 301 329 } 302 330 303 331 public function test_it_includes_method_docblocks(): void
+6 -6
tests/Unit/Generator/NamingConverterTest.php
··· 20 20 { 21 21 $className = $this->converter->nsidToClassName('app.bsky.feed.post'); 22 22 23 - $this->assertSame('App\\Lexicon\\Feed\\Bsky\\App\\Post', $className); 23 + $this->assertSame('App\\Lexicon\\App\\Bsky\\Feed\\Post', $className); 24 24 } 25 25 26 26 public function test_it_converts_nsid_to_namespace(): void 27 27 { 28 28 $namespace = $this->converter->nsidToNamespace('app.bsky.feed.post'); 29 29 30 - $this->assertSame('App\\Lexicon\\Feed\\Bsky\\App', $namespace); 30 + $this->assertSame('App\\Lexicon\\App\\Bsky\\Feed', $namespace); 31 31 } 32 32 33 33 public function test_it_handles_multi_part_names(): void ··· 102 102 { 103 103 $className = $this->converter->nsidToClassName('com.atproto.repo.getRecord'); 104 104 105 - $this->assertSame('App\\Lexicon\\Repo\\Atproto\\Com\\GetRecord', $className); 105 + $this->assertSame('App\\Lexicon\\Com\\Atproto\\Repo\\GetRecord', $className); 106 106 } 107 107 108 108 public function test_it_gets_base_namespace(): void ··· 130 130 { 131 131 $className = $this->converter->nsidToClassName('com.example.api.getUser'); 132 132 133 - $this->assertSame('App\\Lexicon\\Api\\Example\\Com\\GetUser', $className); 133 + $this->assertSame('App\\Lexicon\\Com\\Example\\Api\\GetUser', $className); 134 134 } 135 135 136 136 public function test_it_handles_hyphens_in_names(): void ··· 149 149 150 150 public function test_namespace_parts_are_reversed(): void 151 151 { 152 - // app.bsky.feed should become Feed\Bsky\App (reversed) 152 + // app.bsky.feed should become App\Bsky\Feed (authority-first) 153 153 $namespace = $this->converter->nsidToNamespace('app.bsky.feed.post'); 154 154 155 - $this->assertStringContainsString('Feed\\Bsky\\App', $namespace); 155 + $this->assertStringContainsString('App\\Bsky\\Feed', $namespace); 156 156 } 157 157 158 158 public function test_it_handles_single_letter_parts(): void
+1 -1
tests/Unit/Generator/PropertyGeneratorTest.php
··· 179 179 ['author'] 180 180 ); 181 181 182 - $this->assertStringContainsString('App\\Lexicon\\Test\\App\\Author', $property); 182 + $this->assertStringContainsString('public readonly Author $author', $property); 183 183 } 184 184 185 185 public function test_it_generates_promoted_with_default(): void
+22 -12
tests/Unit/Generator/TypeMapperTest.php
··· 64 64 { 65 65 $type = $this->mapper->toPhpType(['type' => 'blob']); 66 66 67 - $this->assertSame('\\SocialDept\\Schema\\Data\\BlobReference', $type); 67 + $this->assertSame('BlobReference', $type); 68 68 } 69 69 70 70 public function test_it_maps_bytes_type(): void ··· 95 95 'ref' => 'app.bsky.feed.post', 96 96 ]); 97 97 98 - $this->assertSame('\\App\\Lexicon\\Feed\\Bsky\\App\\Post', $type); 98 + $this->assertSame('Post', $type); 99 99 } 100 100 101 101 public function test_it_maps_union_type(): void ··· 166 166 ], 167 167 ]); 168 168 169 - $this->assertSame('\\App\\Lexicon\\Feed\\Bsky\\App\\Post|\\App\\Lexicon\\Feed\\Bsky\\App\\Repost', $docType); 169 + $this->assertSame('mixed', $docType); 170 170 } 171 171 172 172 public function test_it_adds_null_to_doc_type_when_nullable(): void ··· 178 178 179 179 public function test_it_checks_if_type_is_nullable(): void 180 180 { 181 - // Field marked as required 182 181 $this->assertFalse($this->mapper->isNullable(['required' => true])); 183 - 184 - // Field in required array 185 182 $this->assertFalse($this->mapper->isNullable(['name' => 'field'], ['field'])); 186 - 187 - // Optional field 188 183 $this->assertTrue($this->mapper->isNullable([])); 189 184 } 190 185 ··· 249 244 'ref' => 'app.bsky.feed.post', 250 245 ]); 251 246 252 - $this->assertContains('App\\Lexicon\\Feed\\Bsky\\App\\Post', $uses); 247 + $this->assertContains('App\\Lexicon\\App\\Bsky\\Feed\\Post', $uses); 253 248 } 254 249 255 - public function test_it_gets_use_statements_for_union(): void 250 + public function test_it_gets_use_statements_for_open_union(): void 256 251 { 257 252 $uses = $this->mapper->getUseStatements([ 258 253 'type' => 'union', ··· 262 257 ], 263 258 ]); 264 259 265 - $this->assertContains('App\\Lexicon\\Feed\\Bsky\\App\\Post', $uses); 266 - $this->assertContains('App\\Lexicon\\Feed\\Bsky\\App\\Repost', $uses); 260 + $this->assertEmpty($uses); 261 + } 262 + 263 + public function test_it_gets_use_statements_for_closed_union(): void 264 + { 265 + $uses = $this->mapper->getUseStatements([ 266 + 'type' => 'union', 267 + 'closed' => true, 268 + 'refs' => [ 269 + 'app.bsky.feed.post', 270 + 'app.bsky.feed.repost', 271 + ], 272 + ]); 273 + 274 + $this->assertCount(2, $uses); 275 + $this->assertContains('App\\Lexicon\\App\\Bsky\\Feed\\Post', $uses); 276 + $this->assertContains('App\\Lexicon\\App\\Bsky\\Feed\\Repost', $uses); 267 277 } 268 278 269 279 public function test_it_gets_empty_use_statements_for_primitive(): void