aboutsummaryrefslogtreecommitdiff
path: root/internal/cli/cli_test.go
blob: de9220a75f3a4316758e743ca0c32bfa52817bd7 (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
package cli

import (
	"bytes"
	"strings"
	"testing"
)

func TestHelp(t *testing.T) {
	var out, errb bytes.Buffer
	code := Run([]string{"help"}, nil, &out, &errb)
	if code != 0 || !strings.Contains(out.String(), "lectio") {
		t.Errorf("help code=%d out=%q", code, out.String())
	}
}

func TestHelpFlag(t *testing.T) {
	var out, errb bytes.Buffer
	code := Run([]string{"-h"}, nil, &out, &errb)
	if code != 0 || !strings.Contains(out.String(), "lectio") {
		t.Errorf("-h code=%d out=%q", code, out.String())
	}
}

func TestUnexpectedPositional(t *testing.T) {
	var out, errb bytes.Buffer
	if code := Run([]string{"bogus"}, nil, &out, &errb); code != 2 {
		t.Errorf("unexpected positional code=%d want 2 (stderr=%q)", code, errb.String())
	}
}

func TestVersion(t *testing.T) {
	var out, errb bytes.Buffer
	if code := Run([]string{"--version"}, nil, &out, &errb); code != 0 {
		t.Errorf("version code=%d", code)
	}
}

// TestBadDate exercises the usage-error path for a malformed date: a token
// that doesn't match dateRe's YYYY-MM-DD shape is left as an unrecognised
// positional argument once flag parsing is done, which is a usage error.
func TestBadDate(t *testing.T) {
	t.Setenv("XDG_CONFIG_HOME", t.TempDir())
	var out, errb bytes.Buffer
	if code := Run([]string{"13-13-13"}, nil, &out, &errb); code != 2 {
		t.Errorf("bad date code=%d want 2 (stderr=%q)", code, errb.String())
	}
}

// TestTwoDatesIsAmbiguous exercises extractDate's ambiguity check: two
// dateRe-shaped tokens can't both be the positional date.
func TestTwoDatesIsAmbiguous(t *testing.T) {
	t.Setenv("XDG_CONFIG_HOME", t.TempDir())
	var out, errb bytes.Buffer
	if code := Run([]string{"2026-07-22", "2026-07-23"}, nil, &out, &errb); code != 2 {
		t.Errorf("two dates code=%d want 2 (stderr=%q)", code, errb.String())
	}
}

// TestUnknownVersion exercises -b/--bible's version-validation usage-error
// path without touching the network: the version code is checked against
// the five known codes before any fetch is attempted.
func TestUnknownVersion(t *testing.T) {
	t.Setenv("XDG_CONFIG_HOME", t.TempDir())
	var out, errb bytes.Buffer
	if code := Run([]string{"-b", "zzz"}, nil, &out, &errb); code != 2 {
		t.Errorf("unknown version code=%d want 2 (stderr=%q)", code, errb.String())
	}
}

// TestLectionaryTradMapsToTraditional exercises -l/--lectionary's trad ->
// traditional normalisation. It goes through extractDate/flag parsing and
// validation only (no network): a bogus date lets it exit before any fetch
// is attempted, but only after the -l value has already been validated and
// mapped, which is what this test checks indirectly via the exit code (a
// bad --lectionary would exit 2 for a different reason, so this alone
// wouldn't distinguish the two -- see TestLectionaryBogus for that side).
// Exercised directly against parseFlags below.
func TestLectionaryTradMapsToTraditional(t *testing.T) {
	lectionary, err := normalizeLectionary("trad")
	if err != nil {
		t.Fatalf("normalizeLectionary(trad): %v", err)
	}
	if lectionary != "traditional" {
		t.Errorf("normalizeLectionary(trad) = %q, want traditional", lectionary)
	}
}

func TestLectionaryBogus(t *testing.T) {
	t.Setenv("XDG_CONFIG_HOME", t.TempDir())
	var out, errb bytes.Buffer
	if code := Run([]string{"-l", "bogus"}, nil, &out, &errb); code != 2 {
		t.Errorf("bogus lectionary code=%d want 2 (stderr=%q)", code, errb.String())
	}
	if _, err := normalizeLectionary("bogus"); err == nil {
		t.Error("normalizeLectionary(bogus): want error, got nil")
	}
}

func TestLangBogus(t *testing.T) {
	t.Setenv("XDG_CONFIG_HOME", t.TempDir())
	var out, errb bytes.Buffer
	if code := Run([]string{"-g", "de"}, nil, &out, &errb); code != 2 {
		t.Errorf("bogus lang code=%d want 2 (stderr=%q)", code, errb.String())
	}
}

// TestDateTokenAnyPosition exercises extractDate directly: the date token
// is found regardless of where it appears among other flags.
func TestDateTokenAnyPosition(t *testing.T) {
	cases := [][]string{
		{"-a", "2026-07-22"},
		{"2026-07-22", "-a"},
		{"-a", "2026-07-22", "-r"},
	}
	for _, args := range cases {
		date, rest, err := extractDate(args)
		if err != nil {
			t.Fatalf("extractDate(%v): %v", args, err)
		}
		if date != "2026-07-22" {
			t.Errorf("extractDate(%v) date = %q, want 2026-07-22", args, date)
		}
		for _, r := range rest {
			if r == "2026-07-22" {
				t.Errorf("extractDate(%v) rest = %v still contains the date", args, rest)
			}
		}
	}
}

// TestExtractDateDefaultsToday checks that omitting a date token leaves
// today's date and passes every token through untouched.
func TestExtractDateDefaultsToday(t *testing.T) {
	date, rest, err := extractDate([]string{"-a", "-r"})
	if err != nil {
		t.Fatalf("extractDate: %v", err)
	}
	if date != today() {
		t.Errorf("extractDate date = %q, want today() = %q", date, today())
	}
	if len(rest) != 2 || rest[0] != "-a" || rest[1] != "-r" {
		t.Errorf("extractDate rest = %v, want [-a -r]", rest)
	}
}