1package restic
2
3import (
4 "bytes"
5 "fmt"
6 "reflect"
7 "sort"
8 "strings"
9 "testing"
10
11 "git.secluded.site/keld/internal/config"
12)
13
14func TestCommandsSpotChecks(t *testing.T) {
15 t.Parallel()
16
17 tests := []struct {
18 name string
19 command string
20 optionName string
21 wantAlias string
22 wantRepeat bool
23 }{
24 {
25 name: "backup dry-run alias",
26 command: "backup",
27 optionName: "dry-run",
28 wantAlias: "n",
29 wantRepeat: false,
30 },
31 {
32 name: "backup exclude repeatable",
33 command: "backup",
34 optionName: "exclude",
35 wantAlias: "e",
36 wantRepeat: true,
37 },
38 {
39 name: "backup tag repeatable",
40 command: "backup",
41 optionName: "tag",
42 wantAlias: "",
43 wantRepeat: true,
44 },
45 {
46 name: "global repo alias",
47 command: "global",
48 optionName: "repo",
49 wantAlias: "r",
50 wantRepeat: false,
51 },
52 {
53 name: "global verbose alias",
54 command: "global",
55 optionName: "verbose",
56 wantAlias: "v",
57 wantRepeat: false,
58 },
59 }
60
61 for _, tt := range tests {
62 tt := tt
63 t.Run(tt.name, func(t *testing.T) {
64 t.Parallel()
65
66 cmd, ok := Commands[tt.command]
67 if !ok {
68 t.Fatalf("command %q missing from Commands map", tt.command)
69 }
70
71 opt, ok := findOption(cmd.Options, tt.optionName)
72 if !ok {
73 t.Fatalf("option %q missing from command %q", tt.optionName, tt.command)
74 }
75
76 if opt.Alias != tt.wantAlias {
77 t.Fatalf("alias mismatch for %s/%s: got %q, want %q", tt.command, tt.optionName, opt.Alias, tt.wantAlias)
78 }
79 if opt.Repeatable != tt.wantRepeat {
80 t.Fatalf("repeatable mismatch for %s/%s: got %v, want %v", tt.command, tt.optionName, opt.Repeatable, tt.wantRepeat)
81 }
82 })
83 }
84}
85
86func TestEveryCommandHasHelpOption(t *testing.T) {
87 t.Parallel()
88
89 commandNames := make([]string, 0, len(Commands))
90 for name := range Commands {
91 commandNames = append(commandNames, name)
92 }
93 sort.Strings(commandNames)
94
95 for _, name := range commandNames {
96 name := name
97 t.Run(name, func(t *testing.T) {
98 t.Parallel()
99
100 cmd := Commands[name]
101 if _, ok := findOption(cmd.Options, "help"); !ok {
102 t.Fatalf("command %q has no help option", name)
103 }
104 })
105 }
106}
107
108func TestWarnUnknownFlags(t *testing.T) {
109 t.Parallel()
110
111 tests := []struct {
112 name string
113 command string
114 flags []config.Flag
115 wantLines []string
116 }{
117 {
118 name: "known command and global flags are silent",
119 command: "backup",
120 flags: []config.Flag{
121 {Name: "--repo", Value: "/repo"},
122 {Name: "--tag", Value: "daily"},
123 {Name: "-n"},
124 },
125 wantLines: nil,
126 },
127 {
128 name: "unknown flags emit warnings once",
129 command: "backup",
130 flags: []config.Flag{
131 {Name: "--repo", Value: "/repo"},
132 {Name: "--unknown", Value: "x"},
133 {Name: "--unknown", Value: "y"},
134 {Name: "-Z"},
135 },
136 wantLines: []string{
137 `warning: unknown restic flag "--unknown" for command "backup"`,
138 `warning: unknown restic flag "-Z" for command "backup"`,
139 },
140 },
141 {
142 name: "empty command validates against global options",
143 command: "",
144 flags: []config.Flag{
145 {Name: "--repo", Value: "/repo"},
146 {Name: "--exclude", Value: "*.tmp"},
147 },
148 wantLines: []string{
149 `warning: unknown restic flag "--exclude" for command "global"`,
150 },
151 },
152 }
153
154 for _, tt := range tests {
155 tt := tt
156 t.Run(tt.name, func(t *testing.T) {
157 t.Parallel()
158
159 var stderr bytes.Buffer
160 warnUnknownFlags(&stderr, tt.command, tt.flags)
161
162 gotLines := splitLines(strings.TrimSpace(stderr.String()))
163 if !reflect.DeepEqual(gotLines, tt.wantLines) {
164 t.Fatalf("warnUnknownFlags() output mismatch:\n%s", fmtDiff(gotLines, tt.wantLines))
165 }
166 })
167 }
168}
169
170func findOption(options []Option, name string) (Option, bool) {
171 for _, option := range options {
172 if option.Name == name {
173 return option, true
174 }
175 }
176 return Option{}, false
177}
178
179func splitLines(s string) []string {
180 if s == "" {
181 return nil
182 }
183 return strings.Split(s, "\n")
184}
185
186func fmtDiff(got, want []string) string {
187 return fmt.Sprintf("got:\n%q\nwant:\n%q", got, want)
188}