completions_test.go

  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}