test migrate to Alcotest with per-domain test files

Split the monolithic test_ogit.ml into focused modules: - test_helpers.ml: shared fixtures (temp files, env, git) - test_validation.ml: repo name, hash, ref parsing - test_config.ml: configuration loading/errors - test_discovery.ml: repository layout/listing/description - test_tree_paths.ml: Git integration for find_path - test_router.ml: HTTP boundary status tests - test_diff.ml: line diff and hunk generation - test_views.ml: error page rendering - test_static.ml: embedded asset lookup - test_routes.ml: typed route patterns - test_ogit.ml: top-level Alcotest runner Add alcotest >= 1.7.0 as a with-test dependency.

Commit
2d00ae226eafdf80036b22d2ecd61c6c23825499
Author
GPT-5.6 Sol <kiro@amazon.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
dune-project
index de9ee19f..9a6df747 100644..100644
@@ -24,6 +24,7 @@
24 24 (git-unix (>= 3.18.0))
25 25 (crunch (>= 4.0.0))
26 26 (ocamlformat (and :with-dev-setup (= 0.29.0)))
27 Added: (alcotest (and :with-test (>= 1.7.0)))
27 28 (toml (>= 7.1.0)))
28 29 (tags
29 30 (git "web interface")))
ogit.opam
index 750741be..c8bddd70 100644..100644
@@ -16,6 +16,7 @@
16 16 "git-unix" {>= "3.18.0"}
17 17 "crunch" {>= "4.0.0"}
18 18 "ocamlformat" {with-dev-setup & = "0.29.0"}
19 Added: "alcotest" {with-test & >= "1.7.0"}
19 20 "toml" {>= "7.1.0"}
20 21 "odoc" {with-doc}
21 22 ]
test/dune
index 8f4cc71f..4037ce22 100644..100644
@@ -1,3 +1,3 @@
1 1 (test
2 2 (name test_ogit)
3 Removed: (libraries ogit unix lwt.unix))
3 Added: (libraries ogit alcotest unix lwt.unix))
test/test_config.ml
index 00000000..1b0b4d7e 000000..100644
@@ -0,0 +1,122 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: open Test_helpers
4 Added:
5 Added: let test_round_trip () =
6 Added: with_temp_file "ogit" ".toml" (fun file ->
7 Added: let config =
8 Added: Ogit.Config.
9 Added: {
10 Added: user = "alice";
11 Added: default_branch = "main";
12 Added: git_project_root = "/srv/git";
13 Added: commits_max_displayed = 25;
14 Added: host = "127.0.0.1";
15 Added: port = 9000;
16 Added: }
17 Added: in
18 Added: Ogit.Config.write_file ~file (Ogit.Config.to_table config);
19 Added: match Ogit.Config.read_file ~file () with
20 Added: | Ok config' ->
21 Added: Alcotest.(check string) "user" config.user config'.user;
22 Added: Alcotest.(check string)
23 Added: "branch" config.default_branch config'.default_branch;
24 Added: Alcotest.(check string)
25 Added: "root" config.git_project_root config'.git_project_root;
26 Added: Alcotest.(check int)
27 Added: "commits" config.commits_max_displayed config'.commits_max_displayed;
28 Added: Alcotest.(check string) "host" config.host config'.host;
29 Added: Alcotest.(check int) "port" config.port config'.port
30 Added: | Error error -> fail_config_error error)
31 Added:
32 Added: let test_backward_compat () =
33 Added: with_temp_file "ogit" ".toml" (fun file ->
34 Added: Out_channel.with_open_text file (fun channel ->
35 Added: Printf.fprintf channel
36 Added: "user = \"bob\"\n\
37 Added: default_branch = \"main\"\n\
38 Added: git_project_root = \"/srv/git\"\n\
39 Added: commits_max_displayed = 10\n");
40 Added: match Ogit.Config.read_file ~file () with
41 Added: | Ok config ->
42 Added: Alcotest.(check string) "default host" "127.0.0.1" config.host;
43 Added: Alcotest.(check int) "default port" 8081 config.port
44 Added: | Error error -> fail_config_error error)
45 Added:
46 Added: let test_malformed () =
47 Added: with_temp_file "ogit-malformed" ".toml" (fun file ->
48 Added: Out_channel.with_open_text file (fun channel ->
49 Added: output_string channel "user = [\n");
50 Added: match Ogit.Config.read_file ~file () with
51 Added: | Error (Ogit.Config.Parse_error _) -> ()
52 Added: | Error error -> fail_config_error error
53 Added: | Ok _ -> Alcotest.fail "malformed configuration was accepted")
54 Added:
55 Added: let test_invalid_value () =
56 Added: with_temp_file "ogit-invalid" ".toml" (fun file ->
57 Added: let invalid = Ogit.Config.{ default with commits_max_displayed = 0 } in
58 Added: Ogit.Config.write_file ~file (Ogit.Config.to_table invalid);
59 Added: match Ogit.Config.read_file ~file () with
60 Added: | Error (Ogit.Config.Invalid_value _) -> ()
61 Added: | Error error -> fail_config_error error
62 Added: | Ok _ -> Alcotest.fail "invalid commit limit was accepted")
63 Added:
64 Added: let test_directory_rejected () =
65 Added: with_temp_directory "ogit-config-directory" (fun directory ->
66 Added: match Ogit.Config.read_file ~file:directory () with
67 Added: | Error (Ogit.Config.Io_error _) -> ()
68 Added: | Error error -> fail_config_error error
69 Added: | Ok _ -> Alcotest.fail "a directory was accepted as a config file")
70 Added:
71 Added: let test_invalid_implicit () =
72 Added: with_temp_directory "ogit-xdg" (fun config_home ->
73 Added: let directory = Filename.concat config_home "ogit" in
74 Added: Unix.mkdir directory 0o755;
75 Added: let file = Filename.concat directory "config.toml" in
76 Added: Out_channel.with_open_text file (fun channel ->
77 Added: output_string channel "user = [\n");
78 Added: with_environment "OGIT_CONFIG" "" (fun () ->
79 Added: with_environment "XDG_CONFIG_HOME" config_home (fun () ->
80 Added: match Ogit.Config.load () with
81 Added: | Error (Ogit.Config.Parse_error _) -> ()
82 Added: | Error error -> fail_config_error error
83 Added: | Ok _ -> Alcotest.fail "invalid implicit config should fail")))
84 Added:
85 Added: let test_explicit_missing () =
86 Added: let missing = Filename.temp_file "ogit-missing" ".toml" in
87 Added: Sys.remove missing;
88 Added: with_environment "OGIT_CONFIG" missing (fun () ->
89 Added: match Ogit.Config.load () with
90 Added: | Error (Ogit.Config.Not_found file) ->
91 Added: Alcotest.(check string) "path" missing file
92 Added: | Error error -> fail_config_error error
93 Added: | Ok _ -> Alcotest.fail "explicitly missing config should fail")
94 Added:
95 Added: let test_location () =
96 Added: with_environment "OGIT_CONFIG" "" (fun () ->
97 Added: with_environment "XDG_CONFIG_HOME" "/tmp/xdg-config" (fun () ->
98 Added: let expected =
99 Added: Filename.concat
100 Added: (Filename.concat "/tmp/xdg-config" "ogit")
101 Added: "config.toml"
102 Added: in
103 Added: Alcotest.(check string)
104 Added: "XDG path" expected
105 Added: (Ogit.Config.locate_config_file ())));
106 Added: with_environment "OGIT_CONFIG" "/tmp/custom-ogit.toml" (fun () ->
107 Added: Alcotest.(check string)
108 Added: "explicit path" "/tmp/custom-ogit.toml"
109 Added: (Ogit.Config.locate_config_file ()))
110 Added:
111 Added: let suite =
112 Added: ( "config",
113 Added: [
114 Added: Alcotest.test_case "round trip" `Quick test_round_trip;
115 Added: Alcotest.test_case "backward compat" `Quick test_backward_compat;
116 Added: Alcotest.test_case "malformed" `Quick test_malformed;
117 Added: Alcotest.test_case "invalid value" `Quick test_invalid_value;
118 Added: Alcotest.test_case "directory rejected" `Quick test_directory_rejected;
119 Added: Alcotest.test_case "invalid implicit" `Quick test_invalid_implicit;
120 Added: Alcotest.test_case "explicit missing" `Quick test_explicit_missing;
121 Added: Alcotest.test_case "location" `Quick test_location;
122 Added: ] )
test/test_diff.ml
index 00000000..42b91317 000000..100644
@@ -0,0 +1,37 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: let test_line_diff () =
4 Added: let open Ogit.Resolvers.Diff in
5 Added: match line_diff "first\nold\nlast\n" "first\nnew\nlast\n" with
6 Added: | [ ctx1; del; add; ctx2 ] ->
7 Added: Alcotest.(check string) "ctx1 text" "first" ctx1.text;
8 Added: Alcotest.(check string) "del text" "old" del.text;
9 Added: Alcotest.(check string) "add text" "new" add.text;
10 Added: Alcotest.(check string) "ctx2 text" "last" ctx2.text
11 Added: | lines -> Alcotest.failf "expected 4 lines, got %d" (List.length lines)
12 Added:
13 Added: let test_hunks () =
14 Added: let open Ogit.Resolvers.Diff in
15 Added: let old_content = List.init 12 (fun i -> string_of_int (i + 1)) in
16 Added: let new_content =
17 Added: List.mapi (fun i l -> if i = 5 then "changed" else l) old_content
18 Added: in
19 Added: match
20 Added: hunks
21 Added: (line_diff
22 Added: (String.concat "\n" old_content)
23 Added: (String.concat "\n" new_content))
24 Added: with
25 Added: | [ hunk ] ->
26 Added: Alcotest.(check int) "old_start" 3 hunk.old_start;
27 Added: Alcotest.(check int) "old_count" 7 hunk.old_count;
28 Added: Alcotest.(check int) "new_start" 3 hunk.new_start;
29 Added: Alcotest.(check int) "new_count" 7 hunk.new_count
30 Added: | hunks -> Alcotest.failf "expected 1 hunk, got %d" (List.length hunks)
31 Added:
32 Added: let suite =
33 Added: ( "diff",
34 Added: [
35 Added: Alcotest.test_case "line diff" `Quick test_line_diff;
36 Added: Alcotest.test_case "hunks" `Quick test_hunks;
37 Added: ] )
test/test_discovery.ml
index 00000000..4aa0f602 000000..100644
@@ -0,0 +1,98 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: open Test_helpers
4 Added:
5 Added: let test_description_missing () =
6 Added: with_temp_file "ogit-description" ".txt" (fun file ->
7 Added: Sys.remove file;
8 Added: Alcotest.(check string)
9 Added: "missing file" Ogit.Resolvers.default_repo_description
10 Added: (Ogit.Resolvers.read_description_file file))
11 Added:
12 Added: let test_description_empty () =
13 Added: with_temp_file "ogit-description" ".txt" (fun file ->
14 Added: Out_channel.with_open_text file (fun _ -> ());
15 Added: Alcotest.(check string)
16 Added: "empty file" Ogit.Resolvers.default_repo_description
17 Added: (Ogit.Resolvers.read_description_file file))
18 Added:
19 Added: let test_description_content () =
20 Added: with_temp_file "ogit-description" ".txt" (fun file ->
21 Added: Out_channel.with_open_text file (fun channel ->
22 Added: output_string channel "A useful repository\n");
23 Added: Alcotest.(check string)
24 Added: "content" "A useful repository"
25 Added: (Ogit.Resolvers.read_description_file file))
26 Added:
27 Added: let test_layout_bare () =
28 Added: with_temp_directory "ogit-repos" (fun root ->
29 Added: let bare = Filename.concat root "bare.git" in
30 Added: make_git_directory bare;
31 Added: Alcotest.(check bool)
32 Added: "is_repository" true
33 Added: (Ogit.Resolvers.is_repository bare);
34 Added: match Ogit.Resolvers.repository_layout bare with
35 Added: | Some { worktree; git_dir } ->
36 Added: Alcotest.(check string) "worktree" bare worktree;
37 Added: Alcotest.(check string) "git_dir" bare git_dir
38 Added: | None -> Alcotest.fail "expected bare layout")
39 Added:
40 Added: let test_layout_non_bare () =
41 Added: with_temp_directory "ogit-repos" (fun root ->
42 Added: let clone = Filename.concat root "clone" in
43 Added: Unix.mkdir clone 0o755;
44 Added: make_git_directory (Filename.concat clone ".git");
45 Added: Alcotest.(check bool)
46 Added: "is_repository" true
47 Added: (Ogit.Resolvers.is_repository clone);
48 Added: match Ogit.Resolvers.repository_layout clone with
49 Added: | Some { worktree; git_dir } ->
50 Added: Alcotest.(check string) "worktree" clone worktree;
51 Added: Alcotest.(check string)
52 Added: "git_dir"
53 Added: (Filename.concat clone ".git")
54 Added: git_dir
55 Added: | None -> Alcotest.fail "expected non-bare layout")
56 Added:
57 Added: let test_layout_not_a_repo () =
58 Added: with_temp_directory "ogit-repos" (fun root ->
59 Added: let directory = Filename.concat root "not-a-repository" in
60 Added: Unix.mkdir directory 0o755;
61 Added: Alcotest.(check bool)
62 Added: "not a repository" false
63 Added: (Ogit.Resolvers.is_repository directory))
64 Added:
65 Added: let test_listing () =
66 Added: with_temp_directory "ogit-listing" (fun root ->
67 Added: make_git_directory (Filename.concat root "visible.git");
68 Added: make_git_directory (Filename.concat root ".hidden.git");
69 Added: let config = Ogit.Config.{ default with git_project_root = root } in
70 Added: match Ogit.Resolvers.repositories config with
71 Added: | Ok repositories ->
72 Added: Alcotest.(check (list string))
73 Added: "visible only" [ "visible.git" ] repositories
74 Added: | Error error -> Alcotest.failf "%a" Ogit.Resolvers.pp_error error)
75 Added:
76 Added: let test_fallback_branches () =
77 Added: let config = Ogit.Config.{ default with default_branch = "trunk" } in
78 Added: Alcotest.(check (list string))
79 Added: "trunk first"
80 Added: [ "trunk"; "main"; "master" ]
81 Added: (Ogit.Resolvers.fallback_branch_candidates config);
82 Added: let main = Ogit.Config.{ config with default_branch = "main" } in
83 Added: Alcotest.(check (list string))
84 Added: "main deduped" [ "main"; "master" ]
85 Added: (Ogit.Resolvers.fallback_branch_candidates main)
86 Added:
87 Added: let suite =
88 Added: ( "repository discovery",
89 Added: [
90 Added: Alcotest.test_case "description missing" `Quick test_description_missing;
91 Added: Alcotest.test_case "description empty" `Quick test_description_empty;
92 Added: Alcotest.test_case "description content" `Quick test_description_content;
93 Added: Alcotest.test_case "bare layout" `Quick test_layout_bare;
94 Added: Alcotest.test_case "non-bare layout" `Quick test_layout_non_bare;
95 Added: Alcotest.test_case "not a repository" `Quick test_layout_not_a_repo;
96 Added: Alcotest.test_case "listing" `Quick test_listing;
97 Added: Alcotest.test_case "fallback branches" `Quick test_fallback_branches;
98 Added: ] )
test/test_helpers.ml
index 00000000..11531381 000000..100644
@@ -0,0 +1,48 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: (** Shared test fixtures. *)
4 Added:
5 Added: let rec remove_path path =
6 Added: try
7 Added: if Sys.is_directory path then (
8 Added: Sys.readdir path
9 Added: |> Array.iter (fun name -> remove_path (Filename.concat path name));
10 Added: Unix.rmdir path)
11 Added: else Sys.remove path
12 Added: with Sys_error _ -> ()
13 Added:
14 Added: let with_temp_file prefix suffix test =
15 Added: let file = Filename.temp_file prefix suffix in
16 Added: Fun.protect ~finally:(fun () -> remove_path file) (fun () -> test file)
17 Added:
18 Added: let with_temp_directory prefix test =
19 Added: with_temp_file prefix "" (fun root ->
20 Added: Sys.remove root;
21 Added: Unix.mkdir root 0o755;
22 Added: test root)
23 Added:
24 Added: let with_environment name value test =
25 Added: let previous = Sys.getenv_opt name in
26 Added: Unix.putenv name value;
27 Added: Fun.protect
28 Added: ~finally:(fun () -> Unix.putenv name (Option.value previous ~default:""))
29 Added: test
30 Added:
31 Added: let make_git_directory path =
32 Added: Unix.mkdir path 0o755;
33 Added: Out_channel.with_open_text (Filename.concat path "HEAD") (fun _ -> ());
34 Added: Unix.mkdir (Filename.concat path "objects") 0o755
35 Added:
36 Added: let git arguments =
37 Added: let command = "git" in
38 Added: let arguments = Array.of_list (command :: arguments) in
39 Added: let channel = Unix.open_process_args_in command arguments in
40 Added: let output = In_channel.input_all channel |> String.trim in
41 Added: match Unix.close_process_in channel with
42 Added: | Unix.WEXITED 0 -> output
43 Added: | Unix.WEXITED code -> Alcotest.failf "git exited with %d" code
44 Added: | Unix.WSIGNALED signal | Unix.WSTOPPED signal ->
45 Added: Alcotest.failf "git stopped by signal %d" signal
46 Added:
47 Added: let fail_config_error error =
48 Added: Alcotest.fail (Ogit.Config.load_error_to_string error)
test/test_ogit.ml
index 0e822ed4..b577820e 100644..100644
@@ -1,363 +1,15 @@
1 1 (* -*- mode: tuareg; -*- *)
2 2
3 Removed: let rec remove_path path =
4 Removed: try
5 Removed: if Sys.is_directory path then (
6 Removed: Sys.readdir path
7 Removed: |> Array.iter (fun name -> remove_path (Filename.concat path name));
8 Removed: Unix.rmdir path)
9 Removed: else Sys.remove path
10 Removed: with Sys_error _ -> ()
11 Removed:
12 Removed: let with_temp_file prefix suffix test =
13 Removed: let file = Filename.temp_file prefix suffix in
14 Removed: Fun.protect ~finally:(fun () -> remove_path file) (fun () -> test file)
15 Removed:
16 Removed: let with_temp_directory prefix test =
17 Removed: with_temp_file prefix "" (fun root ->
18 Removed: Sys.remove root;
19 Removed: Unix.mkdir root 0o755;
20 Removed: test root)
21 Removed:
22 Removed: let with_environment name value test =
23 Removed: let previous = Sys.getenv_opt name in
24 Removed: Unix.putenv name value;
25 Removed: Fun.protect
26 Removed: ~finally:(fun () -> Unix.putenv name (Option.value previous ~default:""))
27 Removed: test
28 Removed:
29 Removed: let fail_config_error error = failwith (Ogit.Config.load_error_to_string error)
30 Removed:
31 Removed: let test_config_round_trip () =
32 Removed: with_temp_file "ogit" ".toml" (fun file ->
33 Removed: let config =
34 Removed: Ogit.Config.
35 Removed: {
36 Removed: user = "alice";
37 Removed: default_branch = "main";
38 Removed: git_project_root = "/srv/git";
39 Removed: commits_max_displayed = 25;
40 Removed: host = "127.0.0.1";
41 Removed: port = 9000;
42 Removed: }
43 Removed: in
44 Removed: Ogit.Config.write_file ~file (Ogit.Config.to_table config);
45 Removed: match Ogit.Config.read_file ~file () with
46 Removed: | Ok config' -> assert (config' = config)
47 Removed: | Error error -> fail_config_error error)
48 Removed:
49 Removed: let test_config_backward_compat () =
50 Removed: with_temp_file "ogit" ".toml" (fun file ->
51 Removed: Out_channel.with_open_text file (fun channel ->
52 Removed: Printf.fprintf channel
53 Removed: "user = \"bob\"\n\
54 Removed: default_branch = \"main\"\n\
55 Removed: git_project_root = \"/srv/git\"\n\
56 Removed: commits_max_displayed = 10\n");
57 Removed: match Ogit.Config.read_file ~file () with
58 Removed: | Ok config ->
59 Removed: assert (config.host = "127.0.0.1");
60 Removed: assert (config.port = 8081)
61 Removed: | Error error -> fail_config_error error)
62 Removed:
63 Removed: let test_config_errors () =
64 Removed: with_temp_file "ogit-malformed" ".toml" (fun file ->
65 Removed: Out_channel.with_open_text file (fun channel ->
66 Removed: output_string channel "user = [\n");
67 Removed: match Ogit.Config.read_file ~file () with
68 Removed: | Error (Ogit.Config.Parse_error _) -> ()
69 Removed: | Error error -> fail_config_error error
70 Removed: | Ok _ -> failwith "malformed configuration was accepted");
71 Removed: with_temp_file "ogit-invalid" ".toml" (fun file ->
72 Removed: let invalid = Ogit.Config.{ default with commits_max_displayed = 0 } in
73 Removed: Ogit.Config.write_file ~file (Ogit.Config.to_table invalid);
74 Removed: match Ogit.Config.read_file ~file () with
75 Removed: | Error (Ogit.Config.Invalid_value _) -> ()
76 Removed: | Error error -> fail_config_error error
77 Removed: | Ok _ -> failwith "invalid commit limit was accepted");
78 Removed: with_temp_directory "ogit-config-directory" (fun directory ->
79 Removed: match Ogit.Config.read_file ~file:directory () with
80 Removed: | Error (Ogit.Config.Io_error _) -> ()
81 Removed: | Error error -> fail_config_error error
82 Removed: | Ok _ -> failwith "a configuration directory was accepted as a file");
83 Removed: with_temp_directory "ogit-xdg" (fun config_home ->
84 Removed: let directory = Filename.concat config_home "ogit" in
85 Removed: Unix.mkdir directory 0o755;
86 Removed: let file = Filename.concat directory "config.toml" in
87 Removed: Out_channel.with_open_text file (fun channel ->
88 Removed: output_string channel "user = [\n");
89 Removed: with_environment "OGIT_CONFIG" "" (fun () ->
90 Removed: with_environment "XDG_CONFIG_HOME" config_home (fun () ->
91 Removed: match Ogit.Config.load () with
92 Removed: | Error (Ogit.Config.Parse_error _) -> ()
93 Removed: | Error error -> fail_config_error error
94 Removed: | Ok _ -> failwith "an invalid implicit config should fail")));
95 Removed: let missing = Filename.temp_file "ogit-missing" ".toml" in
96 Removed: Sys.remove missing;
97 Removed: with_environment "OGIT_CONFIG" missing (fun () ->
98 Removed: match Ogit.Config.load () with
99 Removed: | Error (Ogit.Config.Not_found file) -> assert (file = missing)
100 Removed: | Error error -> fail_config_error error
101 Removed: | Ok _ -> failwith "an explicitly missing config should fail")
102 Removed:
103 Removed: let test_config_location () =
104 Removed: with_environment "OGIT_CONFIG" "" (fun () ->
105 Removed: with_environment "XDG_CONFIG_HOME" "/tmp/xdg-config" (fun () ->
106 Removed: assert (
107 Removed: Ogit.Config.locate_config_file ()
108 Removed: = Filename.concat
109 Removed: (Filename.concat "/tmp/xdg-config" "ogit")
110 Removed: "config.toml")));
111 Removed: with_environment "OGIT_CONFIG" "/tmp/custom-ogit.toml" (fun () ->
112 Removed: assert (Ogit.Config.locate_config_file () = "/tmp/custom-ogit.toml"))
113 Removed:
114 Removed: let test_description_reader () =
115 Removed: with_temp_file "ogit-description" ".txt" (fun file ->
116 Removed: Sys.remove file;
117 Removed: assert (
118 Removed: Ogit.Resolvers.read_description_file file
119 Removed: = Ogit.Resolvers.default_repo_description);
120 Removed: Out_channel.with_open_text file (fun _ -> ());
121 Removed: assert (
122 Removed: Ogit.Resolvers.read_description_file file
123 Removed: = Ogit.Resolvers.default_repo_description);
124 Removed: Out_channel.with_open_text file (fun channel ->
125 Removed: output_string channel "A useful repository\n");
126 Removed: assert (Ogit.Resolvers.read_description_file file = "A useful repository"))
127 Removed:
128 Removed: let make_git_directory path =
129 Removed: Unix.mkdir path 0o755;
130 Removed: Out_channel.with_open_text (Filename.concat path "HEAD") (fun _ -> ());
131 Removed: Unix.mkdir (Filename.concat path "objects") 0o755
132 Removed:
133 Removed: let test_repository_layout () =
134 Removed: with_temp_directory "ogit-repositories" (fun root ->
135 Removed: let bare = Filename.concat root "bare.git" in
136 Removed: make_git_directory bare;
137 Removed: let clone = Filename.concat root "clone" in
138 Removed: Unix.mkdir clone 0o755;
139 Removed: make_git_directory (Filename.concat clone ".git");
140 Removed: let ordinary_directory = Filename.concat root "not-a-repository" in
141 Removed: Unix.mkdir ordinary_directory 0o755;
142 Removed: assert (Ogit.Resolvers.is_repository bare);
143 Removed: assert (Ogit.Resolvers.is_repository clone);
144 Removed: assert (not (Ogit.Resolvers.is_repository ordinary_directory));
145 Removed: (match Ogit.Resolvers.repository_layout bare with
146 Removed: | Some { worktree; git_dir } ->
147 Removed: assert (worktree = bare);
148 Removed: assert (git_dir = bare)
149 Removed: | None -> failwith "expected a bare repository layout");
150 Removed: match Ogit.Resolvers.repository_layout clone with
151 Removed: | Some { worktree; git_dir } ->
152 Removed: assert (worktree = clone);
153 Removed: assert (git_dir = Filename.concat clone ".git")
154 Removed: | None -> failwith "expected a non-bare repository layout")
155 Removed:
156 Removed: let test_repository_listing () =
157 Removed: with_temp_directory "ogit-listing" (fun root ->
158 Removed: make_git_directory (Filename.concat root "visible.git");
159 Removed: make_git_directory (Filename.concat root ".hidden.git");
160 Removed: let config = Ogit.Config.{ default with git_project_root = root } in
161 Removed: match Ogit.Resolvers.repositories config with
162 Removed: | Ok repositories -> assert (repositories = [ "visible.git" ])
163 Removed: | Error error ->
164 Removed: failwith (Format.asprintf "%a" Ogit.Resolvers.pp_error error))
165 Removed:
166 Removed: let test_fallback_branch_candidates () =
167 Removed: let config = Ogit.Config.{ default with default_branch = "trunk" } in
168 Removed: let names = Ogit.Resolvers.fallback_branch_candidates config in
169 Removed: assert (names = [ "trunk"; "main"; "master" ]);
170 Removed: let main = Ogit.Config.{ config with default_branch = "main" } in
171 Removed: assert (Ogit.Resolvers.fallback_branch_candidates main = [ "main"; "master" ])
172 Removed:
173 Removed: let git arguments =
174 Removed: let command = "git" in
175 Removed: let arguments = Array.of_list (command :: arguments) in
176 Removed: let channel = Unix.open_process_args_in command arguments in
177 Removed: let output = In_channel.input_all channel |> String.trim in
178 Removed: match Unix.close_process_in channel with
179 Removed: | Unix.WEXITED 0 -> output
180 Removed: | Unix.WEXITED code -> failwith (Printf.sprintf "git exited with %d" code)
181 Removed: | Unix.WSIGNALED signal | Unix.WSTOPPED signal ->
182 Removed: failwith (Printf.sprintf "git stopped by signal %d" signal)
183 Removed:
184 Removed: let test_repository_boundaries () =
185 Removed: with_temp_directory "ogit-tree-paths" (fun root ->
186 Removed: let name = "project" in
187 Removed: let path = Filename.concat root name in
188 Removed: Unix.mkdir path 0o755;
189 Removed: ignore (git [ "-C"; path; "init"; "-q"; "-b"; "main" ]);
190 Removed: ignore (git [ "-C"; path; "config"; "user.name"; "Test User" ]);
191 Removed: ignore
192 Removed: (git [ "-C"; path; "config"; "user.email"; "test@example.invalid" ]);
193 Removed: Out_channel.with_open_text (Filename.concat path "tracked.txt")
194 Removed: (fun channel -> output_string channel "tracked\n");
195 Removed: let nested_directory = Filename.concat path "dir" in
196 Removed: Unix.mkdir nested_directory 0o755;
197 Removed: Out_channel.with_open_text (Filename.concat nested_directory "nested.txt")
198 Removed: (fun channel -> output_string channel "nested\n");
199 Removed: ignore (git [ "-C"; path; "add"; "." ]);
200 Removed: ignore (git [ "-C"; path; "commit"; "-q"; "-m"; "initial" ]);
201 Removed: let root_tree = git [ "-C"; path; "rev-parse"; "HEAD^{tree}" ] in
202 Removed: let nested_tree = git [ "-C"; path; "rev-parse"; "HEAD:dir" ] in
203 Removed: let nested_blob =
204 Removed: git [ "-C"; path; "rev-parse"; "HEAD:dir/nested.txt" ]
205 Removed: in
206 Removed: let dangling_file = Filename.concat root "dangling.txt" in
207 Removed: Out_channel.with_open_text dangling_file (fun channel ->
208 Removed: output_string channel "dangling\n");
209 Removed: let dangling_blob =
210 Removed: git [ "-C"; path; "hash-object"; "-w"; dangling_file ]
211 Removed: in
212 Removed: let config = Ogit.Config.{ default with git_project_root = root } in
213 Removed: let repository =
214 Removed: match Lwt_main.run (Ogit.Resolvers.open_repository config name) with
215 Removed: | Ok repository -> repository
216 Removed: | Error error ->
217 Removed: failwith (Format.asprintf "%a" Ogit.Resolvers.pp_error error)
218 Removed: in
219 Removed: Fun.protect
220 Removed: ~finally:(fun () ->
221 Removed: Lwt_main.run (Ogit.Resolvers.close_repository repository))
222 Removed: (fun () ->
223 Removed: (match
224 Removed: Lwt_main.run (Ogit.Resolvers.Tree.find_path repository root_tree)
225 Removed: with
226 Removed: | Ok [] -> ()
227 Removed: | Ok _ ->
228 Removed: failwith "the root tree should have an empty breadcrumb trail"
229 Removed: | Error error ->
230 Removed: failwith (Format.asprintf "%a" Ogit.Resolvers.pp_error error));
231 Removed: (match
232 Removed: Lwt_main.run (Ogit.Resolvers.Tree.find_path repository nested_blob)
233 Removed: with
234 Removed: | Ok [ ("dir", tree); ("nested.txt", blob) ] ->
235 Removed: assert (tree = nested_tree);
236 Removed: assert (blob = nested_blob)
237 Removed: | Ok _ -> failwith "unexpected nested breadcrumb trail"
238 Removed: | Error error ->
239 Removed: failwith (Format.asprintf "%a" Ogit.Resolvers.pp_error error));
240 Removed: match
241 Removed: Lwt_main.run
242 Removed: (Ogit.Resolvers.Tree.find_path repository dangling_blob)
243 Removed: with
244 Removed: | Error (Ogit.Resolvers.Not_found _) -> ()
245 Removed: | Error error ->
246 Removed: failwith (Format.asprintf "%a" Ogit.Resolvers.pp_error error)
247 Removed: | Ok _ ->
248 Removed: failwith "an unreachable object should not look like the root");
249 Removed: let request = Dream.test (Dream.router (Ogit.Handlers.routes config)) in
250 Removed: let status target = Dream.request ~target "" |> request |> Dream.status in
251 Removed: assert (status "/.hidden/summary/" = `Bad_Request);
252 Removed: assert (status "/missing/summary/" = `Not_Found);
253 Removed: assert (status "/project/commit/not-a-hash" = `Bad_Request);
254 Removed: assert (status ("/project/commit/" ^ String.make 40 'a') = `Not_Found))
255 Removed:
256 Removed: let test_line_diff () =
257 Removed: let open Ogit.Resolvers.Diff in
258 Removed: match line_diff "first\nold\nlast\n" "first\nnew\nlast\n" with
259 Removed: | [
260 Removed: { kind = Context; old_number = Some 1; new_number = Some 1; text = "first" };
261 Removed: { kind = Deletion; old_number = Some 2; new_number = None; text = "old" };
262 Removed: { kind = Addition; old_number = None; new_number = Some 2; text = "new" };
263 Removed: { kind = Context; old_number = Some 3; new_number = Some 3; text = "last" };
264 Removed: ] ->
265 Removed: ()
266 Removed: | _ -> failwith "unexpected line diff"
267 Removed:
268 Removed: let test_diff_hunks () =
269 Removed: let open Ogit.Resolvers.Diff in
270 Removed: let old_content = List.init 12 (fun index -> string_of_int (index + 1)) in
271 Removed: let new_content =
272 Removed: List.mapi
273 Removed: (fun index line -> if index = 5 then "changed" else line)
274 Removed: old_content
275 Removed: in
276 Removed: match
277 Removed: hunks
278 Removed: (line_diff
279 Removed: (String.concat "\n" old_content)
280 Removed: (String.concat "\n" new_content))
281 Removed: with
282 Removed: | [ hunk ] ->
283 Removed: assert (hunk.old_start = 3);
284 Removed: assert (hunk.old_count = 7);
285 Removed: assert (hunk.new_start = 3);
286 Removed: assert (hunk.new_count = 7)
287 Removed: | _ -> failwith "expected one diff hunk"
288 Removed:
289 Removed: let test_error_status () =
290 Removed: let response =
291 Removed: Ogit.Views.error_page ~status:`Not_Found ~title:"Not found" "missing"
292 Removed: |> Lwt_main.run
293 Removed: in
294 Removed: assert (Dream.status response = `Not_Found)
295 Removed:
296 Removed: let test_static_assets () =
297 Removed: (match Ogit.Static_assets.read "styles.css" with
298 Removed: | Some content -> assert (String.length content > 0)
299 Removed: | None -> failwith "styles.css should be embedded");
300 Removed: (match Ogit.Static_assets.read "git_icon.svg" with
301 Removed: | Some content -> assert (String.length content > 0)
302 Removed: | None -> failwith "git_icon.svg should be embedded");
303 Removed: assert (Ogit.Static_assets.read "nonexistent" = None)
304 Removed:
305 Removed: let test_routes () =
306 Removed: let path route = Format.asprintf "%a" Dream_html.pp_path route in
307 Removed: assert (path Ogit.Routes.root_path = "/");
308 Removed: assert (path Ogit.Routes.repo_path = "/%s/summary/");
309 Removed: assert (path Ogit.Routes.commit_path = "/%s/commit/%s");
310 Removed: assert (path Ogit.Routes.static_path = "/static/%*s")
311 Removed:
312 Removed: let test_validation () =
313 Removed: assert (Ogit.Resolvers.is_valid_repo_name "project.git");
314 Removed: assert (Ogit.Resolvers.is_valid_repo_name "project");
315 Removed: assert (not (Ogit.Resolvers.is_valid_repo_name ""));
316 Removed: assert (not (Ogit.Resolvers.is_valid_repo_name ".hidden"));
317 Removed: assert (not (Ogit.Resolvers.is_valid_repo_name "."));
318 Removed: assert (not (Ogit.Resolvers.is_valid_repo_name ".."));
319 Removed: assert (not (Ogit.Resolvers.is_valid_repo_name "../outside"));
320 Removed: assert (not (Ogit.Resolvers.is_valid_repo_name "nested/repo"));
321 Removed: assert (not (Ogit.Resolvers.is_valid_repo_name "nested\\repo"));
322 Removed: assert (not (Ogit.Resolvers.is_valid_repo_name "bad\x00repo"));
323 Removed: assert (Ogit.Resolvers.is_valid_hash_hex (String.make 40 'a'));
324 Removed: assert (Ogit.Resolvers.is_valid_hash_hex (String.make 40 'A'));
325 Removed: assert (not (Ogit.Resolvers.is_valid_hash_hex (String.make 39 'a')));
326 Removed: assert (not (Ogit.Resolvers.is_valid_hash_hex (String.make 41 'a')));
327 Removed: assert (not (Ogit.Resolvers.is_valid_hash_hex (String.make 39 'a' ^ "x")));
328 Removed: assert (Ogit.Resolvers.short_hash "abc" = "abc");
329 Removed: assert (Ogit.Resolvers.short_hash "0123456789" = "01234567");
330 Removed: assert (Ogit.Resolvers.Reference.branch_name "refs/heads/main" = Some "main");
331 Removed: assert (
332 Removed: Ogit.Resolvers.Reference.branch_name "refs/heads/feature/topic"
333 Removed: = Some "feature/topic");
334 Removed: assert (Ogit.Resolvers.Reference.branch_name "HEAD" = None);
335 Removed: assert (Ogit.Resolvers.Reference.tag_name "refs/tags/v1.0.0" = Some "v1.0.0");
336 Removed: assert (Ogit.Resolvers.Reference.tag_name "refs/heads/v1.0.0" = None)
337 Removed:
338 Removed: let tests =
339 Removed: [
340 Removed: ("validation", test_validation);
341 Removed: ("config round trip", test_config_round_trip);
342 Removed: ("config backward compatibility", test_config_backward_compat);
343 Removed: ("config errors", test_config_errors);
344 Removed: ("config location", test_config_location);
345 Removed: ("description reader", test_description_reader);
346 Removed: ("repository layout", test_repository_layout);
347 Removed: ("repository listing", test_repository_listing);
348 Removed: ("fallback branches", test_fallback_branch_candidates);
349 Removed: ("repository boundaries", test_repository_boundaries);
350 Removed: ("line diff", test_line_diff);
351 Removed: ("diff hunks", test_diff_hunks);
352 Removed: ("HTTP error status", test_error_status);
353 Removed: ("static assets", test_static_assets);
354 Removed: ("route patterns", test_routes);
355 Removed: ]
356 Removed:
357 3 let () =
358 Removed: List.iter
359 Removed: (fun (name, test) ->
360 Removed: try test ()
361 Removed: with error ->
362 Removed: failwith (Printf.sprintf "%s: %s" name (Printexc.to_string error)))
363 Removed: tests
4 Added: Alcotest.run "ogit"
5 Added: [
6 Added: Test_validation.suite;
7 Added: Test_config.suite;
8 Added: Test_discovery.suite;
9 Added: Test_tree_paths.suite;
10 Added: Test_router.suite;
11 Added: Test_diff.suite;
12 Added: Test_views.suite;
13 Added: Test_static.suite;
14 Added: Test_routes.suite;
15 Added: ]
test/test_router.ml
index 00000000..a7390ccf 000000..100644
@@ -0,0 +1,68 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: open Test_helpers
4 Added:
5 Added: let test_invalid_repo_name () =
6 Added: with_temp_directory "ogit-router" (fun root ->
7 Added: let config = Ogit.Config.{ default with git_project_root = root } in
8 Added: let request = Dream.test (Dream.router (Ogit.Handlers.routes config)) in
9 Added: let status =
10 Added: Dream.request ~target:"/.hidden/summary/" "" |> request |> Dream.status
11 Added: in
12 Added: Alcotest.(check int) "400" 400 (Dream.status_to_int status))
13 Added:
14 Added: let test_missing_repo () =
15 Added: with_temp_directory "ogit-router" (fun root ->
16 Added: let config = Ogit.Config.{ default with git_project_root = root } in
17 Added: let request = Dream.test (Dream.router (Ogit.Handlers.routes config)) in
18 Added: let status =
19 Added: Dream.request ~target:"/missing/summary/" "" |> request |> Dream.status
20 Added: in
21 Added: Alcotest.(check int) "404" 404 (Dream.status_to_int status))
22 Added:
23 Added: let test_invalid_hash () =
24 Added: with_temp_directory "ogit-router" (fun root ->
25 Added: let name = "project" in
26 Added: let path = Filename.concat root name in
27 Added: Unix.mkdir path 0o755;
28 Added: ignore (git [ "-C"; path; "init"; "-q"; "-b"; "main" ]);
29 Added: ignore (git [ "-C"; path; "config"; "user.name"; "Test" ]);
30 Added: ignore (git [ "-C"; path; "config"; "user.email"; "t@t.invalid" ]);
31 Added: Out_channel.with_open_text (Filename.concat path "f.txt") (fun ch ->
32 Added: output_string ch "x\n");
33 Added: ignore (git [ "-C"; path; "add"; "." ]);
34 Added: ignore (git [ "-C"; path; "commit"; "-q"; "-m"; "init" ]);
35 Added: let config = Ogit.Config.{ default with git_project_root = root } in
36 Added: let request = Dream.test (Dream.router (Ogit.Handlers.routes config)) in
37 Added: let status =
38 Added: Dream.request ~target:"/project/commit/not-a-hash" ""
39 Added: |> request |> Dream.status
40 Added: in
41 Added: Alcotest.(check int) "400" 400 (Dream.status_to_int status))
42 Added:
43 Added: let test_missing_object () =
44 Added: with_temp_directory "ogit-router" (fun root ->
45 Added: let name = "project" in
46 Added: let path = Filename.concat root name in
47 Added: Unix.mkdir path 0o755;
48 Added: ignore (git [ "-C"; path; "init"; "-q"; "-b"; "main" ]);
49 Added: ignore (git [ "-C"; path; "config"; "user.name"; "Test" ]);
50 Added: ignore (git [ "-C"; path; "config"; "user.email"; "t@t.invalid" ]);
51 Added: Out_channel.with_open_text (Filename.concat path "f.txt") (fun ch ->
52 Added: output_string ch "x\n");
53 Added: ignore (git [ "-C"; path; "add"; "." ]);
54 Added: ignore (git [ "-C"; path; "commit"; "-q"; "-m"; "init" ]);
55 Added: let config = Ogit.Config.{ default with git_project_root = root } in
56 Added: let request = Dream.test (Dream.router (Ogit.Handlers.routes config)) in
57 Added: let target = "/project/commit/" ^ String.make 40 'a' in
58 Added: let status = Dream.request ~target "" |> request |> Dream.status in
59 Added: Alcotest.(check int) "404" 404 (Dream.status_to_int status))
60 Added:
61 Added: let suite =
62 Added: ( "router",
63 Added: [
64 Added: Alcotest.test_case "invalid repo name" `Slow test_invalid_repo_name;
65 Added: Alcotest.test_case "missing repo" `Slow test_missing_repo;
66 Added: Alcotest.test_case "invalid hash" `Slow test_invalid_hash;
67 Added: Alcotest.test_case "missing object" `Slow test_missing_object;
68 Added: ] )
test/test_routes.ml
index 00000000..6f074ef3 000000..100644
@@ -0,0 +1,12 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: let test_patterns () =
4 Added: let path route = Format.asprintf "%a" Dream_html.pp_path route in
5 Added: Alcotest.(check string) "root" "/" (path Ogit.Routes.root_path);
6 Added: Alcotest.(check string) "repo" "/%s/summary/" (path Ogit.Routes.repo_path);
7 Added: Alcotest.(check string)
8 Added: "commit" "/%s/commit/%s"
9 Added: (path Ogit.Routes.commit_path);
10 Added: Alcotest.(check string) "static" "/static/%*s" (path Ogit.Routes.static_path)
11 Added:
12 Added: let suite = ("routes", [ Alcotest.test_case "patterns" `Quick test_patterns ])
test/test_static.ml
index 00000000..fb010fcc 000000..100644
@@ -0,0 +1,26 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: let test_styles () =
4 Added: match Ogit.Static_assets.read "styles.css" with
5 Added: | Some content ->
6 Added: Alcotest.(check bool) "non-empty" true (String.length content > 0)
7 Added: | None -> Alcotest.fail "styles.css should be embedded"
8 Added:
9 Added: let test_icon () =
10 Added: match Ogit.Static_assets.read "git_icon.svg" with
11 Added: | Some content ->
12 Added: Alcotest.(check bool) "non-empty" true (String.length content > 0)
13 Added: | None -> Alcotest.fail "git_icon.svg should be embedded"
14 Added:
15 Added: let test_missing () =
16 Added: Alcotest.(check (option string))
17 Added: "nonexistent" None
18 Added: (Ogit.Static_assets.read "nonexistent")
19 Added:
20 Added: let suite =
21 Added: ( "static assets",
22 Added: [
23 Added: Alcotest.test_case "styles.css" `Quick test_styles;
24 Added: Alcotest.test_case "git_icon.svg" `Quick test_icon;
25 Added: Alcotest.test_case "missing asset" `Quick test_missing;
26 Added: ] )
test/test_tree_paths.ml
index 00000000..3fcf6ba1 000000..100644
@@ -0,0 +1,116 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: open Test_helpers
4 Added:
5 Added: let test_root_tree () =
6 Added: with_temp_directory "ogit-paths" (fun root ->
7 Added: let name = "project" in
8 Added: let path = Filename.concat root name in
9 Added: Unix.mkdir path 0o755;
10 Added: ignore (git [ "-C"; path; "init"; "-q"; "-b"; "main" ]);
11 Added: ignore (git [ "-C"; path; "config"; "user.name"; "Test" ]);
12 Added: ignore (git [ "-C"; path; "config"; "user.email"; "t@t.invalid" ]);
13 Added: Out_channel.with_open_text (Filename.concat path "file.txt") (fun ch ->
14 Added: output_string ch "data\n");
15 Added: ignore (git [ "-C"; path; "add"; "." ]);
16 Added: ignore (git [ "-C"; path; "commit"; "-q"; "-m"; "init" ]);
17 Added: let root_tree = git [ "-C"; path; "rev-parse"; "HEAD^{tree}" ] in
18 Added: let config = Ogit.Config.{ default with git_project_root = root } in
19 Added: let repository =
20 Added: match Lwt_main.run (Ogit.Resolvers.open_repository config name) with
21 Added: | Ok r -> r
22 Added: | Error e -> Alcotest.failf "%a" Ogit.Resolvers.pp_error e
23 Added: in
24 Added: Fun.protect
25 Added: ~finally:(fun () ->
26 Added: Lwt_main.run (Ogit.Resolvers.close_repository repository))
27 Added: (fun () ->
28 Added: match
29 Added: Lwt_main.run (Ogit.Resolvers.Tree.find_path repository root_tree)
30 Added: with
31 Added: | Ok trail ->
32 Added: Alcotest.(check (list (pair string string)))
33 Added: "empty trail" [] trail
34 Added: | Error e -> Alcotest.failf "%a" Ogit.Resolvers.pp_error e))
35 Added:
36 Added: let test_nested () =
37 Added: with_temp_directory "ogit-paths" (fun root ->
38 Added: let name = "project" in
39 Added: let path = Filename.concat root name in
40 Added: Unix.mkdir path 0o755;
41 Added: ignore (git [ "-C"; path; "init"; "-q"; "-b"; "main" ]);
42 Added: ignore (git [ "-C"; path; "config"; "user.name"; "Test" ]);
43 Added: ignore (git [ "-C"; path; "config"; "user.email"; "t@t.invalid" ]);
44 Added: let dir = Filename.concat path "dir" in
45 Added: Unix.mkdir dir 0o755;
46 Added: Out_channel.with_open_text (Filename.concat dir "nested.txt") (fun ch ->
47 Added: output_string ch "nested\n");
48 Added: ignore (git [ "-C"; path; "add"; "." ]);
49 Added: ignore (git [ "-C"; path; "commit"; "-q"; "-m"; "init" ]);
50 Added: let nested_tree = git [ "-C"; path; "rev-parse"; "HEAD:dir" ] in
51 Added: let nested_blob =
52 Added: git [ "-C"; path; "rev-parse"; "HEAD:dir/nested.txt" ]
53 Added: in
54 Added: let config = Ogit.Config.{ default with git_project_root = root } in
55 Added: let repository =
56 Added: match Lwt_main.run (Ogit.Resolvers.open_repository config name) with
57 Added: | Ok r -> r
58 Added: | Error e -> Alcotest.failf "%a" Ogit.Resolvers.pp_error e
59 Added: in
60 Added: Fun.protect
61 Added: ~finally:(fun () ->
62 Added: Lwt_main.run (Ogit.Resolvers.close_repository repository))
63 Added: (fun () ->
64 Added: match
65 Added: Lwt_main.run (Ogit.Resolvers.Tree.find_path repository nested_blob)
66 Added: with
67 Added: | Ok trail ->
68 Added: Alcotest.(check (list (pair string string)))
69 Added: "nested trail"
70 Added: [ ("dir", nested_tree); ("nested.txt", nested_blob) ]
71 Added: trail
72 Added: | Error e -> Alcotest.failf "%a" Ogit.Resolvers.pp_error e))
73 Added:
74 Added: let test_unreachable () =
75 Added: with_temp_directory "ogit-paths" (fun root ->
76 Added: let name = "project" in
77 Added: let path = Filename.concat root name in
78 Added: Unix.mkdir path 0o755;
79 Added: ignore (git [ "-C"; path; "init"; "-q"; "-b"; "main" ]);
80 Added: ignore (git [ "-C"; path; "config"; "user.name"; "Test" ]);
81 Added: ignore (git [ "-C"; path; "config"; "user.email"; "t@t.invalid" ]);
82 Added: Out_channel.with_open_text (Filename.concat path "file.txt") (fun ch ->
83 Added: output_string ch "data\n");
84 Added: ignore (git [ "-C"; path; "add"; "." ]);
85 Added: ignore (git [ "-C"; path; "commit"; "-q"; "-m"; "init" ]);
86 Added: let dangling_file = Filename.concat root "dangling.txt" in
87 Added: Out_channel.with_open_text dangling_file (fun ch ->
88 Added: output_string ch "dangling\n");
89 Added: let dangling_blob =
90 Added: git [ "-C"; path; "hash-object"; "-w"; dangling_file ]
91 Added: in
92 Added: let config = Ogit.Config.{ default with git_project_root = root } in
93 Added: let repository =
94 Added: match Lwt_main.run (Ogit.Resolvers.open_repository config name) with
95 Added: | Ok r -> r
96 Added: | Error e -> Alcotest.failf "%a" Ogit.Resolvers.pp_error e
97 Added: in
98 Added: Fun.protect
99 Added: ~finally:(fun () ->
100 Added: Lwt_main.run (Ogit.Resolvers.close_repository repository))
101 Added: (fun () ->
102 Added: match
103 Added: Lwt_main.run
104 Added: (Ogit.Resolvers.Tree.find_path repository dangling_blob)
105 Added: with
106 Added: | Error (Ogit.Resolvers.Not_found _) -> ()
107 Added: | Error e -> Alcotest.failf "%a" Ogit.Resolvers.pp_error e
108 Added: | Ok _ -> Alcotest.fail "unreachable object should return Not_found"))
109 Added:
110 Added: let suite =
111 Added: ( "tree paths",
112 Added: [
113 Added: Alcotest.test_case "root tree" `Slow test_root_tree;
114 Added: Alcotest.test_case "nested path" `Slow test_nested;
115 Added: Alcotest.test_case "unreachable object" `Slow test_unreachable;
116 Added: ] )
test/test_validation.ml
index 00000000..bb2a2a6d 000000..100644
@@ -0,0 +1,83 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: let test_valid_repo_names () =
4 Added: Alcotest.(check bool)
5 Added: "project.git" true
6 Added: (Ogit.Resolvers.is_valid_repo_name "project.git");
7 Added: Alcotest.(check bool)
8 Added: "project" true
9 Added: (Ogit.Resolvers.is_valid_repo_name "project")
10 Added:
11 Added: let test_invalid_repo_names () =
12 Added: List.iter
13 Added: (fun name ->
14 Added: Alcotest.(check bool)
15 Added: (Printf.sprintf "reject %S" name)
16 Added: false
17 Added: (Ogit.Resolvers.is_valid_repo_name name))
18 Added: [
19 Added: "";
20 Added: ".hidden";
21 Added: ".";
22 Added: "..";
23 Added: "../outside";
24 Added: "nested/repo";
25 Added: "nested\\repo";
26 Added: "bad\x00repo";
27 Added: ]
28 Added:
29 Added: let test_valid_hash_hex () =
30 Added: Alcotest.(check bool)
31 Added: "40 lowercase hex" true
32 Added: (Ogit.Resolvers.is_valid_hash_hex (String.make 40 'a'));
33 Added: Alcotest.(check bool)
34 Added: "40 uppercase hex" true
35 Added: (Ogit.Resolvers.is_valid_hash_hex (String.make 40 'A'))
36 Added:
37 Added: let test_invalid_hash_hex () =
38 Added: Alcotest.(check bool)
39 Added: "39 chars" false
40 Added: (Ogit.Resolvers.is_valid_hash_hex (String.make 39 'a'));
41 Added: Alcotest.(check bool)
42 Added: "41 chars" false
43 Added: (Ogit.Resolvers.is_valid_hash_hex (String.make 41 'a'));
44 Added: Alcotest.(check bool)
45 Added: "non-hex char" false
46 Added: (Ogit.Resolvers.is_valid_hash_hex (String.make 39 'a' ^ "x"))
47 Added:
48 Added: let test_short_hash () =
49 Added: Alcotest.(check string) "short input" "abc" (Ogit.Resolvers.short_hash "abc");
50 Added: Alcotest.(check string)
51 Added: "long input" "01234567"
52 Added: (Ogit.Resolvers.short_hash "0123456789")
53 Added:
54 Added: let test_branch_name () =
55 Added: Alcotest.(check (option string))
56 Added: "main" (Some "main")
57 Added: (Ogit.Resolvers.Reference.branch_name "refs/heads/main");
58 Added: Alcotest.(check (option string))
59 Added: "feature/topic" (Some "feature/topic")
60 Added: (Ogit.Resolvers.Reference.branch_name "refs/heads/feature/topic");
61 Added: Alcotest.(check (option string))
62 Added: "HEAD" None
63 Added: (Ogit.Resolvers.Reference.branch_name "HEAD")
64 Added:
65 Added: let test_tag_name () =
66 Added: Alcotest.(check (option string))
67 Added: "v1.0.0" (Some "v1.0.0")
68 Added: (Ogit.Resolvers.Reference.tag_name "refs/tags/v1.0.0");
69 Added: Alcotest.(check (option string))
70 Added: "not a tag" None
71 Added: (Ogit.Resolvers.Reference.tag_name "refs/heads/v1.0.0")
72 Added:
73 Added: let suite =
74 Added: ( "validation",
75 Added: [
76 Added: Alcotest.test_case "valid repo names" `Quick test_valid_repo_names;
77 Added: Alcotest.test_case "invalid repo names" `Quick test_invalid_repo_names;
78 Added: Alcotest.test_case "valid hash hex" `Quick test_valid_hash_hex;
79 Added: Alcotest.test_case "invalid hash hex" `Quick test_invalid_hash_hex;
80 Added: Alcotest.test_case "short hash" `Quick test_short_hash;
81 Added: Alcotest.test_case "branch name" `Quick test_branch_name;
82 Added: Alcotest.test_case "tag name" `Quick test_tag_name;
83 Added: ] )
test/test_views.ml
index 00000000..79887416 000000..100644
@@ -0,0 +1,12 @@
1 Added: (* -*- mode: tuareg; -*- *)
2 Added:
3 Added: let test_error_page_status () =
4 Added: let response =
5 Added: Ogit.Views.error_page ~status:`Not_Found ~title:"Not found" "missing"
6 Added: |> Lwt_main.run
7 Added: in
8 Added: Alcotest.(check int) "404" 404 (Dream.status response |> Dream.status_to_int)
9 Added:
10 Added: let suite =
11 Added: ( "views",
12 Added: [ Alcotest.test_case "error page status" `Quick test_error_page_status ] )