[OCaml] Mobile-friendly clone of cgit.
test Cover commit list pagination arithmetic
Exercise the orphan-absorption rule through the router: plain first pages, the widest absorbed page at the window boundary, remainders beyond the window, single pages, and page numbers past the end.
Changed files
test/test_ogit.ml
@@ -10,6 +10,7 @@
10
10
Test_tree_paths.suite;
11
11
Test_commit_walk.suite;
12
12
Test_router.suite;
13
Added:
Test_pagination.suite;
13
14
Test_dispatch.suite;
14
15
Test_list_ext.suite;
15
16
Test_cache.suite;
test/test_pagination.ml
@@ -0,0 +1,104 @@
1
Added:
(** Commit list pagination arithmetic, exercised through the router.
2
Added:
3
Added:
The orphan-absorption rule: a page absorbs up to ten commits beyond the
4
Added:
page size when they would otherwise form a near-empty final page. *)
5
Added:
6
Added:
open Test_helpers
7
Added:
8
Added:
let with_repository_of ~commits ~page_size test =
9
Added:
with_temp_directory "ogit-pagination" (fun root ->
10
Added:
let name = "project" in
11
Added:
let path = Filename.concat root name in
12
Added:
Unix.mkdir path 0o755;
13
Added:
ignore (git [ "-C"; path; "init"; "-q"; "-b"; "main" ]);
14
Added:
ignore (git [ "-C"; path; "config"; "user.name"; "Test" ]);
15
Added:
ignore (git [ "-C"; path; "config"; "user.email"; "t@t.invalid" ]);
16
Added:
for index = 1 to commits do
17
Added:
Out_channel.with_open_text (Filename.concat path "f.txt") (fun ch ->
18
Added:
Printf.fprintf ch "revision %d\n" index);
19
Added:
ignore (git [ "-C"; path; "add"; "." ]);
20
Added:
ignore
21
Added:
(git [ "-C"; path; "commit"; "-q"; "-m"; Printf.sprintf "c%d" index ])
22
Added:
done;
23
Added:
let config =
24
Added:
Ogit.Config.
25
Added:
{
26
Added:
default with
27
Added:
git_project_root = root;
28
Added:
commits_max_displayed = page_size;
29
Added:
}
30
Added:
in
31
Added:
let request = Dream.test (Dream.router (Ogit.Handlers.routes config)) in
32
Added:
test (fun target ->
33
Added:
Dream.request ~target "" |> request |> Dream.body |> Lwt_main.run))
34
Added:
35
Added:
let count_occurrences needle haystack =
36
Added:
let nl = String.length needle and hl = String.length haystack in
37
Added:
let rec go index count =
38
Added:
if index + nl > hl then count
39
Added:
else if String.sub haystack index nl = needle then go (index + 1) (count + 1)
40
Added:
else go (index + 1) count
41
Added:
in
42
Added:
go 0 0
43
Added:
44
Added:
let commit_rows body = count_occurrences "commit-title" body
45
Added:
let has_link_to_page n body = count_occurrences (Printf.sprintf "page=%d" n) body > 0
46
Added:
47
Added:
(* 25 commits at page size 10: the first page holds exactly the page size and
48
Added:
links onward. *)
49
Added:
let test_first_page () =
50
Added:
with_repository_of ~commits:25 ~page_size:10 (fun body ->
51
Added:
let page = body "/project/commits/" in
52
Added:
Alcotest.(check int) "rows" 10 (commit_rows page);
53
Added:
Alcotest.(check bool) "next link" true (has_link_to_page 2 page))
54
Added:
55
Added:
(* The remaining 15 commits fit within page size + 10, so the second page
56
Added:
absorbs them all rather than leaving a five-commit final page. *)
57
Added:
let test_orphan_absorption () =
58
Added:
with_repository_of ~commits:25 ~page_size:10 (fun body ->
59
Added:
let page = body "/project/commits/?page=2" in
60
Added:
Alcotest.(check int) "absorbed rows" 15 (commit_rows page);
61
Added:
Alcotest.(check bool) "no next link" false (has_link_to_page 3 page))
62
Added:
63
Added:
(* 35 commits at page size 10: 25 remain after page one, beyond the
64
Added:
absorption window of page size + 10, so page two stays at the page size and
65
Added:
links onward. *)
66
Added:
let test_beyond_absorption_window () =
67
Added:
with_repository_of ~commits:35 ~page_size:10 (fun body ->
68
Added:
let page = body "/project/commits/?page=2" in
69
Added:
Alcotest.(check int) "rows" 10 (commit_rows page);
70
Added:
Alcotest.(check bool) "next link" true (has_link_to_page 3 page))
71
Added:
72
Added:
(* Exactly page size + 10 remaining is the widest page absorption produces:
73
Added:
with 30 commits, the 20 left after page one all land on page two. *)
74
Added:
let test_absorption_window_boundary () =
75
Added:
with_repository_of ~commits:30 ~page_size:10 (fun body ->
76
Added:
let page = body "/project/commits/?page=2" in
77
Added:
Alcotest.(check int) "rows" 20 (commit_rows page);
78
Added:
Alcotest.(check bool) "no next link" false (has_link_to_page 3 page))
79
Added:
80
Added:
(* Fewer commits than one page: a single page and no pagination links. *)
81
Added:
let test_single_page () =
82
Added:
with_repository_of ~commits:4 ~page_size:10 (fun body ->
83
Added:
let page = body "/project/commits/" in
84
Added:
Alcotest.(check int) "rows" 4 (commit_rows page);
85
Added:
Alcotest.(check bool) "no next link" false (has_link_to_page 2 page))
86
Added:
87
Added:
(* A page number beyond the history renders an empty list, not an error. *)
88
Added:
let test_page_past_end () =
89
Added:
with_repository_of ~commits:4 ~page_size:10 (fun body ->
90
Added:
let page = body "/project/commits/?page=9" in
91
Added:
Alcotest.(check int) "no rows" 0 (commit_rows page))
92
Added:
93
Added:
let suite =
94
Added:
( "pagination",
95
Added:
[
96
Added:
Alcotest.test_case "first page" `Slow test_first_page;
97
Added:
Alcotest.test_case "orphan absorption" `Slow test_orphan_absorption;
98
Added:
Alcotest.test_case "beyond absorption window" `Slow
99
Added:
test_beyond_absorption_window;
100
Added:
Alcotest.test_case "absorption window boundary" `Slow
101
Added:
test_absorption_window_boundary;
102
Added:
Alcotest.test_case "single page" `Slow test_single_page;
103
Added:
Alcotest.test_case "page past end" `Slow test_page_past_end;
104
Added:
] )