[OCaml] Mobile-friendly clone of cgit.
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.
Changed files
dune-project
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 ] )