summaryrefslogtreecommitdiff
path: root/internal/caldata/caldata_test.go
blob: 2ba052de51bd4de9007112c877396c4d6b6351db (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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
package caldata

import (
	"strings"
	"testing"

	"github.com/lukaszkasprzak/lectio/internal/calendar"
)

func TestUniversalLoads(t *testing.T) {
	l := Universal()
	if l.ID != "universal" {
		t.Fatalf("layer id = %q", l.ID)
	}
	// the Assumption (08-15, solemnity) must be present under some slug.
	assumption := false
	for _, rc := range l.Cels {
		if rc.Fields["date"] == "08-15" && rc.Fields["rank"] == "solemnity" {
			assumption = true
		}
	}
	if !assumption {
		t.Fatal("no 08-15 solemnity (Assumption) in the universal calendar")
	}
	// every entry must build into a typed Celebration whose rank parsed.
	for slug, rc := range l.Cels {
		c := calendar.BuildCelebrationForTest(slug, rc)
		if c.Rank == calendar.RankFerial && rc.Fields["rank"] != "" {
			t.Errorf("%s: rank did not parse (%q)", slug, rc.Fields["rank"])
		}
	}
}

func TestUniversalSanctoralReadings(t *testing.T) {
	l := Universal()
	// A feast/solemnity or a PROPER-reading memorial carries its own inline Mass.
	// Partial-proper memorials keep ONLY the strictly-proper part (the resolver
	// fills the rest from the ferial): the Guardian Angels a proper gospel, St
	// Barnabas a proper first reading, Our Lady of Sorrows a proper gospel.
	want := map[string][2]string{ // slug -> {reading.first substr, reading.gospel substr; "" = absent}
		"assumption-of-the-blessed-virgin-mary": {"Revelation 11", "Luke 1:39-56"},
		"all-saints":                            {"Revelation 7", "Matthew 5:1-12"},
		"barnabas-the-apostle":                  {"Acts 11", ""},
		"guardian-angels":                       {"", "Matthew 18:1-5"},
		"our-lady-of-sorrows":                   {"", "John 19:25-27"},
	}
	for slug, w := range want {
		rc, ok := l.Cels[slug]
		if !ok {
			t.Errorf("missing %q", slug)
			continue
		}
		for i, part := range []string{"reading.first", "reading.gospel"} {
			got := rc.Fields[part]
			if w[i] == "" {
				if got != "" {
					t.Errorf("%s: %s = %q, want absent (ferial)", slug, part, got)
				}
			} else if !strings.Contains(got, w[i]) {
				t.Errorf("%s: %s = %q, want containing %q", slug, part, got, w[i])
			}
		}
	}
}

func TestTridentineLoads(t *testing.T) {
	l := Tridentine()
	if l.ID != "tridentine" {
		t.Fatalf("layer id = %q", l.ID)
	}
	// The full 1962 sanctoral is populated (generated from missalemeum), not the
	// 16-entry bootstrap.
	if len(l.Cels) < 250 {
		t.Fatalf("EF sanctoral has only %d entries; expected the full calendar", len(l.Cels))
	}
	// Landmark feasts must be present with the right rank/class: Sts Peter & Paul
	// (I class, 06-29), the Purification/Presentation (II class, 02-02, class
	// = lord -- see classOf's own doc comment in scripts/gen-sanctoral-ef.go
	// for why: the calendarium's own title names the BLESSED VIRGIN, but the
	// tag exists to drive OCCURRENCE behaviour, and missalemeum -- this
	// data's own oracle -- shows the Purification taking a II-class Sunday's
	// place outright, the "festum Domini" pattern, not the ordinary-BVM
	// pattern), the Immaculate Conception (I class, 12-08).
	want := map[string]struct{ date, rank, class string }{
		"sts-peter-paul": {"06-29", "class-1", ""},
		"purification-of-the-blessed-virgin-mary":          {"02-02", "class-2", "lord"},
		"immaculate-conception-of-the-blessed-virgin-mary": {"12-08", "class-1", ""},
	}
	for slug, w := range want {
		rc, ok := l.Cels[slug]
		if !ok {
			t.Errorf("missing landmark feast %q", slug)
			continue
		}
		if rc.Fields["date"] != w.date || rc.Fields["rank"] != w.rank {
			t.Errorf("%s: date/rank = %q/%q, want %q/%q", slug, rc.Fields["date"], rc.Fields["rank"], w.date, w.rank)
		}
		if rc.Fields["class"] != w.class {
			t.Errorf("%s: class = %q, want %q", slug, rc.Fields["class"], w.class)
		}
	}
	// every entry must build into a typed Celebration whose rank parsed.
	for slug, rc := range l.Cels {
		c := calendar.BuildCelebrationForTest(slug, rc)
		if c.Rank == calendar.RankFerial && rc.Fields["rank"] != "" {
			t.Errorf("%s: rank did not parse (%q)", slug, rc.Fields["rank"])
		}
	}
}

// TestTridentineClassOfLord: scripts/gen-sanctoral-ef.go's classOf tags
// feasts of the Lord (class = lord). "Most Holy Name of Mary" was wrongly
// tagged lord (a title-substring false positive: "holy name" alone matches
// both "Holy Name of Jesus" and "Most Holy Name of MARY", calendarium
// "Sanctissimi Nominis Mariae") and is fixed here, not contested. The
// Baptism commemoration was missing the tag entirely (13 January's title
// ends "of THE Lord", not "of OUR Lord", the only suffix classOf used to
// check -- calendarium "IN COMMEMORATIONE BAPTISMATIS D. N. I. C."), also
// fixed and not contested. The Purification is DELIBERATELY absent from
// this table: it stays tagged lord, on occurrence-behaviour evidence, not
// the calendarium's title -- see TestTridentineLoads and classOf's own doc
// comment in scripts/gen-sanctoral-ef.go for the full account of why it is
// the one contested case, not a clean-cut fix.
func TestTridentineClassOfLord(t *testing.T) {
	l := Tridentine()
	want := map[string]string{
		"most-holy-name-of-mary":                   "", // BVM, not the Lord
		"commemoration-of-the-baptism-of-the-lord": "lord",
	}
	for slug, wantClass := range want {
		rc, ok := l.Cels[slug]
		if !ok {
			t.Errorf("missing landmark feast %q", slug)
			continue
		}
		if got := rc.Fields["class"]; got != wantClass {
			t.Errorf("%s: class = %q, want %q", slug, got, wantClass)
		}
	}
}

// TestTridentineLentRankNotCommemoration: 15 III-class feasts, every one
// falling 6 March - 5 April, were tagged rank = commemoration because all
// six of the generator's reference years happen to place that fixed date
// within Lent, where a privileged Lenten feria always outranks an
// equal-class feast (RG 109(e)) -- the generator mistook "how the entry
// presented on the sampled days" for the entry's true, intrinsic rank. A
// sample of the 15 (full list in the report); the calendarium gives each one
// III class outright, e.g. 5 April "S. Vincentii Ferrerii Conf., III
// classis."
func TestTridentineLentRankNotCommemoration(t *testing.T) {
	l := Tridentine()
	for _, slug := range []string{
		"sts-felicitas-perpetua", "thomas-aquinas", "frances-rome",
		"gregory-the-great", "patrick", "benedict", "francis-of-paola",
		"isidore-of-seville", "vincent-ferrer",
	} {
		rc, ok := l.Cels[slug]
		if !ok {
			t.Errorf("missing %q", slug)
			continue
		}
		if got := rc.Fields["rank"]; got != "class-3" {
			t.Errorf("%s: rank = %q, want class-3 (not commemoration)", slug, got)
		}
	}
}

// TestTridentineGenuineCommemorationStaysCommemoration: the fix above must
// NOT promote every commemoration-only entry indiscriminately -- some really
// are without an independent Mass in the 1960-reformed books, demoted even
// on an ORDINARY, unprivileged day (not explained by Lent/Passiontide or any
// higher-class temporal day). St Blaise (3 Feb, an ordinary Septuagesima-
// season feria) is live-confirmed by missalemeum as a mere commemoration
// even then, so RankCommemoration is the honest, correct rank -- promoting
// him to class-4 would let him wrongly win an ordinary green-season feria he
// has no independent Mass to celebrate.
func TestTridentineGenuineCommemorationStaysCommemoration(t *testing.T) {
	l := Tridentine()
	rc, ok := l.Cels["blaise"]
	if !ok {
		t.Fatal("missing blaise")
	}
	if got := rc.Fields["rank"]; got != "commemoration" {
		t.Errorf("blaise: rank = %q, want commemoration (genuinely no independent Mass)", got)
	}
}

// TestTridentineNoMissingEntries: four entries present in missalemeum (and
// so in the calendarium) were silently dropped by the generator's own
// dedup, which keyed a single global map by SLUG alone -- two by a literal
// slug collision with an unrelated feast of the same English title on a
// different date (28 January's "St. Agnes", the traditional SECOND
// commemoration of 21 January's own Agnes; 14 May's "St. Boniface", a
// different martyr from 5 June's Boniface of Mainz), and two (Evaristus,
// Theodore) by the generator returning on the first reference year that
// showed an observed office, before ever reaching the later year that
// revealed their commemoration.
func TestTridentineNoMissingEntries(t *testing.T) {
	l := Tridentine()
	want := map[string]string{
		"agnes-secundo":   "01-28",
		"boniface-martyr": "05-14",
		"evaristus":       "10-26",
		"theodore":        "11-09",
	}
	for slug, date := range want {
		rc, ok := l.Cels[slug]
		if !ok {
			t.Errorf("missing %q (%s)", slug, date)
			continue
		}
		if got := rc.Fields["date"]; got != date {
			t.Errorf("%s: date = %q, want %q", slug, got, date)
		}
	}
}

// TestTridentineRomanusAndEusebiusPresent: CORRECTED after review found the
// opposite claim resting on an incomplete primary-source check. An earlier
// version of this test (and of scripts/gen-sanctoral-ef.go's
// knownSpuriousComm) asserted Romanus ABSENT, on the strength of ONE of the
// three local Missal scans -- "1962-06-23,…LT.pdf", an ELECTRONIC
// TRANSCRIPTION that silently drops vigil commemorations generally (also
// missing: 7 August Donatus, 25 December Anastasia, both present elsewhere).
// The other two, PHOTOGRAPHIC scans of the actual 1962 Missale Romanum, both
// carry it: "missale-romanum-1962.pdf" calendarium, 9 August row: "XVI d V
// 9 Vigilia, III classis, Commemoratio S. Romani Mart.", with the saint's
// own proper text elsewhere in the same scan ("Et fit commemoratio S. Romani
// Mar-"), and its own back-of-book index ("Romani Mart., 9 augusti ... 621").
// Where the scans and the transcription disagree, the scans win -- see
// gen-sanctoral-ef.go's own primary-source note, same rule recorded there so
// it is not lost a second time.
//
// 14 August's "St. Eusebius" is the identical shape (calendarium: "XI b XIX
// 14 Vigilia, II classis, Commemoratio S. Eusebii Conf.") and a DIFFERENT
// person from 16 December's "St. Eusebius, Ep. et Mart." (calendarium: "V
// XVII S. Eusebii Ep. et Mart., III classis.") -- a Confessor and a Bishop
// and Martyr, not the same saint moved or duplicated. missalemeum gives both
// the same bare English title, so they collide by slug; slugOverride
// disambiguates rather than either being dropped.
func TestTridentineRomanusAndEusebiusPresent(t *testing.T) {
	l := Tridentine()
	if rc, ok := l.Cels["romanus"]; !ok {
		t.Error("romanus missing: the calendarium's photographic scans both carry \"Commemoratio S. Romani Mart.\" on 9 August")
	} else if rc.Fields["date"] != "08-09" {
		t.Errorf("romanus: date = %q, want 08-09", rc.Fields["date"])
	}
	want := map[string]string{
		"eusebius-confessor": "08-14", // "S. Eusebii Conf." -- a Confessor
		"eusebius":           "12-16", // "S. Eusebii Ep. et Mart." -- a Bishop and Martyr, a different person
	}
	for slug, date := range want {
		rc, ok := l.Cels[slug]
		if !ok {
			t.Errorf("missing %q (%s)", slug, date)
			continue
		}
		if rc.Fields["date"] != date {
			t.Errorf("%s: date = %q, want %q", slug, rc.Fields["date"], date)
		}
	}
}

// TestTridentineNoTransferArtifacts: a movable-transfer feast displayed on
// whatever civil date it actually landed on in a given reference year (St
// Joseph pushed to 20 March by a Sunday of Lent; the Annunciation deferred
// past Holy Week; All Souls moved to the Monday; St Matthias shown on the
// 25th in a leap year) must not leak into the sanctoral as a phantom
// fixed-date entry keyed to that transferred civil date.
func TestTridentineNoTransferArtifacts(t *testing.T) {
	l := Tridentine()
	for _, slug := range []string{
		"joseph-spouse-of-the-bl-virgin-mary-0320",
		"annunciation-of-the-blessed-virgin-mary-0405",
		"annunciation-of-the-blessed-virgin-mary-0408",
		"annunciation-of-the-blessed-virgin-mary-0409",
		"commemoration-of-all-souls-1103",
		"matthias-0225",
	} {
		if _, ok := l.Cels[slug]; ok {
			t.Errorf("phantom transfer-artifact entry %q present", slug)
		}
	}
}

// TestTridentineNamesPreservedAcrossRegeneration is the coverage guard a
// regeneration silently destroying a whole language's names needed and did
// not have: an earlier version of scripts/gen-sanctoral-ef.go's main()
// preserved existing name.la values across a regeneration (missalemeum
// supplies English only) but had no equivalent for name.pl -- and, because
// the bootstrapped file has in fact never carried a name.la value, that
// mechanism looked correct while doing nothing at all. A regeneration
// deleted all 322 Polish names outright (name.pl count 322 -> 0), silently:
// no test here asserted anything about a name.* field, and
// `len(l.Cels) < 250` (TestTridentineLoads) does not notice a field going
// missing within entries that still exist. `naming.CelebrationName`'s own
// name[lang] -> name.en fallback (internal/naming/naming.go) then quietly
// substituted English for Polish on every EF display in that language,
// reaching mobile.Day(date, "ef", version, "pl") -- a shipped dlectio entry
// point -- with no error anywhere in the chain.
//
// Checks both a broad coverage floor (not exactly 322: a handful of entries
// added by this same task's own fixes -- St Agnes secundo, St Boniface
// Martyr, St Evaristus, St Theodore, St Romanus, St Eusebius Confessor --
// never had a curated Polish name to preserve in the first place, so 322 is
// not achievable) and one specific, checkable value (the coordinator's own
// example) so a coverage-only guard cannot itself be satisfied by silently
// wrong values.
func TestTridentineNamesPreservedAcrossRegeneration(t *testing.T) {
	l := Tridentine()
	n := 0
	for _, rc := range l.Cels {
		if rc.Fields["name.pl"] != "" {
			n++
		}
	}
	if n < 315 {
		t.Errorf("name.pl coverage = %d entries, want >= 315 (was 322 before any of this task's fixes; a regeneration must preserve it, not merely not-crash)", n)
	}
	rc, ok := l.Cels["assumption-of-the-blessed-virgin-mary"]
	if !ok {
		t.Fatal("missing assumption-of-the-blessed-virgin-mary")
	}
	if got := rc.Fields["name.pl"]; got != "Wniebowzięcie N. M. P." {
		t.Errorf("assumption-of-the-blessed-virgin-mary: name.pl = %q, want the preserved Polish name", got)
	}
}