summaryrefslogtreecommitdiff
path: root/test/test_lectionary.ml
blob: 8fcf8e9d647c28cc7c27fc39fc2844d7153c6230 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
open Colitur_kernel

let slug = Slug.of_string_exn
let cit part reference = { Citation.part; reference }

let entry s cs = (slug s, cs)

let ok_exn = function Ok x -> x | Error e -> Alcotest.fail e

(* Stdlib-only substring search -- Astring is not among the frozen deps. *)
let contains_substring ~needle haystack =
  let hn = String.length needle and hh = String.length haystack in
  let rec go i = i + hn <= hh && (String.sub haystack i hn = needle || go (i + 1)) in
  hn = 0 || go 0

let test_find_present () =
  let l =
    ok_exn
      (Lectionary.of_entries
         [ entry "ef-advent-sunday-1"
             [ cit Citation.First "Rom 13:11-14"; cit Citation.Gospel "Luke 21:25-33" ] ])
  in
  match Lectionary.find l (slug "ef-advent-sunday-1") with
  | Some [ a; b ] ->
      Alcotest.(check string) "epistle" "Rom 13:11-14" a.Citation.reference;
      Alcotest.(check string) "gospel" "Luke 21:25-33" b.Citation.reference
  | Some _ -> Alcotest.fail "expected exactly two citations"
  | None -> Alcotest.fail "slug not found"

let test_find_absent () =
  let l = ok_exn (Lectionary.of_entries []) in
  Alcotest.(check bool) "absent" true
    (Lectionary.find l (slug "ef-advent-sunday-1") = None)

let test_duplicate_slug_rejected () =
  let e = entry "ef-advent-sunday-1" [ cit Citation.First "Rom 13:11-14" ] in
  match Lectionary.of_entries [ e; e ] with
  | Ok _ -> Alcotest.fail "duplicate slug must be rejected"
  | Error msg ->
      (* Strengthened beyond the brief's "non-empty and contains 'e'" check:
         the interface doc promises the error names the slug, so assert the
         offending slug string actually appears in the message. *)
      Alcotest.(check bool) "names the slug" true
        (contains_substring ~needle:"ef-advent-sunday-1" msg)

let test_sexp_round_trip () =
  let l =
    ok_exn
      (Lectionary.of_entries
         [ entry "ef-lent-1-monday"
             [ cit Citation.First "Ezech 34:11-16"; cit Citation.Gospel "Matt 25:31-46" ] ])
  in
  let l' = Lectionary.t_of_sexp (Lectionary.sexp_of_t l) in
  Alcotest.(check bool) "round trips" true (l = l')

let with_temp_file contents f =
  let path = Filename.temp_file "lectionary_test" ".sexp" in
  let oc = open_out path in
  output_string oc contents;
  close_out oc;
  Fun.protect ~finally:(fun () -> Sys.remove path) (fun () -> f path)

let load_is_error contents what =
  with_temp_file contents (fun path ->
      match Lectionary.load path with
      | Error _ -> ()
      | Ok _ -> Alcotest.fail (what ^ ": expected Error, got Ok"))

let test_load_never_raises () =
  load_is_error "((ef-advent-sunday-1 (" "unterminated list";
  load_is_error "((ef-advent-sunday-1 ((part First) (reference \"x" "unterminated string";
  load_is_error "" "empty file";
  load_is_error "() ()" "more than one sexp";
  load_is_error "not-a-list" "wrong shape";
  load_is_error "((\"NOT A SLUG\" ()))" "invalid slug";
  match Lectionary.load "/nonexistent/path/lectionary.sexp" with
  | Error _ -> ()
  | Ok _ -> Alcotest.fail "missing file: expected Error, got Ok"

let test_ef_data_file_loads () =
  match Lectionary.load "../data/ef/lectionary.sexp" with
  | Error e -> Alcotest.fail e
  | Ok l ->
      (* 119 lectio ini sections, translated/widened/renamed into colitur's
         own Temporal_ef vocabulary, PLUS hand-authored/derived entries
         (tools/bootstrap_lectionary.ml's own [colitur_keys]/
         [vigil_entries]/[holy_week_entries]/[nativity_octave_entries]/
         [derived_entries] comments have the full account, task 8, branch
         ef-lectionary, fix round 1): "ef-christmas-sunday-0" widens into
         itself PLUS "ef-holy-name-sunday" (+1); the six Passiontide/Lent-
         Ember sections translate/rename 1:1 (no count change: Passiontide
         no longer widens into two colitur keys since fix round 1's
         Critical 1 correction, and the three Lent Ember sections rename
         rather than widen); one hand-authored entry sourced from lectio's
         SANCTORAL calendar, not this lectionary ini ([ef-nativity-vigil],
         +1); four hand-authored directly from the Missal, Holy Week
         (+4); three hand-authored directly from the Missal, the fixed
         Nativity-Octave days (+3); one entry derived from an
         already-translated one rather than re-typed ([ef-holy-name], +1).
         119 + 1 + 1 + 4 + 3 + 1 = 129. *)
      Alcotest.(check int) "entry count" 129
        (List.length (Lectionary.entries l));
      (match Lectionary.find l (slug "ef-lent-1-monday") with
       | Some [ a; b ] ->
           Alcotest.(check string) "epistle" "Ezech 34:11-16" a.Citation.reference;
           Alcotest.(check string) "gospel" "Matt 25:31-46" b.Citation.reference
       | Some _ -> Alcotest.fail "expected exactly two citations"
       | None -> Alcotest.fail "ef-lent-1-monday missing");
      (* Task 8's own renamed/derived/hand-authored keys, spot-checked here
         so a future regeneration that silently drops one of them fails
         loudly and locally, not only via the much bigger differential
         suite. *)
      let check_entry name first gospel =
        match Lectionary.find l (slug name) with
        | Some [ a; b ] ->
            Alcotest.(check string) (name ^ ": epistle") first a.Citation.reference;
            Alcotest.(check string) (name ^ ": gospel") gospel b.Citation.reference
        | Some _ -> Alcotest.fail (name ^ ": expected exactly two citations")
        | None -> Alcotest.fail (name ^ ": missing")
      in
      check_entry "ef-holy-name-sunday" "Gal 4:1-7" "Luke 2:33-40";
      check_entry "ef-christmas-sunday-0" "Gal 4:1-7" "Luke 2:33-40";
      check_entry "ef-holy-name" "Gal 4:1-7" "Luke 2:33-40";
      check_entry "ef-nativity-vigil" "Rom 1:1-6" "Matt 1:18-21";
      check_entry "ef-pentecost-ember-wed" "Acts 5:12-16" "John 6:44-52.";
      check_entry "ef-pentecost-ember-fri" "Joel 2:23-24; 26-27" "Luke 5:17-26";
      check_entry "ef-pentecost-ember-sat" "Rom 5:1-5." "Luke 4:38-44.";
      (* Critical 2 fix (coordinator review round 1): the Lenten Ember days,
         previously dead keys under lectio's own "ef-lent-1-<weekday>"
         naming -- now reachable under colitur's own slugs. *)
      check_entry "ef-lent-ember-wed" "3 Kgs. 19:3-8" "Matt 12:38-50";
      (* Passiontide widens into Passion week ONLY now (Critical 1 fix) --
         "ef-passiontide-1-monday" keeps lectio's own citation;
         "ef-passiontide-2-monday" (Holy Monday) is Missal-sourced,
         DIFFERENT text, hand-authored in [holy_week_entries]. *)
      check_entry "ef-passiontide-1-monday" "Jonas 3:1-10" "John 7:32-39";
      check_entry "ef-passiontide-2-monday" "Isai. 50, 5-10" "Io. 12, 1-9";
      check_entry "ef-passiontide-2-tuesday" "Ier. 11, 18-20" "Mark 14, 32-72; 15, 1-46";
      check_entry "ef-passiontide-2-thursday" "1 Cor. 11, 20-32" "Io. 13, 1-15";
      check_entry "ef-passiontide-2-saturday" "Col. 3, 1-4" "Matt. 28, 1-7";
      (* Important 3(a) fix (coordinator review round 1): the fixed
         Nativity-Octave days now carry their own direct Missal formulary
         ("Diebus infra octavam Nativitatis Domini"), not Holy Name
         Sunday's (a first guess, measured wrong, reverted -- see
         [nativity_octave_entries]'s own comment). *)
      check_entry "ef-nativity-octave-day-5" "Tit. 3, 4-7" "Luc. 2, 15-20";
      check_entry "ef-nativity-octave-day-6" "Tit. 3, 4-7" "Luc. 2, 15-20";
      check_entry "ef-nativity-octave-day-7" "Tit. 3, 4-7" "Luc. 2, 15-20";
      (* These three MUST stay absent -- Holy Wednesday and Good Friday are
         deliberately not hand-authored (multi-lesson liturgies, no single
         Epistle slot; see [holy_week_entries]'s own comment for why), and
         Lent's OWN "ef-lent-1-wednesday/friday/saturday" keys must not
         reappear once renamed away (colitur never computes them as its
         own slug at all). A regression net for those decisions, not
         merely documentation of them. *)
      let check_absent name =
        match Lectionary.find l (slug name) with
        | None -> ()
        | Some _ -> Alcotest.fail (name ^ ": must stay absent -- see bootstrap_lectionary.ml")
      in
      check_absent "ef-passiontide-2-wednesday";
      check_absent "ef-passiontide-2-friday";
      check_absent "ef-lent-1-wednesday"

let suite =
  [ ("find present", `Quick, test_find_present);
    ("find absent", `Quick, test_find_absent);
    ("duplicate slug rejected", `Quick, test_duplicate_slug_rejected);
    ("sexp round-trip", `Quick, test_sexp_round_trip);
    ("load never raises", `Quick, test_load_never_raises);
    ("ef data file loads", `Quick, test_ef_data_file_loads) ]