list_snapshots_test.go

  1package restic
  2
  3import (
  4	"errors"
  5	"testing"
  6
  7	"git.secluded.site/keld/internal/config"
  8)
  9
 10func TestBuildSnapshotCmd(t *testing.T) {
 11	t.Parallel()
 12
 13	tests := []struct {
 14		name     string
 15		cfg      *config.ResolvedConfig
 16		wantArgv []string
 17		wantErr  bool
 18	}{
 19		{
 20			name: "basic repo flag",
 21			cfg: &config.ResolvedConfig{
 22				Command: "restore",
 23				Flags: []config.Flag{
 24					{Name: "--repo", Value: "/srv/backup"},
 25					{Name: "--target", Value: "/tmp/restore"},
 26				},
 27			},
 28			wantArgv: []string{"restic", "snapshots", "--json", "--repo", "/srv/backup"},
 29		},
 30		{
 31			name: "repo with password-file",
 32			cfg: &config.ResolvedConfig{
 33				Command: "restore",
 34				Flags: []config.Flag{
 35					{Name: "--repo", Value: "rclone:remote:/backup"},
 36					{Name: "--password-file", Value: "/etc/restic/pw"},
 37					{Name: "--target", Value: "/tmp/restore"},
 38					{Name: "--overwrite", Value: "if-changed"},
 39				},
 40			},
 41			wantArgv: []string{
 42				"restic", "snapshots", "--json",
 43				"--repo", "rclone:remote:/backup",
 44				"--password-file", "/etc/restic/pw",
 45			},
 46		},
 47		{
 48			name: "repo with cache-dir and no-lock",
 49			cfg: &config.ResolvedConfig{
 50				Command: "restore",
 51				Flags: []config.Flag{
 52					{Name: "--repo", Value: "/srv/backup"},
 53					{Name: "--cache-dir", Value: "/tmp/cache"},
 54					{Name: "--no-lock"},
 55					{Name: "--target", Value: "/tmp/restore"},
 56				},
 57			},
 58			wantArgv: []string{
 59				"restic", "snapshots", "--json",
 60				"--repo", "/srv/backup",
 61				"--cache-dir", "/tmp/cache",
 62				"--no-lock",
 63			},
 64		},
 65		{
 66			name: "repo via repository-file",
 67			cfg: &config.ResolvedConfig{
 68				Command: "restore",
 69				Flags: []config.Flag{
 70					{Name: "--repository-file", Value: "/etc/restic/repo"},
 71					{Name: "--target", Value: "/tmp/restore"},
 72				},
 73			},
 74			wantArgv: []string{
 75				"restic", "snapshots", "--json",
 76				"--repository-file", "/etc/restic/repo",
 77			},
 78		},
 79		{
 80			name: "multiple connection flags preserved",
 81			cfg: &config.ResolvedConfig{
 82				Command: "restore",
 83				Flags: []config.Flag{
 84					{Name: "--repo", Value: "s3:https://bucket/backup"},
 85					{Name: "--cacert", Value: "/etc/ssl/custom.pem"},
 86					{Name: "--insecure-tls"},
 87					{Name: "--limit-download", Value: "1024"},
 88					{Name: "--tls-client-cert", Value: "/etc/ssl/client.pem"},
 89					{Name: "--key-hint", Value: "abc123"},
 90					{Name: "--target", Value: "/tmp/restore"},
 91					{Name: "--exclude", Value: "*.log"},
 92				},
 93			},
 94			wantArgv: []string{
 95				"restic", "snapshots", "--json",
 96				"--repo", "s3:https://bucket/backup",
 97				"--cacert", "/etc/ssl/custom.pem",
 98				"--insecure-tls",
 99				"--limit-download", "1024",
100				"--tls-client-cert", "/etc/ssl/client.pem",
101				"--key-hint", "abc123",
102			},
103		},
104		{
105			name: "repo from environ only",
106			cfg: &config.ResolvedConfig{
107				Command: "restore",
108				Flags: []config.Flag{
109					{Name: "--target", Value: "/tmp/restore"},
110				},
111				Environ: map[string]string{
112					"RESTIC_REPOSITORY": "/srv/backup",
113				},
114			},
115			// No --repo flag needed; restic reads the env var directly.
116			wantArgv: []string{"restic", "snapshots", "--json"},
117		},
118		{
119			name: "password-command flag preserved",
120			cfg: &config.ResolvedConfig{
121				Command: "restore",
122				Flags: []config.Flag{
123					{Name: "--repo", Value: "/srv/backup"},
124					{Name: "--password-command", Value: "op read 'op://Vault/Backup/password'"},
125					{Name: "--target", Value: "/tmp/restore"},
126				},
127			},
128			wantArgv: []string{
129				"restic", "snapshots", "--json",
130				"--repo", "/srv/backup",
131				"--password-command", "op read 'op://Vault/Backup/password'",
132			},
133		},
134	}
135
136	for _, tt := range tests {
137		t.Run(tt.name, func(t *testing.T) {
138			t.Parallel()
139
140			argv, err := buildSnapshotCmd(tt.cfg)
141			if (err != nil) != tt.wantErr {
142				t.Fatalf("buildSnapshotCmd(): err=%v, wantErr=%v", err, tt.wantErr)
143			}
144			if err != nil {
145				return
146			}
147
148			if len(argv) != len(tt.wantArgv) {
149				t.Fatalf("argv length: got %d %v, want %d %v",
150					len(argv), argv, len(tt.wantArgv), tt.wantArgv)
151			}
152			for i := range argv {
153				if argv[i] != tt.wantArgv[i] {
154					t.Errorf("argv[%d]: got %q, want %q", i, argv[i], tt.wantArgv[i])
155				}
156			}
157		})
158	}
159}
160
161func TestBuildSnapshotCmdNoRepoSentinel(t *testing.T) {
162	// Not parallel: uses t.Setenv to control process environment.
163	t.Setenv("RESTIC_REPOSITORY", "")
164	t.Setenv("RESTIC_REPOSITORY_FILE", "")
165
166	cfg := &config.ResolvedConfig{
167		Command: "restore",
168		Flags: []config.Flag{
169			{Name: "--target", Value: "/tmp/restore"},
170		},
171	}
172
173	_, err := buildSnapshotCmd(cfg)
174	if !errors.Is(err, ErrNoRepo) {
175		t.Errorf("expected ErrNoRepo, got %v", err)
176	}
177}
178
179func TestBuildSnapshotCmdProcessEnv(t *testing.T) {
180	// Not parallel: uses t.Setenv to control process environment.
181	t.Setenv("RESTIC_REPOSITORY", "/srv/from-env")
182	t.Setenv("RESTIC_REPOSITORY_FILE", "")
183
184	cfg := &config.ResolvedConfig{
185		Command: "restore",
186		Flags: []config.Flag{
187			{Name: "--target", Value: "/tmp/restore"},
188		},
189	}
190
191	argv, err := buildSnapshotCmd(cfg)
192	if err != nil {
193		t.Fatalf("unexpected error: %v", err)
194	}
195
196	// No --repo flag in argv since repo comes from process env;
197	// restic reads it directly.
198	wantArgv := []string{"restic", "snapshots", "--json"}
199	if len(argv) != len(wantArgv) {
200		t.Fatalf("argv: got %v, want %v", argv, wantArgv)
201	}
202	for i := range argv {
203		if argv[i] != wantArgv[i] {
204			t.Errorf("argv[%d]: got %q, want %q", i, argv[i], wantArgv[i])
205		}
206	}
207}
208
209func TestCopyEnviron(t *testing.T) {
210	t.Parallel()
211
212	original := map[string]string{
213		"RESTIC_REPOSITORY":         "/srv/backup",
214		"RESTIC_PASSWORD_COMMAND":   "op read 'op://Vault/pw'",
215		"AWS_ACCESS_KEY_ID_COMMAND": "op read 'op://Vault/aws-key'",
216	}
217
218	copied := copyEnviron(original)
219
220	// Mutation of the copy must not affect the original.
221	copied["RESTIC_REPOSITORY"] = "changed"
222	delete(copied, "RESTIC_PASSWORD_COMMAND")
223	copied["NEW_KEY"] = "new_value"
224
225	if original["RESTIC_REPOSITORY"] != "/srv/backup" {
226		t.Error("original RESTIC_REPOSITORY was mutated")
227	}
228	if _, ok := original["RESTIC_PASSWORD_COMMAND"]; !ok {
229		t.Error("original RESTIC_PASSWORD_COMMAND was deleted")
230	}
231	if _, ok := original["NEW_KEY"]; ok {
232		t.Error("original gained NEW_KEY from copy")
233	}
234}