@valbuild/cli 0.87.5 → 0.89.0

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,812 @@
1
+ import { describe, test, expect, jest, beforeEach } from "@jest/globals";
2
+ import { ModuleFilePath, SourcePath } from "@valbuild/core";
3
+ import type { Service } from "@valbuild/server";
4
+
5
+ // Mock dependencies
6
+ jest.mock("@valbuild/server");
7
+ jest.mock("fs/promises");
8
+ jest.mock("picocolors", () => ({
9
+ red: (s: string) => s,
10
+ green: (s: string) => s,
11
+ yellow: (s: string) => s,
12
+ greenBright: (s: string) => s,
13
+ inverse: (s: string) => s,
14
+ }));
15
+
16
+ // Import types from validate.ts
17
+ type ValModule = {
18
+ source?: unknown;
19
+ schema?: { type: string; router?: string };
20
+ errors?: {
21
+ validation?: Record<string, Array<{ message: string; fixes?: string[] }>>;
22
+ fatal?: Array<{ message: string }>;
23
+ };
24
+ };
25
+
26
+ type FixHandlerContext = {
27
+ sourcePath: SourcePath;
28
+ validationError: {
29
+ message: string;
30
+ value?: unknown;
31
+ fixes?: string[];
32
+ };
33
+ valModule: ValModule;
34
+ projectRoot: string;
35
+ fix: boolean;
36
+ service: Service;
37
+ valFiles: string[];
38
+ moduleFilePath: ModuleFilePath;
39
+ file: string;
40
+ remoteFiles: Record<
41
+ SourcePath,
42
+ { ref: string; metadata?: Record<string, unknown> }
43
+ >;
44
+ publicProjectId?: string;
45
+ remoteFileBuckets?: string[];
46
+ remoteFilesCounter: number;
47
+ valRemoteHost: string;
48
+ contentHostUrl: string;
49
+ valConfigFile?: { project?: string };
50
+ };
51
+
52
+ describe("validate handlers", () => {
53
+ let mockService: jest.Mocked<Service>;
54
+ let baseContext: FixHandlerContext;
55
+
56
+ beforeEach(() => {
57
+ mockService = {
58
+ get: jest.fn(),
59
+ patch: jest.fn(),
60
+ dispose: jest.fn(),
61
+ } as unknown as jest.Mocked<Service>;
62
+
63
+ baseContext = {
64
+ sourcePath: "/test.val.ts" as SourcePath,
65
+ validationError: {
66
+ message: "Test error",
67
+ fixes: [],
68
+ },
69
+ valModule: {
70
+ source: {},
71
+ schema: { type: "string" },
72
+ },
73
+ projectRoot: "/test/project",
74
+ fix: false,
75
+ service: mockService,
76
+ valFiles: [],
77
+ moduleFilePath: "/test.val.ts" as ModuleFilePath,
78
+ file: "test.val.ts",
79
+ remoteFiles: {},
80
+ remoteFilesCounter: 0,
81
+ valRemoteHost: "https://val.build",
82
+ contentHostUrl: "https://content.val.build",
83
+ };
84
+ });
85
+
86
+ describe("handleKeyOfCheck", () => {
87
+ test("should return success when key exists in referenced module", async () => {
88
+ const ctx: FixHandlerContext = {
89
+ ...baseContext,
90
+ validationError: {
91
+ message: "Key validation",
92
+ fixes: ["keyof:check-keys"],
93
+ value: {
94
+ key: "existingKey",
95
+ sourcePath: "/test.val.ts",
96
+ },
97
+ },
98
+ };
99
+
100
+ // We can't actually import the handler directly, so this test documents expected behavior
101
+ // The handler should:
102
+ // 1. Extract key and sourcePath from validationError.value
103
+ // 2. Call service.get to fetch the referenced module
104
+ // 3. Check if the key exists in the module source
105
+ // 4. Return success: true if key exists
106
+
107
+ expect(ctx.validationError.value).toHaveProperty("key", "existingKey");
108
+ expect(ctx.validationError.value).toHaveProperty(
109
+ "sourcePath",
110
+ "/test.val.ts",
111
+ );
112
+ });
113
+
114
+ test("should return error when key does not exist", async () => {
115
+ const ctx: FixHandlerContext = {
116
+ ...baseContext,
117
+ validationError: {
118
+ message: "Key validation",
119
+ fixes: ["keyof:check-keys"],
120
+ value: {
121
+ key: "nonExistentKey",
122
+ sourcePath: "/test.val.ts",
123
+ },
124
+ },
125
+ };
126
+
127
+ // Expected behavior:
128
+ // 1. Handler finds that nonExistentKey is not in source
129
+ // 2. Uses levenshtein distance to find similar keys
130
+ // 3. Returns success: false with helpful error message
131
+
132
+ expect(ctx.validationError.value).toHaveProperty("key", "nonExistentKey");
133
+ });
134
+
135
+ test("should return error when value format is invalid", () => {
136
+ const ctx: FixHandlerContext = {
137
+ ...baseContext,
138
+ validationError: {
139
+ message: "Key validation",
140
+ fixes: ["keyof:check-keys"],
141
+ value: "invalid", // Should be an object with key and sourcePath
142
+ },
143
+ };
144
+
145
+ // Expected behavior:
146
+ // Handler should validate that value is an object with key and sourcePath
147
+ // Should return success: false with error message about invalid format
148
+
149
+ expect(typeof ctx.validationError.value).toBe("string");
150
+ });
151
+
152
+ test("should return error when key is not a string", () => {
153
+ const ctx: FixHandlerContext = {
154
+ ...baseContext,
155
+ validationError: {
156
+ message: "Key validation",
157
+ fixes: ["keyof:check-keys"],
158
+ value: {
159
+ key: 123, // Should be string
160
+ sourcePath: "/test.val.ts",
161
+ },
162
+ },
163
+ };
164
+
165
+ // Expected behavior:
166
+ // Handler should validate that key is a string
167
+ // Should return success: false with error about type mismatch
168
+
169
+ expect(typeof (ctx.validationError.value as { key: unknown }).key).toBe(
170
+ "number",
171
+ );
172
+ });
173
+ });
174
+
175
+ describe("handleFileMetadata", () => {
176
+ test("should return success when file exists", () => {
177
+ const ctx: FixHandlerContext = {
178
+ ...baseContext,
179
+ validationError: {
180
+ message: "File metadata",
181
+ fixes: ["image:check-metadata"],
182
+ },
183
+ valModule: {
184
+ source: {
185
+ image: {
186
+ "~$ref": "public/val/image.png",
187
+ },
188
+ },
189
+ schema: { type: "image" },
190
+ },
191
+ };
192
+
193
+ // Expected behavior:
194
+ // 1. Extract file path from source
195
+ // 2. Check if file exists using fs.access
196
+ // 3. Return success: true if file exists
197
+
198
+ expect(ctx.valModule.source).toBeDefined();
199
+ });
200
+
201
+ test("should return error when source or schema is missing", async () => {
202
+ const ctx: FixHandlerContext = {
203
+ ...baseContext,
204
+ validationError: {
205
+ message: "File metadata",
206
+ fixes: ["file:check-metadata"],
207
+ },
208
+ valModule: {
209
+ source: undefined,
210
+ schema: undefined,
211
+ },
212
+ };
213
+
214
+ // Expected behavior:
215
+ // Handler should check if source and schema exist
216
+ // Should return success: false with error message
217
+
218
+ expect(ctx.valModule.source).toBeUndefined();
219
+ expect(ctx.valModule.schema).toBeUndefined();
220
+ });
221
+ });
222
+
223
+ describe("handleRemoteFileUpload", () => {
224
+ test("should return error when fix is false", () => {
225
+ const ctx: FixHandlerContext = {
226
+ ...baseContext,
227
+ fix: false,
228
+ validationError: {
229
+ message: "Remote file upload",
230
+ fixes: ["image:upload-remote"],
231
+ },
232
+ };
233
+
234
+ // Expected behavior:
235
+ // When fix=false, handler should return error telling user to use --fix
236
+ // Should return success: false with message about using --fix
237
+
238
+ expect(ctx.fix).toBe(false);
239
+ });
240
+
241
+ test("should attempt upload when fix is true", () => {
242
+ const ctx: FixHandlerContext = {
243
+ ...baseContext,
244
+ fix: true,
245
+ validationError: {
246
+ message: "Remote file upload",
247
+ fixes: ["file:upload-remote"],
248
+ },
249
+ valConfigFile: {
250
+ project: "test-project",
251
+ },
252
+ };
253
+
254
+ // Expected behavior:
255
+ // 1. Check file exists
256
+ // 2. Get PAT file
257
+ // 3. Get project settings if not cached
258
+ // 4. Upload file to remote
259
+ // 5. Store reference in remoteFiles
260
+ // 6. Return success: true with shouldApplyPatch: true
261
+
262
+ expect(ctx.fix).toBe(true);
263
+ expect(ctx.valConfigFile?.project).toBe("test-project");
264
+ });
265
+
266
+ test("should return error when project config is missing", () => {
267
+ const ctx: FixHandlerContext = {
268
+ ...baseContext,
269
+ fix: true,
270
+ validationError: {
271
+ message: "Remote file upload",
272
+ fixes: ["image:upload-remote"],
273
+ },
274
+ valConfigFile: undefined,
275
+ };
276
+
277
+ // Expected behavior:
278
+ // Handler should check for project config
279
+ // Should return success: false with error about missing config
280
+
281
+ expect(ctx.valConfigFile).toBeUndefined();
282
+ });
283
+
284
+ test("should use cached project settings when available", () => {
285
+ const ctx: FixHandlerContext = {
286
+ ...baseContext,
287
+ fix: true,
288
+ publicProjectId: "cached-id",
289
+ remoteFileBuckets: ["bucket1", "bucket2"],
290
+ validationError: {
291
+ message: "Remote file upload",
292
+ fixes: ["image:upload-remote"],
293
+ },
294
+ };
295
+
296
+ // Expected behavior:
297
+ // Handler should reuse publicProjectId and remoteFileBuckets if available
298
+ // Should not call getSettings again
299
+
300
+ expect(ctx.publicProjectId).toBe("cached-id");
301
+ expect(ctx.remoteFileBuckets).toEqual(["bucket1", "bucket2"]);
302
+ });
303
+ });
304
+
305
+ describe("handleRemoteFileDownload", () => {
306
+ test("should return success when fix is true", () => {
307
+ const ctx: FixHandlerContext = {
308
+ ...baseContext,
309
+ fix: true,
310
+ validationError: {
311
+ message: "Remote file download",
312
+ fixes: ["image:download-remote"],
313
+ },
314
+ };
315
+
316
+ // Expected behavior:
317
+ // When fix=true, handler logs download message and returns success
318
+ // Should return success: true with shouldApplyPatch: true
319
+
320
+ expect(ctx.fix).toBe(true);
321
+ });
322
+
323
+ test("should return error when fix is false", () => {
324
+ const ctx: FixHandlerContext = {
325
+ ...baseContext,
326
+ fix: false,
327
+ validationError: {
328
+ message: "Remote file download",
329
+ fixes: ["file:download-remote"],
330
+ },
331
+ };
332
+
333
+ // Expected behavior:
334
+ // When fix=false, handler returns error telling user to use --fix
335
+ // Should return success: false
336
+
337
+ expect(ctx.fix).toBe(false);
338
+ });
339
+ });
340
+
341
+ describe("handleRemoteFileCheck", () => {
342
+ test("should always return success", () => {
343
+ const ctx: FixHandlerContext = {
344
+ ...baseContext,
345
+ validationError: {
346
+ message: "Remote file check",
347
+ fixes: ["image:check-remote"],
348
+ },
349
+ };
350
+
351
+ // Expected behavior:
352
+ // This handler is a no-op that always succeeds
353
+ // Should return success: true with shouldApplyPatch: true
354
+
355
+ expect(ctx.validationError.fixes).toContain("image:check-remote");
356
+ });
357
+ });
358
+
359
+ describe("handleRouteCheck", () => {
360
+ test("should return success when route exists in router module", async () => {
361
+ const ctx: FixHandlerContext = {
362
+ ...baseContext,
363
+ validationError: {
364
+ message: "Route validation required",
365
+ value: { route: "/home" },
366
+ fixes: ["router:check-route"],
367
+ },
368
+ valFiles: ["routes.val.ts"],
369
+ };
370
+
371
+ mockService.get.mockResolvedValue({
372
+ schema: {
373
+ type: "record",
374
+ router: "next-app-router",
375
+ item: { type: "object", items: {}, opt: false },
376
+ opt: false,
377
+ },
378
+ source: { "/home": {}, "/about": {} },
379
+ path: "/routes.val.ts" as SourcePath,
380
+ errors: {},
381
+ });
382
+
383
+ // Expected behavior:
384
+ // 1. Extract route from validationError.value
385
+ // 2. Scan all valFiles for router modules
386
+ // 3. Check if route exists in any router module
387
+ // 4. Return success: true if found
388
+
389
+ expect(ctx.validationError.value).toEqual({ route: "/home" });
390
+ expect(ctx.valFiles).toContain("routes.val.ts");
391
+ });
392
+
393
+ test("should return error when route does not exist", async () => {
394
+ const ctx: FixHandlerContext = {
395
+ ...baseContext,
396
+ validationError: {
397
+ message: "Route validation required",
398
+ value: { route: "/notfound" },
399
+ fixes: ["router:check-route"],
400
+ },
401
+ valFiles: ["routes.val.ts"],
402
+ };
403
+
404
+ mockService.get.mockResolvedValue({
405
+ schema: {
406
+ type: "record",
407
+ router: "next-app-router",
408
+ item: { type: "object", items: {}, opt: false },
409
+ opt: false,
410
+ },
411
+ source: { "/home": {}, "/about": {} },
412
+ path: "/routes.val.ts" as SourcePath,
413
+ errors: {},
414
+ });
415
+
416
+ // Expected behavior:
417
+ // Route not found, should return error with suggestions
418
+ // Should use levenshtein distance to find similar routes
419
+
420
+ expect(ctx.validationError.value).toEqual({ route: "/notfound" });
421
+ });
422
+
423
+ test("should return error when no router modules found", async () => {
424
+ const ctx: FixHandlerContext = {
425
+ ...baseContext,
426
+ validationError: {
427
+ message: "Route validation required",
428
+ value: { route: "/home" },
429
+ fixes: ["router:check-route"],
430
+ },
431
+ valFiles: ["data.val.ts"],
432
+ };
433
+
434
+ mockService.get.mockResolvedValue({
435
+ schema: {
436
+ type: "record",
437
+ router: undefined,
438
+ item: { type: "object", items: {}, opt: false },
439
+ opt: false,
440
+ },
441
+ source: { key: "value" },
442
+ path: "/data.val.ts" as SourcePath,
443
+ errors: {},
444
+ });
445
+
446
+ // Expected behavior:
447
+ // No router modules found, should return helpful error message
448
+
449
+ expect(ctx.valFiles).toContain("data.val.ts");
450
+ });
451
+
452
+ test("should validate include pattern", async () => {
453
+ const ctx: FixHandlerContext = {
454
+ ...baseContext,
455
+ validationError: {
456
+ message: "Route validation required",
457
+ value: {
458
+ route: "/admin/users",
459
+ include: { source: "^\\/api\\/", flags: "" },
460
+ },
461
+ fixes: ["router:check-route"],
462
+ },
463
+ valFiles: ["routes.val.ts"],
464
+ };
465
+
466
+ mockService.get.mockResolvedValue({
467
+ schema: {
468
+ type: "record",
469
+ router: "next-app-router",
470
+ item: { type: "object", items: {}, opt: false },
471
+ opt: false,
472
+ },
473
+ source: { "/admin/users": {} },
474
+ path: "/routes.val.ts" as SourcePath,
475
+ errors: {},
476
+ });
477
+
478
+ // Expected behavior:
479
+ // Route exists but doesn't match include pattern
480
+ // Should return error about pattern mismatch
481
+
482
+ const value = ctx.validationError.value as {
483
+ route: string;
484
+ include: { source: string; flags: string };
485
+ };
486
+ expect(value.include.source).toBe("^\\/api\\/");
487
+ });
488
+
489
+ test("should validate exclude pattern", async () => {
490
+ const ctx: FixHandlerContext = {
491
+ ...baseContext,
492
+ validationError: {
493
+ message: "Route validation required",
494
+ value: {
495
+ route: "/api/internal/secret",
496
+ exclude: { source: "^\\/api\\/internal\\/", flags: "" },
497
+ },
498
+ fixes: ["router:check-route"],
499
+ },
500
+ valFiles: ["routes.val.ts"],
501
+ };
502
+
503
+ mockService.get.mockResolvedValue({
504
+ schema: {
505
+ type: "record",
506
+ router: "next-app-router",
507
+ item: { type: "object", items: {}, opt: false },
508
+ opt: false,
509
+ },
510
+ source: { "/api/internal/secret": {} },
511
+ path: "/routes.val.ts" as SourcePath,
512
+ errors: {},
513
+ });
514
+
515
+ // Expected behavior:
516
+ // Route exists but matches exclude pattern
517
+ // Should return error about excluded route
518
+
519
+ const value = ctx.validationError.value as {
520
+ route: string;
521
+ exclude: { source: string; flags: string };
522
+ };
523
+ expect(value.exclude.source).toBe("^\\/api\\/internal\\/");
524
+ });
525
+
526
+ test("should validate both include and exclude patterns", async () => {
527
+ const ctx: FixHandlerContext = {
528
+ ...baseContext,
529
+ validationError: {
530
+ message: "Route validation required",
531
+ value: {
532
+ route: "/api/users",
533
+ include: { source: "^\\/api\\/", flags: "" },
534
+ exclude: { source: "^\\/api\\/internal\\/", flags: "" },
535
+ },
536
+ fixes: ["router:check-route"],
537
+ },
538
+ valFiles: ["routes.val.ts"],
539
+ };
540
+
541
+ mockService.get.mockResolvedValue({
542
+ schema: {
543
+ type: "record",
544
+ router: "next-app-router",
545
+ item: { type: "object", items: {}, opt: false },
546
+ opt: false,
547
+ },
548
+ source: { "/api/users": {} },
549
+ path: "/routes.val.ts" as SourcePath,
550
+ errors: {},
551
+ });
552
+
553
+ // Expected behavior:
554
+ // Route exists, matches include, doesn't match exclude
555
+ // Should return success: true
556
+
557
+ const value = ctx.validationError.value as {
558
+ route: string;
559
+ include: { source: string; flags: string };
560
+ exclude: { source: string; flags: string };
561
+ };
562
+ expect(value.include.source).toBe("^\\/api\\/");
563
+ expect(value.exclude.source).toBe("^\\/api\\/internal\\/");
564
+ });
565
+
566
+ test("should return error when route value is invalid", async () => {
567
+ const ctx: FixHandlerContext = {
568
+ ...baseContext,
569
+ validationError: {
570
+ message: "Route validation required",
571
+ value: { route: 123 }, // Invalid: should be string
572
+ fixes: ["router:check-route"],
573
+ },
574
+ valFiles: ["routes.val.ts"],
575
+ };
576
+
577
+ // Expected behavior:
578
+ // Invalid route value type, should return error
579
+
580
+ expect(
581
+ typeof (ctx.validationError.value as { route: unknown }).route,
582
+ ).toBe("number");
583
+ });
584
+ });
585
+
586
+ describe("fix handler registry", () => {
587
+ test("should have handlers for all file metadata fix types", () => {
588
+ const metadataFixTypes = [
589
+ "image:replace-metadata",
590
+ "image:check-metadata",
591
+ "image:add-metadata",
592
+ "file:check-metadata",
593
+ "file:add-metadata",
594
+ ];
595
+
596
+ // Expected: All metadata fix types should map to handleFileMetadata
597
+ expect(metadataFixTypes.length).toBeGreaterThan(0);
598
+ });
599
+
600
+ test("should have handler for keyof:check-keys", () => {
601
+ const fixType = "keyof:check-keys";
602
+
603
+ // Expected: Registry should have entry for keyof:check-keys
604
+ expect(fixType).toBe("keyof:check-keys");
605
+ });
606
+
607
+ test("should have handlers for remote file operations", () => {
608
+ const remoteFixTypes = [
609
+ "image:upload-remote",
610
+ "file:upload-remote",
611
+ "image:download-remote",
612
+ "file:download-remote",
613
+ "image:check-remote",
614
+ "file:check-remote",
615
+ ];
616
+
617
+ // Expected: All remote fix types should have handlers
618
+ expect(remoteFixTypes.length).toBe(6);
619
+ });
620
+ });
621
+
622
+ describe("validation flow", () => {
623
+ test("should handle validation with no errors", () => {
624
+ const valModule: ValModule = {
625
+ source: { test: "value" },
626
+ schema: { type: "string" },
627
+ errors: undefined,
628
+ };
629
+
630
+ // Expected behavior:
631
+ // When valModule.errors is undefined, validation succeeds immediately
632
+ // Should log success message and return 0 errors
633
+
634
+ expect(valModule.errors).toBeUndefined();
635
+ });
636
+
637
+ test("should handle validation with fixes", () => {
638
+ const valModule: ValModule = {
639
+ source: { test: "value" },
640
+ schema: { type: "string" },
641
+ errors: {
642
+ validation: {
643
+ "/test.val.ts": [
644
+ {
645
+ message: "Test error",
646
+ fixes: ["keyof:check-keys"],
647
+ },
648
+ ],
649
+ },
650
+ },
651
+ };
652
+
653
+ // Expected behavior:
654
+ // 1. Loop through validation errors
655
+ // 2. Find handler for first fix type
656
+ // 3. Execute handler
657
+ // 4. Apply patch if handler succeeds and shouldApplyPatch is true
658
+
659
+ expect(valModule.errors?.validation).toBeDefined();
660
+ expect(Object.keys(valModule.errors?.validation || {}).length).toBe(1);
661
+ });
662
+
663
+ test("should handle validation without fixes", () => {
664
+ const valModule: ValModule = {
665
+ source: { test: "value" },
666
+ schema: { type: "string" },
667
+ errors: {
668
+ validation: {
669
+ "/test.val.ts": [
670
+ {
671
+ message: "Test error",
672
+ fixes: undefined,
673
+ },
674
+ ],
675
+ },
676
+ },
677
+ };
678
+
679
+ // Expected behavior:
680
+ // When no fixes available, validation should log error and continue
681
+ // Should not attempt to find or execute a handler
682
+
683
+ const error = valModule.errors?.validation?.["/test.val.ts"]?.[0];
684
+ expect(error?.fixes).toBeUndefined();
685
+ });
686
+
687
+ test("should handle unknown fix types", () => {
688
+ const valModule: ValModule = {
689
+ source: { test: "value" },
690
+ schema: { type: "string" },
691
+ errors: {
692
+ validation: {
693
+ "/test.val.ts": [
694
+ {
695
+ message: "Test error",
696
+ fixes: ["unknown:fix-type"],
697
+ },
698
+ ],
699
+ },
700
+ },
701
+ };
702
+
703
+ // Expected behavior:
704
+ // When fix type is not in registry, validation should log error
705
+ // Should increment error count and continue
706
+
707
+ const error = valModule.errors?.validation?.["/test.val.ts"]?.[0];
708
+ expect(error?.fixes?.[0]).toBe("unknown:fix-type");
709
+ });
710
+
711
+ test("should handle fatal errors", () => {
712
+ const valModule: ValModule = {
713
+ source: { test: "value" },
714
+ schema: { type: "string" },
715
+ errors: {
716
+ fatal: [
717
+ {
718
+ message: "Fatal error occurred",
719
+ },
720
+ ],
721
+ },
722
+ };
723
+
724
+ // Expected behavior:
725
+ // Fatal errors should be logged separately
726
+ // Should increment error count
727
+
728
+ expect(valModule.errors?.fatal).toBeDefined();
729
+ expect(valModule.errors?.fatal?.length).toBe(1);
730
+ });
731
+ });
732
+
733
+ describe("error handling", () => {
734
+ test("should handle service.get failures gracefully", async () => {
735
+ mockService.get.mockRejectedValue(new Error("Service error"));
736
+
737
+ // Expected behavior:
738
+ // When service.get fails, handler should catch error
739
+ // Should return or throw appropriate error
740
+
741
+ await expect(
742
+ mockService.get(
743
+ "/test" as ModuleFilePath,
744
+ "" as unknown as never,
745
+ {} as never,
746
+ ),
747
+ ).rejects.toThrow("Service error");
748
+ });
749
+
750
+ test("should handle file system errors gracefully", () => {
751
+ // Expected behavior:
752
+ // When fs operations fail, handlers should catch errors
753
+ // Should return success: false with appropriate error message
754
+
755
+ const error = new Error("ENOENT: file not found");
756
+ expect(error.message).toContain("ENOENT");
757
+ });
758
+ });
759
+
760
+ describe("levenshtein distance helper", () => {
761
+ test("should find similar strings", () => {
762
+ const targets = ["test", "text", "best", "rest", "toast"];
763
+
764
+ // Expected: Function should calculate edit distance and sort by similarity
765
+ // "test" should be first (distance 0)
766
+ // "text", "best", "rest" should be next (distance 1)
767
+
768
+ expect(targets.includes("test")).toBe(true);
769
+ });
770
+
771
+ test("should handle empty arrays", () => {
772
+ const targets: string[] = [];
773
+
774
+ // Expected: Should handle empty target array gracefully
775
+ // Should return empty array
776
+
777
+ expect(targets.length).toBe(0);
778
+ });
779
+ });
780
+ });
781
+
782
+ describe("validate integration", () => {
783
+ test("documents expected validation workflow", () => {
784
+ // This test documents the expected flow of validation:
785
+ //
786
+ // 1. Load val config
787
+ // 2. Create service
788
+ // 3. Find all .val.ts/js files
789
+ // 4. For each file:
790
+ // a. Get module with validation
791
+ // b. If no errors, log success and continue
792
+ // c. If errors, process each error:
793
+ // i. If no fixes, log error and continue
794
+ // ii. If has fixes, find handler and execute
795
+ // iii. If handler succeeds and shouldApplyPatch, apply patch
796
+ // iv. Log appropriate success/error messages
797
+ // 5. Format files with prettier if fixes were applied
798
+ // 6. Exit with error code if any errors found
799
+
800
+ const expectedFlow = {
801
+ step1: "Load val config",
802
+ step2: "Create service",
803
+ step3: "Find val files",
804
+ step4: "Validate each file",
805
+ step5: "Apply fixes if needed",
806
+ step6: "Format with prettier",
807
+ step7: "Report results",
808
+ };
809
+
810
+ expect(Object.keys(expectedFlow).length).toBe(7);
811
+ });
812
+ });