Parse and validate AT Protocol Lexicons with DTO generation for Laravel
1<?php
2
3namespace SocialDept\AtpSchema\Tests\Unit\Validation;
4
5use Orchestra\Testbench\TestCase;
6use SocialDept\AtpSchema\Data\LexiconDocument;
7use SocialDept\AtpSchema\Exceptions\RecordValidationException;
8use SocialDept\AtpSchema\Exceptions\SchemaValidationException;
9use SocialDept\AtpSchema\Parser\SchemaLoader;
10use SocialDept\AtpSchema\Validation\LexiconValidator;
11
12class LexiconValidatorTest extends TestCase
13{
14 protected LexiconValidator $validator;
15
16 protected SchemaLoader $loader;
17
18 protected function setUp(): void
19 {
20 parent::setUp();
21
22 $fixturesPath = __DIR__.'/../../fixtures';
23 $this->loader = new SchemaLoader([$fixturesPath], false);
24 $this->validator = new LexiconValidator($this->loader);
25 }
26
27 public function test_it_validates_valid_record(): void
28 {
29 $record = [
30 'text' => 'Hello, World!',
31 'createdAt' => '2024-01-01T00:00:00Z',
32 ];
33
34 $this->validator->validateByNsid('app.bsky.feed.post', $record);
35
36 $this->assertTrue(true);
37 }
38
39 public function test_it_throws_on_missing_required_field(): void
40 {
41 $record = [
42 'text' => 'Hello, World!',
43 // Missing createdAt
44 ];
45
46 $this->expectException(RecordValidationException::class);
47
48 $this->validator->validateByNsid('app.bsky.feed.post', $record);
49 }
50
51 public function test_it_throws_on_invalid_field_type(): void
52 {
53 $record = [
54 'text' => 123, // Should be string
55 'createdAt' => '2024-01-01T00:00:00Z',
56 ];
57
58 $this->expectException(RecordValidationException::class);
59
60 $this->validator->validateByNsid('app.bsky.feed.post', $record);
61 }
62
63 public function test_it_validates_record_with_lexicon_document(): void
64 {
65 $document = LexiconDocument::fromArray([
66 'lexicon' => 1,
67 'id' => 'com.example.test',
68 'defs' => [
69 'main' => [
70 'type' => 'record',
71 'record' => [
72 'type' => 'object',
73 'required' => ['name'],
74 'properties' => [
75 'name' => ['type' => 'string'],
76 ],
77 ],
78 ],
79 ],
80 ]);
81
82 $this->validator->validateRecord($document, ['name' => 'John']);
83
84 $this->assertTrue(true);
85 }
86
87 public function test_it_throws_on_non_record_schema(): void
88 {
89 $document = LexiconDocument::fromArray([
90 'lexicon' => 1,
91 'id' => 'com.example.test',
92 'defs' => [
93 'main' => [
94 'type' => 'query',
95 ],
96 ],
97 ]);
98
99 $this->expectException(SchemaValidationException::class);
100 $this->expectExceptionMessage('Schema is not a record type');
101
102 $this->validator->validateRecord($document, ['name' => 'John']);
103 }
104
105 public function test_it_validates_procedure_input(): void
106 {
107 $document = LexiconDocument::fromArray([
108 'lexicon' => 1,
109 'id' => 'com.example.test',
110 'defs' => [
111 'main' => [
112 'type' => 'procedure',
113 'input' => [
114 'type' => 'object',
115 'required' => ['name'],
116 'properties' => [
117 'name' => ['type' => 'string'],
118 ],
119 ],
120 ],
121 ],
122 ]);
123
124 $this->validator->validateProcedure($document, ['name' => 'John']);
125
126 $this->assertTrue(true);
127 }
128
129 public function test_it_validates_with_contract_method(): void
130 {
131 $document = $this->loader->load('app.bsky.feed.post');
132
133 $record = [
134 'text' => 'Hello, World!',
135 'createdAt' => '2024-01-01T00:00:00Z',
136 ];
137
138 $this->assertTrue($this->validator->validate($record, $document));
139 }
140
141 public function test_it_returns_false_for_invalid_record(): void
142 {
143 $document = $this->loader->load('app.bsky.feed.post');
144
145 $record = [
146 'text' => 'Hello, World!',
147 // Missing createdAt
148 ];
149
150 $this->assertFalse($this->validator->validate($record, $document));
151 }
152
153 public function test_it_validates_with_errors(): void
154 {
155 $document = $this->loader->load('app.bsky.feed.post');
156
157 $record = [
158 'text' => 'Hello, World!',
159 'createdAt' => '2024-01-01T00:00:00Z',
160 ];
161
162 $errors = $this->validator->validateWithErrors($record, $document);
163
164 $this->assertEmpty($errors);
165 }
166
167 public function test_it_returns_errors_for_invalid_record(): void
168 {
169 $document = $this->loader->load('app.bsky.feed.post');
170
171 $record = [
172 'text' => 'Hello, World!',
173 // Missing createdAt
174 ];
175
176 $errors = $this->validator->validateWithErrors($record, $document);
177
178 $this->assertNotEmpty($errors);
179 $this->assertIsArray($errors);
180 }
181
182 public function test_it_validates_specific_field(): void
183 {
184 $document = $this->loader->load('app.bsky.feed.post');
185
186 $this->assertTrue($this->validator->validateField('Hello, World!', 'text', $document));
187 $this->assertFalse($this->validator->validateField(123, 'text', $document));
188 }
189
190 public function test_it_sets_validation_mode(): void
191 {
192 $this->validator->setMode('lenient');
193
194 $this->assertTrue(true);
195 }
196}