@agentica/core 0.36.0 → 0.36.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1034 @@
1
+ import type { IValidation } from "@samchon/openapi";
2
+
3
+ import { stringifyValidateFailure } from "./stringifyValidateFailure";
4
+
5
+ describe("stringifyValidateFailure", () => {
6
+ describe("missing Properties", () => {
7
+ it("should display missing required property as undefined with error comment", () => {
8
+ const failure: IValidation.IFailure = {
9
+ success: false,
10
+ data: {
11
+ name: "John",
12
+ },
13
+ errors: [
14
+ {
15
+ path: "$input.email",
16
+ expected: "string",
17
+ value: undefined,
18
+ },
19
+ ],
20
+ };
21
+
22
+ const result = stringifyValidateFailure(failure);
23
+
24
+ expect(result).toContain("\"email\": undefined");
25
+ expect(result).toContain("// ❌");
26
+ expect(result).toContain("$input.email");
27
+ expect(result).toContain("\"expected\":\"string\"");
28
+ });
29
+
30
+ it("should display multiple missing properties", () => {
31
+ const failure: IValidation.IFailure = {
32
+ success: false,
33
+ data: {
34
+ name: "John",
35
+ },
36
+ errors: [
37
+ {
38
+ path: "$input.email",
39
+ expected: "string",
40
+ value: undefined,
41
+ },
42
+ {
43
+ path: "$input.age",
44
+ expected: "number",
45
+ value: undefined,
46
+ },
47
+ ],
48
+ };
49
+
50
+ const result = stringifyValidateFailure(failure);
51
+
52
+ expect(result).toContain("\"email\": undefined");
53
+ expect(result).toContain("\"age\": undefined");
54
+ expect(result).toContain("$input.email");
55
+ expect(result).toContain("$input.age");
56
+ });
57
+
58
+ it("should display missing property in nested object", () => {
59
+ const failure: IValidation.IFailure = {
60
+ success: false,
61
+ data: {
62
+ user: {
63
+ name: "John",
64
+ },
65
+ },
66
+ errors: [
67
+ {
68
+ path: "$input.user.email",
69
+ expected: "string",
70
+ value: undefined,
71
+ },
72
+ ],
73
+ };
74
+
75
+ const result = stringifyValidateFailure(failure);
76
+
77
+ expect(result).toContain("\"user\":");
78
+ expect(result).toContain("\"email\": undefined");
79
+ expect(result).toContain("$input.user.email");
80
+ });
81
+
82
+ it("should handle mixed existing and missing properties", () => {
83
+ const failure: IValidation.IFailure = {
84
+ success: false,
85
+ data: {
86
+ name: "John",
87
+ age: 30,
88
+ },
89
+ errors: [
90
+ {
91
+ path: "$input.email",
92
+ expected: "string",
93
+ value: undefined,
94
+ },
95
+ {
96
+ path: "$input.phone",
97
+ expected: "string",
98
+ value: undefined,
99
+ },
100
+ ],
101
+ };
102
+
103
+ const result = stringifyValidateFailure(failure);
104
+
105
+ expect(result).toContain("\"name\": \"John\"");
106
+ expect(result).toContain("\"age\": 30");
107
+ expect(result).toContain("\"email\": undefined");
108
+ expect(result).toContain("\"phone\": undefined");
109
+ });
110
+
111
+ it("should not display grandchild property as direct child", () => {
112
+ const failure: IValidation.IFailure = {
113
+ success: false,
114
+ data: {
115
+ user: {
116
+ name: "John",
117
+ },
118
+ },
119
+ errors: [
120
+ {
121
+ path: "$input.user.profile.email",
122
+ expected: "string",
123
+ value: undefined,
124
+ },
125
+ ],
126
+ };
127
+
128
+ const result = stringifyValidateFailure(failure);
129
+
130
+ // Should NOT add "profile" at user level
131
+ // The error is too deep to be represented at the user object level
132
+ expect(result).toContain("\"user\":");
133
+ expect(result).toContain("\"name\": \"John\"");
134
+ });
135
+ });
136
+
137
+ describe("type Mismatches", () => {
138
+ it("should display type error for string instead of number", () => {
139
+ const failure: IValidation.IFailure = {
140
+ success: false,
141
+ data: {
142
+ age: "25",
143
+ },
144
+ errors: [
145
+ {
146
+ path: "$input.age",
147
+ expected: "number",
148
+ value: "25",
149
+ },
150
+ ],
151
+ };
152
+
153
+ const result = stringifyValidateFailure(failure);
154
+
155
+ expect(result).toContain("\"age\": \"25\"");
156
+ expect(result).toContain("// ❌");
157
+ expect(result).toContain("\"expected\":\"number\"");
158
+ });
159
+
160
+ it("should display type error for number instead of string", () => {
161
+ const failure: IValidation.IFailure = {
162
+ success: false,
163
+ data: {
164
+ name: 123,
165
+ },
166
+ errors: [
167
+ {
168
+ path: "$input.name",
169
+ expected: "string",
170
+ value: 123,
171
+ },
172
+ ],
173
+ };
174
+
175
+ const result = stringifyValidateFailure(failure);
176
+
177
+ expect(result).toContain("\"name\": 123");
178
+ expect(result).toContain("// ❌");
179
+ expect(result).toContain("\"expected\":\"string\"");
180
+ });
181
+
182
+ it("should display type error for boolean instead of string", () => {
183
+ const failure: IValidation.IFailure = {
184
+ success: false,
185
+ data: {
186
+ status: true,
187
+ },
188
+ errors: [
189
+ {
190
+ path: "$input.status",
191
+ expected: "string",
192
+ value: true,
193
+ },
194
+ ],
195
+ };
196
+
197
+ const result = stringifyValidateFailure(failure);
198
+
199
+ expect(result).toContain("\"status\": true");
200
+ expect(result).toContain("// ❌");
201
+ expect(result).toContain("\"expected\":\"string\"");
202
+ });
203
+
204
+ it("should display type error for null instead of string", () => {
205
+ const failure: IValidation.IFailure = {
206
+ success: false,
207
+ data: {
208
+ value: null,
209
+ },
210
+ errors: [
211
+ {
212
+ path: "$input.value",
213
+ expected: "string",
214
+ value: null,
215
+ },
216
+ ],
217
+ };
218
+
219
+ const result = stringifyValidateFailure(failure);
220
+
221
+ expect(result).toContain("\"value\": null");
222
+ expect(result).toContain("// ❌");
223
+ expect(result).toContain("\"expected\":\"string\"");
224
+ });
225
+ });
226
+
227
+ describe("format Violations", () => {
228
+ it("should display email format error", () => {
229
+ const failure: IValidation.IFailure = {
230
+ success: false,
231
+ data: {
232
+ email: "invalid-email",
233
+ },
234
+ errors: [
235
+ {
236
+ path: "$input.email",
237
+ expected: "string & Format<'email'>",
238
+ value: "invalid-email",
239
+ },
240
+ ],
241
+ };
242
+
243
+ const result = stringifyValidateFailure(failure);
244
+
245
+ expect(result).toContain("\"email\": \"invalid-email\"");
246
+ expect(result).toContain("// ❌");
247
+ expect(result).toContain("\"expected\":\"string & Format<'email'>\"");
248
+ });
249
+
250
+ it("should display UUID format error", () => {
251
+ const failure: IValidation.IFailure = {
252
+ success: false,
253
+ data: {
254
+ id: "not-a-uuid",
255
+ },
256
+ errors: [
257
+ {
258
+ path: "$input.id",
259
+ expected: "string & Format<'uuid'>",
260
+ value: "not-a-uuid",
261
+ },
262
+ ],
263
+ };
264
+
265
+ const result = stringifyValidateFailure(failure);
266
+
267
+ expect(result).toContain("\"id\": \"not-a-uuid\"");
268
+ expect(result).toContain("// ❌");
269
+ expect(result).toContain("\"expected\":\"string & Format<'uuid'>\"");
270
+ });
271
+
272
+ it("should display date-time format error", () => {
273
+ const failure: IValidation.IFailure = {
274
+ success: false,
275
+ data: {
276
+ createdAt: "tomorrow",
277
+ },
278
+ errors: [
279
+ {
280
+ path: "$input.createdAt",
281
+ expected: "string & Format<'date-time'>",
282
+ value: "tomorrow",
283
+ },
284
+ ],
285
+ };
286
+
287
+ const result = stringifyValidateFailure(failure);
288
+
289
+ expect(result).toContain("\"createdAt\": \"tomorrow\"");
290
+ expect(result).toContain("// ❌");
291
+ expect(result).toContain("\"expected\":\"string & Format<'date-time'>\"");
292
+ });
293
+
294
+ it("should display error with description field", () => {
295
+ const failure: IValidation.IFailure = {
296
+ success: false,
297
+ data: {
298
+ email: "invalid",
299
+ },
300
+ errors: [
301
+ {
302
+ path: "$input.email",
303
+ expected: "string & Format<'email'>",
304
+ value: "invalid",
305
+ description: "Invalid email format",
306
+ },
307
+ ],
308
+ };
309
+
310
+ const result = stringifyValidateFailure(failure);
311
+
312
+ expect(result).toContain("\"email\": \"invalid\"");
313
+ expect(result).toContain("// ❌");
314
+ expect(result).toContain("\"description\":\"Invalid email format\"");
315
+ });
316
+ });
317
+
318
+ describe("nested Objects", () => {
319
+ it("should display errors in nested objects", () => {
320
+ const failure: IValidation.IFailure = {
321
+ success: false,
322
+ data: {
323
+ user: {
324
+ name: "John",
325
+ age: "30",
326
+ },
327
+ },
328
+ errors: [
329
+ {
330
+ path: "$input.user.age",
331
+ expected: "number",
332
+ value: "30",
333
+ },
334
+ ],
335
+ };
336
+
337
+ const result = stringifyValidateFailure(failure);
338
+
339
+ expect(result).toContain("\"user\":");
340
+ expect(result).toContain("\"name\": \"John\"");
341
+ expect(result).toContain("\"age\": \"30\"");
342
+ expect(result).toContain("// ❌");
343
+ expect(result).toContain("$input.user.age");
344
+ });
345
+
346
+ it("should display errors in deeply nested objects", () => {
347
+ const failure: IValidation.IFailure = {
348
+ success: false,
349
+ data: {
350
+ user: {
351
+ profile: {
352
+ contact: {
353
+ email: "invalid-email",
354
+ },
355
+ },
356
+ },
357
+ },
358
+ errors: [
359
+ {
360
+ path: "$input.user.profile.contact.email",
361
+ expected: "string & Format<'email'>",
362
+ value: "invalid-email",
363
+ },
364
+ ],
365
+ };
366
+
367
+ const result = stringifyValidateFailure(failure);
368
+
369
+ expect(result).toContain("\"user\":");
370
+ expect(result).toContain("\"profile\":");
371
+ expect(result).toContain("\"contact\":");
372
+ expect(result).toContain("\"email\": \"invalid-email\"");
373
+ expect(result).toContain("// ❌");
374
+ });
375
+
376
+ it("should display multiple errors at different nesting levels", () => {
377
+ const failure: IValidation.IFailure = {
378
+ success: false,
379
+ data: {
380
+ name: 123,
381
+ user: {
382
+ age: "30",
383
+ },
384
+ },
385
+ errors: [
386
+ {
387
+ path: "$input.name",
388
+ expected: "string",
389
+ value: 123,
390
+ },
391
+ {
392
+ path: "$input.user.age",
393
+ expected: "number",
394
+ value: "30",
395
+ },
396
+ ],
397
+ };
398
+
399
+ const result = stringifyValidateFailure(failure);
400
+
401
+ expect(result).toContain("\"name\": 123");
402
+ expect(result).toContain("\"user\":");
403
+ expect(result).toContain("\"age\": \"30\"");
404
+ // Should have two error comments
405
+ expect((result.match(/\/\/ ❌/g) ?? []).length).toBe(2);
406
+ });
407
+
408
+ it("should display missing property in deeply nested object", () => {
409
+ const failure: IValidation.IFailure = {
410
+ success: false,
411
+ data: {
412
+ user: {
413
+ profile: {
414
+ name: "John",
415
+ },
416
+ },
417
+ },
418
+ errors: [
419
+ {
420
+ path: "$input.user.profile.email",
421
+ expected: "string",
422
+ value: undefined,
423
+ },
424
+ ],
425
+ };
426
+
427
+ const result = stringifyValidateFailure(failure);
428
+
429
+ expect(result).toContain("\"user\":");
430
+ expect(result).toContain("\"profile\":");
431
+ expect(result).toContain("\"name\": \"John\"");
432
+ expect(result).toContain("\"email\": undefined");
433
+ expect(result).toContain("// ❌");
434
+ });
435
+ });
436
+
437
+ describe("arrays", () => {
438
+ it("should display error for array item", () => {
439
+ const failure: IValidation.IFailure = {
440
+ success: false,
441
+ data: {
442
+ tags: ["valid", 123, "another"],
443
+ },
444
+ errors: [
445
+ {
446
+ path: "$input.tags[1]",
447
+ expected: "string",
448
+ value: 123,
449
+ },
450
+ ],
451
+ };
452
+
453
+ const result = stringifyValidateFailure(failure);
454
+
455
+ expect(result).toContain("\"tags\":");
456
+ expect(result).toContain("[");
457
+ expect(result).toContain("\"valid\"");
458
+ expect(result).toContain("123");
459
+ expect(result).toContain("\"another\"");
460
+ expect(result).toContain("// ❌");
461
+ expect(result).toContain("$input.tags[1]");
462
+ });
463
+
464
+ it("should display errors for multiple array items", () => {
465
+ const failure: IValidation.IFailure = {
466
+ success: false,
467
+ data: {
468
+ numbers: [1, "2", 3, "4"],
469
+ },
470
+ errors: [
471
+ {
472
+ path: "$input.numbers[1]",
473
+ expected: "number",
474
+ value: "2",
475
+ },
476
+ {
477
+ path: "$input.numbers[3]",
478
+ expected: "number",
479
+ value: "4",
480
+ },
481
+ ],
482
+ };
483
+
484
+ const result = stringifyValidateFailure(failure);
485
+
486
+ expect(result).toContain("\"numbers\":");
487
+ expect(result).toContain("1,");
488
+ expect(result).toContain("\"2\"");
489
+ expect(result).toContain("3,");
490
+ expect(result).toContain("\"4\"");
491
+ expect((result.match(/\/\/ ❌/g) ?? []).length).toBe(2);
492
+ });
493
+
494
+ it("should display errors in nested array of objects", () => {
495
+ const failure: IValidation.IFailure = {
496
+ success: false,
497
+ data: {
498
+ users: [
499
+ { name: "John", age: 30 },
500
+ { name: "Jane", age: "25" },
501
+ ],
502
+ },
503
+ errors: [
504
+ {
505
+ path: "$input.users[1].age",
506
+ expected: "number",
507
+ value: "25",
508
+ },
509
+ ],
510
+ };
511
+
512
+ const result = stringifyValidateFailure(failure);
513
+
514
+ expect(result).toContain("\"users\":");
515
+ expect(result).toContain("\"name\": \"John\"");
516
+ expect(result).toContain("\"age\": 30");
517
+ expect(result).toContain("\"name\": \"Jane\"");
518
+ expect(result).toContain("\"age\": \"25\"");
519
+ expect(result).toContain("// ❌");
520
+ expect(result).toContain("$input.users[1].age");
521
+ });
522
+
523
+ it("should handle empty array", () => {
524
+ const failure: IValidation.IFailure = {
525
+ success: false,
526
+ data: {
527
+ items: [],
528
+ },
529
+ errors: [],
530
+ };
531
+
532
+ const result = stringifyValidateFailure(failure);
533
+
534
+ expect(result).toContain("\"items\":");
535
+ expect(result).toContain("[]");
536
+ });
537
+
538
+ it("should display error on array itself", () => {
539
+ const failure: IValidation.IFailure = {
540
+ success: false,
541
+ data: {
542
+ tags: "single-value",
543
+ },
544
+ errors: [
545
+ {
546
+ path: "$input.tags",
547
+ expected: "Array<string>",
548
+ value: "single-value",
549
+ },
550
+ ],
551
+ };
552
+
553
+ const result = stringifyValidateFailure(failure);
554
+
555
+ expect(result).toContain("\"tags\": \"single-value\"");
556
+ expect(result).toContain("// ❌");
557
+ expect(result).toContain("\"expected\":\"Array<string>\"");
558
+ });
559
+ });
560
+
561
+ describe("complex Scenarios", () => {
562
+ it("should handle complex nested structure with multiple errors", () => {
563
+ const failure: IValidation.IFailure = {
564
+ success: false,
565
+ data: {
566
+ user: {
567
+ name: "John",
568
+ profile: {
569
+ age: "30",
570
+ },
571
+ },
572
+ tags: ["valid", 123],
573
+ },
574
+ errors: [
575
+ {
576
+ path: "$input.user.email",
577
+ expected: "string",
578
+ value: undefined,
579
+ },
580
+ {
581
+ path: "$input.user.profile.age",
582
+ expected: "number",
583
+ value: "30",
584
+ },
585
+ {
586
+ path: "$input.tags[1]",
587
+ expected: "string",
588
+ value: 123,
589
+ },
590
+ ],
591
+ };
592
+
593
+ const result = stringifyValidateFailure(failure);
594
+
595
+ expect(result).toContain("\"user\":");
596
+ expect(result).toContain("\"email\": undefined");
597
+ expect(result).toContain("\"profile\":");
598
+ expect(result).toContain("\"age\": \"30\"");
599
+ expect(result).toContain("\"tags\":");
600
+ expect(result).toContain("123");
601
+ expect((result.match(/\/\/ ❌/g) ?? []).length).toBe(3);
602
+ });
603
+
604
+ it("should handle very deep nesting", () => {
605
+ const failure: IValidation.IFailure = {
606
+ success: false,
607
+ data: {
608
+ level1: {
609
+ level2: {
610
+ level3: {
611
+ level4: {
612
+ level5: {
613
+ value: "wrong-type",
614
+ },
615
+ },
616
+ },
617
+ },
618
+ },
619
+ },
620
+ errors: [
621
+ {
622
+ path: "$input.level1.level2.level3.level4.level5.value",
623
+ expected: "number",
624
+ value: "wrong-type",
625
+ },
626
+ ],
627
+ };
628
+
629
+ const result = stringifyValidateFailure(failure);
630
+
631
+ expect(result).toContain("\"level1\":");
632
+ expect(result).toContain("\"level2\":");
633
+ expect(result).toContain("\"level3\":");
634
+ expect(result).toContain("\"level4\":");
635
+ expect(result).toContain("\"level5\":");
636
+ expect(result).toContain("\"value\": \"wrong-type\"");
637
+ expect(result).toContain("// ❌");
638
+ });
639
+
640
+ it("should handle mix of all error types", () => {
641
+ const failure: IValidation.IFailure = {
642
+ success: false,
643
+ data: {
644
+ name: 123,
645
+ email: "invalid-email",
646
+ age: "30",
647
+ tags: ["valid", 456],
648
+ user: {
649
+ status: true,
650
+ },
651
+ },
652
+ errors: [
653
+ {
654
+ path: "$input.name",
655
+ expected: "string",
656
+ value: 123,
657
+ },
658
+ {
659
+ path: "$input.email",
660
+ expected: "string & Format<'email'>",
661
+ value: "invalid-email",
662
+ },
663
+ {
664
+ path: "$input.age",
665
+ expected: "number",
666
+ value: "30",
667
+ },
668
+ {
669
+ path: "$input.tags[1]",
670
+ expected: "string",
671
+ value: 456,
672
+ },
673
+ {
674
+ path: "$input.user.status",
675
+ expected: "string",
676
+ value: true,
677
+ },
678
+ {
679
+ path: "$input.user.role",
680
+ expected: "string",
681
+ value: undefined,
682
+ },
683
+ ],
684
+ };
685
+
686
+ const result = stringifyValidateFailure(failure);
687
+
688
+ expect(result).toContain("\"name\": 123");
689
+ expect(result).toContain("\"email\": \"invalid-email\"");
690
+ expect(result).toContain("\"age\": \"30\"");
691
+ expect(result).toContain("\"tags\":");
692
+ expect(result).toContain("456");
693
+ expect(result).toContain("\"user\":");
694
+ expect(result).toContain("\"status\": true");
695
+ expect(result).toContain("\"role\": undefined");
696
+ expect((result.match(/\/\/ ❌/g) ?? []).length).toBe(6);
697
+ });
698
+
699
+ it("should handle object with special characters in property names", () => {
700
+ const failure: IValidation.IFailure = {
701
+ success: false,
702
+ data: {
703
+ "special-key": "value",
704
+ "key.with.dots": 123,
705
+ },
706
+ errors: [
707
+ {
708
+ path: "$input[\"key.with.dots\"]",
709
+ expected: "string",
710
+ value: 123,
711
+ },
712
+ ],
713
+ };
714
+
715
+ const result = stringifyValidateFailure(failure);
716
+
717
+ expect(result).toContain("\"special-key\": \"value\"");
718
+ expect(result).toContain("\"key.with.dots\": 123");
719
+ expect(result).toContain("// ❌");
720
+ });
721
+ });
722
+
723
+ describe("edge Cases", () => {
724
+ it("should handle empty object with no errors", () => {
725
+ const failure: IValidation.IFailure = {
726
+ success: false,
727
+ data: {},
728
+ errors: [],
729
+ };
730
+
731
+ const result = stringifyValidateFailure(failure);
732
+
733
+ expect(result).toBe("{}");
734
+ });
735
+
736
+ it("should handle empty object with errors", () => {
737
+ const failure: IValidation.IFailure = {
738
+ success: false,
739
+ data: {},
740
+ errors: [
741
+ {
742
+ path: "$input.name",
743
+ expected: "string",
744
+ value: undefined,
745
+ },
746
+ ],
747
+ };
748
+
749
+ const result = stringifyValidateFailure(failure);
750
+
751
+ expect(result).toContain("{");
752
+ expect(result).toContain("\"name\": undefined");
753
+ expect(result).toContain("// ❌");
754
+ expect(result).toContain("}");
755
+ });
756
+
757
+ it("should handle primitive value with error", () => {
758
+ const failure: IValidation.IFailure = {
759
+ success: false,
760
+ data: "wrong-type",
761
+ errors: [
762
+ {
763
+ path: "$input",
764
+ expected: "number",
765
+ value: "wrong-type",
766
+ },
767
+ ],
768
+ };
769
+
770
+ const result = stringifyValidateFailure(failure);
771
+
772
+ expect(result).toContain("\"wrong-type\"");
773
+ expect(result).toContain("// ❌");
774
+ expect(result).toContain("\"expected\":\"number\"");
775
+ });
776
+
777
+ it("should handle null value with error", () => {
778
+ const failure: IValidation.IFailure = {
779
+ success: false,
780
+ data: null,
781
+ errors: [
782
+ {
783
+ path: "$input",
784
+ expected: "object",
785
+ value: null,
786
+ },
787
+ ],
788
+ };
789
+
790
+ const result = stringifyValidateFailure(failure);
791
+
792
+ expect(result).toContain("null");
793
+ expect(result).toContain("// ❌");
794
+ });
795
+
796
+ it("should handle undefined in array", () => {
797
+ const failure: IValidation.IFailure = {
798
+ success: false,
799
+ data: {
800
+ items: ["valid", undefined, "another"],
801
+ },
802
+ errors: [
803
+ {
804
+ path: "$input.items[1]",
805
+ expected: "string",
806
+ value: undefined,
807
+ },
808
+ ],
809
+ };
810
+
811
+ const result = stringifyValidateFailure(failure);
812
+
813
+ expect(result).toContain("\"items\":");
814
+ expect(result).toContain("\"valid\"");
815
+ expect(result).toContain("undefined");
816
+ expect(result).toContain("\"another\"");
817
+ expect(result).toContain("// ❌");
818
+ });
819
+
820
+ it("should handle multiple errors on same path", () => {
821
+ const failure: IValidation.IFailure = {
822
+ success: false,
823
+ data: {
824
+ email: "invalid",
825
+ },
826
+ errors: [
827
+ {
828
+ path: "$input.email",
829
+ expected: "string & Format<'email'>",
830
+ value: "invalid",
831
+ },
832
+ {
833
+ path: "$input.email",
834
+ expected: "string & MinLength<5>",
835
+ value: "invalid",
836
+ },
837
+ ],
838
+ };
839
+
840
+ const result = stringifyValidateFailure(failure);
841
+
842
+ expect(result).toContain("\"email\": \"invalid\"");
843
+ expect(result).toContain("// ❌");
844
+ // Should contain both errors in the comment
845
+ expect(result).toContain("\"expected\":\"string & Format<'email'>\"");
846
+ expect(result).toContain("\"expected\":\"string & MinLength<5>\"");
847
+ });
848
+
849
+ it("should handle object with toJSON method", () => {
850
+ class CustomObject {
851
+ constructor(public value: string) {}
852
+ toJSON() {
853
+ return { serialized: this.value };
854
+ }
855
+ }
856
+
857
+ const failure: IValidation.IFailure = {
858
+ success: false,
859
+ data: {
860
+ custom: new CustomObject("test"),
861
+ },
862
+ errors: [
863
+ {
864
+ path: "$input.custom.serialized",
865
+ expected: "number",
866
+ value: "test",
867
+ },
868
+ ],
869
+ };
870
+
871
+ const result = stringifyValidateFailure(failure);
872
+
873
+ expect(result).toContain("\"custom\":");
874
+ expect(result).toContain("\"serialized\": \"test\"");
875
+ expect(result).toContain("// ❌");
876
+ });
877
+
878
+ it("should handle large object", () => {
879
+ const largeData: any = {};
880
+ const errors: IValidation.IError[] = [];
881
+
882
+ for (let i = 0; i < 50; i++) {
883
+ largeData[`field${i}`] = i % 2 === 0 ? i : `${i}`;
884
+ if (i % 2 !== 0) {
885
+ errors.push({
886
+ path: `$input.field${i}`,
887
+ expected: "number",
888
+ value: `${i}`,
889
+ });
890
+ }
891
+ }
892
+
893
+ const failure: IValidation.IFailure = {
894
+ success: false,
895
+ data: largeData,
896
+ errors,
897
+ };
898
+
899
+ const result = stringifyValidateFailure(failure);
900
+
901
+ // Should contain all fields
902
+ for (let i = 0; i < 50; i++) {
903
+ expect(result).toContain(`"field${i}"`);
904
+ }
905
+ // Should have 25 error comments (for odd numbers)
906
+ expect((result.match(/\/\/ ❌/g) ?? []).length).toBe(25);
907
+ });
908
+
909
+ it("should preserve correct indentation", () => {
910
+ const failure: IValidation.IFailure = {
911
+ success: false,
912
+ data: {
913
+ level1: {
914
+ level2: {
915
+ value: "test",
916
+ },
917
+ },
918
+ },
919
+ errors: [],
920
+ };
921
+
922
+ const result = stringifyValidateFailure(failure);
923
+
924
+ // Check that indentation increases with nesting
925
+ const lines = result.split("\n");
926
+ expect(lines[0]).toMatch(/^\{/); // No indent
927
+ expect(lines[1]).toMatch(/^ {2}"level1":/); // 2 spaces
928
+ expect(lines[2]).toMatch(/^ {4}"level2":/); // 4 spaces
929
+ expect(lines[3]).toMatch(/^ {6}"value":/); // 6 spaces
930
+ });
931
+
932
+ it("should handle array with error on non-existent index", () => {
933
+ const failure: IValidation.IFailure = {
934
+ success: false,
935
+ data: {
936
+ items: ["a", "b"],
937
+ },
938
+ errors: [
939
+ {
940
+ path: "$input.items[5]",
941
+ expected: "string",
942
+ value: undefined,
943
+ },
944
+ ],
945
+ };
946
+
947
+ const result = stringifyValidateFailure(failure);
948
+
949
+ expect(result).toContain("\"items\":");
950
+ expect(result).toContain("\"a\"");
951
+ expect(result).toContain("\"b\"");
952
+ // Array index errors are not added as missing properties
953
+ // This is correct behavior - array indices work differently
954
+ });
955
+ });
956
+
957
+ describe("error Comment Format", () => {
958
+ it("should include path and expected in error comment", () => {
959
+ const failure: IValidation.IFailure = {
960
+ success: false,
961
+ data: {
962
+ age: "25",
963
+ },
964
+ errors: [
965
+ {
966
+ path: "$input.age",
967
+ expected: "number",
968
+ value: "25",
969
+ },
970
+ ],
971
+ };
972
+
973
+ const result = stringifyValidateFailure(failure);
974
+
975
+ expect(result).toContain("// ❌");
976
+ expect(result).toContain("\"path\":\"$input.age\"");
977
+ expect(result).toContain("\"expected\":\"number\"");
978
+ // Note: value is not included in error comment since it's already visible in the JSON structure
979
+ });
980
+
981
+ it("should include description in error comment when present", () => {
982
+ const failure: IValidation.IFailure = {
983
+ success: false,
984
+ data: {
985
+ email: "invalid",
986
+ },
987
+ errors: [
988
+ {
989
+ path: "$input.email",
990
+ expected: "string & Format<'email'>",
991
+ value: "invalid",
992
+ description: "Must be a valid email address",
993
+ },
994
+ ],
995
+ };
996
+
997
+ const result = stringifyValidateFailure(failure);
998
+
999
+ expect(result).toContain("// ❌");
1000
+ expect(result).toContain("\"description\":\"Must be a valid email address\"");
1001
+ });
1002
+
1003
+ it("should format error comment as valid JSON", () => {
1004
+ const failure: IValidation.IFailure = {
1005
+ success: false,
1006
+ data: {
1007
+ value: "test",
1008
+ },
1009
+ errors: [
1010
+ {
1011
+ path: "$input.value",
1012
+ expected: "number",
1013
+ value: "test",
1014
+ },
1015
+ ],
1016
+ };
1017
+
1018
+ const result = stringifyValidateFailure(failure);
1019
+
1020
+ // Extract error comment
1021
+ const match = result.match(/\/\/ ❌ (.+)/);
1022
+ expect(match).not.toBeNull();
1023
+
1024
+ // Should be parseable JSON array
1025
+ const errorJson = JSON.parse(match![1]!);
1026
+ expect(Array.isArray(errorJson)).toBe(true);
1027
+ expect(errorJson[0]).toHaveProperty("path");
1028
+ expect(errorJson[0]).toHaveProperty("expected");
1029
+ // Note: value is not included in error comment to avoid redundancy
1030
+ expect(errorJson[0].path).toBe("$input.value");
1031
+ expect(errorJson[0].expected).toBe("number");
1032
+ });
1033
+ });
1034
+ });