Skip to content

Commit 1a2c5a6

Browse files
committed
Reject unedited example config with placeholder values
1 parent 44009a8 commit 1a2c5a6

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

internal/config/config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ func (c *Config) applyDefaults() {
102102
}
103103
}
104104

105+
// Placeholder values from init-config that must be changed before use.
106+
var placeholders = map[string]string{
107+
"s3.bucket": "my-backups",
108+
"s3.access_key": "AKIAIOSFODNN7EXAMPLE",
109+
"s3.secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
110+
"restic_password": "change-me-to-a-strong-password",
111+
"slack_webhook_url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
112+
}
113+
105114
func (c *Config) validate() error {
106115
if c.S3.Bucket == "" {
107116
return fmt.Errorf("s3.bucket is required")
@@ -126,6 +135,21 @@ func (c *Config) validate() error {
126135
if c.SlackWebhookURL == "" {
127136
return fmt.Errorf("slack_webhook_url is required")
128137
}
138+
139+
// Catch unedited example config
140+
checks := map[string]string{
141+
"s3.bucket": c.S3.Bucket,
142+
"s3.access_key": c.S3.AccessKey,
143+
"s3.secret_key": c.S3.SecretKey,
144+
"restic_password": c.ResticPassword,
145+
"slack_webhook_url": c.SlackWebhookURL,
146+
}
147+
for field, value := range checks {
148+
if placeholder, ok := placeholders[field]; ok && value == placeholder {
149+
return fmt.Errorf("%s still has the example value — edit your config file", field)
150+
}
151+
}
152+
129153
return nil
130154
}
131155

internal/config/config_test.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"encoding/json"
45
"os"
56
"path/filepath"
67
"strings"
@@ -13,14 +14,14 @@ func TestLoadValidConfig(t *testing.T) {
1314

1415
content := `{
1516
"s3": {
16-
"bucket": "my-backups",
17-
"access_key": "AKIA1234",
18-
"secret_key": "secret1234",
17+
"bucket": "real-bucket",
18+
"access_key": "AKIAREAL1234",
19+
"secret_key": "realSecretKey1234",
1920
"region": "us-east-1"
2021
},
21-
"restic_password": "testpass",
22+
"restic_password": "real-strong-password",
2223
"paths": ["C:\\Users\\Me\\Documents"],
23-
"slack_webhook_url": "https://hooks.slack.com/services/T/B/X",
24+
"slack_webhook_url": "https://hooks.slack.com/services/T123/B456/realwebhook",
2425
"schedule_interval_hours": 4
2526
}`
2627

@@ -33,8 +34,8 @@ func TestLoadValidConfig(t *testing.T) {
3334
t.Fatalf("unexpected error: %v", err)
3435
}
3536

36-
if cfg.S3.Bucket != "my-backups" {
37-
t.Errorf("bucket = %q, want %q", cfg.S3.Bucket, "my-backups")
37+
if cfg.S3.Bucket != "real-bucket" {
38+
t.Errorf("bucket = %q, want %q", cfg.S3.Bucket, "real-bucket")
3839
}
3940
if cfg.ScheduleIntervalHours != 4 {
4041
t.Errorf("schedule = %d, want 4", cfg.ScheduleIntervalHours)
@@ -95,6 +96,22 @@ func TestLoadMissingFields(t *testing.T) {
9596
}
9697
}
9798

99+
func TestLoadPlaceholderValues(t *testing.T) {
100+
dir := t.TempDir()
101+
path := filepath.Join(dir, "config.json")
102+
103+
// Use the example config directly — should be rejected
104+
WriteExample(path)
105+
106+
_, err := Load(path)
107+
if err == nil {
108+
t.Fatal("expected error for unedited example config")
109+
}
110+
if !strings.Contains(err.Error(), "still has the example value") {
111+
t.Errorf("expected placeholder error, got: %v", err)
112+
}
113+
}
114+
98115
func TestRepoURL(t *testing.T) {
99116
tests := []struct {
100117
name string
@@ -136,12 +153,21 @@ func TestWriteExample(t *testing.T) {
136153
t.Fatal(err)
137154
}
138155

139-
cfg, err := Load(path)
156+
// Verify it's valid JSON with expected fields
157+
data, err := os.ReadFile(path)
140158
if err != nil {
141-
t.Fatalf("example config should be loadable: %v", err)
159+
t.Fatal(err)
160+
}
161+
162+
var cfg Config
163+
if err := json.Unmarshal(data, &cfg); err != nil {
164+
t.Fatalf("example config is not valid JSON: %v", err)
142165
}
143166

144167
if cfg.S3.Bucket == "" {
145168
t.Error("example config should have a non-empty bucket")
146169
}
170+
if cfg.ResticPassword == "" {
171+
t.Error("example config should have a non-empty restic_password")
172+
}
147173
}

0 commit comments

Comments
 (0)