Something went wrong. Try again.
Parse and validate AT Protocol Lexicons with DTO generation for Laravel
Something went wrong. Try again.
11 kB · 316 lines
PHP
at dev
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317<?php
namespace SocialDept\AtpSchema\Tests\Unit\Generator;
use Orchestra\Testbench\TestCase;use SocialDept\AtpSchema\Data\LexiconDocument;use SocialDept\AtpSchema\Exceptions\GenerationException;use SocialDept\AtpSchema\Generator\ClassGenerator;use SocialDept\AtpSchema\Parser\Nsid;
class ClassGeneratorTest extends TestCase{ protected ClassGenerator $generator;
protected function setUp(): void { parent::setUp();
$this->generator = new ClassGenerator(); }
public function test_it_generates_simple_record_class(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', 'properties' => [ 'text' => ['type' => 'string'], 'createdAt' => ['type' => 'string'], ], 'required' => ['text', 'createdAt'], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('namespace App\\Lexicons\\App\\Test;', $code); $this->assertStringContainsString('class Post extends Data', $code); $this->assertStringContainsString('public static function getLexicon(): string', $code); $this->assertStringContainsString("return 'app.test.post';", $code); }
public function test_it_handles_optional_properties(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', 'properties' => [ 'title' => ['type' => 'string'], 'subtitle' => ['type' => 'string'], ], 'required' => ['title'], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('@property string $title', $code); $this->assertStringContainsString('@property string|null $subtitle', $code); $this->assertStringContainsString('class Post extends Data', $code); }
public function test_it_generates_constructor_with_parameters(): void { $document = $this->createDocument('app.test.user', [ 'type' => 'record', 'properties' => [ 'name' => ['type' => 'string'], 'age' => ['type' => 'integer'], ], 'required' => ['name'], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('@property string $name', $code); $this->assertStringContainsString('@property int|null $age', $code); $this->assertStringContainsString('class User extends Data', $code); }
public function test_it_generates_from_array_method(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', 'record' => [ 'properties' => [ 'text' => ['type' => 'string'], ], 'required' => ['text'], ], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('public static function fromArray(array $data): static', $code); $this->assertStringContainsString('return new static(', $code); $this->assertStringContainsString("text: \$data['text']", $code); }
public function test_it_includes_use_statements(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', 'properties' => [ 'text' => ['type' => 'string'], ], 'required' => ['text'], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('use SocialDept\\AtpSchema\\Data\\Data;', $code); }
public function test_it_includes_ref_use_statements(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', 'properties' => [ 'author' => [ 'type' => 'ref', 'ref' => 'app.test.author', ], ], 'required' => ['author'], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('class Post extends Data', $code); $this->assertStringContainsString('public static function fromArray(array $data): static', $code); }
public function test_it_includes_blob_use_statements(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', 'properties' => [ 'image' => ['type' => 'blob'], ], 'required' => [], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('@property', $code); $this->assertStringContainsString('class Post extends Data', $code); }
public function test_it_generates_class_docblock(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', 'description' => 'A social media post', 'properties' => ['text' => ['type' => 'string']], 'required' => ['text'], ], 'A social media post');
$code = $this->generator->generate($document);
$this->assertStringContainsString('/**', $code); $this->assertStringContainsString('* A social media post', $code); $this->assertStringContainsString('* Lexicon: app.test.post', $code); $this->assertStringContainsString('* Type: record', $code); }
public function test_it_generates_property_docblocks(): void { $document = $this->createDocument('app.test.post', [ 'type' => 'record', 'properties' => [ 'text' => [ 'type' => 'string', 'description' => 'The post content', ], ], 'required' => ['text'], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('@property string $text', $code); }
public function test_it_throws_when_no_main_definition(): void { $document = new LexiconDocument( lexicon: 1, id: Nsid::parse('app.test.empty'), defs: [], description: null, source: null, raw: [] );
$this->expectException(GenerationException::class); $this->expectExceptionMessage('No main definition found');
$this->generator->generate($document); }
public function test_it_throws_for_non_record_types(): void { $document = $this->createDocument('app.test.query', [ 'type' => 'query', ]);
$this->expectException(GenerationException::class); $this->expectExceptionMessage('Can only generate classes for record and object types');
$this->generator->generate($document); }
public function test_it_handles_empty_properties(): void { $document = $this->createDocument('app.test.empty', [ 'type' => 'record', 'properties' => [], 'required' => [], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('class Empty', $code); $this->assertStringContainsString('public static function fromArray(array $data): static', $code); $this->assertStringContainsString('return new static();', $code); }
public function test_it_handles_array_of_refs(): void { $document = $this->createDocument('app.test.feed', [ 'type' => 'record', 'properties' => [ 'posts' => [ 'type' => 'array', 'items' => [ 'type' => 'ref', 'ref' => 'app.test.post', ], ], ], 'required' => ['posts'], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('class Feed extends Data', $code); $this->assertStringContainsString('public static function fromArray(array $data): static', $code); }
public function test_it_generates_object_type(): void { $document = $this->createDocument('app.test.config', [ 'type' => 'object', 'properties' => [ 'enabled' => ['type' => 'boolean'], ], 'required' => ['enabled'], ]);
$code = $this->generator->generate($document);
$this->assertStringContainsString('class Config', $code); $this->assertStringContainsString('public readonly bool $enabled', $code); }
public function test_it_sorts_use_statements(): void { $document = $this->createDocument('app.test.complex', [ 'type' => 'record', 'properties' => [ 'image' => ['type' => 'blob'], 'author' => [ 'type' => 'ref', 'ref' => 'app.test.author', ], ], 'required' => [], ]);
$code = $this->generator->generate($document);
$basePos = strpos($code, 'use SocialDept\\AtpSchema\\Data\\Data;');
$this->assertNotFalse($basePos); $this->assertStringContainsString('class Complex extends Data', $code); }
public function test_it_provides_accessor_methods(): void { $naming = $this->generator->getNaming(); $typeMapper = $this->generator->getTypeMapper(); $renderer = $this->generator->getRenderer();
$this->assertInstanceOf(\SocialDept\AtpSchema\Generator\NamingConverter::class, $naming); $this->assertInstanceOf(\SocialDept\AtpSchema\Generator\TypeMapper::class, $typeMapper); $this->assertInstanceOf(\SocialDept\AtpSchema\Generator\StubRenderer::class, $renderer); }
/** * Helper to create a test document. * * @param array<string, mixed> $mainDef */ protected function createDocument(string $nsid, array $mainDef, ?string $description = null): LexiconDocument { return new LexiconDocument( lexicon: 1, id: Nsid::parse($nsid), defs: ['main' => $mainDef], description: $description, source: null, raw: [ 'lexicon' => 1, 'id' => $nsid, 'defs' => ['main' => $mainDef], ] ); }}