View raw

1 (** URL dispatch: that {!Ogit.Routes.dispatch} recovers the intended route, 2 rejects malformed input, and inverts {!Ogit.Routes.path_of}. *) 3 4 let route_to_string = function 5 | Ogit.Routes.Root -> "Root" 6 | Ogit.Routes.Project_dir d -> "Project_dir " ^ d 7 | Ogit.Routes.Repo r -> "Repo " ^ r 8 | Ogit.Routes.Commits r -> "Commits " ^ r 9 | Ogit.Routes.Commits_branch (r, b) -> "Commits_branch (" ^ r ^ ", " ^ b ^ ")" 10 | Ogit.Routes.Commit (r, h) -> "Commit (" ^ r ^ ", " ^ h ^ ")" 11 | Ogit.Routes.Files r -> "Files " ^ r 12 | Ogit.Routes.File (r, h) -> "File (" ^ r ^ ", " ^ h ^ ")" 13 | Ogit.Routes.File_at (r, p) -> "File_at (" ^ r ^ ", " ^ p ^ ")" 14 | Ogit.Routes.Raw_file (r, h) -> "Raw_file (" ^ r ^ ", " ^ h ^ ")" 15 | Ogit.Routes.Raw_at (r, p) -> "Raw_at (" ^ r ^ ", " ^ p ^ ")" 16 17 let route_testable = 18 let pp fmt = function 19 | None -> Format.fprintf fmt "None" 20 | Some route -> Format.fprintf fmt "Some (%s)" (route_to_string route) 21 in 22 Alcotest.testable pp ( = ) 23 24 let check_dispatch msg path expected = 25 Alcotest.check route_testable msg expected (Ogit.Routes.dispatch path) 26 27 let test_basic_actions () = 28 check_dispatch "summary" "myrepo/summary/" (Some (Repo "myrepo")); 29 check_dispatch "commits" "myrepo/commits/" (Some (Commits "myrepo")); 30 check_dispatch "files" "myrepo/files/" (Some (Files "myrepo")) 31 32 let full_hash = String.make 40 'a' 33 34 let test_parametric_actions () = 35 check_dispatch "commit with hash" "myrepo/commit/abc123" 36 (Some (Commit ("myrepo", "abc123"))); 37 check_dispatch "file with full hash" 38 ("myrepo/file/" ^ full_hash) 39 (Some (File ("myrepo", full_hash))); 40 check_dispatch "raw with full hash" 41 ("myrepo/raw/" ^ full_hash) 42 (Some (Raw_file ("myrepo", full_hash))); 43 check_dispatch "commits for branch" "myrepo/commits/main" 44 (Some (Commits_branch ("myrepo", "main"))) 45 46 let test_path_actions () = 47 check_dispatch "file at path" "myrepo/file/src/main.ml" 48 (Some (File_at ("myrepo", "src/main.ml"))); 49 check_dispatch "file at single segment" "myrepo/file/README.md" 50 (Some (File_at ("myrepo", "README.md"))); 51 check_dispatch "raw at path" "myrepo/raw/doc/logo.svg" 52 (Some (Raw_at ("myrepo", "doc/logo.svg"))); 53 check_dispatch "encoded segment decodes" "myrepo/file/a%20b.txt" 54 (Some (File_at ("myrepo", "a b.txt"))) 55 56 let test_nested_repo () = 57 check_dispatch "nested summary" "sub/dir/repo/summary/" 58 (Some (Repo "sub/dir/repo")); 59 check_dispatch "nested commit" "sub/repo/commit/abc" 60 (Some (Commit ("sub/repo", "abc"))); 61 check_dispatch "deeply nested" "a/b/c/repo/files/" 62 (Some (Files "a/b/c/repo")) 63 64 (* A path without a reserved segment is reported as [Project_dir]; the handler 65 decides whether the filesystem holds a repository or a directory there. *) 66 let test_implicit_summary () = 67 check_dispatch "bare repo path" "myrepo" (Some (Project_dir "myrepo")); 68 check_dispatch "bare with trailing slash" "myrepo/" 69 (Some (Project_dir "myrepo")) 70 71 let test_root () = check_dispatch "empty path is root" "" (Some Root) 72 73 let test_malformed () = 74 check_dispatch "commit without hash" "myrepo/commit/" None; 75 check_dispatch "file without hash" "myrepo/file/" None; 76 check_dispatch "raw without hash" "myrepo/raw/" None; 77 check_dispatch "action without repo" "summary/" None; 78 check_dispatch "trailing junk after summary" "myrepo/summary/junk" None; 79 check_dispatch "trailing junk after files" "myrepo/files/extra" None; 80 check_dispatch "trailing junk after hash" "myrepo/commit/abc/def" None 81 82 (* The documented contract: dispatch inverts path_of for every route shape, 83 including file paths that need percent-encoding. *) 84 let test_round_trip () = 85 let samples = 86 Ogit.Routes. 87 [ 88 Root; 89 Project_dir "dir"; 90 Project_dir "nested/dir"; 91 Repo "repo"; 92 Repo "sub/repo"; 93 Commits "repo"; 94 Commits_branch ("repo", "main"); 95 Commit ("repo", "abc123"); 96 Files "repo"; 97 File ("repo", String.make 40 'a'); 98 File_at ("repo", "src/main.ml"); 99 File_at ("repo", "dir with space/na%me.txt"); 100 Raw_file ("repo", String.make 40 'b'); 101 Raw_at ("repo", "doc/logo.svg"); 102 ] 103 in 104 List.iter 105 (fun route -> 106 let path = Ogit.Routes.path_of route in 107 let stripped = String.sub path 1 (String.length path - 1) in 108 Alcotest.check route_testable 109 (Printf.sprintf "round trip %s" (route_to_string route)) 110 (Some route) 111 (Ogit.Routes.dispatch stripped)) 112 samples 113 114 let suite = 115 ( "dispatch", 116 [ 117 Alcotest.test_case "basic actions" `Quick test_basic_actions; 118 Alcotest.test_case "parametric actions" `Quick test_parametric_actions; 119 Alcotest.test_case "path actions" `Quick test_path_actions; 120 Alcotest.test_case "nested repo" `Quick test_nested_repo; 121 Alcotest.test_case "implicit summary" `Quick test_implicit_summary; 122 Alcotest.test_case "root" `Quick test_root; 123 Alcotest.test_case "malformed URLs" `Quick test_malformed; 124 Alcotest.test_case "round trip" `Quick test_round_trip; 125 ] ) 126