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\Validation\ValidationError;
7use SocialDept\AtpSchema\Validation\ValidationErrorFormatter;
8
9class ValidationErrorFormatterTest extends TestCase
10{
11 protected ValidationErrorFormatter $formatter;
12
13 protected function setUp(): void
14 {
15 parent::setUp();
16
17 $this->formatter = new ValidationErrorFormatter();
18 }
19
20 public function test_it_formats_for_laravel(): void
21 {
22 $errors = [
23 ValidationError::make('name', 'Name is required'),
24 ValidationError::make('email', 'Email is invalid'),
25 ];
26
27 $formatted = $this->formatter->formatForLaravel($errors);
28
29 $this->assertEquals([
30 'name' => ['Name is required'],
31 'email' => ['Email is invalid'],
32 ], $formatted);
33 }
34
35 public function test_it_groups_multiple_errors_per_field(): void
36 {
37 $errors = [
38 ValidationError::make('name', 'Name is required'),
39 ValidationError::make('name', 'Name must be a string'),
40 ];
41
42 $formatted = $this->formatter->formatForLaravel($errors);
43
44 $this->assertEquals([
45 'name' => ['Name is required', 'Name must be a string'],
46 ], $formatted);
47 }
48
49 public function test_it_converts_field_paths(): void
50 {
51 $errors = [
52 ValidationError::make('$.user.name', 'Invalid name'),
53 ValidationError::make('$.items[0]', 'Invalid item'),
54 ];
55
56 $formatted = $this->formatter->formatForLaravel($errors);
57
58 $this->assertArrayHasKey('user.name', $formatted);
59 $this->assertArrayHasKey('items.0', $formatted);
60 }
61
62 public function test_it_handles_root_field(): void
63 {
64 $errors = [
65 ValidationError::make('$', 'Invalid data'),
66 ];
67
68 $formatted = $this->formatter->formatForLaravel($errors);
69
70 $this->assertArrayHasKey('_root', $formatted);
71 }
72
73 public function test_it_formats_as_messages(): void
74 {
75 $errors = [
76 ValidationError::make('name', 'Name is required'),
77 ValidationError::make('email', 'Email is invalid'),
78 ];
79
80 $messages = $this->formatter->formatAsMessages($errors);
81
82 $this->assertEquals(['Name is required', 'Email is invalid'], $messages);
83 }
84
85 public function test_it_formats_with_fields(): void
86 {
87 $errors = [
88 ValidationError::make('name', 'Name is required'),
89 ValidationError::make('email', 'Email is invalid'),
90 ];
91
92 $messages = $this->formatter->formatWithFields($errors);
93
94 $this->assertEquals([
95 'name: Name is required',
96 'email: Email is invalid',
97 ], $messages);
98 }
99
100 public function test_it_formats_detailed(): void
101 {
102 $errors = [
103 ValidationError::withContext('age', 'Too high', 'max', 100, 150),
104 ];
105
106 $detailed = $this->formatter->formatDetailed($errors);
107
108 $this->assertEquals([
109 [
110 'field' => 'age',
111 'message' => 'Too high',
112 'rule' => 'max',
113 'expected' => 100,
114 'actual' => 150,
115 ],
116 ], $detailed);
117 }
118
119 public function test_it_groups_by_field(): void
120 {
121 $errors = [
122 ValidationError::make('name', 'Required'),
123 ValidationError::make('name', 'Must be string'),
124 ValidationError::make('email', 'Invalid'),
125 ];
126
127 $grouped = $this->formatter->groupByField($errors);
128
129 $this->assertCount(2, $grouped);
130 $this->assertCount(2, $grouped['name']);
131 $this->assertCount(1, $grouped['email']);
132 }
133
134 public function test_it_formats_single_error(): void
135 {
136 $error = ValidationError::withContext('age', 'Too high', 'max', 100, 150);
137
138 $formatted = $this->formatter->formatError($error);
139
140 $this->assertStringContainsString('Too high', $formatted);
141 $this->assertStringContainsString('Rule: max', $formatted);
142 $this->assertStringContainsString('Expected: 100', $formatted);
143 $this->assertStringContainsString('Got: 150', $formatted);
144 }
145
146 public function test_it_formats_error_without_context(): void
147 {
148 $error = ValidationError::make('name', 'Required');
149
150 $formatted = $this->formatter->formatError($error);
151
152 $this->assertEquals('Required', $formatted);
153 }
154
155 public function test_it_creates_summary_for_no_errors(): void
156 {
157 $summary = $this->formatter->createSummary([]);
158
159 $this->assertEquals('No validation errors', $summary);
160 }
161
162 public function test_it_creates_summary_for_single_error(): void
163 {
164 $errors = [
165 ValidationError::make('name', 'Name is required'),
166 ];
167
168 $summary = $this->formatter->createSummary($errors);
169
170 $this->assertEquals('Validation failed: Name is required', $summary);
171 }
172
173 public function test_it_creates_summary_for_multiple_errors(): void
174 {
175 $errors = [
176 ValidationError::make('name', 'Required'),
177 ValidationError::make('email', 'Invalid'),
178 ];
179
180 $summary = $this->formatter->createSummary($errors);
181
182 $this->assertStringContainsString('2 errors', $summary);
183 $this->assertStringContainsString('2 fields', $summary);
184 }
185
186 public function test_it_creates_summary_for_multiple_errors_same_field(): void
187 {
188 $errors = [
189 ValidationError::make('name', 'Required'),
190 ValidationError::make('name', 'Must be string'),
191 ];
192
193 $summary = $this->formatter->createSummary($errors);
194
195 $this->assertStringContainsString('2 errors', $summary);
196 $this->assertStringContainsString('1 fields', $summary);
197 }
198
199 public function test_it_converts_to_json(): void
200 {
201 $errors = [
202 ValidationError::withRule('name', 'Required', 'required'),
203 ];
204
205 $json = $this->formatter->toJson($errors);
206 $decoded = json_decode($json, true);
207
208 $this->assertCount(1, $decoded);
209 $this->assertEquals('name', $decoded[0]['field']);
210 $this->assertEquals('Required', $decoded[0]['message']);
211 }
212
213 public function test_it_converts_to_pretty_json(): void
214 {
215 $errors = [
216 ValidationError::make('name', 'Required'),
217 ];
218
219 $json = $this->formatter->toPrettyJson($errors);
220
221 $this->assertStringContainsString("\n", $json);
222 $this->assertStringContainsString(' ', $json);
223 }
224
225 public function test_it_formats_null_value(): void
226 {
227 $error = ValidationError::withContext('field', 'message', 'type', 'string', null);
228
229 $formatted = $this->formatter->formatError($error);
230
231 $this->assertStringContainsString('Got: null', $formatted);
232 }
233
234 public function test_it_formats_boolean_values(): void
235 {
236 $error = ValidationError::withContext('field', 'message', 'type', true, false);
237
238 $formatted = $this->formatter->formatError($error);
239
240 $this->assertStringContainsString('Expected: true', $formatted);
241 $this->assertStringContainsString('Got: false', $formatted);
242 }
243
244 public function test_it_formats_array_value(): void
245 {
246 $error = ValidationError::withContext('field', 'message', 'type', 'string', [1, 2, 3]);
247
248 $formatted = $this->formatter->formatError($error);
249
250 $this->assertStringContainsString('Got: array(3)', $formatted);
251 }
252
253 public function test_it_formats_long_string(): void
254 {
255 $longString = str_repeat('a', 100);
256 $error = ValidationError::withContext('field', 'message', 'type', 'short', $longString);
257
258 $formatted = $this->formatter->formatError($error);
259
260 $this->assertStringContainsString('...', $formatted);
261 }
262
263 public function test_it_handles_empty_errors_array(): void
264 {
265 $formatted = $this->formatter->formatForLaravel([]);
266
267 $this->assertEmpty($formatted);
268 }
269
270 public function test_it_formats_nested_field_paths(): void
271 {
272 $errors = [
273 ValidationError::make('$.user.profile.bio', 'Too long'),
274 ];
275
276 $formatted = $this->formatter->formatForLaravel($errors);
277
278 $this->assertArrayHasKey('user.profile.bio', $formatted);
279 }
280
281 public function test_it_formats_array_index_paths(): void
282 {
283 $errors = [
284 ValidationError::make('$.items[0].name', 'Required'),
285 ValidationError::make('$.items[1].name', 'Required'),
286 ];
287
288 $formatted = $this->formatter->formatForLaravel($errors);
289
290 $this->assertArrayHasKey('items.0.name', $formatted);
291 $this->assertArrayHasKey('items.1.name', $formatted);
292 }
293}