Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Tests\Unit\Data;
4
5use Orchestra\Testbench\TestCase;
6use SocialDept\AtpSchema\Data\Data;
7
8class DataTest extends TestCase
9{
10 public function test_it_converts_to_array(): void
11 {
12 $data = new TestData('John', 30);
13
14 $array = $data->toArray();
15
16 $this->assertSame([
17 'name' => 'John',
18 'age' => 30,
19 ], $array);
20 }
21
22 public function test_it_converts_to_json(): void
23 {
24 $data = new TestData('John', 30);
25
26 $json = $data->toJson();
27
28 $this->assertJson($json);
29 $this->assertSame('{"name":"John","age":30}', $json);
30 }
31
32 public function test_it_is_json_serializable(): void
33 {
34 $data = new TestData('John', 30);
35
36 $json = json_encode($data);
37
38 $this->assertSame('{"name":"John","age":30}', $json);
39 }
40
41 public function test_it_converts_to_string(): void
42 {
43 $data = new TestData('John', 30);
44
45 $string = (string) $data;
46
47 $this->assertSame('{"name":"John","age":30}', $string);
48 }
49
50 public function test_it_creates_from_array(): void
51 {
52 $data = TestData::fromArray([
53 'name' => 'Jane',
54 'age' => 25,
55 ]);
56
57 $this->assertSame('Jane', $data->name);
58 $this->assertSame(25, $data->age);
59 }
60
61 public function test_it_creates_from_json(): void
62 {
63 $data = TestData::fromJson('{"name":"Bob","age":40}');
64
65 $this->assertSame('Bob', $data->name);
66 $this->assertSame(40, $data->age);
67 }
68
69 public function test_it_throws_on_invalid_json(): void
70 {
71 $this->expectException(\InvalidArgumentException::class);
72 $this->expectExceptionMessage('Invalid JSON');
73
74 TestData::fromJson('{invalid json}');
75 }
76
77 public function test_it_creates_from_record(): void
78 {
79 $data = TestData::fromRecord([
80 'name' => 'Alice',
81 'age' => 35,
82 ]);
83
84 $this->assertSame('Alice', $data->name);
85 $this->assertSame(35, $data->age);
86 }
87
88 public function test_it_converts_to_record(): void
89 {
90 $data = new TestData('John', 30);
91
92 $record = $data->toRecord();
93
94 $this->assertSame([
95 'name' => 'John',
96 'age' => 30,
97 '$type' => 'app.test.data',
98 ], $record);
99 }
100
101 public function test_it_checks_equality(): void
102 {
103 $data1 = new TestData('John', 30);
104 $data2 = new TestData('John', 30);
105 $data3 = new TestData('Jane', 25);
106
107 $this->assertTrue($data1->equals($data2));
108 $this->assertFalse($data1->equals($data3));
109 }
110
111 public function test_it_generates_hash(): void
112 {
113 $data = new TestData('John', 30);
114
115 $hash = $data->hash();
116
117 $this->assertIsString($hash);
118 $this->assertSame(64, strlen($hash)); // SHA256 produces 64 hex characters
119 }
120
121 public function test_it_generates_same_hash_for_equal_data(): void
122 {
123 $data1 = new TestData('John', 30);
124 $data2 = new TestData('John', 30);
125
126 $this->assertSame($data1->hash(), $data2->hash());
127 }
128
129 public function test_it_generates_different_hash_for_different_data(): void
130 {
131 $data1 = new TestData('John', 30);
132 $data2 = new TestData('Jane', 25);
133
134 $this->assertNotSame($data1->hash(), $data2->hash());
135 }
136
137 public function test_it_gets_property_dynamically(): void
138 {
139 $data = new TestData('John', 30);
140
141 $this->assertSame('John', $data->__get('name'));
142 $this->assertSame(30, $data->__get('age'));
143 }
144
145 public function test_it_throws_on_nonexistent_property(): void
146 {
147 $data = new TestData('John', 30);
148
149 $this->expectException(\InvalidArgumentException::class);
150 $this->expectExceptionMessage('Property nonexistent does not exist');
151
152 $data->__get('nonexistent');
153 }
154
155 public function test_it_checks_if_property_exists(): void
156 {
157 $data = new TestData('John', 30);
158
159 $this->assertTrue($data->__isset('name'));
160 $this->assertTrue($data->__isset('age'));
161 $this->assertFalse($data->__isset('nonexistent'));
162 }
163
164 public function test_it_clones_with_modified_properties(): void
165 {
166 $data = new TestData('John', 30);
167
168 $modified = $data->with(['age' => 31]);
169
170 $this->assertSame('John', $modified->name);
171 $this->assertSame(31, $modified->age);
172 $this->assertNotSame($data, $modified);
173 $this->assertSame(30, $data->age); // Original unchanged
174 }
175
176 public function test_it_serializes_nested_data_objects(): void
177 {
178 $nested = new TestData('Inner', 20);
179 $parent = new TestDataWithNested('Outer', $nested);
180
181 $array = $parent->toArray();
182
183 $this->assertSame([
184 'name' => 'Outer',
185 'nested' => [
186 'name' => 'Inner',
187 'age' => 20,
188 '$type' => 'app.test.data',
189 ],
190 ], $array);
191 }
192
193 public function test_it_serializes_arrays_of_data_objects(): void
194 {
195 $items = [
196 new TestData('First', 10),
197 new TestData('Second', 20),
198 ];
199 $collection = new TestDataWithArray('Collection', $items);
200
201 $array = $collection->toArray();
202
203 $this->assertSame([
204 'name' => 'Collection',
205 'items' => [
206 ['name' => 'First', 'age' => 10, '$type' => 'app.test.data'],
207 ['name' => 'Second', 'age' => 20, '$type' => 'app.test.data'],
208 ],
209 ], $array);
210 }
211
212 public function test_it_serializes_datetime_objects(): void
213 {
214 $date = new \DateTime('2024-01-01T12:00:00Z');
215 $data = new TestDataWithDate('Event', $date);
216
217 $array = $data->toArray();
218
219 $this->assertArrayHasKey('createdAt', $array);
220 $this->assertIsString($array['createdAt']);
221 $this->assertStringContainsString('2024-01-01', $array['createdAt']);
222 }
223
224 public function test_it_returns_lexicon_nsid(): void
225 {
226 $lexicon = TestData::getLexicon();
227
228 $this->assertSame('app.test.data', $lexicon);
229 }
230
231 public function test_validation_returns_true_when_helper_not_available(): void
232 {
233 $data = new TestData('John', 30);
234
235 // Schema helper may not be available in unit tests
236 $result = $data->validate();
237
238 $this->assertTrue($result);
239 }
240
241 public function test_validation_errors_returns_empty_when_helper_not_available(): void
242 {
243 $data = new TestData('John', 30);
244
245 // Schema helper may not be available in unit tests
246 $errors = $data->validateWithErrors();
247
248 $this->assertIsArray($errors);
249 }
250}
251
252// Test implementations
253
254class TestData extends Data
255{
256 public function __construct(
257 public readonly string $name,
258 public readonly int $age
259 ) {
260 }
261
262 public static function getLexicon(): string
263 {
264 return 'app.test.data';
265 }
266
267 public static function fromArray(array $data): static
268 {
269 return new static(
270 name: $data['name'],
271 age: $data['age']
272 );
273 }
274}
275
276class TestDataWithNested extends Data
277{
278 public function __construct(
279 public readonly string $name,
280 public readonly TestData $nested
281 ) {
282 }
283
284 public static function getLexicon(): string
285 {
286 return 'app.test.nested';
287 }
288
289 public static function fromArray(array $data): static
290 {
291 return new static(
292 name: $data['name'],
293 nested: TestData::fromArray($data['nested'])
294 );
295 }
296}
297
298class TestDataWithArray extends Data
299{
300 /**
301 * @param array<TestData> $items
302 */
303 public function __construct(
304 public readonly string $name,
305 public readonly array $items
306 ) {
307 }
308
309 public static function getLexicon(): string
310 {
311 return 'app.test.collection';
312 }
313
314 public static function fromArray(array $data): static
315 {
316 return new static(
317 name: $data['name'],
318 items: array_map(
319 fn ($item) => TestData::fromArray($item),
320 $data['items']
321 )
322 );
323 }
324}
325
326class TestDataWithDate extends Data
327{
328 public function __construct(
329 public readonly string $name,
330 public readonly \DateTimeInterface $createdAt
331 ) {
332 }
333
334 public static function getLexicon(): string
335 {
336 return 'app.test.dated';
337 }
338
339 public static function fromArray(array $data): static
340 {
341 return new static(
342 name: $data['name'],
343 createdAt: new \DateTime($data['createdAt'])
344 );
345 }
346}