Skip to content
File

Blob: src/workerd/api/node/tests/fs-cp-test.js

javascript2150 lines
1// Copyright (c) 2025 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4import {
5 writeFileSync,
6 mkdirSync,
7 symlinkSync,
8 cpSync,
9 cp,
10 existsSync,
11 readFileSync,
12 lstatSync,
13 statSync,
14 promises as fsPromises,
15} from 'node:fs';
16 
17import { ok, deepStrictEqual, strictEqual, throws, rejects } from 'node:assert';
18 
19const pathA = new URL('file:///tmp/a'); // File
20const pathB = new URL('file:///tmp/b'); // Directory
21const pathBE = new URL('file:///tmp/b/e'); // File in directory
22const pathC = new URL('file:///tmp/c'); // Symlink to file
23const pathD = new URL('file:///tmp/d'); // Symlink to directory
24const pathE = new URL('file:///tmp/e'); // Directory
25const pathF = new URL('file:///tmp/f'); // Path to copy to
26 
27function setupPaths() {
28 writeFileSync(pathA, 'foo');
29 mkdirSync(pathB);
30 symlinkSync(pathA, pathC);
31 symlinkSync(pathB, pathD);
32 mkdirSync(pathE);
33 writeFileSync(pathBE, 'bar');
34}
35 
36export const simpleFileCopy = {
37 test() {
38 setupPaths();
39 ok(existsSync(pathA));
40 deepStrictEqual(readFileSync(pathA, 'utf8'), 'foo');
41 ok(!existsSync(pathF));
42 cpSync(pathA, pathF);
43 ok(existsSync(pathF));
44 deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');
45 writeFileSync(pathA, 'baz');
46 deepStrictEqual(readFileSync(pathA, 'utf8'), 'baz');
47 deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');
48 cpSync(pathA, pathF);
49 cpSync(pathA, pathF, { errorOnExist: true }); // force is true by default, weird but true.
50 
51 // Copying a file when the destination already exists and force is false and
52 // errorOnExist is true, should throw an error.
53 throws(() => cpSync(pathA, pathF, { force: false, errorOnExist: true }), {
54 code: 'ERR_FS_CP_EEXIST',
55 });
56 
57 // Copying a file to a directory should throw an error.
58 throws(() => cpSync(pathA, pathB), {
59 code: 'ERR_FS_CP_NON_DIR_TO_DIR',
60 });
61 
62 // Copy a file to a symlink to a directory should throw by default
63 throws(() => cpSync(pathA, pathD, { dereference: true }), {
64 code: 'ERR_FS_CP_NON_DIR_TO_DIR',
65 });
66 
67 ok(statSync(pathD).isDirectory());
68 ok(lstatSync(pathD).isSymbolicLink());
69 cpSync(pathA, pathD);
70 ok(lstatSync(pathD).isFile());
71 },
72};
73 
74// Copy file to non-existent directory (should fail)
75export const copyFileToNonExistentDirectory = {
76 test() {
77 setupPaths();
78 // The directory will be created by cpSync if it doesn't exist.
79 const pathG = new URL('file:///tmp/nonexistent');
80 const pathH = new URL('file:///tmp/nonexistent/g');
81 ok(!existsSync(pathG));
82 ok(!existsSync(pathH));
83 cpSync(pathA, pathH);
84 ok(existsSync(pathG));
85 ok(existsSync(pathH));
86 },
87};
88 
89// Copy file to existing file with force
90export const copyFileToExistingFileWithForce = {
91 test() {
92 setupPaths();
93 const pathG = new URL('file:///tmp/g');
94 writeFileSync(pathG, 'original');
95 cpSync(pathA, pathG, { force: true });
96 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
97 },
98};
99 
100// Copy file to existing file without force
101export const copyFileToExistingFileWithoutForce = {
102 test() {
103 setupPaths();
104 const pathG = new URL('file:///tmp/g');
105 writeFileSync(pathG, 'original');
106 // When force is false, it should not overwrite the existing file.
107 cpSync(pathA, pathG, { force: false });
108 deepStrictEqual(readFileSync(pathG, 'utf8'), 'original');
109 },
110};
111 
112// Copy file to existing directory
113export const copyFileToExistingDirectory = {
114 test() {
115 setupPaths();
116 throws(() => cpSync(pathA, pathB), {
117 code: 'ERR_FS_CP_NON_DIR_TO_DIR',
118 });
119 },
120};
121 
122// Copy file to existing symlink to directory
123export const copyFileToExistingSymlinkToDirectory = {
124 test() {
125 setupPaths();
126 // With dereference: true, should throw EISDIR
127 throws(() => cpSync(pathA, pathD, { dereference: true }), {
128 code: 'ERR_FS_CP_NON_DIR_TO_DIR',
129 });
130 
131 // Without dereference (default), should replace the symlink
132 cpSync(pathA, pathD);
133 ok(lstatSync(pathD).isFile());
134 deepStrictEqual(readFileSync(pathD, 'utf8'), 'foo');
135 },
136};
137 
138// Copy directory to non-existent directory
139export const copyDirectoryToNonExistentDirectory = {
140 test() {
141 setupPaths();
142 const pathG = new URL('file:///tmp/g');
143 cpSync(pathB, pathG, { recursive: true });
144 ok(existsSync(pathG));
145 ok(lstatSync(pathG).isDirectory());
146 ok(existsSync(new URL('file:///tmp/g/e')));
147 deepStrictEqual(readFileSync(new URL('file:///tmp/g/e'), 'utf8'), 'bar');
148 },
149};
150 
151// Copy directory to existing directory
152export const copyDirectoryToExistingDirectory = {
153 test() {
154 setupPaths();
155 cpSync(pathB, pathE, { recursive: true });
156 ok(existsSync(new URL('file:///tmp/e/e')));
157 deepStrictEqual(readFileSync(new URL('file:///tmp/e/e'), 'utf8'), 'bar');
158 },
159};
160 
161// Copy directory to existing file
162export const copyDirectoryToExistingFile = {
163 test() {
164 setupPaths();
165 throws(() => cpSync(pathB, pathA, { recursive: true }), {
166 code: 'ERR_FS_CP_DIR_TO_NON_DIR',
167 });
168 },
169};
170 
171// Copy directory to existing symlink to file
172export const copyDirectoryToExistingSymlinkToFile = {
173 test() {
174 setupPaths();
175 throws(() => cpSync(pathB, pathC, { recursive: true, dereference: true }), {
176 code: 'ERR_FS_CP_DIR_TO_NON_DIR',
177 });
178 },
179};
180 
181// Copy symlink to file
182export const copySymlinkToFile = {
183 test() {
184 setupPaths();
185 const pathG = new URL('file:///tmp/g');
186 
187 // With dereference: false (default), should copy the symlink
188 cpSync(pathC, pathG);
189 ok(lstatSync(pathG).isSymbolicLink());
190 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
191 
192 // With dereference: true, should copy the target file
193 const pathH = new URL('file:///tmp/h');
194 cpSync(pathC, pathH, { dereference: true });
195 ok(lstatSync(pathH).isFile());
196 deepStrictEqual(readFileSync(pathH, 'utf8'), 'foo');
197 },
198};
199 
200// Copy symlink to directory
201export const copySymlinkToDirectory = {
202 test() {
203 setupPaths();
204 const pathG = new URL('file:///tmp/g');
205 
206 // With dereference: false (default), should copy the symlink
207 cpSync(pathD, pathG);
208 ok(lstatSync(pathG).isSymbolicLink());
209 ok(statSync(pathG).isDirectory());
210 
211 // With dereference: true, should copy the target directory
212 const pathH = new URL('file:///tmp/h');
213 cpSync(pathD, pathH, { recursive: true, dereference: true });
214 ok(lstatSync(pathH).isDirectory());
215 ok(existsSync(new URL('file:///tmp/h/e')));
216 },
217};
218 
219// Copy symlink to existing file
220export const copySymlinkToExistingFile = {
221 test() {
222 setupPaths();
223 const pathG = new URL('file:///tmp/g');
224 writeFileSync(pathG, 'original');
225 
226 // Should replace the file with symlink
227 cpSync(pathC, pathG);
228 ok(lstatSync(pathG).isSymbolicLink());
229 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
230 },
231};
232 
233// Copy symlink to existing directory
234export const copySymlinkToExistingDirectory = {
235 test() {
236 setupPaths();
237 throws(() => cpSync(pathC, pathE), {
238 code: 'ENOTDIR',
239 });
240 },
241};
242 
243// Copy symlink to existing symlink to directory
244export const copySymlinkToExistingSymlinkToDirectory = {
245 test() {
246 setupPaths();
247 const pathG = new URL('file:///tmp/g');
248 const pathH = new URL('file:///tmp/h');
249 mkdirSync(pathH);
250 symlinkSync(pathH, pathG);
251 
252 // Should replace the symlink
253 cpSync(pathD, pathG);
254 ok(lstatSync(pathG).isSymbolicLink());
255 ok(statSync(pathG).isDirectory());
256 },
257};
258 
259// Copy symlink operations with various option combinations
260export const copySymlinkWithDereferenceOptions = {
261 test() {
262 setupPaths();
263 const pathG = new URL('file:///tmp/g');
264 const pathH = new URL('file:///tmp/h');
265 const pathI = new URL('file:///tmp/i');
266 
267 // Test with force: false, errorOnExist: true
268 symlinkSync(pathA, pathG);
269 throws(() => cpSync(pathC, pathG, { force: false, errorOnExist: true }), {
270 code: 'ERR_FS_CP_EEXIST',
271 });
272 
273 // Test with force: false, errorOnExist: false
274 cpSync(pathC, pathG, { force: false, errorOnExist: false });
275 ok(lstatSync(pathG).isSymbolicLink());
276 
277 // Test with dereference and errorOnExist combinations
278 writeFileSync(pathH, 'existing');
279 throws(
280 () =>
281 cpSync(pathC, pathH, {
282 dereference: true,
283 force: false,
284 errorOnExist: true,
285 }),
286 {
287 code: 'ERR_FS_CP_EEXIST',
288 }
289 );
290 
291 // Test copying to non-existent path
292 cpSync(pathC, pathI);
293 ok(lstatSync(pathI).isSymbolicLink());
294 deepStrictEqual(readFileSync(pathI, 'utf8'), 'foo');
295 },
296};
297 
298// Option validation tests
299export const optionValidationTests = {
300 test() {
301 setupPaths();
302 const pathG = new URL('file:///tmp/g');
303 
304 // Test invalid option types
305 throws(() => cpSync(pathA, pathG, { dereference: 'invalid' }), {
306 code: 'ERR_INVALID_ARG_TYPE',
307 });
308 
309 throws(() => cpSync(pathA, pathG, { errorOnExist: 'invalid' }), {
310 code: 'ERR_INVALID_ARG_TYPE',
311 });
312 
313 throws(() => cpSync(pathA, pathG, { force: 'invalid' }), {
314 code: 'ERR_INVALID_ARG_TYPE',
315 });
316 
317 throws(() => cpSync(pathA, pathG, { recursive: 'invalid' }), {
318 code: 'ERR_INVALID_ARG_TYPE',
319 });
320 
321 throws(() => cpSync(pathA, pathG, { preserveTimestamps: 'invalid' }), {
322 code: 'ERR_INVALID_ARG_TYPE',
323 });
324 
325 throws(() => cpSync(pathA, pathG, { verbatimSymlinks: 'invalid' }), {
326 code: 'ERR_INVALID_ARG_TYPE',
327 });
328 
329 // Test invalid filter type
330 throws(() => cpSync(pathA, pathG, { filter: 'not-a-function' }), {
331 code: 'ERR_INVALID_ARG_TYPE',
332 });
333 
334 // Test filter option throws unsupported operation
335 throws(() => cpSync(pathA, pathG, { filter: () => true }), {
336 code: 'ERR_UNSUPPORTED_OPERATION',
337 });
338 
339 // Test COPYFILE_FICLONE_FORCE mode
340 throws(() => cpSync(pathA, pathG, { mode: 4 }), {
341 // COPYFILE_FICLONE_FORCE = 4
342 code: 'ERR_INVALID_ARG_VALUE',
343 });
344 },
345};
346 
347// Recursive option tests
348export const recursiveOptionTests = {
349 test() {
350 setupPaths();
351 const pathG = new URL('file:///tmp/g');
352 const pathH = new URL('file:///tmp/h');
353 
354 // Test copying directory without recursive option should fail
355 throws(() => cpSync(pathB, pathG), {
356 code: 'ERR_FS_EISDIR',
357 });
358 
359 // Test with recursive: false explicitly
360 throws(() => cpSync(pathB, pathG, { recursive: false }), {
361 code: 'ERR_FS_EISDIR',
362 });
363 
364 // Test copying directory with recursive: true should work
365 cpSync(pathB, pathG, { recursive: true });
366 ok(existsSync(pathG));
367 ok(lstatSync(pathG).isDirectory());
368 ok(existsSync(new URL('file:///tmp/g/e')));
369 
370 // Test deep directory structure
371 mkdirSync(new URL('file:///tmp/deep'));
372 mkdirSync(new URL('file:///tmp/deep/nested'));
373 mkdirSync(new URL('file:///tmp/deep/nested/very'));
374 writeFileSync(new URL('file:///tmp/deep/nested/very/deep.txt'), 'content');
375 
376 cpSync(new URL('file:///tmp/deep'), pathH, { recursive: true });
377 ok(existsSync(new URL('file:///tmp/h/nested/very/deep.txt')));
378 deepStrictEqual(
379 readFileSync(new URL('file:///tmp/h/nested/very/deep.txt'), 'utf8'),
380 'content'
381 );
382 },
383};
384 
385// PreserveTimestamps option tests
386export const preserveTimestampsTests = {
387 test() {
388 setupPaths();
389 const pathG = new URL('file:///tmp/g');
390 const pathH = new URL('file:///tmp/h');
391 
392 // Get original timestamps
393 const originalStat = lstatSync(pathA);
394 const originalMtime = originalStat.mtime;
395 
396 // Copy without preserveTimestamps (default behavior)
397 cpSync(pathA, pathG);
398 const _copiedStat = lstatSync(pathG);
399 
400 // Copy with preserveTimestamps: true
401 cpSync(pathA, pathH, { preserveTimestamps: true });
402 const preservedStat = lstatSync(pathH);
403 
404 // The preserved timestamps should match the original
405 // Note: This test depends on the implementation actually preserving timestamps
406 // Since the implementation is noted as having this option, we test it
407 ok(
408 Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000
409 );
410 },
411};
412 
413// Error handling edge cases
414export const errorHandlingTests = {
415 test() {
416 setupPaths();
417 const pathG = new URL('file:///tmp/g');
418 const nonExistent = new URL('file:///tmp/nonexistent');
419 
420 // Test copying from non-existent source
421 throws(() => cpSync(nonExistent, pathG), {
422 code: 'ENOENT',
423 });
424 },
425};
426 
427// Symlink edge cases
428export const symlinkEdgeCases = {
429 test() {
430 setupPaths();
431 const pathG = new URL('file:///tmp/g');
432 const pathH = new URL('file:///tmp/h');
433 const pathI = new URL('file:///tmp/i');
434 const pathJ = new URL('file:///tmp/j');
435 
436 // Create a broken symlink
437 const brokenTarget = new URL('file:///tmp/broken-target');
438 symlinkSync(brokenTarget, pathG);
439 
440 // Test copying broken symlink without dereference
441 cpSync(pathG, pathH);
442 ok(lstatSync(pathH).isSymbolicLink());
443 
444 // Test copying broken symlink with dereference should fail
445 throws(() => cpSync(pathG, pathI, { dereference: true }), {
446 code: 'ENOENT',
447 });
448 
449 // Create symlink chain: J -> I -> A
450 symlinkSync(pathA, pathI);
451 symlinkSync(pathI, pathJ);
452 
453 // Test copying symlink chain
454 const pathK = new URL('file:///tmp/k');
455 cpSync(pathJ, pathK);
456 ok(lstatSync(pathK).isSymbolicLink());
457 deepStrictEqual(readFileSync(pathK, 'utf8'), 'foo');
458 },
459};
460 
461// Combined options tests
462export const combinedOptionsTests = {
463 test() {
464 setupPaths();
465 const pathG = new URL('file:///tmp/g');
466 
467 // Test multiple options together
468 cpSync(pathB, pathG, {
469 recursive: true,
470 dereference: true,
471 force: true,
472 preserveTimestamps: true,
473 errorOnExist: false,
474 });
475 
476 ok(existsSync(pathG));
477 ok(lstatSync(pathG).isDirectory());
478 },
479};
480 
481// Path type tests
482export const pathTypeTests = {
483 test() {
484 setupPaths();
485 
486 // Test with Buffer paths
487 const pathG = new URL('file:///tmp/g');
488 const bufferSrc = Buffer.from('/tmp/a');
489 const bufferDest = Buffer.from('/tmp/g');
490 
491 // Note: The implementation uses normalizePath which should handle Buffer inputs
492 // This tests the path normalization behavior
493 cpSync(bufferSrc, bufferDest);
494 ok(existsSync(pathG));
495 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
496 },
497};
498 
499// ===== CALLBACK-BASED API TESTS =====
500 
501// Simple file copy - callback version
502export const simpleFileCopyCallback = {
503 async test() {
504 setupPaths();
505 ok(existsSync(pathA));
506 deepStrictEqual(readFileSync(pathA, 'utf8'), 'foo');
507 ok(!existsSync(pathF));
508 
509 await new Promise((resolve, reject) => {
510 cp(pathA, pathF, (err) => {
511 if (err) reject(err);
512 else resolve();
513 });
514 });
515 
516 ok(existsSync(pathF));
517 deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');
518 writeFileSync(pathA, 'baz');
519 deepStrictEqual(readFileSync(pathA, 'utf8'), 'baz');
520 deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');
521 
522 // Test with errorOnExist: true
523 await new Promise((resolve, reject) => {
524 cp(pathA, pathF, { errorOnExist: true }, (err) => {
525 if (err) reject(err);
526 else resolve();
527 });
528 });
529 
530 // Test error case: force: false, errorOnExist: true
531 await new Promise((resolve) => {
532 cp(pathA, pathF, { force: false, errorOnExist: true }, (err) => {
533 ok(err);
534 strictEqual(err.code, 'ERR_FS_CP_EEXIST');
535 resolve();
536 });
537 });
538 
539 // Test error case: copying file to directory
540 await new Promise((resolve) => {
541 cp(pathA, pathB, (err) => {
542 ok(err);
543 strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');
544 resolve();
545 });
546 });
547 
548 // Test copying to symlink to directory
549 await new Promise((resolve) => {
550 cp(pathA, pathD, { dereference: true }, (err) => {
551 ok(err);
552 strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');
553 resolve();
554 });
555 });
556 
557 // Test copying to symlink without dereference
558 await new Promise((resolve, reject) => {
559 cp(pathA, pathD, (err) => {
560 if (err) reject(err);
561 else {
562 ok(lstatSync(pathD).isFile());
563 resolve();
564 }
565 });
566 });
567 },
568};
569 
570// Copy file to non-existent directory - callback version
571export const copyFileToNonExistentDirectoryCallback = {
572 async test() {
573 setupPaths();
574 const pathG = new URL('file:///tmp/nonexistent');
575 const pathH = new URL('file:///tmp/nonexistent/g');
576 ok(!existsSync(pathG));
577 ok(!existsSync(pathH));
578 
579 await new Promise((resolve, reject) => {
580 cp(pathA, pathH, (err) => {
581 if (err) reject(err);
582 else resolve();
583 });
584 });
585 
586 ok(existsSync(pathG));
587 ok(existsSync(pathH));
588 },
589};
590 
591// Copy file to existing file with force - callback version
592export const copyFileToExistingFileWithForceCallback = {
593 async test() {
594 setupPaths();
595 const pathG = new URL('file:///tmp/g');
596 writeFileSync(pathG, 'original');
597 
598 await new Promise((resolve, reject) => {
599 cp(pathA, pathG, { force: true }, (err) => {
600 if (err) reject(err);
601 else resolve();
602 });
603 });
604 
605 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
606 },
607};
608 
609// Copy file to existing file without force - callback version
610export const copyFileToExistingFileWithoutForceCallback = {
611 async test() {
612 setupPaths();
613 const pathG = new URL('file:///tmp/g');
614 writeFileSync(pathG, 'original');
615 
616 await new Promise((resolve, reject) => {
617 cp(pathA, pathG, { force: false }, (err) => {
618 if (err) reject(err);
619 else resolve();
620 });
621 });
622 
623 deepStrictEqual(readFileSync(pathG, 'utf8'), 'original');
624 },
625};
626 
627// Copy file to existing directory - callback version
628export const copyFileToExistingDirectoryCallback = {
629 async test() {
630 setupPaths();
631 
632 await new Promise((resolve) => {
633 cp(pathA, pathB, (err) => {
634 ok(err);
635 strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');
636 resolve();
637 });
638 });
639 },
640};
641 
642// Copy file to existing symlink to directory - callback version
643export const copyFileToExistingSymlinkToDirectoryCallback = {
644 async test() {
645 setupPaths();
646 
647 // With dereference: true, should throw EISDIR
648 await new Promise((resolve) => {
649 cp(pathA, pathD, { dereference: true }, (err) => {
650 ok(err);
651 strictEqual(err.code, 'ERR_FS_CP_NON_DIR_TO_DIR');
652 resolve();
653 });
654 });
655 
656 // Without dereference (default), should replace the symlink
657 await new Promise((resolve, reject) => {
658 cp(pathA, pathD, (err) => {
659 if (err) reject(err);
660 else {
661 ok(lstatSync(pathD).isFile());
662 deepStrictEqual(readFileSync(pathD, 'utf8'), 'foo');
663 resolve();
664 }
665 });
666 });
667 },
668};
669 
670// Copy directory to non-existent directory - callback version
671export const copyDirectoryToNonExistentDirectoryCallback = {
672 async test() {
673 setupPaths();
674 const pathG = new URL('file:///tmp/g');
675 
676 await new Promise((resolve, reject) => {
677 cp(pathB, pathG, { recursive: true }, (err) => {
678 if (err) reject(err);
679 else resolve();
680 });
681 });
682 
683 ok(existsSync(pathG));
684 ok(lstatSync(pathG).isDirectory());
685 ok(existsSync(new URL('file:///tmp/g/e')));
686 deepStrictEqual(readFileSync(new URL('file:///tmp/g/e'), 'utf8'), 'bar');
687 },
688};
689 
690// Copy directory to existing directory - callback version
691export const copyDirectoryToExistingDirectoryCallback = {
692 async test() {
693 setupPaths();
694 
695 await new Promise((resolve, reject) => {
696 cp(pathB, pathE, { recursive: true }, (err) => {
697 if (err) reject(err);
698 else resolve();
699 });
700 });
701 
702 ok(existsSync(new URL('file:///tmp/e/e')));
703 deepStrictEqual(readFileSync(new URL('file:///tmp/e/e'), 'utf8'), 'bar');
704 },
705};
706 
707// Copy directory to existing file - callback version
708export const copyDirectoryToExistingFileCallback = {
709 async test() {
710 setupPaths();
711 
712 await new Promise((resolve) => {
713 cp(pathB, pathA, { recursive: true }, (err) => {
714 ok(err);
715 strictEqual(err.code, 'ERR_FS_CP_DIR_TO_NON_DIR');
716 resolve();
717 });
718 });
719 },
720};
721 
722// Copy directory to existing symlink to file - callback version
723export const copyDirectoryToExistingSymlinkToFileCallback = {
724 async test() {
725 setupPaths();
726 
727 await new Promise((resolve) => {
728 cp(pathB, pathC, { recursive: true, dereference: true }, (err) => {
729 ok(err);
730 strictEqual(err.code, 'ERR_FS_CP_DIR_TO_NON_DIR');
731 resolve();
732 });
733 });
734 },
735};
736 
737// Copy symlink to file - callback version
738export const copySymlinkToFileCallback = {
739 async test() {
740 setupPaths();
741 const pathG = new URL('file:///tmp/g');
742 
743 // With dereference: false (default), should copy the symlink
744 await new Promise((resolve, reject) => {
745 cp(pathC, pathG, (err) => {
746 if (err) reject(err);
747 else resolve();
748 });
749 });
750 
751 ok(lstatSync(pathG).isSymbolicLink());
752 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
753 
754 // With dereference: true, should copy the target file
755 const pathH = new URL('file:///tmp/h');
756 await new Promise((resolve, reject) => {
757 cp(pathC, pathH, { dereference: true }, (err) => {
758 if (err) reject(err);
759 else resolve();
760 });
761 });
762 
763 ok(lstatSync(pathH).isFile());
764 deepStrictEqual(readFileSync(pathH, 'utf8'), 'foo');
765 },
766};
767 
768// Copy symlink to directory - callback version
769export const copySymlinkToDirectoryCallback = {
770 async test() {
771 setupPaths();
772 const pathG = new URL('file:///tmp/g');
773 
774 // With dereference: false (default), should copy the symlink
775 await new Promise((resolve, reject) => {
776 cp(pathD, pathG, (err) => {
777 if (err) reject(err);
778 else resolve();
779 });
780 });
781 
782 ok(lstatSync(pathG).isSymbolicLink());
783 ok(statSync(pathG).isDirectory());
784 
785 // With dereference: true, should copy the target directory
786 const pathH = new URL('file:///tmp/h');
787 await new Promise((resolve, reject) => {
788 cp(pathD, pathH, { recursive: true, dereference: true }, (err) => {
789 if (err) reject(err);
790 else resolve();
791 });
792 });
793 
794 ok(lstatSync(pathH).isDirectory());
795 ok(existsSync(new URL('file:///tmp/h/e')));
796 },
797};
798 
799// Copy symlink to existing file - callback version
800export const copySymlinkToExistingFileCallback = {
801 async test() {
802 setupPaths();
803 const pathG = new URL('file:///tmp/g');
804 writeFileSync(pathG, 'original');
805 
806 // Should replace the file with symlink
807 await new Promise((resolve, reject) => {
808 cp(pathC, pathG, (err) => {
809 if (err) reject(err);
810 else resolve();
811 });
812 });
813 
814 ok(lstatSync(pathG).isSymbolicLink());
815 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
816 },
817};
818 
819// Copy symlink to existing directory - callback version
820export const copySymlinkToExistingDirectoryCallback = {
821 async test() {
822 setupPaths();
823 
824 await new Promise((resolve) => {
825 cp(pathC, pathE, (err) => {
826 ok(err);
827 strictEqual(err.code, 'ENOTDIR');
828 resolve();
829 });
830 });
831 },
832};
833 
834// Copy symlink to existing symlink to directory - callback version
835export const copySymlinkToExistingSymlinkToDirectoryCallback = {
836 async test() {
837 setupPaths();
838 const pathG = new URL('file:///tmp/g');
839 const pathH = new URL('file:///tmp/h');
840 mkdirSync(pathH);
841 symlinkSync(pathH, pathG);
842 
843 // Should replace the symlink
844 await new Promise((resolve, reject) => {
845 cp(pathD, pathG, (err) => {
846 if (err) reject(err);
847 else resolve();
848 });
849 });
850 
851 ok(lstatSync(pathG).isSymbolicLink());
852 ok(statSync(pathG).isDirectory());
853 },
854};
855 
856// Copy symlink operations with various option combinations - callback version
857export const copySymlinkWithDereferenceOptionsCallback = {
858 async test() {
859 setupPaths();
860 const pathG = new URL('file:///tmp/g');
861 const pathH = new URL('file:///tmp/h');
862 const pathI = new URL('file:///tmp/i');
863 
864 // Test with force: false, errorOnExist: true
865 symlinkSync(pathA, pathG);
866 await new Promise((resolve) => {
867 cp(pathC, pathG, { force: false, errorOnExist: true }, (err) => {
868 ok(err);
869 strictEqual(err.code, 'ERR_FS_CP_EEXIST');
870 resolve();
871 });
872 });
873 
874 // Test with force: false, errorOnExist: false
875 await new Promise((resolve, reject) => {
876 cp(pathC, pathG, { force: false, errorOnExist: false }, (err) => {
877 if (err) reject(err);
878 else resolve();
879 });
880 });
881 ok(lstatSync(pathG).isSymbolicLink());
882 
883 // Test with dereference and errorOnExist combinations
884 writeFileSync(pathH, 'existing');
885 await new Promise((resolve) => {
886 cp(
887 pathC,
888 pathH,
889 { dereference: true, force: false, errorOnExist: true },
890 (err) => {
891 ok(err);
892 strictEqual(err.code, 'ERR_FS_CP_EEXIST');
893 resolve();
894 }
895 );
896 });
897 
898 // Test copying to non-existent path
899 await new Promise((resolve, reject) => {
900 cp(pathC, pathI, (err) => {
901 if (err) reject(err);
902 else resolve();
903 });
904 });
905 ok(lstatSync(pathI).isSymbolicLink());
906 deepStrictEqual(readFileSync(pathI, 'utf8'), 'foo');
907 },
908};
909 
910// Option validation tests - callback version
911export const optionValidationTestsCallback = {
912 test() {
913 setupPaths();
914 const pathG = new URL('file:///tmp/g');
915 
916 // Test invalid option types - these throw synchronously
917 throws(() => cp(pathA, pathG, { dereference: 'invalid' }, () => {}), {
918 code: 'ERR_INVALID_ARG_TYPE',
919 });
920 
921 throws(() => cp(pathA, pathG, { errorOnExist: 'invalid' }, () => {}), {
922 code: 'ERR_INVALID_ARG_TYPE',
923 });
924 
925 throws(() => cp(pathA, pathG, { force: 'invalid' }, () => {}), {
926 code: 'ERR_INVALID_ARG_TYPE',
927 });
928 
929 throws(() => cp(pathA, pathG, { recursive: 'invalid' }, () => {}), {
930 code: 'ERR_INVALID_ARG_TYPE',
931 });
932 
933 throws(
934 () => cp(pathA, pathG, { preserveTimestamps: 'invalid' }, () => {}),
935 {
936 code: 'ERR_INVALID_ARG_TYPE',
937 }
938 );
939 
940 throws(() => cp(pathA, pathG, { verbatimSymlinks: 'invalid' }, () => {}), {
941 code: 'ERR_INVALID_ARG_TYPE',
942 });
943 
944 // Test invalid filter type - throws synchronously
945 throws(() => cp(pathA, pathG, { filter: 'not-a-function' }, () => {}), {
946 code: 'ERR_INVALID_ARG_TYPE',
947 });
948 
949 // Test filter option throws unsupported operation - throws synchronously
950 throws(() => cp(pathA, pathG, { filter: () => true }, () => {}), {
951 code: 'ERR_UNSUPPORTED_OPERATION',
952 });
953 
954 // Test COPYFILE_FICLONE_FORCE mode - throws synchronously
955 throws(() => cp(pathA, pathG, { mode: 4 }, () => {}), {
956 code: 'ERR_INVALID_ARG_VALUE',
957 });
958 },
959};
960 
961// Recursive option tests - callback version
962export const recursiveOptionTestsCallback = {
963 async test() {
964 setupPaths();
965 const pathG = new URL('file:///tmp/g');
966 const pathH = new URL('file:///tmp/h');
967 
968 // Test copying directory without recursive option should fail
969 await new Promise((resolve) => {
970 cp(pathB, pathG, (err) => {
971 ok(err);
972 strictEqual(err.code, 'ERR_FS_EISDIR');
973 resolve();
974 });
975 });
976 
977 // Test with recursive: false explicitly
978 await new Promise((resolve) => {
979 cp(pathB, pathG, { recursive: false }, (err) => {
980 ok(err);
981 strictEqual(err.code, 'ERR_FS_EISDIR');
982 resolve();
983 });
984 });
985 
986 // Test copying directory with recursive: true should work
987 await new Promise((resolve, reject) => {
988 cp(pathB, pathG, { recursive: true }, (err) => {
989 if (err) reject(err);
990 else resolve();
991 });
992 });
993 
994 ok(existsSync(pathG));
995 ok(lstatSync(pathG).isDirectory());
996 ok(existsSync(new URL('file:///tmp/g/e')));
997 
998 // Test deep directory structure
999 mkdirSync(new URL('file:///tmp/deep'));
1000 mkdirSync(new URL('file:///tmp/deep/nested'));
1001 mkdirSync(new URL('file:///tmp/deep/nested/very'));
1002 writeFileSync(new URL('file:///tmp/deep/nested/very/deep.txt'), 'content');
1003 
1004 await new Promise((resolve, reject) => {
1005 cp(new URL('file:///tmp/deep'), pathH, { recursive: true }, (err) => {
1006 if (err) reject(err);
1007 else resolve();
1008 });
1009 });
1010 
1011 ok(existsSync(new URL('file:///tmp/h/nested/very/deep.txt')));
1012 deepStrictEqual(
1013 readFileSync(new URL('file:///tmp/h/nested/very/deep.txt'), 'utf8'),
1014 'content'
1015 );
1016 },
1017};
1018 
1019// PreserveTimestamps option tests - callback version
1020export const preserveTimestampsTestsCallback = {
1021 async test() {
1022 setupPaths();
1023 const pathG = new URL('file:///tmp/g');
1024 const pathH = new URL('file:///tmp/h');
1025 
1026 // Get original timestamps
1027 const originalStat = lstatSync(pathA);
1028 const originalMtime = originalStat.mtime;
1029 
1030 // Copy without preserveTimestamps (default behavior)
1031 await new Promise((resolve, reject) => {
1032 cp(pathA, pathG, (err) => {
1033 if (err) reject(err);
1034 else resolve();
1035 });
1036 });
1037 const _copiedStat = lstatSync(pathG);
1038 
1039 // Copy with preserveTimestamps: true
1040 await new Promise((resolve, reject) => {
1041 cp(pathA, pathH, { preserveTimestamps: true }, (err) => {
1042 if (err) reject(err);
1043 else resolve();
1044 });
1045 });
1046 const preservedStat = lstatSync(pathH);
1047 
1048 ok(
1049 Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000
1050 );
1051 },
1052};
1053 
1054// Error handling edge cases - callback version
1055export const errorHandlingTestsCallback = {
1056 async test() {
1057 setupPaths();
1058 const pathG = new URL('file:///tmp/g');
1059 const nonExistent = new URL('file:///tmp/nonexistent');
1060 
1061 // Test copying from non-existent source
1062 await new Promise((resolve) => {
1063 cp(nonExistent, pathG, (err) => {
1064 ok(err);
1065 strictEqual(err.code, 'ENOENT');
1066 resolve();
1067 });
1068 });
1069 },
1070};
1071 
1072// Symlink edge cases - callback version
1073export const symlinkEdgeCasesCallback = {
1074 async test() {
1075 setupPaths();
1076 const pathG = new URL('file:///tmp/g');
1077 const pathH = new URL('file:///tmp/h');
1078 const pathI = new URL('file:///tmp/i');
1079 const pathJ = new URL('file:///tmp/j');
1080 
1081 // Create a broken symlink
1082 const brokenTarget = new URL('file:///tmp/broken-target');
1083 symlinkSync(brokenTarget, pathG);
1084 
1085 // Test copying broken symlink without dereference
1086 await new Promise((resolve, reject) => {
1087 cp(pathG, pathH, (err) => {
1088 if (err) reject(err);
1089 else resolve();
1090 });
1091 });
1092 ok(lstatSync(pathH).isSymbolicLink());
1093 
1094 // Test copying broken symlink with dereference should fail
1095 await new Promise((resolve) => {
1096 cp(pathG, pathI, { dereference: true }, (err) => {
1097 ok(err);
1098 strictEqual(err.code, 'ENOENT');
1099 resolve();
1100 });
1101 });
1102 
1103 // Create symlink chain: J -> I -> A
1104 symlinkSync(pathA, pathI);
1105 symlinkSync(pathI, pathJ);
1106 
1107 // Test copying symlink chain
1108 const pathK = new URL('file:///tmp/k');
1109 await new Promise((resolve, reject) => {
1110 cp(pathJ, pathK, (err) => {
1111 if (err) reject(err);
1112 else resolve();
1113 });
1114 });
1115 
1116 ok(lstatSync(pathK).isSymbolicLink());
1117 deepStrictEqual(readFileSync(pathK, 'utf8'), 'foo');
1118 },
1119};
1120 
1121// Combined options tests - callback version
1122export const combinedOptionsTestsCallback = {
1123 async test() {
1124 setupPaths();
1125 const pathG = new URL('file:///tmp/g');
1126 
1127 // Test multiple options together
1128 await new Promise((resolve, reject) => {
1129 cp(
1130 pathB,
1131 pathG,
1132 {
1133 recursive: true,
1134 dereference: true,
1135 force: true,
1136 preserveTimestamps: true,
1137 errorOnExist: false,
1138 },
1139 (err) => {
1140 if (err) reject(err);
1141 else resolve();
1142 }
1143 );
1144 });
1145 
1146 ok(existsSync(pathG));
1147 ok(lstatSync(pathG).isDirectory());
1148 },
1149};
1150 
1151// Path type tests - callback version
1152export const pathTypeTestsCallback = {
1153 async test() {
1154 setupPaths();
1155 
1156 // Test with Buffer paths
1157 const pathG = new URL('file:///tmp/g');
1158 const bufferSrc = Buffer.from('/tmp/a');
1159 const bufferDest = Buffer.from('/tmp/g');
1160 
1161 await new Promise((resolve, reject) => {
1162 cp(bufferSrc, bufferDest, (err) => {
1163 if (err) reject(err);
1164 else resolve();
1165 });
1166 });
1167 
1168 ok(existsSync(pathG));
1169 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
1170 },
1171};
1172 
1173// ===== PROMISES-BASED API TESTS =====
1174 
1175// Simple file copy - promises version
1176export const simpleFileCopyPromises = {
1177 async test() {
1178 setupPaths();
1179 ok(existsSync(pathA));
1180 deepStrictEqual(readFileSync(pathA, 'utf8'), 'foo');
1181 ok(!existsSync(pathF));
1182 
1183 await fsPromises.cp(pathA, pathF);
1184 ok(existsSync(pathF));
1185 deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');
1186 writeFileSync(pathA, 'baz');
1187 deepStrictEqual(readFileSync(pathA, 'utf8'), 'baz');
1188 deepStrictEqual(readFileSync(pathF, 'utf8'), 'foo');
1189 
1190 // Test with errorOnExist: true
1191 await fsPromises.cp(pathA, pathF, { errorOnExist: true });
1192 
1193 // Test error case: force: false, errorOnExist: true
1194 await rejects(
1195 async () => {
1196 await fsPromises.cp(pathA, pathF, { force: false, errorOnExist: true });
1197 },
1198 { code: 'ERR_FS_CP_EEXIST' }
1199 );
1200 
1201 // Test error case: copying file to directory
1202 await rejects(
1203 async () => {
1204 await fsPromises.cp(pathA, pathB);
1205 },
1206 { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }
1207 );
1208 
1209 // Test copying to symlink to directory
1210 await rejects(
1211 async () => {
1212 await fsPromises.cp(pathA, pathD, { dereference: true });
1213 },
1214 { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }
1215 );
1216 
1217 // Test copying to symlink without dereference
1218 await fsPromises.cp(pathA, pathD);
1219 ok(lstatSync(pathD).isFile());
1220 },
1221};
1222 
1223// Copy file to non-existent directory - promises version
1224export const copyFileToNonExistentDirectoryPromises = {
1225 async test() {
1226 setupPaths();
1227 const pathG = new URL('file:///tmp/nonexistent');
1228 const pathH = new URL('file:///tmp/nonexistent/g');
1229 ok(!existsSync(pathG));
1230 ok(!existsSync(pathH));
1231 
1232 await fsPromises.cp(pathA, pathH);
1233 ok(existsSync(pathG));
1234 ok(existsSync(pathH));
1235 },
1236};
1237 
1238// Copy file to existing file with force - promises version
1239export const copyFileToExistingFileWithForcePromises = {
1240 async test() {
1241 setupPaths();
1242 const pathG = new URL('file:///tmp/g');
1243 writeFileSync(pathG, 'original');
1244 
1245 await fsPromises.cp(pathA, pathG, { force: true });
1246 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
1247 },
1248};
1249 
1250// Copy file to existing file without force - promises version
1251export const copyFileToExistingFileWithoutForcePromises = {
1252 async test() {
1253 setupPaths();
1254 const pathG = new URL('file:///tmp/g');
1255 writeFileSync(pathG, 'original');
1256 
1257 await fsPromises.cp(pathA, pathG, { force: false });
1258 deepStrictEqual(readFileSync(pathG, 'utf8'), 'original');
1259 },
1260};
1261 
1262// Copy file to existing directory - promises version
1263export const copyFileToExistingDirectoryPromises = {
1264 async test() {
1265 setupPaths();
1266 
1267 await rejects(
1268 async () => {
1269 await fsPromises.cp(pathA, pathB);
1270 },
1271 { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }
1272 );
1273 },
1274};
1275 
1276// Copy file to existing symlink to directory - promises version
1277export const copyFileToExistingSymlinkToDirectoryPromises = {
1278 async test() {
1279 setupPaths();
1280 
1281 // With dereference: true, should throw EISDIR
1282 await rejects(
1283 async () => {
1284 await fsPromises.cp(pathA, pathD, { dereference: true });
1285 },
1286 { code: 'ERR_FS_CP_NON_DIR_TO_DIR' }
1287 );
1288 
1289 // Without dereference (default), should replace the symlink
1290 await fsPromises.cp(pathA, pathD);
1291 ok(lstatSync(pathD).isFile());
1292 deepStrictEqual(readFileSync(pathD, 'utf8'), 'foo');
1293 },
1294};
1295 
1296// Copy directory to non-existent directory - promises version
1297export const copyDirectoryToNonExistentDirectoryPromises = {
1298 async test() {
1299 setupPaths();
1300 const pathG = new URL('file:///tmp/g');
1301 
1302 await fsPromises.cp(pathB, pathG, { recursive: true });
1303 ok(existsSync(pathG));
1304 ok(lstatSync(pathG).isDirectory());
1305 ok(existsSync(new URL('file:///tmp/g/e')));
1306 deepStrictEqual(readFileSync(new URL('file:///tmp/g/e'), 'utf8'), 'bar');
1307 },
1308};
1309 
1310// Copy directory to existing directory - promises version
1311export const copyDirectoryToExistingDirectoryPromises = {
1312 async test() {
1313 setupPaths();
1314 
1315 await fsPromises.cp(pathB, pathE, { recursive: true });
1316 ok(existsSync(new URL('file:///tmp/e/e')));
1317 deepStrictEqual(readFileSync(new URL('file:///tmp/e/e'), 'utf8'), 'bar');
1318 },
1319};
1320 
1321// Copy directory to existing file - promises version
1322export const copyDirectoryToExistingFilePromises = {
1323 async test() {
1324 setupPaths();
1325 
1326 await rejects(
1327 async () => {
1328 await fsPromises.cp(pathB, pathA, { recursive: true });
1329 },
1330 { code: 'ERR_FS_CP_DIR_TO_NON_DIR' }
1331 );
1332 },
1333};
1334 
1335// Copy directory to existing symlink to file - promises version
1336export const copyDirectoryToExistingSymlinkToFilePromises = {
1337 async test() {
1338 setupPaths();
1339 
1340 await rejects(
1341 async () => {
1342 await fsPromises.cp(pathB, pathC, {
1343 recursive: true,
1344 dereference: true,
1345 });
1346 },
1347 { code: 'ERR_FS_CP_DIR_TO_NON_DIR' }
1348 );
1349 },
1350};
1351 
1352// Copy symlink to file - promises version
1353export const copySymlinkToFilePromises = {
1354 async test() {
1355 setupPaths();
1356 const pathG = new URL('file:///tmp/g');
1357 
1358 // With dereference: false (default), should copy the symlink
1359 await fsPromises.cp(pathC, pathG);
1360 ok(lstatSync(pathG).isSymbolicLink());
1361 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
1362 
1363 // With dereference: true, should copy the target file
1364 const pathH = new URL('file:///tmp/h');
1365 await fsPromises.cp(pathC, pathH, { dereference: true });
1366 ok(lstatSync(pathH).isFile());
1367 deepStrictEqual(readFileSync(pathH, 'utf8'), 'foo');
1368 },
1369};
1370 
1371// Copy symlink to directory - promises version
1372export const copySymlinkToDirectoryPromises = {
1373 async test() {
1374 setupPaths();
1375 const pathG = new URL('file:///tmp/g');
1376 
1377 // With dereference: false (default), should copy the symlink
1378 await fsPromises.cp(pathD, pathG);
1379 ok(lstatSync(pathG).isSymbolicLink());
1380 ok(statSync(pathG).isDirectory());
1381 
1382 // With dereference: true, should copy the target directory
1383 const pathH = new URL('file:///tmp/h');
1384 await fsPromises.cp(pathD, pathH, { recursive: true, dereference: true });
1385 ok(lstatSync(pathH).isDirectory());
1386 ok(existsSync(new URL('file:///tmp/h/e')));
1387 },
1388};
1389 
1390// Copy symlink to existing file - promises version
1391export const copySymlinkToExistingFilePromises = {
1392 async test() {
1393 setupPaths();
1394 const pathG = new URL('file:///tmp/g');
1395 writeFileSync(pathG, 'original');
1396 
1397 // Should replace the file with symlink
1398 await fsPromises.cp(pathC, pathG);
1399 ok(lstatSync(pathG).isSymbolicLink());
1400 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
1401 },
1402};
1403 
1404// Copy symlink to existing directory - promises version
1405export const copySymlinkToExistingDirectoryPromises = {
1406 async test() {
1407 setupPaths();
1408 
1409 await rejects(
1410 async () => {
1411 await fsPromises.cp(pathC, pathE);
1412 },
1413 { code: 'ENOTDIR' }
1414 );
1415 },
1416};
1417 
1418// Copy symlink to existing symlink to directory - promises version
1419export const copySymlinkToExistingSymlinkToDirectoryPromises = {
1420 async test() {
1421 setupPaths();
1422 const pathG = new URL('file:///tmp/g');
1423 const pathH = new URL('file:///tmp/h');
1424 mkdirSync(pathH);
1425 symlinkSync(pathH, pathG);
1426 
1427 // Should replace the symlink
1428 await fsPromises.cp(pathD, pathG);
1429 ok(lstatSync(pathG).isSymbolicLink());
1430 ok(statSync(pathG).isDirectory());
1431 },
1432};
1433 
1434// Copy symlink operations with various option combinations - promises version
1435export const copySymlinkWithDereferenceOptionsPromises = {
1436 async test() {
1437 setupPaths();
1438 const pathG = new URL('file:///tmp/g');
1439 const pathH = new URL('file:///tmp/h');
1440 const pathI = new URL('file:///tmp/i');
1441 
1442 // Test with force: false, errorOnExist: true
1443 symlinkSync(pathA, pathG);
1444 await rejects(
1445 async () => {
1446 await fsPromises.cp(pathC, pathG, { force: false, errorOnExist: true });
1447 },
1448 { code: 'ERR_FS_CP_EEXIST' }
1449 );
1450 
1451 // Test with force: false, errorOnExist: false
1452 await fsPromises.cp(pathC, pathG, { force: false, errorOnExist: false });
1453 ok(lstatSync(pathG).isSymbolicLink());
1454 
1455 // Test with dereference and errorOnExist combinations
1456 writeFileSync(pathH, 'existing');
1457 await rejects(
1458 async () => {
1459 await fsPromises.cp(pathC, pathH, {
1460 dereference: true,
1461 force: false,
1462 errorOnExist: true,
1463 });
1464 },
1465 { code: 'ERR_FS_CP_EEXIST' }
1466 );
1467 
1468 // Test copying to non-existent path
1469 await fsPromises.cp(pathC, pathI);
1470 ok(lstatSync(pathI).isSymbolicLink());
1471 deepStrictEqual(readFileSync(pathI, 'utf8'), 'foo');
1472 },
1473};
1474 
1475// Option validation tests - promises version
1476export const optionValidationTestsPromises = {
1477 async test() {
1478 setupPaths();
1479 const pathG = new URL('file:///tmp/g');
1480 
1481 // Test invalid option types
1482 await rejects(
1483 async () => {
1484 await fsPromises.cp(pathA, pathG, { dereference: 'invalid' });
1485 },
1486 { code: 'ERR_INVALID_ARG_TYPE' }
1487 );
1488 
1489 await rejects(
1490 async () => {
1491 await fsPromises.cp(pathA, pathG, { errorOnExist: 'invalid' });
1492 },
1493 { code: 'ERR_INVALID_ARG_TYPE' }
1494 );
1495 
1496 await rejects(
1497 async () => {
1498 await fsPromises.cp(pathA, pathG, { force: 'invalid' });
1499 },
1500 { code: 'ERR_INVALID_ARG_TYPE' }
1501 );
1502 
1503 await rejects(
1504 async () => {
1505 await fsPromises.cp(pathA, pathG, { recursive: 'invalid' });
1506 },
1507 { code: 'ERR_INVALID_ARG_TYPE' }
1508 );
1509 
1510 await rejects(
1511 async () => {
1512 await fsPromises.cp(pathA, pathG, { preserveTimestamps: 'invalid' });
1513 },
1514 { code: 'ERR_INVALID_ARG_TYPE' }
1515 );
1516 
1517 await rejects(
1518 async () => {
1519 await fsPromises.cp(pathA, pathG, { verbatimSymlinks: 'invalid' });
1520 },
1521 { code: 'ERR_INVALID_ARG_TYPE' }
1522 );
1523 
1524 // Test invalid filter type
1525 await rejects(
1526 async () => {
1527 await fsPromises.cp(pathA, pathG, { filter: 'not-a-function' });
1528 },
1529 { code: 'ERR_INVALID_ARG_TYPE' }
1530 );
1531 
1532 // Test filter option throws unsupported operation
1533 await rejects(
1534 async () => {
1535 await fsPromises.cp(pathA, pathG, { filter: () => true });
1536 },
1537 { code: 'ERR_UNSUPPORTED_OPERATION' }
1538 );
1539 
1540 // Test COPYFILE_FICLONE_FORCE mode
1541 await rejects(
1542 async () => {
1543 await fsPromises.cp(pathA, pathG, { mode: 4 });
1544 },
1545 { code: 'ERR_INVALID_ARG_VALUE' }
1546 );
1547 },
1548};
1549 
1550// Recursive option tests - promises version
1551export const recursiveOptionTestsPromises = {
1552 async test() {
1553 setupPaths();
1554 const pathG = new URL('file:///tmp/g');
1555 const pathH = new URL('file:///tmp/h');
1556 
1557 // Test copying directory without recursive option should fail
1558 await rejects(
1559 async () => {
1560 await fsPromises.cp(pathB, pathG);
1561 },
1562 { code: 'ERR_FS_EISDIR' }
1563 );
1564 
1565 // Test with recursive: false explicitly
1566 await rejects(
1567 async () => {
1568 await fsPromises.cp(pathB, pathG, { recursive: false });
1569 },
1570 { code: 'ERR_FS_EISDIR' }
1571 );
1572 
1573 // Test copying directory with recursive: true should work
1574 await fsPromises.cp(pathB, pathG, { recursive: true });
1575 ok(existsSync(pathG));
1576 ok(lstatSync(pathG).isDirectory());
1577 ok(existsSync(new URL('file:///tmp/g/e')));
1578 
1579 // Test deep directory structure
1580 mkdirSync(new URL('file:///tmp/deep'));
1581 mkdirSync(new URL('file:///tmp/deep/nested'));
1582 mkdirSync(new URL('file:///tmp/deep/nested/very'));
1583 writeFileSync(new URL('file:///tmp/deep/nested/very/deep.txt'), 'content');
1584 
1585 await fsPromises.cp(new URL('file:///tmp/deep'), pathH, {
1586 recursive: true,
1587 });
1588 ok(existsSync(new URL('file:///tmp/h/nested/very/deep.txt')));
1589 deepStrictEqual(
1590 readFileSync(new URL('file:///tmp/h/nested/very/deep.txt'), 'utf8'),
1591 'content'
1592 );
1593 },
1594};
1595 
1596// PreserveTimestamps option tests - promises version
1597export const preserveTimestampsTestsPromises = {
1598 async test() {
1599 setupPaths();
1600 const pathG = new URL('file:///tmp/g');
1601 const pathH = new URL('file:///tmp/h');
1602 
1603 // Get original timestamps
1604 const originalStat = lstatSync(pathA);
1605 const originalMtime = originalStat.mtime;
1606 
1607 // Copy without preserveTimestamps (default behavior)
1608 await fsPromises.cp(pathA, pathG);
1609 const _copiedStat = lstatSync(pathG);
1610 
1611 // Copy with preserveTimestamps: true
1612 await fsPromises.cp(pathA, pathH, { preserveTimestamps: true });
1613 const preservedStat = lstatSync(pathH);
1614 
1615 ok(
1616 Math.abs(preservedStat.mtime.getTime() - originalMtime.getTime()) < 1000
1617 );
1618 },
1619};
1620 
1621// Error handling edge cases - promises version
1622export const errorHandlingTestsPromises = {
1623 async test() {
1624 setupPaths();
1625 const pathG = new URL('file:///tmp/g');
1626 const nonExistent = new URL('file:///tmp/nonexistent');
1627 
1628 // Test copying from non-existent source
1629 await rejects(
1630 async () => {
1631 await fsPromises.cp(nonExistent, pathG);
1632 },
1633 { code: 'ENOENT' }
1634 );
1635 },
1636};
1637 
1638// Symlink edge cases - promises version
1639export const symlinkEdgeCasesPromises = {
1640 async test() {
1641 setupPaths();
1642 const pathG = new URL('file:///tmp/g');
1643 const pathH = new URL('file:///tmp/h');
1644 const pathI = new URL('file:///tmp/i');
1645 const pathJ = new URL('file:///tmp/j');
1646 
1647 // Create a broken symlink
1648 const brokenTarget = new URL('file:///tmp/broken-target');
1649 symlinkSync(brokenTarget, pathG);
1650 
1651 // Test copying broken symlink without dereference
1652 await fsPromises.cp(pathG, pathH);
1653 ok(lstatSync(pathH).isSymbolicLink());
1654 
1655 // Test copying broken symlink with dereference should fail
1656 await rejects(
1657 async () => {
1658 await fsPromises.cp(pathG, pathI, { dereference: true });
1659 },
1660 { code: 'ENOENT' }
1661 );
1662 
1663 // Create symlink chain: J -> I -> A
1664 symlinkSync(pathA, pathI);
1665 symlinkSync(pathI, pathJ);
1666 
1667 // Test copying symlink chain
1668 const pathK = new URL('file:///tmp/k');
1669 await fsPromises.cp(pathJ, pathK);
1670 
1671 ok(lstatSync(pathK).isSymbolicLink());
1672 deepStrictEqual(readFileSync(pathK, 'utf8'), 'foo');
1673 },
1674};
1675 
1676// Combined options tests - promises version
1677export const combinedOptionsTestsPromises = {
1678 async test() {
1679 setupPaths();
1680 const pathG = new URL('file:///tmp/g');
1681 
1682 // Test multiple options together
1683 await fsPromises.cp(pathB, pathG, {
1684 recursive: true,
1685 dereference: true,
1686 force: true,
1687 preserveTimestamps: true,
1688 errorOnExist: false,
1689 });
1690 
1691 ok(existsSync(pathG));
1692 ok(lstatSync(pathG).isDirectory());
1693 },
1694};
1695 
1696// Path type tests - promises version
1697export const pathTypeTestsPromises = {
1698 async test() {
1699 setupPaths();
1700 
1701 // Test with Buffer paths
1702 const pathG = new URL('file:///tmp/g');
1703 const bufferSrc = Buffer.from('/tmp/a');
1704 const bufferDest = Buffer.from('/tmp/g');
1705 
1706 await fsPromises.cp(bufferSrc, bufferDest);
1707 ok(existsSync(pathG));
1708 deepStrictEqual(readFileSync(pathG, 'utf8'), 'foo');
1709 },
1710};
1711 
1712// Deep directory structure tests - sync version
1713export const deepDirectoryStructureTestsSync = {
1714 test() {
1715 const sourceRoot = '/tmp/deep_source';
1716 const destRoot = '/tmp/deep_dest';
1717 
1718 // Create source directory structure
1719 mkdirSync(sourceRoot, { recursive: true });
1720 mkdirSync(`${sourceRoot}/level1`, { recursive: true });
1721 mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true });
1722 mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true });
1723 mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true });
1724 mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, {
1725 recursive: true,
1726 });
1727 
1728 // Create files in various levels
1729 writeFileSync(`${sourceRoot}/root_file.txt`, 'root content');
1730 writeFileSync(`${sourceRoot}/level1/level1_file.txt`, 'level1 content');
1731 writeFileSync(
1732 `${sourceRoot}/level1/level2/level2_file.txt`,
1733 'level2 content'
1734 );
1735 writeFileSync(
1736 `${sourceRoot}/level1/level2/level3/level3_file.txt`,
1737 'level3 content'
1738 );
1739 writeFileSync(
1740 `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`,
1741 'level4 content'
1742 );
1743 writeFileSync(
1744 `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`,
1745 'level5 content'
1746 );
1747 
1748 // Create symlinks (will not be followed)
1749 writeFileSync(`${sourceRoot}/link_target.txt`, 'link target content');
1750 symlinkSync(
1751 `${sourceRoot}/link_target.txt`,
1752 `${sourceRoot}/level1/level2/symlink_to_file`
1753 );
1754 symlinkSync(
1755 `${sourceRoot}/level1`,
1756 `${sourceRoot}/level1/level2/level3/symlink_to_dir`
1757 );
1758 
1759 // Create destination directory structure with some existing files (duplicates)
1760 mkdirSync(destRoot, { recursive: true });
1761 mkdirSync(`${destRoot}/level1`, { recursive: true });
1762 mkdirSync(`${destRoot}/level1/level2`, { recursive: true });
1763 mkdirSync(`${destRoot}/existing_dir`, { recursive: true });
1764 
1765 // Create files that will be duplicates (should be ignored)
1766 writeFileSync(`${destRoot}/duplicate_file.txt`, 'existing content');
1767 writeFileSync(
1768 `${destRoot}/level1/duplicate_file.txt`,
1769 'existing level1 content'
1770 );
1771 
1772 // Add some files to source that match existing files (duplicates)
1773 writeFileSync(
1774 `${sourceRoot}/duplicate_file.txt`,
1775 'source duplicate content'
1776 );
1777 writeFileSync(
1778 `${sourceRoot}/level1/duplicate_file.txt`,
1779 'source level1 duplicate content'
1780 );
1781 
1782 // Perform recursive copy (should ignore duplicates and not follow symlinks)
1783 cpSync(sourceRoot, destRoot, { recursive: true, force: false });
1784 
1785 // Verify destination structure
1786 ok(existsSync(`${destRoot}/root_file.txt`));
1787 ok(existsSync(`${destRoot}/level1/level1_file.txt`));
1788 ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`));
1789 ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`));
1790 ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`));
1791 ok(
1792 existsSync(
1793 `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`
1794 )
1795 );
1796 
1797 // Verify file contents
1798 deepStrictEqual(
1799 readFileSync(`${destRoot}/root_file.txt`, 'utf8'),
1800 'root content'
1801 );
1802 deepStrictEqual(
1803 readFileSync(`${destRoot}/level1/level1_file.txt`, 'utf8'),
1804 'level1 content'
1805 );
1806 deepStrictEqual(
1807 readFileSync(`${destRoot}/level1/level2/level2_file.txt`, 'utf8'),
1808 'level2 content'
1809 );
1810 deepStrictEqual(
1811 readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, 'utf8'),
1812 'level3 content'
1813 );
1814 deepStrictEqual(
1815 readFileSync(
1816 `${destRoot}/level1/level2/level3/level4/level4_file.txt`,
1817 'utf8'
1818 ),
1819 'level4 content'
1820 );
1821 deepStrictEqual(
1822 readFileSync(
1823 `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`,
1824 'utf8'
1825 ),
1826 'level5 content'
1827 );
1828 
1829 // Verify symlinks are copied as symlinks (not followed)
1830 ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink());
1831 ok(
1832 lstatSync(
1833 `${destRoot}/level1/level2/level3/symlink_to_dir`
1834 ).isSymbolicLink()
1835 );
1836 
1837 // Verify duplicate files were ignored (original content preserved)
1838 deepStrictEqual(
1839 readFileSync(`${destRoot}/duplicate_file.txt`, 'utf8'),
1840 'existing content'
1841 );
1842 deepStrictEqual(
1843 readFileSync(`${destRoot}/level1/duplicate_file.txt`, 'utf8'),
1844 'existing level1 content'
1845 );
1846 
1847 // Verify existing directory structure remains
1848 ok(existsSync(`${destRoot}/existing_dir`));
1849 },
1850};
1851 
1852// Deep directory structure tests - callback version
1853export const deepDirectoryStructureTestsCallback = {
1854 async test() {
1855 const sourceRoot = '/tmp/deep_source_cb';
1856 const destRoot = '/tmp/deep_dest_cb';
1857 
1858 // Create source directory structure
1859 mkdirSync(sourceRoot, { recursive: true });
1860 mkdirSync(`${sourceRoot}/level1`, { recursive: true });
1861 mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true });
1862 mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true });
1863 mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true });
1864 mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, {
1865 recursive: true,
1866 });
1867 
1868 // Create files in various levels
1869 writeFileSync(`${sourceRoot}/root_file.txt`, 'root content');
1870 writeFileSync(`${sourceRoot}/level1/level1_file.txt`, 'level1 content');
1871 writeFileSync(
1872 `${sourceRoot}/level1/level2/level2_file.txt`,
1873 'level2 content'
1874 );
1875 writeFileSync(
1876 `${sourceRoot}/level1/level2/level3/level3_file.txt`,
1877 'level3 content'
1878 );
1879 writeFileSync(
1880 `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`,
1881 'level4 content'
1882 );
1883 writeFileSync(
1884 `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`,
1885 'level5 content'
1886 );
1887 
1888 // Create symlinks (will not be followed)
1889 writeFileSync(`${sourceRoot}/link_target.txt`, 'link target content');
1890 symlinkSync(
1891 `${sourceRoot}/link_target.txt`,
1892 `${sourceRoot}/level1/level2/symlink_to_file`
1893 );
1894 symlinkSync(
1895 `${sourceRoot}/level1`,
1896 `${sourceRoot}/level1/level2/level3/symlink_to_dir`
1897 );
1898 
1899 // Create destination directory structure with some existing files (duplicates)
1900 mkdirSync(destRoot, { recursive: true });
1901 mkdirSync(`${destRoot}/level1`, { recursive: true });
1902 mkdirSync(`${destRoot}/level1/level2`, { recursive: true });
1903 mkdirSync(`${destRoot}/existing_dir`, { recursive: true });
1904 
1905 // Create files that will be duplicates (should be ignored)
1906 writeFileSync(`${destRoot}/duplicate_file.txt`, 'existing content');
1907 writeFileSync(
1908 `${destRoot}/level1/duplicate_file.txt`,
1909 'existing level1 content'
1910 );
1911 
1912 // Add some files to source that match existing files (duplicates)
1913 writeFileSync(
1914 `${sourceRoot}/duplicate_file.txt`,
1915 'source duplicate content'
1916 );
1917 writeFileSync(
1918 `${sourceRoot}/level1/duplicate_file.txt`,
1919 'source level1 duplicate content'
1920 );
1921 // Perform recursive copy (should ignore duplicates and not follow symlinks)
1922 await new Promise((resolve, reject) => {
1923 cp(sourceRoot, destRoot, { recursive: true, force: false }, (err) => {
1924 if (err) reject(err);
1925 else resolve();
1926 });
1927 });
1928 
1929 // Verify destination structure
1930 ok(existsSync(`${destRoot}/root_file.txt`));
1931 ok(existsSync(`${destRoot}/level1/level1_file.txt`));
1932 ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`));
1933 ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`));
1934 ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`));
1935 ok(
1936 existsSync(
1937 `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`
1938 )
1939 );
1940 
1941 // Verify file contents
1942 deepStrictEqual(
1943 readFileSync(`${destRoot}/root_file.txt`, 'utf8'),
1944 'root content'
1945 );
1946 deepStrictEqual(
1947 readFileSync(`${destRoot}/level1/level1_file.txt`, 'utf8'),
1948 'level1 content'
1949 );
1950 deepStrictEqual(
1951 readFileSync(`${destRoot}/level1/level2/level2_file.txt`, 'utf8'),
1952 'level2 content'
1953 );
1954 deepStrictEqual(
1955 readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, 'utf8'),
1956 'level3 content'
1957 );
1958 deepStrictEqual(
1959 readFileSync(
1960 `${destRoot}/level1/level2/level3/level4/level4_file.txt`,
1961 'utf8'
1962 ),
1963 'level4 content'
1964 );
1965 deepStrictEqual(
1966 readFileSync(
1967 `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`,
1968 'utf8'
1969 ),
1970 'level5 content'
1971 );
1972 
1973 // Verify symlinks are copied as symlinks (not followed)
1974 ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink());
1975 ok(
1976 lstatSync(
1977 `${destRoot}/level1/level2/level3/symlink_to_dir`
1978 ).isSymbolicLink()
1979 );
1980 
1981 // Verify duplicate files were ignored (original content preserved)
1982 deepStrictEqual(
1983 readFileSync(`${destRoot}/duplicate_file.txt`, 'utf8'),
1984 'existing content'
1985 );
1986 deepStrictEqual(
1987 readFileSync(`${destRoot}/level1/duplicate_file.txt`, 'utf8'),
1988 'existing level1 content'
1989 );
1990 
1991 // Verify existing directory structure remains
1992 ok(existsSync(`${destRoot}/existing_dir`));
1993 },
1994};
1995 
1996// Deep directory structure tests - promises version
1997export const deepDirectoryStructureTestsPromises = {
1998 async test() {
1999 const sourceRoot = '/tmp/deep_source_pr';
2000 const destRoot = '/tmp/deep_dest_pr';
2001 
2002 // Create source directory structure
2003 mkdirSync(sourceRoot, { recursive: true });
2004 mkdirSync(`${sourceRoot}/level1`, { recursive: true });
2005 mkdirSync(`${sourceRoot}/level1/level2`, { recursive: true });
2006 mkdirSync(`${sourceRoot}/level1/level2/level3`, { recursive: true });
2007 mkdirSync(`${sourceRoot}/level1/level2/level3/level4`, { recursive: true });
2008 mkdirSync(`${sourceRoot}/level1/level2/level3/level4/level5`, {
2009 recursive: true,
2010 });
2011 
2012 // Create files in various levels
2013 writeFileSync(`${sourceRoot}/root_file.txt`, 'root content');
2014 writeFileSync(`${sourceRoot}/level1/level1_file.txt`, 'level1 content');
2015 writeFileSync(
2016 `${sourceRoot}/level1/level2/level2_file.txt`,
2017 'level2 content'
2018 );
2019 writeFileSync(
2020 `${sourceRoot}/level1/level2/level3/level3_file.txt`,
2021 'level3 content'
2022 );
2023 writeFileSync(
2024 `${sourceRoot}/level1/level2/level3/level4/level4_file.txt`,
2025 'level4 content'
2026 );
2027 writeFileSync(
2028 `${sourceRoot}/level1/level2/level3/level4/level5/level5_file.txt`,
2029 'level5 content'
2030 );
2031 
2032 // Create symlinks (will not be followed)
2033 writeFileSync(`${sourceRoot}/link_target.txt`, 'link target content');
2034 symlinkSync(
2035 `${sourceRoot}/link_target.txt`,
2036 `${sourceRoot}/level1/level2/symlink_to_file`
2037 );
2038 symlinkSync(
2039 `${sourceRoot}/level1`,
2040 `${sourceRoot}/level1/level2/level3/symlink_to_dir`
2041 );
2042 
2043 // Create destination directory structure with some existing files (duplicates)
2044 mkdirSync(destRoot, { recursive: true });
2045 mkdirSync(`${destRoot}/level1`, { recursive: true });
2046 mkdirSync(`${destRoot}/level1/level2`, { recursive: true });
2047 mkdirSync(`${destRoot}/existing_dir`, { recursive: true });
2048 
2049 // Create files that will be duplicates (should be ignored)
2050 writeFileSync(`${destRoot}/duplicate_file.txt`, 'existing content');
2051 writeFileSync(
2052 `${destRoot}/level1/duplicate_file.txt`,
2053 'existing level1 content'
2054 );
2055 
2056 // Add some files to source that match existing files (duplicates)
2057 writeFileSync(
2058 `${sourceRoot}/duplicate_file.txt`,
2059 'source duplicate content'
2060 );
2061 writeFileSync(
2062 `${sourceRoot}/level1/duplicate_file.txt`,
2063 'source level1 duplicate content'
2064 );
2065 
2066 // Perform recursive copy (should ignore duplicates and not follow symlinks)
2067 await fsPromises.cp(sourceRoot, destRoot, {
2068 recursive: true,
2069 force: false,
2070 });
2071 
2072 // Verify destination structure
2073 ok(existsSync(`${destRoot}/root_file.txt`));
2074 ok(existsSync(`${destRoot}/level1/level1_file.txt`));
2075 ok(existsSync(`${destRoot}/level1/level2/level2_file.txt`));
2076 ok(existsSync(`${destRoot}/level1/level2/level3/level3_file.txt`));
2077 ok(existsSync(`${destRoot}/level1/level2/level3/level4/level4_file.txt`));
2078 ok(
2079 existsSync(
2080 `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`
2081 )
2082 );
2083 
2084 // Verify file contents
2085 deepStrictEqual(
2086 readFileSync(`${destRoot}/root_file.txt`, 'utf8'),
2087 'root content'
2088 );
2089 deepStrictEqual(
2090 readFileSync(`${destRoot}/level1/level1_file.txt`, 'utf8'),
2091 'level1 content'
2092 );
2093 deepStrictEqual(
2094 readFileSync(`${destRoot}/level1/level2/level2_file.txt`, 'utf8'),
2095 'level2 content'
2096 );
2097 deepStrictEqual(
2098 readFileSync(`${destRoot}/level1/level2/level3/level3_file.txt`, 'utf8'),
2099 'level3 content'
2100 );
2101 deepStrictEqual(
2102 readFileSync(
2103 `${destRoot}/level1/level2/level3/level4/level4_file.txt`,
2104 'utf8'
2105 ),
2106 'level4 content'
2107 );
2108 deepStrictEqual(
2109 readFileSync(
2110 `${destRoot}/level1/level2/level3/level4/level5/level5_file.txt`,
2111 'utf8'
2112 ),
2113 'level5 content'
2114 );
2115 
2116 // Verify symlinks are copied as symlinks (not followed)
2117 ok(lstatSync(`${destRoot}/level1/level2/symlink_to_file`).isSymbolicLink());
2118 ok(
2119 lstatSync(
2120 `${destRoot}/level1/level2/level3/symlink_to_dir`
2121 ).isSymbolicLink()
2122 );
2123 
2124 // Verify duplicate files were ignored (original content preserved)
2125 deepStrictEqual(
2126 readFileSync(`${destRoot}/duplicate_file.txt`, 'utf8'),
2127 'existing content'
2128 );
2129 deepStrictEqual(
2130 readFileSync(`${destRoot}/level1/duplicate_file.txt`, 'utf8'),
2131 'existing level1 content'
2132 );
2133 
2134 // Verify existing directory structure remains
2135 ok(existsSync(`${destRoot}/existing_dir`));
2136 },
2137};
2138 
2139export const copyTwiceTest = {
2140 test() {
2141 cpSync('/bundle/worker', '/tmp/a');
2142 cpSync('/tmp/a', '/tmp/b');
2143 const a = readFileSync('/bundle/worker');
2144 const b = readFileSync('/tmp/a');
2145 const c = readFileSync('/tmp/b');
2146 deepStrictEqual(a, b);
2147 deepStrictEqual(b, c);
2148 },
2149};