Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Tests\Unit\Generator;
4
5use Orchestra\Testbench\TestCase;
6use SocialDept\AtpSchema\Generator\ConstructorGenerator;
7
8class ConstructorGeneratorTest extends TestCase
9{
10 protected ConstructorGenerator $generator;
11
12 protected function setUp(): void
13 {
14 parent::setUp();
15
16 $this->generator = new ConstructorGenerator();
17 }
18
19 public function test_it_generates_constructor_with_promoted_properties(): void
20 {
21 $constructor = $this->generator->generate(
22 [
23 'name' => ['type' => 'string'],
24 'age' => ['type' => 'integer'],
25 ],
26 ['name', 'age']
27 );
28
29 $this->assertStringContainsString('public function __construct(', $constructor);
30 $this->assertStringContainsString('public readonly string $name', $constructor);
31 $this->assertStringContainsString('public readonly int $age', $constructor);
32 }
33
34 public function test_it_handles_optional_parameters(): void
35 {
36 $constructor = $this->generator->generate(
37 [
38 'name' => ['type' => 'string'],
39 'nickname' => ['type' => 'string'],
40 ],
41 ['name']
42 );
43
44 $this->assertStringContainsString('public readonly string $name', $constructor);
45 $this->assertStringContainsString('public readonly ?string $nickname', $constructor);
46 }
47
48 public function test_it_returns_empty_for_no_properties(): void
49 {
50 $constructor = $this->generator->generate([], []);
51
52 $this->assertEmpty($constructor);
53 }
54
55 public function test_it_generates_docblock(): void
56 {
57 $constructor = $this->generator->generate(
58 [
59 'name' => ['type' => 'string'],
60 ],
61 ['name']
62 );
63
64 $this->assertStringContainsString('/**', $constructor);
65 $this->assertStringContainsString('* Create a new instance.', $constructor);
66 $this->assertStringContainsString('* @param string $name', $constructor);
67 }
68
69 public function test_it_includes_descriptions_in_docblock(): void
70 {
71 $constructor = $this->generator->generate(
72 [
73 'name' => [
74 'type' => 'string',
75 'description' => 'The user name',
76 ],
77 ],
78 ['name']
79 );
80
81 $this->assertStringContainsString('The user name', $constructor);
82 }
83
84 public function test_it_handles_multiple_properties(): void
85 {
86 $constructor = $this->generator->generate(
87 [
88 'id' => ['type' => 'string'],
89 'name' => ['type' => 'string'],
90 'email' => ['type' => 'string'],
91 'age' => ['type' => 'integer'],
92 'active' => ['type' => 'boolean'],
93 ],
94 ['id', 'name', 'email']
95 );
96
97 $this->assertStringContainsString('$id', $constructor);
98 $this->assertStringContainsString('$name', $constructor);
99 $this->assertStringContainsString('$email', $constructor);
100 $this->assertStringContainsString('$age', $constructor);
101 $this->assertStringContainsString('$active', $constructor);
102 }
103
104 public function test_it_generates_with_assignments(): void
105 {
106 $constructor = $this->generator->generateWithAssignments(
107 [
108 'name' => ['type' => 'string'],
109 'age' => ['type' => 'integer'],
110 ],
111 ['name', 'age']
112 );
113
114 $this->assertStringContainsString('public function __construct(', $constructor);
115 $this->assertStringContainsString('string $name,', $constructor);
116 $this->assertStringContainsString('int $age', $constructor);
117 $this->assertStringContainsString('$this->name = $name;', $constructor);
118 $this->assertStringContainsString('$this->age = $age;', $constructor);
119 }
120
121 public function test_it_checks_if_constructor_should_be_generated(): void
122 {
123 $this->assertTrue($this->generator->shouldGenerate(['name' => ['type' => 'string']]));
124 $this->assertFalse($this->generator->shouldGenerate([]));
125 }
126
127 public function test_it_handles_default_values(): void
128 {
129 $constructor = $this->generator->generate(
130 [
131 'status' => [
132 'type' => 'string',
133 'default' => 'active',
134 ],
135 ],
136 []
137 );
138
139 $this->assertStringContainsString("= 'active'", $constructor);
140 }
141
142 public function test_it_handles_nullable_with_null_default(): void
143 {
144 $constructor = $this->generator->generate(
145 [
146 'middleName' => [
147 'type' => 'string',
148 'default' => null,
149 ],
150 ],
151 []
152 );
153
154 $this->assertStringContainsString('?string $middleName = null', $constructor);
155 }
156
157 public function test_it_handles_all_primitive_types(): void
158 {
159 $constructor = $this->generator->generate(
160 [
161 'name' => ['type' => 'string'],
162 'age' => ['type' => 'integer'],
163 'price' => ['type' => 'number'],
164 'active' => ['type' => 'boolean'],
165 'tags' => ['type' => 'array'],
166 ],
167 ['name', 'age', 'price', 'active', 'tags']
168 );
169
170 $this->assertStringContainsString('string $name', $constructor);
171 $this->assertStringContainsString('int $age', $constructor);
172 $this->assertStringContainsString('float $price', $constructor);
173 $this->assertStringContainsString('bool $active', $constructor);
174 $this->assertStringContainsString('array $tags', $constructor);
175 }
176
177 public function test_it_does_not_add_trailing_comma_to_last_parameter(): void
178 {
179 $constructor = $this->generator->generate(
180 [
181 'first' => ['type' => 'string'],
182 'last' => ['type' => 'string'],
183 ],
184 ['first', 'last']
185 );
186
187 // Last parameter should not have a trailing comma
188 $this->assertStringNotContainsString('$last,', $constructor);
189 $this->assertStringContainsString('$last', $constructor);
190 }
191
192 public function test_it_formats_parameters_with_proper_indentation(): void
193 {
194 $constructor = $this->generator->generate(
195 [
196 'name' => ['type' => 'string'],
197 ],
198 ['name']
199 );
200
201 // Check for proper indentation (8 spaces for parameters)
202 $this->assertStringContainsString(' public readonly string $name', $constructor);
203 }
204
205 public function test_it_generates_empty_body_for_promoted_properties(): void
206 {
207 $constructor = $this->generator->generate(
208 [
209 'name' => ['type' => 'string'],
210 ],
211 ['name']
212 );
213
214 // Constructor body should be empty for promoted properties
215 $this->assertMatchesRegularExpression('/\) \{\s+\}/', $constructor);
216 }
217
218 public function test_it_handles_blob_type(): void
219 {
220 $constructor = $this->generator->generate(
221 [
222 'image' => ['type' => 'blob'],
223 ],
224 []
225 );
226
227 $this->assertStringContainsString('BlobReference', $constructor);
228 }
229
230 public function test_it_handles_ref_type(): void
231 {
232 $constructor = $this->generator->generate(
233 [
234 'author' => [
235 'type' => 'ref',
236 'ref' => 'app.test.author',
237 ],
238 ],
239 ['author']
240 );
241
242 $this->assertStringContainsString('public readonly Author $author', $constructor);
243 }
244}