@@ -113,7 +113,19 @@ pub fn ensure_directory(
113
113
path : & Path ,
114
114
dir_type : & str ,
115
115
) -> Result < ( ) , ProcessError > {
116
- if !path. exists ( ) {
116
+ if path. exists ( ) {
117
+ // Check if the existing path is a directory
118
+ if !path. is_dir ( ) {
119
+ return Err ( ProcessError :: DirectoryCreation {
120
+ dir_type : dir_type. to_string ( ) ,
121
+ path : path. display ( ) . to_string ( ) ,
122
+ source : std:: io:: Error :: new (
123
+ std:: io:: ErrorKind :: AlreadyExists ,
124
+ "Path exists but is not a directory" ,
125
+ ) ,
126
+ } ) ;
127
+ }
128
+ } else {
117
129
fs:: create_dir_all ( path) . map_err ( |e| {
118
130
ProcessError :: DirectoryCreation {
119
131
dir_type : dir_type. to_string ( ) ,
@@ -262,6 +274,8 @@ pub fn args(matches: &ArgMatches) -> Result<(), ProcessError> {
262
274
mod tests {
263
275
use super :: * ;
264
276
use clap:: { arg, Command } ;
277
+ use std:: fs:: Permissions ;
278
+ use std:: fs:: { self , File } ;
265
279
use tempfile:: tempdir;
266
280
267
281
/// Helper function to create a test `ArgMatches` with all required arguments.
@@ -460,4 +474,358 @@ mod tests {
460
474
461
475
Ok ( ( ) )
462
476
}
477
+ #[ test]
478
+ fn test_process_frontmatter_with_valid_frontmatter (
479
+ ) -> Result < ( ) , ProcessError > {
480
+ let content = "\
481
+ ---
482
+ title: Test Post
483
+ date: 2024-01-01
484
+ ---
485
+ # Main Content
486
+ This is the main content." ;
487
+
488
+ let processed = process_frontmatter ( content) ?;
489
+ assert ! ( processed. contains( "<!--frontmatter-processed-->" ) ) ;
490
+ assert ! ( processed. contains( "title: Test Post" ) ) ;
491
+ assert ! ( processed. contains( "# Main Content" ) ) ;
492
+ Ok ( ( ) )
493
+ }
494
+
495
+ #[ test]
496
+ fn test_process_frontmatter_without_frontmatter (
497
+ ) -> Result < ( ) , ProcessError > {
498
+ let content = "# Just Content\n No frontmatter here." ;
499
+ let processed = process_frontmatter ( content) ?;
500
+ assert_eq ! ( processed, content) ;
501
+ Ok ( ( ) )
502
+ }
503
+
504
+ #[ test]
505
+ fn test_process_frontmatter_with_empty_frontmatter (
506
+ ) -> Result < ( ) , ProcessError > {
507
+ let content = "---\n ---\n Content after empty frontmatter" ;
508
+ let processed = process_frontmatter ( content) ?;
509
+ assert ! ( processed. contains( "<!--frontmatter-processed-->" ) ) ;
510
+ Ok ( ( ) )
511
+ }
512
+
513
+ #[ test]
514
+ fn test_preprocess_content_with_multiple_files (
515
+ ) -> Result < ( ) , ProcessError > {
516
+ let temp_dir = tempdir ( ) ?;
517
+
518
+ // Create multiple markdown files
519
+ let file1_path = temp_dir. path ( ) . join ( "post1.md" ) ;
520
+ let file2_path = temp_dir. path ( ) . join ( "post2.md" ) ;
521
+ let non_md_path = temp_dir. path ( ) . join ( "other.txt" ) ;
522
+
523
+ fs:: write ( & file1_path, "---\n title: Post 1\n ---\n Content 1" ) ?;
524
+ fs:: write ( & file2_path, "---\n title: Post 2\n ---\n Content 2" ) ?;
525
+ fs:: write ( & non_md_path, "Not a markdown file" ) ?;
526
+
527
+ preprocess_content ( temp_dir. path ( ) ) ?;
528
+
529
+ // Verify markdown files were processed
530
+ let content1 = fs:: read_to_string ( & file1_path) ?;
531
+ let content2 = fs:: read_to_string ( & file2_path) ?;
532
+ let other = fs:: read_to_string ( & non_md_path) ?;
533
+
534
+ assert ! ( content1. contains( "<!--frontmatter-processed-->" ) ) ;
535
+ assert ! ( content2. contains( "<!--frontmatter-processed-->" ) ) ;
536
+ assert_eq ! ( other, "Not a markdown file" ) ;
537
+
538
+ Ok ( ( ) )
539
+ }
540
+
541
+ #[ test]
542
+ fn test_preprocess_content_with_non_existent_directory (
543
+ ) -> Result < ( ) , ProcessError > {
544
+ let non_existent = Path :: new ( "non_existent_directory" ) ;
545
+ let result = preprocess_content ( non_existent) ;
546
+ assert ! ( result. is_ok( ) ) ;
547
+ Ok ( ( ) )
548
+ }
549
+
550
+ #[ test]
551
+ fn test_preprocess_content_with_invalid_permissions ( ) {
552
+ use std:: os:: unix:: fs:: PermissionsExt ;
553
+
554
+ let temp_dir = tempdir ( ) . unwrap ( ) ;
555
+ let file_path = temp_dir. path ( ) . join ( "readonly.md" ) ;
556
+
557
+ // Create file with frontmatter
558
+ fs:: write ( & file_path, "---\n title: Test\n ---\n Content" )
559
+ . unwrap ( ) ;
560
+
561
+ // Make file read-only
562
+ fs:: set_permissions ( & file_path, Permissions :: from_mode ( 0o444 ) )
563
+ . unwrap ( ) ;
564
+
565
+ let result = preprocess_content ( temp_dir. path ( ) ) ;
566
+ assert ! ( result. is_err( ) ) ;
567
+
568
+ // Reset permissions for cleanup
569
+ fs:: set_permissions ( & file_path, Permissions :: from_mode ( 0o666 ) )
570
+ . unwrap ( ) ;
571
+ }
572
+
573
+ #[ test]
574
+ fn test_internal_compile_error_handling ( ) {
575
+ let temp_dir = tempdir ( ) . unwrap ( ) ;
576
+ let result = internal_compile (
577
+ & temp_dir. path ( ) . join ( "build" ) ,
578
+ & temp_dir. path ( ) . join ( "content" ) ,
579
+ & temp_dir. path ( ) . join ( "site" ) ,
580
+ & temp_dir. path ( ) . join ( "template" ) ,
581
+ ) ;
582
+ assert ! ( result. is_err( ) ) ;
583
+ }
584
+
585
+ #[ test]
586
+ fn test_get_argument_with_empty_value ( ) {
587
+ let matches = Command :: new ( "test" )
588
+ . arg ( arg ! ( --"empty" <EMPTY > "Empty value" ) )
589
+ . get_matches_from ( vec ! [ "test" , "--empty" , "" ] ) ;
590
+
591
+ let result = get_argument ( & matches, "empty" ) ;
592
+ assert ! ( result. is_ok( ) ) ;
593
+ assert_eq ! ( result. unwrap( ) , "" ) ;
594
+ }
595
+
596
+ #[ test]
597
+ fn test_ensure_directory_with_existing_file (
598
+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
599
+ let temp_dir = tempdir ( ) ?;
600
+ let file_path = temp_dir. path ( ) . join ( "existing_file" ) ;
601
+
602
+ // Create a file instead of a directory
603
+ let _file = File :: create ( & file_path) ?;
604
+
605
+ // Attempt to ensure directory at the same path
606
+ let result = ensure_directory ( & file_path, "test" ) ;
607
+
608
+ // Verify that the operation failed because path exists but is not a directory
609
+ assert ! ( result. is_err( ) ) ;
610
+ if let Err ( ProcessError :: DirectoryCreation { source, .. } ) =
611
+ result
612
+ {
613
+ assert_eq ! (
614
+ source. kind( ) ,
615
+ std:: io:: ErrorKind :: AlreadyExists
616
+ ) ;
617
+ } else {
618
+ panic ! ( "Expected DirectoryCreation error" ) ;
619
+ }
620
+
621
+ Ok ( ( ) )
622
+ }
623
+
624
+ #[ test]
625
+ fn test_ensure_directory_with_existing_directory (
626
+ ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
627
+ let temp_dir = tempdir ( ) ?;
628
+ let dir_path = temp_dir. path ( ) . join ( "existing_dir" ) ;
629
+
630
+ // First create the directory
631
+ fs:: create_dir ( & dir_path) ?;
632
+
633
+ // Attempt to ensure directory at the same path
634
+ let result = ensure_directory ( & dir_path, "test" ) ;
635
+
636
+ // Should succeed because path exists and is a directory
637
+ assert ! ( result. is_ok( ) ) ;
638
+
639
+ Ok ( ( ) )
640
+ }
641
+
642
+ #[ test]
643
+ fn test_preprocess_content_with_invalid_utf8 ( ) -> Result < ( ) > {
644
+ let temp_dir = tempdir ( ) ?;
645
+ let file_path = temp_dir. path ( ) . join ( "invalid.md" ) ;
646
+
647
+ // Write invalid UTF-8 bytes
648
+ let invalid_bytes = vec ! [ 0xFF , 0xFF ] ;
649
+ fs:: write ( & file_path, invalid_bytes) ?;
650
+
651
+ let result = preprocess_content ( temp_dir. path ( ) ) ;
652
+ assert ! ( result. is_err( ) ) ;
653
+ Ok ( ( ) )
654
+ }
655
+
656
+ #[ test]
657
+ fn test_process_frontmatter_with_multiple_delimiters ( ) -> Result < ( ) >
658
+ {
659
+ let content = "\
660
+ ---
661
+ title: First
662
+ ---
663
+ ---
664
+ title: Second
665
+ ---
666
+ Content" ;
667
+
668
+ let processed = process_frontmatter ( content) ?;
669
+ // Should only process the first frontmatter section
670
+ assert ! ( processed. contains( "title: First" ) ) ;
671
+ assert ! ( processed. contains( "---\n title: Second" ) ) ;
672
+ Ok ( ( ) )
673
+ }
674
+
675
+ #[ test]
676
+ fn test_process_frontmatter_with_malformed_delimiters (
677
+ ) -> Result < ( ) , ProcessError > {
678
+ // Test case where there's only one delimiter
679
+ let content = "---\n title: Test\n Content" ;
680
+ let processed = process_frontmatter ( content) ?;
681
+ assert_eq ! ( processed, content) ; // Should remain unchanged with single delimiter
682
+
683
+ // Test case with extra spaces in delimiters (this should still be valid frontmatter)
684
+ let content = "---\n title: Test\n ---\n Content" ;
685
+ let processed = process_frontmatter ( content) ?;
686
+ assert ! ( processed. contains( "<!--frontmatter-processed-->" ) ) ;
687
+ assert ! ( processed. contains( "title: Test" ) ) ;
688
+ assert ! ( processed. contains( "Content" ) ) ;
689
+
690
+ Ok ( ( ) )
691
+ }
692
+
693
+ #[ test]
694
+ fn test_process_frontmatter_with_whitespace (
695
+ ) -> Result < ( ) , ProcessError > {
696
+ // Test with whitespace before first delimiter
697
+ let content = "\n \n ---\n title: Test\n ---\n Content" ;
698
+ let processed = process_frontmatter ( content) ?;
699
+ // Should still process valid frontmatter even with leading whitespace
700
+ assert ! ( processed. contains( "<!--frontmatter-processed-->" ) ) ;
701
+ assert ! ( processed. contains( "title: Test" ) ) ;
702
+ assert ! ( processed. contains( "Content" ) ) ;
703
+
704
+ // Test with mixed whitespace in frontmatter
705
+ let content =
706
+ "---\n title: Test \n author: Someone \n ---\n Content" ;
707
+ let processed = process_frontmatter ( content) ?;
708
+ assert ! ( processed. contains( "<!--frontmatter-processed-->" ) ) ;
709
+ assert ! ( processed. contains( "title: Test" ) ) ;
710
+ assert ! ( processed. contains( "author: Someone" ) ) ;
711
+ assert ! ( processed. contains( "Content" ) ) ;
712
+
713
+ Ok ( ( ) )
714
+ }
715
+
716
+ #[ test]
717
+ fn test_process_frontmatter_with_invalid_format (
718
+ ) -> Result < ( ) , ProcessError > {
719
+ // Missing second delimiter completely
720
+ let content = "---\n title: Test\n Content" ;
721
+ let processed = process_frontmatter ( content) ?;
722
+ assert_eq ! ( processed, content) ;
723
+
724
+ // Wrong delimiter character
725
+ let content = "===\n title: Test\n ===\n Content" ;
726
+ let processed = process_frontmatter ( content) ?;
727
+ assert_eq ! ( processed, content) ;
728
+
729
+ // Empty content between delimiters
730
+ let content = "---\n \n ---\n Content" ;
731
+ let processed = process_frontmatter ( content) ?;
732
+ assert ! ( processed. contains( "<!--frontmatter-processed-->" ) ) ;
733
+
734
+ Ok ( ( ) )
735
+ }
736
+
737
+ #[ test]
738
+ fn test_preprocess_content_with_nested_directories (
739
+ ) -> Result < ( ) , ProcessError > {
740
+ let temp_dir = tempdir ( ) ?;
741
+ let nested_dir = temp_dir. path ( ) . join ( "nested" ) ;
742
+ fs:: create_dir ( & nested_dir) ?;
743
+
744
+ // Create files in both root and nested directory
745
+ let root_file = temp_dir. path ( ) . join ( "root.md" ) ;
746
+ let nested_file = nested_dir. join ( "nested.md" ) ;
747
+
748
+ fs:: write ( & root_file, "---\n title: Root\n ---\n Root content" ) ?;
749
+ fs:: write (
750
+ & nested_file,
751
+ "---\n title: Nested\n ---\n Nested content" ,
752
+ ) ?;
753
+
754
+ preprocess_content ( temp_dir. path ( ) ) ?;
755
+
756
+ // Verify only root file was processed (since we don't recurse into subdirectories)
757
+ let root_content = fs:: read_to_string ( & root_file) ?;
758
+ assert ! ( root_content. contains( "<!--frontmatter-processed-->" ) ) ;
759
+
760
+ let nested_content = fs:: read_to_string ( & nested_file) ?;
761
+ assert ! (
762
+ !nested_content. contains( "<!--frontmatter-processed-->" )
763
+ ) ;
764
+
765
+ Ok ( ( ) )
766
+ }
767
+
768
+ #[ test]
769
+ fn test_preprocess_content_with_empty_files (
770
+ ) -> Result < ( ) , ProcessError > {
771
+ let temp_dir = tempdir ( ) ?;
772
+ let empty_file = temp_dir. path ( ) . join ( "empty.md" ) ;
773
+
774
+ // Create empty markdown file
775
+ fs:: write ( & empty_file, "" ) ?;
776
+
777
+ preprocess_content ( temp_dir. path ( ) ) ?;
778
+
779
+ // Verify empty file remains unchanged
780
+ let content = fs:: read_to_string ( & empty_file) ?;
781
+ assert ! ( content. is_empty( ) ) ;
782
+
783
+ Ok ( ( ) )
784
+ }
785
+
786
+ #[ test]
787
+ fn test_ensure_directory_with_symlink ( ) -> Result < ( ) , ProcessError >
788
+ {
789
+ let temp_dir = tempdir ( ) ?;
790
+ let real_dir = temp_dir. path ( ) . join ( "real_dir" ) ;
791
+ let symlink = temp_dir. path ( ) . join ( "symlink_dir" ) ;
792
+
793
+ fs:: create_dir ( & real_dir) ?;
794
+
795
+ #[ cfg( unix) ]
796
+ std:: os:: unix:: fs:: symlink ( & real_dir, & symlink) ?;
797
+ #[ cfg( windows) ]
798
+ std:: os:: windows:: fs:: symlink_dir ( & real_dir, & symlink) ?;
799
+
800
+ // Should succeed as symlink points to a valid directory
801
+ let result = ensure_directory ( & symlink, "symlink" ) ;
802
+ assert ! ( result. is_ok( ) ) ;
803
+
804
+ Ok ( ( ) )
805
+ }
806
+
807
+ #[ test]
808
+ fn test_internal_compile_with_empty_directories ( ) {
809
+ let temp_dir = tempdir ( ) . unwrap ( ) ;
810
+
811
+ // Create empty required directories
812
+ let build_dir = temp_dir. path ( ) . join ( "build" ) ;
813
+ let content_dir = temp_dir. path ( ) . join ( "content" ) ;
814
+ let site_dir = temp_dir. path ( ) . join ( "site" ) ;
815
+ let template_dir = temp_dir. path ( ) . join ( "template" ) ;
816
+
817
+ fs:: create_dir_all ( & build_dir) . unwrap ( ) ;
818
+ fs:: create_dir_all ( & content_dir) . unwrap ( ) ;
819
+ fs:: create_dir_all ( & site_dir) . unwrap ( ) ;
820
+ fs:: create_dir_all ( & template_dir) . unwrap ( ) ;
821
+
822
+ let result = internal_compile (
823
+ & build_dir,
824
+ & content_dir,
825
+ & site_dir,
826
+ & template_dir,
827
+ ) ;
828
+
829
+ assert ! ( result. is_err( ) ) ;
830
+ }
463
831
}
0 commit comments