1package cmd
2
3import (
4 "os"
5 "path/filepath"
6 "reflect"
7 "sort"
8 "testing"
9
10 "github.com/spf13/cobra"
11
12 "git.secluded.site/keld/internal/restic"
13)
14
15// setupCompletionConfig writes a small TOML fixture and points KELD_CONFIG_FILE
16// at it, so config.Presets() returns deterministic results.
17func setupCompletionConfig(t *testing.T) {
18 t.Helper()
19
20 dir := t.TempDir()
21 cfg := filepath.Join(dir, "config.toml")
22 err := os.WriteFile(cfg, []byte(`
23[global]
24verbose = true
25
26["home@"]
27host = "laptop"
28
29["@cloud"]
30repo = "/repos/cloud"
31
32["@nas"]
33repo = "/repos/nas"
34
35[archive]
36json = true
37`), 0o600)
38 if err != nil {
39 t.Fatalf("writing fixture config: %v", err)
40 }
41 t.Setenv("KELD_CONFIG_FILE", cfg)
42 t.Setenv("HOME", dir)
43}
44
45func TestCompletePresetOffersPrefixesAndPresets(t *testing.T) {
46 setupCompletionConfig(t)
47
48 completions, directive := completePreset(nil, nil, "")
49 if directive != cobra.ShellCompDirectiveNoFileComp {
50 t.Fatalf("expected NoFileComp, got %v", directive)
51 }
52
53 has := make(map[string]bool)
54 for _, c := range completions {
55 has[c] = true
56 }
57
58 for _, want := range []string{
59 "home@", // prefix
60 "archive", // plain preset
61 "home@cloud", // composite preset
62 "home@nas", // composite preset
63 "@cloud", // suffix preset
64 "@nas", // suffix preset
65 } {
66 if !has[want] {
67 t.Errorf("missing expected completion %q in %v", want, completions)
68 }
69 }
70}
71
72func TestCompletePresetSuffixCompletion(t *testing.T) {
73 setupCompletionConfig(t)
74
75 completions, directive := completePreset(nil, nil, "home@")
76 if directive != cobra.ShellCompDirectiveNoFileComp {
77 t.Fatalf("expected NoFileComp, got %v", directive)
78 }
79
80 sort.Strings(completions)
81 want := []string{"home@cloud", "home@nas"}
82 if !reflect.DeepEqual(completions, want) {
83 t.Fatalf("suffix completions mismatch: got %v, want %v", completions, want)
84 }
85}
86
87func TestCompletePresetPartialSuffixCompletion(t *testing.T) {
88 setupCompletionConfig(t)
89
90 completions, directive := completePreset(nil, nil, "home@c")
91 if directive != cobra.ShellCompDirectiveNoFileComp {
92 t.Fatalf("expected NoFileComp, got %v", directive)
93 }
94
95 want := []string{"home@cloud"}
96 if !reflect.DeepEqual(completions, want) {
97 t.Fatalf("partial suffix completions mismatch: got %v, want %v", completions, want)
98 }
99}
100
101func TestCompletePresetPrefixFilter(t *testing.T) {
102 setupCompletionConfig(t)
103
104 completions, directive := completePreset(nil, nil, "arc")
105 if directive != cobra.ShellCompDirectiveNoFileComp {
106 t.Fatalf("expected NoFileComp, got %v", directive)
107 }
108
109 want := []string{"archive"}
110 if !reflect.DeepEqual(completions, want) {
111 t.Fatalf("prefix-filtered completions mismatch: got %v, want %v", completions, want)
112 }
113}
114
115func TestSubcommandsRegisteredFromResticMetadata(t *testing.T) {
116 t.Parallel()
117
118 subcommands := rootCmd.Commands()
119 if got, want := len(subcommands), len(restic.Commands)-1; got != want {
120 t.Fatalf("subcommand count mismatch: got %d, want %d", got, want)
121 }
122
123 seen := make(map[string]*cobra.Command, len(subcommands))
124 for _, subcmd := range subcommands {
125 seen[subcmd.Name()] = subcmd
126 }
127
128 if _, ok := seen["global"]; ok {
129 t.Fatal("global command should not be registered as a subcommand")
130 }
131
132 for name := range restic.Commands {
133 name := name
134 if name == "global" {
135 continue
136 }
137
138 subcmd, ok := seen[name]
139 if !ok {
140 t.Fatalf("missing subcommand %q", name)
141 }
142 if !subcmd.DisableFlagParsing {
143 t.Fatalf("subcommand %q should have DisableFlagParsing enabled", name)
144 }
145 }
146}
147
148func TestRegisteredRootAndSubcommandFlags(t *testing.T) {
149 t.Parallel()
150
151 for _, flagName := range []string{"preset", "show-command", "config", "repo", "verbose"} {
152 if rootCmd.PersistentFlags().Lookup(flagName) == nil {
153 t.Fatalf("missing expected root persistent flag %q", flagName)
154 }
155 }
156
157 if rootCmd.PersistentFlags().Lookup("dry-run") != nil {
158 t.Fatal("root should not expose a --dry-run flag")
159 }
160
161 if got := rootCmd.PersistentFlags().Lookup("verbose").Value.Type(); got != "count" {
162 t.Fatalf("expected --verbose to be a count flag, got type %q", got)
163 }
164
165 backup := rootCmd.Commands()[0]
166 for _, subcmd := range rootCmd.Commands() {
167 if subcmd.Name() == "backup" {
168 backup = subcmd
169 break
170 }
171 }
172
173 for _, flagName := range []string{"exclude", "dry-run"} {
174 if backup.Flags().Lookup(flagName) == nil {
175 t.Fatalf("backup subcommand missing expected command flag %q", flagName)
176 }
177 }
178
179 if backup.InheritedFlags().Lookup("repo") == nil {
180 t.Fatal("backup subcommand missing inherited global --repo flag")
181 }
182}
183
184func TestPresetFlagCompletionRegistered(t *testing.T) {
185 setupCompletionConfig(t)
186
187 completionFn, ok := rootCmd.GetFlagCompletionFunc("preset")
188 if !ok {
189 t.Fatal("root --preset completion function is not registered")
190 }
191
192 completions, directive := completionFn(rootCmd, nil, "home@")
193 if directive != cobra.ShellCompDirectiveNoFileComp {
194 t.Fatalf("expected NoFileComp, got %v", directive)
195 }
196
197 sort.Strings(completions)
198 want := []string{"home@cloud", "home@nas"}
199 if !reflect.DeepEqual(completions, want) {
200 t.Fatalf("completion function returned %v, want %v", completions, want)
201 }
202}