-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathmain_test.go
More file actions
239 lines (216 loc) · 7.23 KB
/
main_test.go
File metadata and controls
239 lines (216 loc) · 7.23 KB
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
package main
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/cockroachdb/errors"
"github.com/cozystack/talm/pkg/commands"
"github.com/spf13/cobra"
)
// buildCommandHierarchy creates a cobra command hierarchy from a path like
// ["talm", "completion", "bash"] and returns the leaf command.
func buildCommandHierarchy(path []string) *cobra.Command {
if len(path) == 0 {
return nil
}
root := &cobra.Command{Use: path[0]}
parent := root
for _, name := range path[1:] {
child := &cobra.Command{Use: name}
parent.AddCommand(child)
parent = child
}
return parent
}
func TestIsCommandOrParent(t *testing.T) {
tests := []struct {
name string
cmdPath []string
names []string
expected bool
}{
{
name: "direct completion command",
cmdPath: []string{"talm", "completion"},
names: []string{"init", "completion"},
expected: true,
},
{
name: "completion bash subcommand",
cmdPath: []string{"talm", "completion", "bash"},
names: []string{"init", "completion"},
expected: true,
},
{
name: "init command",
cmdPath: []string{"talm", "init"},
names: []string{"init", "completion"},
expected: true,
},
{
name: "apply command should not match",
cmdPath: []string{"talm", "apply"},
names: []string{"init", "completion"},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
leaf := buildCommandHierarchy(tt.cmdPath)
result := isCommandOrParent(leaf, tt.names...)
if result != tt.expected {
t.Errorf("isCommandOrParent() = %v, want %v", result, tt.expected)
}
})
}
}
// snapshotConfigState captures and restores the package-level
// commands.Config and commands.GlobalArgs that loadConfig mutates.
// loadConfig writes to Config (yaml.Unmarshal) AND to GlobalArgs
// (the Talosconfig-fallback assignment), so both must be saved to
// avoid cross-test leakage.
func snapshotConfigState(t *testing.T) {
t.Helper()
savedConfig := commands.Config
savedArgs := commands.GlobalArgs
t.Cleanup(func() {
commands.Config = savedConfig
commands.GlobalArgs = savedArgs
})
}
// TestLoadConfig_InvalidApplyTimeoutReturnsError pins that a
// malformed `applyOptions.timeout` in the project Chart.yaml
// surfaces as a regular error (with operator-facing hint), not a
// runtime panic. main used to call `panic(err)` here, which
// crashed the talm process before cobra could format the error;
// the error path lets the surrounding command runner print a
// cleanly-wrapped failure instead.
func TestLoadConfig_InvalidApplyTimeoutReturnsError(t *testing.T) {
dir := t.TempDir()
chartPath := filepath.Join(dir, "Chart.yaml")
body := "apiVersion: v2\nname: test\nversion: 0.1.0\napplyOptions:\n timeout: \"this-is-not-a-duration\"\n"
if err := os.WriteFile(chartPath, []byte(body), 0o644); err != nil {
t.Fatalf("write Chart.yaml: %v", err)
}
snapshotConfigState(t)
err := loadConfig(chartPath)
if err == nil {
t.Fatal("expected error for malformed applyOptions.timeout, got nil")
}
if !strings.Contains(err.Error(), "applyOptions.timeout") {
t.Errorf("error message must mention applyOptions.timeout (the bad field); got: %v", err)
}
if !strings.Contains(err.Error(), "this-is-not-a-duration") {
t.Errorf("error message must echo the bad value so the operator can correlate; got: %v", err)
}
// The hint chain must keep an operator-actionable explanation
// of the expected format. Without it, the operator sees only
// "time: invalid duration" and has to guess the canonical form.
hints := errors.GetAllHints(err)
if len(hints) == 0 {
t.Errorf("expected at least one hint guiding the operator on the duration format; got bare error: %v", err)
}
combined := strings.Join(hints, "\n")
if !strings.Contains(combined, "duration") {
t.Errorf("hint chain must mention the duration format; got %q", combined)
}
}
// TestLoadConfig_ValidApplyTimeoutParses pins the success path:
// a well-formed duration parses into TimeoutDuration. Guards
// against a refactor that flips the polarity of the error branch
// or stops storing the parsed duration.
func TestLoadConfig_ValidApplyTimeoutParses(t *testing.T) {
dir := t.TempDir()
chartPath := filepath.Join(dir, "Chart.yaml")
body := "apiVersion: v2\nname: test\nversion: 0.1.0\napplyOptions:\n timeout: \"45s\"\n"
if err := os.WriteFile(chartPath, []byte(body), 0o644); err != nil {
t.Fatalf("write Chart.yaml: %v", err)
}
snapshotConfigState(t)
if err := loadConfig(chartPath); err != nil {
t.Fatalf("loadConfig: %v", err)
}
if got := commands.Config.ApplyOptions.TimeoutDuration; got.String() != "45s" {
t.Errorf("TimeoutDuration = %v, want 45s", got)
}
}
// TestLoadConfig_EmptyApplyTimeoutResolvesDefault pins that the
// default-string path also populates TimeoutDuration. Pre-existing
// on main: the parse used to live only in the else branch, so an
// empty applyOptions.timeout left TimeoutDuration at its zero
// value despite the Timeout string being filled with the default.
// The current shape parses unconditionally; this test guards a
// future refactor that splits the branches again.
func TestLoadConfig_EmptyApplyTimeoutResolvesDefault(t *testing.T) {
dir := t.TempDir()
chartPath := filepath.Join(dir, "Chart.yaml")
body := "apiVersion: v2\nname: test\nversion: 0.1.0\n"
if err := os.WriteFile(chartPath, []byte(body), 0o644); err != nil {
t.Fatalf("write Chart.yaml: %v", err)
}
snapshotConfigState(t)
if err := loadConfig(chartPath); err != nil {
t.Fatalf("loadConfig: %v", err)
}
if got := commands.Config.ApplyOptions.TimeoutDuration; got == 0 {
t.Errorf("TimeoutDuration is zero after loadConfig with empty applyOptions.timeout; the default-string path must parse the resolved default into the duration")
}
if got := commands.Config.ApplyOptions.Timeout; got == "" {
t.Errorf("Timeout string is empty after loadConfig; the default-string path must fill it from constants.ConfigTryTimeout")
}
}
func TestSkipConfigCommands(t *testing.T) {
tests := []struct {
name string
cmdPath []string
expected bool // true = should skip config loading
}{
{
name: "completion command",
cmdPath: []string{"talm", "completion"},
expected: true,
},
{
name: "completion bash",
cmdPath: []string{"talm", "completion", "bash"},
expected: true,
},
{
name: "completion zsh",
cmdPath: []string{"talm", "completion", "zsh"},
expected: true,
},
{
name: "__complete (cobra internal for shell autocompletion)",
cmdPath: []string{"talm", "__complete"},
expected: true,
},
{
name: "init command",
cmdPath: []string{"talm", "init"},
expected: true,
},
{
name: "apply command should load config",
cmdPath: []string{"talm", "apply"},
expected: false,
},
{
name: "template command should load config",
cmdPath: []string{"talm", "template"},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
leaf := buildCommandHierarchy(tt.cmdPath)
// This uses the actual skipConfigCommands from main.go
result := isCommandOrParent(leaf, skipConfigCommands...)
if result != tt.expected {
t.Errorf("skipConfigCommands check = %v, want %v (skipConfigCommands = %v)",
result, tt.expected, skipConfigCommands)
}
})
}
}