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
|
package cli
import (
"bytes"
"encoding/json"
"path/filepath"
"strings"
"testing"
)
// feedTestConfig points LECTIO_CONFIG at a fresh, non-existent file in a
// temp dir so each test gets lectio's built-in defaults (Universal Roman
// Calendar, no user layers) regardless of the machine's real config.
func feedTestConfig(t *testing.T) {
t.Helper()
dir := t.TempDir()
t.Setenv("LECTIO_CONFIG", filepath.Join(dir, "config.ini"))
}
func TestFeedJSONRange(t *testing.T) {
feedTestConfig(t)
var out, errb bytes.Buffer
code := Run([]string{"--format", "json", "--from", "2026-01-01", "--to", "2026-01-03"}, nil, &out, &errb)
if code != 0 {
t.Fatalf("exit code %d (stderr=%q)", code, errb.String())
}
var payload struct {
Schema string `json:"schema"`
Days []struct {
Date string `json:"date"`
} `json:"days"`
}
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
t.Fatalf("stdout did not parse as JSON: %v\n%s", err, out.String())
}
if payload.Schema != "lectio.calendar/1" {
t.Errorf("schema = %q, want lectio.calendar/1", payload.Schema)
}
if len(payload.Days) != 3 {
t.Errorf("len(days) = %d, want 3", len(payload.Days))
}
}
func TestFeedICalYear(t *testing.T) {
feedTestConfig(t)
var out, errb bytes.Buffer
code := Run([]string{"--format", "ical", "--year", "2026"}, nil, &out, &errb)
if code != 0 {
t.Fatalf("exit code %d (stderr=%q)", code, errb.String())
}
got := out.String()
if !strings.HasPrefix(got, "BEGIN:VCALENDAR") {
end := len(got)
if end > 80 {
end = 80
}
t.Fatalf("output does not start with BEGIN:VCALENDAR: %q", got[:end])
}
if n := strings.Count(got, "BEGIN:VEVENT"); n != 365 {
t.Errorf("BEGIN:VEVENT count = %d, want 365 (2026 is not a leap year)", n)
}
}
func TestFeedInvertedRangeIsUsageError(t *testing.T) {
feedTestConfig(t)
var out, errb bytes.Buffer
code := Run([]string{"--format", "json", "--from", "2026-01-05", "--to", "2026-01-01"}, nil, &out, &errb)
if code != 2 {
t.Fatalf("exit code %d, want 2 (stdout=%q stderr=%q)", code, out.String(), errb.String())
}
}
func TestFeedYearOutOfDomainIsUsageError(t *testing.T) {
feedTestConfig(t)
var out, errb bytes.Buffer
code := Run([]string{"--format", "json", "--year", "1500"}, nil, &out, &errb)
if code != 2 {
t.Fatalf("exit code %d, want 2 (stdout=%q stderr=%q)", code, out.String(), errb.String())
}
}
// TestFeedSingleDayDefaultsToday exercises --format with no range flags at
// all: the positional DATE (given explicitly here) is used as a single-day
// range.
func TestFeedSingleDayFromPositionalDate(t *testing.T) {
feedTestConfig(t)
var out, errb bytes.Buffer
code := Run([]string{"2026-01-06", "--format", "json"}, nil, &out, &errb)
if code != 0 {
t.Fatalf("exit code %d (stderr=%q)", code, errb.String())
}
var payload struct {
Days []struct {
Date string `json:"date"`
} `json:"days"`
}
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
t.Fatalf("stdout did not parse as JSON: %v\n%s", err, out.String())
}
if len(payload.Days) != 1 || payload.Days[0].Date != "2026-01-06" {
t.Errorf("days = %+v, want a single 2026-01-06", payload.Days)
}
}
// TestFeedFromWithoutFormatIsUsageError: --from/--to/--year/--form only mean
// anything alongside --format; given alone they are a usage error rather
// than silently ignored.
func TestFeedFromWithoutFormatIsUsageError(t *testing.T) {
feedTestConfig(t)
var out, errb bytes.Buffer
code := Run([]string{"--from", "2026-01-01", "--to", "2026-01-03"}, nil, &out, &errb)
if code != 2 {
t.Fatalf("exit code %d, want 2 (stdout=%q stderr=%q)", code, out.String(), errb.String())
}
}
// TestFeedFormOverridesSelection: --form old switches the built calendar to
// the Extraordinary Form even when config defaults to the OF (new).
func TestFeedFormOverridesSelection(t *testing.T) {
feedTestConfig(t)
var out, errb bytes.Buffer
code := Run([]string{"--format", "json", "--form", "old", "2026-01-06"}, nil, &out, &errb)
if code != 0 {
t.Fatalf("exit code %d (stderr=%q)", code, errb.String())
}
var payload struct {
Form string `json:"form"`
}
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
t.Fatalf("stdout did not parse as JSON: %v\n%s", err, out.String())
}
if payload.Form != "old" {
t.Errorf("form = %q, want old", payload.Form)
}
}
func TestFeedBadFormatIsUsageError(t *testing.T) {
feedTestConfig(t)
var out, errb bytes.Buffer
code := Run([]string{"--format", "xml"}, nil, &out, &errb)
if code != 2 {
t.Fatalf("exit code %d, want 2 (stdout=%q stderr=%q)", code, out.String(), errb.String())
}
}
|