aboutsummaryrefslogtreecommitdiff
path: root/test/test_lectionary_ef.ml
blob: b1c9e7de971bac9a91dd6c70320c24231cef268c (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
open Colitur_kernel
open Rite_ef

(* Same pattern test_rite_ef.ml already uses: both [Calendar.year] and
   [Calendar.day] take the rite AND the sanctoral layer (Rite.t carries no
   layer of its own -- see rite_ef.mli's own note on why), so step 1's
   sanctoral-proper case needs the REAL data/ef/sanctoral.sexp +
   data/ef/adjustments.sexp loaded, not a bare [Rite_ef.context]. Relative
   to this test's own build directory (_build/default/test/); test/dune
   declares both as deps. *)
let sanctoral_path = "../data/ef/sanctoral.sexp"
let adjustments_path = "../data/ef/adjustments.sexp"
let lectionary_path = "../data/ef/lectionary.sexp"

let real_layer () =
  let layer =
    match Layer.load Vocab_ef.rank_of_sexp sanctoral_path with
    | Ok l -> l
    | Error e -> Alcotest.failf "%s: failed to load: %s" sanctoral_path e
  in
  let overlay =
    match Overlay.load Vocab_ef.rank_of_sexp adjustments_path with
    | Ok o -> o
    | Error e -> Alcotest.failf "%s: failed to load: %s" adjustments_path e
  in
  let layer, diagnostics = Overlay.apply layer overlay in
  Alcotest.(check (list string)) "the committed overlay applies cleanly, no diagnostics" []
    (List.map Overlay.diagnostic_to_string diagnostics);
  layer

(* [Rite_ef.context] takes [~lectionary] (fix round 1, coordinator review):
   the module used to load data/ef/lectionary.sexp itself, as a side effect
   of being linked, which meant a bare `dune build` produced a `colitur`
   that died at startup on EVERY subcommand -- including ones (`easter`)
   that touch no lectionary data at all -- the moment that file was absent
   from the default build target. Caller-supplied now, same as the
   sanctoral layer above. *)
let real_lectionary () =
  match Lectionary.load lectionary_path with
  | Ok l -> l
  | Error e -> Alcotest.failf "%s: failed to load: %s" lectionary_path e

(* [Calendar.day] (not [year]): the liturgical year "opening in civil year y"
   is Advent-anchored (RG 61), so [Calendar.year _ _ 2030] covers Advent 2030
   through November 2031 -- it would never contain 13 January 2030, which
   belongs to the liturgical year that opened in Advent 2029. [Calendar.day]
   finds the containing liturgical year itself; see calendar.mli. *)
let day y m d =
  let date = match Date.make ~year:y ~month:m ~day:d with
    | Ok x -> x | Error e -> Alcotest.fail e in
  Calendar.day (Rite_ef.context ~lectionary:(real_lectionary ())) (real_layer ()) date

let refs (ld : _ Liturgical_day.t) =
  List.map (fun c -> c.Citation.reference) ld.citations

(* Chain step 1: the observed celebration's own proper wins.

   NOT 2030: that is the one pinned collision year (test_golden.ml,
   [test_holy_family_excludes_baptism_2030]) where 13 January is itself the
   Holy Family Sunday and RG 112(a) excludes the Baptism commemoration
   entirely -- [observed] there is [ef-time-after-epiphany-sunday-1], not
   this slug, so it is the wrong year to exercise step 1 against. 2026 is an
   ordinary year (13 January a Tuesday, no Sunday collision), where the
   fixed Commemoration of the Baptism of the Lord (Class2, subject Lord,
   data/ef/sanctoral.sexp) is observed outright. *)
let test_step1_sanctoral_proper () =
  Alcotest.(check (list string))
    "13 January 2026, Commemoration of the Baptism of the Lord"
    [ "Isa 60:1-6"; "John 1:29-34" ]
    (refs (day 2026 1 13))

(* Chain step 2: the day's own temporal slug has a proper (Lent has one daily). *)
let test_step2_lenten_feria_has_its_own () =
  Alcotest.(check (list string))
    "Monday of the 1st week of Lent is not the Sunday's Mass"
    [ "Ezech 34:11-16"; "Matt 25:31-46" ]
    (refs (day 2026 2 23))

(* Fix round 1 (coordinator review, Important finding 2): neither test above
   actually distinguishes step 1 from step 2 -- both survive swapping the
   chain order. 13 January 2026's temporal slug (ef-time-after-epiphany-1-
   tuesday) has no lectionary entry at all, so a swapped chain falls through
   to the same []-then-sanctoral answer; 23 February 2026's OBSERVED
   celebration IS the temporal office (ef-lent-1-monday carries no sanctoral
   entry of its own), so [observed.citations] and the temporal-slug lookup
   are the same lookup wearing two names -- order is a no-op either way.

   19 March 2026 (St Joseph) genuinely needs step 1 to run FIRST: the
   observed celebration (Joseph, Class1, a real sanctoral entry with its own
   citations) and the day's own temporal slug (ef-lent-4-thursday, ALSO a
   real lectionary entry, with different citations) disagree. Verified
   directly against the real data (not transcribed): both value pairs below
   were read off the actual resolved day and the actual
   data/ef/lectionary.sexp entry, not assumed. *)
let test_step1_wins_over_a_competing_step2_entry () =
  Alcotest.(check (list string))
    "19 March 2026: Joseph's own proper wins over Lent 4 Thursday's, which the temporal slug also has"
    [ "Ecclus 45:1-6"; "Matt 1:18-21" ]
    (refs (day 2026 3 19))

(* Fix round 1 (coordinator review): the mirror-image pin for step 2 -- a day
   whose OBSERVED celebration carries no citations of its own (Holy Family,
   synthesised by [Temporal_ef] itself, not sourced from
   data/ef/sanctoral.sexp, so [Celebration.citations] is empty) falls
   through to the temporal slug, and the temporal slug's own lectionary
   entry is genuinely Holy Family's Mass, not the Baptism's -- RG 112(a)
   (see test_golden.ml's own [test_holy_family_excludes_baptism_2030])
   excludes the Baptism from this day entirely, so there is no sanctoral
   citation anywhere to fall back to even in principle. Verified directly
   against the real data. *)
let test_step2_holy_family_reached_through_temporal_slug () =
  Alcotest.(check (list string))
    "13 January 2030: Holy Family reached via the temporal slug, the Baptism entirely absent"
    [ "Col 3:12-17"; "Luke 2:42-52" ]
    (refs (day 2030 1 13))

let suite =
  [ ("step 1: sanctoral proper", `Quick, test_step1_sanctoral_proper);
    ("step 2: own temporal proper", `Quick, test_step2_lenten_feria_has_its_own);
    ("step 1 wins over a competing step 2 entry", `Quick,
      test_step1_wins_over_a_competing_step2_entry);
    ("step 2: Holy Family reached through the temporal slug, Baptism absent", `Quick,
      test_step2_holy_family_reached_through_temporal_slug) ]