diff options
Diffstat (limited to 'test/test_rubrics_of.ml')
| -rw-r--r-- | test/test_rubrics_of.ml | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/test/test_rubrics_of.ml b/test/test_rubrics_of.ml new file mode 100644 index 0000000..db8744a --- /dev/null +++ b/test/test_rubrics_of.ml @@ -0,0 +1,307 @@ +(* IGMR n. 53 (Gloria), n. 67-68 (Symbolum/Credo), n. 364-365 and the + Missale's own INDEX PRAEFATIONUM -- see lib/rites/rite_of/rubrics_of.ml + for every rubric quoted in full and every branch's own citation. The OF + counterpart of test/test_rubrics_ef.ml; one Alcotest case per rule. + + NO FULL OF [Colitur_kernel.Rite.t]/[Calendar] EXISTS YET (this task + supplies three of its four remaining fields; wiring them into a bundle + is a later task) -- so, unlike test_rubrics_ef.ml, there is no + `Cal.day`-style pipeline here to resolve a real SANCTORAL winner for a + date. Every TEMPORAL-origin row below therefore builds [temporal] (and, + where the temporal cycle's own office IS what is observed -- an ordinary + Sunday, a feria, a named solemnity like the Nativity) also [observed], + via [Temporal_of.temporal] on a real calendar date -- genuine Phase 1 + output, per the task brief. Every SANCTORAL-origin row (a saint's + solemnity/feast/memorial outranking the day's own temporal office) + builds [temporal] the same way, via a real date, but necessarily builds + [observed] directly -- there is no pipeline yet to derive it. It is + NOT fabricated: every field is copied verbatim from the matching real + entry in data/of/calendar-2002.sexp (cited at each site), the same + "grounded in shipped data, not invented" discipline + test/test_precedence_of.ml's own [mk_cel] already establishes for this + rite. *) + +module RO = Rite_of.Rubrics_of +module T = Rite_of.Temporal_of +module D = Colitur_kernel.Date +module Cel = Colitur_kernel.Celebration +module Colour = Colitur_kernel.Colour +module Subject = Colitur_kernel.Subject +module Slug = Colitur_kernel.Slug +module Vocab_of = Rite_of.Vocab_of + +let mk y m d = match D.make ~year:y ~month:m ~day:d with Ok t -> t | Error e -> failwith e + +(* A real, shipped sanctoral entry (data/of/calendar-2002.sexp), reused + directly as [observed] where no full precedence pipeline exists yet to + derive it. Status defaults to [Feast] (every entry cited below is). *) +let mk_saint ~slug ~rank ~colour ~subject () = + Cel.make ~slug:(Slug.of_string_exn slug) ~rank ~colour ~subject ~layer:"of-universal" () + +(* ---- Gloria (IGMR n. 53) ---- *) + +let gloria_on_temporal y m d = + let temporal = T.temporal (mk y m d) in + RO.gloria ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d) + +let check_gloria name expected actual = Alcotest.(check bool) name expected actual + +let test_gloria_advent_sunday () = + (* 2026-12-06: the Second Sunday of Advent -- confirmed via `date -d`, + [Temporal_of.temporal] tags it [Vocab_of.Sollemnitas] (the privileged- + season convention, temporal_of.ml's own [build]), which is exactly the + trap n. 53's own "extra tempus Adventus" clause exists to catch: a + naive [observed.rank = Sollemnitas] test would wrongly say [true]. *) + check_gloria "no Gloria on a Sunday of Advent" false (gloria_on_temporal 2026 12 6) + +let test_gloria_lent_sunday () = + (* 2026-03-01: the First Sunday of Lent 2026 (Ash Wednesday 2026-02-18, + Easter 2026-04-05). Same trap as Advent, the other of n. 53's two + named exceptions. *) + check_gloria "no Gloria on a Sunday of Lent" false (gloria_on_temporal 2026 3 1) + +let test_gloria_palm_sunday () = + (* 2026-03-29: Palm Sunday, [Vocab_of.Lent], [Sollemnitas] (named's own + entry) -- confirms the Advent/Lent exclusion above also correctly + reaches a NAMED solemnity, not only a plain numbered Sunday of Lent, + matching real, uncontested practice (Palm Sunday's Mass has no + Gloria). *) + check_gloria "no Gloria on Palm Sunday" false (gloria_on_temporal 2026 3 29) + +let test_gloria_ordinary_sunday () = + (* 2026-01-25: an Ordinary Time Sunday (Baptism of the Lord 2026-01-11, + so Ordinary Time begins 2026-01-12) -- [Festum]-ranked + (temporal_of.ml's own [build]), reaching [true] via n. 53's "diebus + dominicis" clause, not its "et festis" one (see the .ml's own + citation for why [temporal.weekday], not [observed.rank], is what + this must be read off). *) + check_gloria "Gloria on a Sunday per annum" true (gloria_on_temporal 2026 1 25) + +let test_gloria_christmas_sunday () = + (* 2026-12-27: a Sunday within the Octave of the Nativity (temporal_of.ml's + own [holy_family]/generic-Sunday dispatch), [Vocab_of.Christmas] + season, outside Advent/Lent -- a second, independent witness to the + same "diebus dominicis extra Adventum/Quadragesimam" clause as the + Ordinary Time row above, from the other season n. 53 does not + exclude. *) + check_gloria "Gloria on a Sunday of Christmas time" true (gloria_on_temporal 2026 12 27) + +let test_gloria_nativity () = + (* 2026-12-25: the Nativity itself -- [Vocab_of.Christmas], [Sollemnitas], + a Friday (never a Sunday that year), reaching [true] via n. 53's "in + sollemnitatibus" clause on its own, independent of the Sunday + clauses. Pure [Temporal_of.temporal] output; no sanctoral data + needed, since the Nativity IS the temporal cycle's own named office + (temporal_of.ml's own [named]). *) + check_gloria "Gloria on the Nativity" true (gloria_on_temporal 2026 12 25) + +let test_gloria_holy_thursday () = + (* 2026-04-02: the Mass of the Lord's Supper (Easter-3, Easter 2026-04-05) + -- Missale Romanum, "FERIA V IN CENA DOMINI" n. 7's own explicit + "Dicitur Gloria in excelsis" (see the .ml's own citation), overriding + what would otherwise be a Lenten-feria [false]. *) + check_gloria "Gloria at the Mass of the Lord's Supper" true (gloria_on_temporal 2026 4 2) + +let test_gloria_easter_vigil () = + (* 2026-04-04: the Easter Vigil (Easter-1) -- the same Missale rubric's + own "usque ad Gloria in excelsis Vigiliae paschalis", confirming this + Mass also has one. *) + check_gloria "Gloria at the Easter Vigil Mass" true (gloria_on_temporal 2026 4 4) + +let test_gloria_solemnity_in_lent () = + (* Saint Joseph, Husband of the Blessed Virgin Mary, 19 March -- + data/of/calendar-2002.sexp: (rank Sollemnitas) (colour White) + (subject Saint). 2026-03-19 is a Thursday (Easter 2026-04-05, so Holy + Week is 2026-03-29..04-04 -- this date sits safely inside ordinary + Lent, not impeded) -- confirmed, not assumed, via `date -d`. 19 March + is ALWAYS inside Lent by construction (Gregorian Easter's earliest + possible date is 22 March, so Ash Wednesday can never fall later than + 10 March; see the .ml's own citation). Exercises n. 53's "in + sollemnitatibus" clause on a genuine sanctoral winner, the one row in + this suite [observed] cannot come from [Temporal_of.temporal] alone + -- see this file's own header for why. *) + let temporal = T.temporal (mk 2026 3 19) in + let observed = + mk_saint ~slug:"joseph-husband-of-the-blessed-virgin-mary" ~rank:Vocab_of.Sollemnitas + ~colour:Colour.White ~subject:Subject.Saint () + in + check_gloria "Gloria: a solemnity falling in Lent" true + (RO.gloria ~temporal ~observed ~date:(mk 2026 3 19)) + +let test_gloria_festum () = + (* Chair of Saint Peter, apostle, 22 February -- data/of/calendar-2002.sexp: + (rank Festum) (colour White) (subject Saint). 2027-02-22 is a Monday + (checked via `date -d`, so this date is not itself impeded by an + Ordinary Time Sunday outranking a Festum, Tabula entries 6 vs 7). + Exercises n. 53's "et festis" clause specifically -- the one member of + n. 53's list that n. 68 (Credo, below) does NOT share. *) + let temporal = T.temporal (mk 2027 2 22) in + let observed = + mk_saint ~slug:"chair-of-saint-peter-apostle" ~rank:Vocab_of.Festum ~colour:Colour.White + ~subject:Subject.Saint () + in + check_gloria "Gloria on an ordinary Festum" true (RO.gloria ~temporal ~observed ~date:(mk 2027 2 22)) + +let test_gloria_obligatory_memorial () = + (* Saint Aloysius Gonzaga, religious, 21 June -- data/of/calendar-2002.sexp: + (rank Memoria_obligatoria) (colour White) (subject Saint). 2027-06-21 + is a Monday. Neither n. 53's "sollemnitatibus et festis" nor its + "dominicis" clause names a memorial. *) + let temporal = T.temporal (mk 2027 6 21) in + let observed = + mk_saint ~slug:"aloysius-gonzaga-religious" ~rank:Vocab_of.Memoria_obligatoria + ~colour:Colour.White ~subject:Subject.Saint () + in + check_gloria "no Gloria on a weekday obligatory memorial" false + (RO.gloria ~temporal ~observed ~date:(mk 2027 6 21)) + +let test_gloria_ordinary_feria () = + (* 2026-01-27: an Ordinary Time Tuesday feria, no memorial -- pure + [Temporal_of.temporal] output, [Vocab_of.Feria], [Subject.Temporal]. + Named by neither n. 53 clause. *) + check_gloria "no Gloria on a plain feria" false (gloria_on_temporal 2026 1 27) + +let test_gloria_all_souls () = + (* All Souls, 2 November -- data/of/calendar-2002.sexp: (rank Sollemnitas) + (colour Violet) (subject Saint) -- the ONE Requiem-shaped entity this + engine's shipped OF data can construct as [observed] + ({!Rite_of.Rubrics_of.requiem_slugs}). Its own [rank] is tagged + [Sollemnitas] by the extractor's own override (precedence_of.ml's own + citation), so WITHOUT the requiem guard n. 53's "in sollemnitatibus" + clause would wrongly grant it [true] -- exactly the case this guard + exists for. 2026-11-02 is a Monday. *) + let temporal = T.temporal (mk 2026 11 2) in + let observed = + mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint () + in + check_gloria "no Gloria on All Souls" false (RO.gloria ~temporal ~observed ~date:(mk 2026 11 2)) + +(* ---- Credo / Symbolum (IGMR n. 67-68) ---- *) + +let credo_on_temporal y m d = + let temporal = T.temporal (mk y m d) in + RO.credo ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d) + +let check_credo name expected actual = Alcotest.(check bool) name expected actual + +let test_credo_advent_sunday () = + (* Same date as [test_gloria_advent_sunday], deliberately: n. 68 carries + no Advent/Lent exception at all, unlike n. 53 -- this is [true] here + where the identical day was [false] for Gloria, proving the two rules + genuinely diverge rather than one silently mirroring the other. *) + check_credo "Credo on a Sunday of Advent (unlike Gloria)" true (credo_on_temporal 2026 12 6) + +let test_credo_ordinary_sunday () = + check_credo "Credo on a Sunday per annum" true (credo_on_temporal 2026 1 25) + +let test_credo_solemnity () = + (* Same St Joseph data as [test_gloria_solemnity_in_lent]. *) + let temporal = T.temporal (mk 2026 3 19) in + let observed = + mk_saint ~slug:"joseph-husband-of-the-blessed-virgin-mary" ~rank:Vocab_of.Sollemnitas + ~colour:Colour.White ~subject:Subject.Saint () + in + check_credo "Credo: a solemnity" true (RO.credo ~temporal ~observed ~date:(mk 2026 3 19)) + +let test_credo_festum () = + (* Same Chair-of-Peter data as [test_gloria_festum] -- [false] here, + unlike Gloria's [true] on the identical day: n. 68 has no "et festis" + clause at all (see the .ml's own citation), the single sharpest + divergence between this function and [gloria]. *) + let temporal = T.temporal (mk 2027 2 22) in + let observed = + mk_saint ~slug:"chair-of-saint-peter-apostle" ~rank:Vocab_of.Festum ~colour:Colour.White + ~subject:Subject.Saint () + in + check_credo "no Credo on an ordinary Festum (unlike Gloria)" false + (RO.credo ~temporal ~observed ~date:(mk 2027 2 22)) + +let test_credo_weekday_obligatory_memorial () = + let temporal = T.temporal (mk 2027 6 21) in + let observed = + mk_saint ~slug:"aloysius-gonzaga-religious" ~rank:Vocab_of.Memoria_obligatoria + ~colour:Colour.White ~subject:Subject.Saint () + in + check_credo "no Credo on a weekday obligatory memorial" false + (RO.credo ~temporal ~observed ~date:(mk 2027 6 21)) + +let test_credo_ordinary_feria () = + check_credo "no Credo on a plain feria" false (credo_on_temporal 2026 1 27) + +let test_credo_all_souls () = + let temporal = T.temporal (mk 2026 11 2) in + let observed = + mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint () + in + check_credo "no Credo on All Souls" false (RO.credo ~temporal ~observed ~date:(mk 2026 11 2)) + +(* ---- Preface -- always [None]; see rubrics_of.ml's own header for the + full INDEX-PRAEFATIONUM census this rests on. One row per major class, + proving the function was deliberately built to reject every shape + rather than merely left unimplemented. ---- *) + +let check_preface_none name y m d = + let temporal = T.temporal (mk y m d) in + Alcotest.(check bool) name true + (RO.preface ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:(mk y m d) = None) + +let test_preface_none_nativity () = check_preface_none "preface: Nativity is None" 2026 12 25 +let test_preface_none_ordinary_sunday () = check_preface_none "preface: Sunday per annum is None" 2026 1 25 +let test_preface_none_feria () = check_preface_none "preface: a plain feria is None" 2026 1 27 + +let test_preface_none_solemnity_with_own_single_text () = + (* Trinity Sunday -- exactly the "single OF text, same feast as an EF + constructor" near-miss the .ml's own header explains rejecting: still + [None], on purpose. *) + let easter = Colitur_kernel.Computus.gregorian_easter 2026 in + let trinity = D.add_days easter 56 in + let temporal = T.temporal trinity in + Alcotest.(check bool) "preface: Trinity Sunday is None" true + (RO.preface ~temporal ~observed:temporal.Colitur_kernel.Temporal.office ~date:trinity = None) + +let test_preface_none_all_souls () = + let temporal = T.temporal (mk 2026 11 2) in + let observed = + mk_saint ~slug:"all-souls" ~rank:Vocab_of.Sollemnitas ~colour:Colour.Violet ~subject:Subject.Saint () + in + Alcotest.(check bool) "preface: All Souls is None" true + (RO.preface ~temporal ~observed ~date:(mk 2026 11 2) = None) + +(* Population guard, mirroring test_rubrics_ef.ml's own + [test_colour_black_population_is_exactly_two]: fails loudly the day a + second Requiem-shaped slug is added anywhere in the shipped OF data + without this list being revisited. *) +let test_requiem_slugs_population () = + Alcotest.(check (list string)) "requiem_slugs is exactly {all-souls}" [ "all-souls" ] RO.requiem_slugs + +let suite = + ( "rubrics-of", + [ Alcotest.test_case "Gloria: no Gloria on a Sunday of Advent" `Quick test_gloria_advent_sunday; + Alcotest.test_case "Gloria: no Gloria on a Sunday of Lent" `Quick test_gloria_lent_sunday; + Alcotest.test_case "Gloria: no Gloria on Palm Sunday" `Quick test_gloria_palm_sunday; + Alcotest.test_case "Gloria: Gloria on a Sunday per annum" `Quick test_gloria_ordinary_sunday; + Alcotest.test_case "Gloria: Gloria on a Sunday of Christmas time" `Quick test_gloria_christmas_sunday; + Alcotest.test_case "Gloria: Gloria on the Nativity" `Quick test_gloria_nativity; + Alcotest.test_case "Gloria: Gloria at the Mass of the Lord's Supper" `Quick test_gloria_holy_thursday; + Alcotest.test_case "Gloria: Gloria at the Easter Vigil Mass" `Quick test_gloria_easter_vigil; + Alcotest.test_case "Gloria: a solemnity falling in Lent" `Quick test_gloria_solemnity_in_lent; + Alcotest.test_case "Gloria: an ordinary Festum" `Quick test_gloria_festum; + Alcotest.test_case "Gloria: no Gloria on a weekday obligatory memorial" `Quick + test_gloria_obligatory_memorial; + Alcotest.test_case "Gloria: no Gloria on a plain feria" `Quick test_gloria_ordinary_feria; + Alcotest.test_case "Gloria: no Gloria on All Souls" `Quick test_gloria_all_souls; + Alcotest.test_case "Credo: Credo on a Sunday of Advent" `Quick test_credo_advent_sunday; + Alcotest.test_case "Credo: Credo on a Sunday per annum" `Quick test_credo_ordinary_sunday; + Alcotest.test_case "Credo: a solemnity" `Quick test_credo_solemnity; + Alcotest.test_case "Credo: no Credo on an ordinary Festum" `Quick test_credo_festum; + Alcotest.test_case "Credo: no Credo on a weekday obligatory memorial" `Quick + test_credo_weekday_obligatory_memorial; + Alcotest.test_case "Credo: no Credo on a plain feria" `Quick test_credo_ordinary_feria; + Alcotest.test_case "Credo: no Credo on All Souls" `Quick test_credo_all_souls; + Alcotest.test_case "Preface: Nativity is None" `Quick test_preface_none_nativity; + Alcotest.test_case "Preface: Sunday per annum is None" `Quick test_preface_none_ordinary_sunday; + Alcotest.test_case "Preface: a plain feria is None" `Quick test_preface_none_feria; + Alcotest.test_case "Preface: Trinity Sunday is None" `Quick test_preface_none_solemnity_with_own_single_text; + Alcotest.test_case "Preface: All Souls is None" `Quick test_preface_none_all_souls; + Alcotest.test_case "requiem_slugs population is exactly {all-souls}" `Quick + test_requiem_slugs_population ] ) |
