diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-27 21:37:47 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-27 21:37:47 +0200 |
| commit | f8c92f253693e269edbc76da9a99e65df7d6f699 (patch) | |
| tree | 0d3024bff169b404d6831422f813431d849913bd /internal/cli/feed_test.go | |
| parent | bec7c034aca15537900d74a7063d85c0f4deacae (diff) | |
| download | lectio-f8c92f253693e269edbc76da9a99e65df7d6f699.tar.gz lectio-f8c92f253693e269edbc76da9a99e65df7d6f699.zip | |
feat(cli): --format json|ical calendar emitters with range + validation
Diffstat (limited to 'internal/cli/feed_test.go')
| -rw-r--r-- | internal/cli/feed_test.go | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/internal/cli/feed_test.go b/internal/cli/feed_test.go new file mode 100644 index 0000000..efbcfc9 --- /dev/null +++ b/internal/cli/feed_test.go @@ -0,0 +1,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()) + } +} |
