1package app
2
3import (
4 "testing"
5
6 "github.com/spf13/viper"
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9
10 "github.com/bnema/gordon/internal/domain"
11)
12
13func TestValidateBackupRetentionRejectsNegativeValues(t *testing.T) {
14 tests := []struct {
15 name string
16 set func(*Config)
17 err string
18 }{
19 {
20 name: "hourly",
21 set: func(cfg *Config) {
22 cfg.Backups.Databases.Retention.Hourly = -1
23 },
24 err: "backups.databases.retention.hourly",
25 },
26 {
27 name: "daily",
28 set: func(cfg *Config) {
29 cfg.Backups.Databases.Retention.Daily = -1
30 },
31 err: "backups.databases.retention.daily",
32 },
33 {
34 name: "weekly",
35 set: func(cfg *Config) {
36 cfg.Backups.Databases.Retention.Weekly = -1
37 },
38 err: "backups.databases.retention.weekly",
39 },
40 {
41 name: "monthly",
42 set: func(cfg *Config) {
43 cfg.Backups.Databases.Retention.Monthly = -1
44 },
45 err: "backups.databases.retention.monthly",
46 },
47 }
48
49 for _, tt := range tests {
50 t.Run(tt.name, func(t *testing.T) {
51 var cfg Config
52 tt.set(&cfg)
53
54 _, err := validateBackupRetention(cfg)
55 require.Error(t, err)
56 assert.Contains(t, err.Error(), tt.err)
57 })
58 }
59}
60
61func TestValidateBackupRetentionAcceptsZeroAndPositiveValues(t *testing.T) {
62 t.Run("positive values", func(t *testing.T) {
63 var cfg Config
64 cfg.Backups.Databases.Retention.Hourly = 1
65 cfg.Backups.Databases.Retention.Daily = 7
66 cfg.Backups.Databases.Retention.Weekly = 4
67 cfg.Backups.Databases.Retention.Monthly = 12
68
69 retention, err := validateBackupRetention(cfg)
70 require.NoError(t, err)
71 assert.Equal(t, 1, retention.Hourly)
72 assert.Equal(t, 7, retention.Daily)
73 assert.Equal(t, 4, retention.Weekly)
74 assert.Equal(t, 12, retention.Monthly)
75 })
76
77 t.Run("zero values", func(t *testing.T) {
78 var cfg Config
79 cfg.Backups.Databases.Retention.Hourly = 0
80 cfg.Backups.Databases.Retention.Daily = 0
81 cfg.Backups.Databases.Retention.Weekly = 0
82 cfg.Backups.Databases.Retention.Monthly = 0
83
84 retention, err := validateBackupRetention(cfg)
85 require.NoError(t, err)
86 assert.Equal(t, 0, retention.Hourly)
87 assert.Equal(t, 0, retention.Daily)
88 assert.Equal(t, 0, retention.Weekly)
89 assert.Equal(t, 0, retention.Monthly)
90 })
91}
92
93func TestValidateBackupRetentionAcceptsLegacyBackupsConfig(t *testing.T) {
94 var cfg Config
95 cfg.Backups.Retention.Daily = 3
96
97 retention, err := validateBackupRetention(cfg)
98
99 require.NoError(t, err)
100 assert.Equal(t, 3, retention.Daily)
101}
102
103func TestDatabaseBackupSettingsPreferEnabledLegacySchedule(t *testing.T) {
104 var cfg Config
105 cfg.Backups.Enabled = true
106 cfg.Backups.Schedule = string(domain.ScheduleHourly)
107 cfg.Backups.Databases.Schedule = string(domain.ScheduleDaily)
108
109 settings := databaseBackupSettings(cfg)
110
111 assert.True(t, settings.Enabled)
112 assert.Equal(t, string(domain.ScheduleHourly), settings.Schedule)
113}
114
115func TestValidateVolumeBackupConfig(t *testing.T) {
116 t.Run("disabled accepts defaults", func(t *testing.T) {
117 var cfg Config
118
119 volumeCfg, err := validateVolumeBackupConfig(cfg)
120 require.NoError(t, err)
121 assert.False(t, volumeCfg.Enabled)
122 assert.Equal(t, domain.VolumeBackupCompressionGzip, volumeCfg.Compression)
123 assert.Equal(t, 0, volumeCfg.Retention.Keep)
124 assert.Equal(t, 2, volumeCfg.MaxConcurrency)
125 assert.Equal(t, "alpine:3.20", volumeCfg.HelperImage)
126 assert.Equal(t, "gordon", volumeCfg.VolumePrefix)
127 })
128
129 t.Run("enabled requires bucket and region", func(t *testing.T) {
130 var cfg Config
131 cfg.Backups.Volumes.Enabled = true
132 cfg.Backups.Volumes.Retention.Keep = 14
133
134 _, err := validateVolumeBackupConfig(cfg)
135 require.Error(t, err)
136 assert.Contains(t, err.Error(), "backups.volumes.s3.bucket")
137
138 cfg.Backups.Volumes.S3.Bucket = "gordon-backups"
139 _, err = validateVolumeBackupConfig(cfg)
140 require.Error(t, err)
141 assert.Contains(t, err.Error(), "backups.volumes.s3.region")
142 })
143
144 t.Run("enabled accepts s3 config", func(t *testing.T) {
145 var cfg Config
146 cfg.Backups.Volumes.Enabled = true
147 cfg.Backups.Volumes.Interval = "6h"
148 cfg.Backups.Volumes.Timeout = "30m"
149 cfg.Backups.Volumes.Compression = "gzip"
150 cfg.Backups.Volumes.MaxConcurrency = 3
151 cfg.Backups.Volumes.HelperImage = "example/helper:1"
152 cfg.Volumes.Prefix = "acme"
153 cfg.Backups.Volumes.S3.Bucket = "gordon-backups"
154 cfg.Backups.Volumes.S3.Region = "eu-west-3"
155 cfg.Backups.Volumes.S3.Prefix = "prod/gordon"
156 cfg.Backups.Volumes.Retention.Keep = 7
157
158 volumeCfg, err := validateVolumeBackupConfig(cfg)
159 require.NoError(t, err)
160 assert.True(t, volumeCfg.Enabled)
161 assert.Equal(t, domain.VolumeBackupCompressionGzip, volumeCfg.Compression)
162 assert.Equal(t, 7, volumeCfg.Retention.Keep)
163 assert.Equal(t, 3, volumeCfg.MaxConcurrency)
164 assert.Equal(t, "acme", volumeCfg.VolumePrefix)
165 assert.Equal(t, "prod/gordon", volumeCfg.S3Prefix)
166 })
167
168 t.Run("zstd requires helper image with zstd", func(t *testing.T) {
169 var cfg Config
170 cfg.Backups.Volumes.Compression = "zstd"
171
172 _, err := validateVolumeBackupConfig(cfg)
173
174 require.Error(t, err)
175 assert.Contains(t, err.Error(), "helper_image")
176 })
177
178 t.Run("enabled rejects zero retention", func(t *testing.T) {
179 var cfg Config
180 cfg.Backups.Volumes.Enabled = true
181 cfg.Backups.Volumes.S3.Bucket = "gordon-backups"
182 cfg.Backups.Volumes.S3.Region = "eu-west-3"
183 cfg.Backups.Volumes.Retention.Keep = 0
184
185 _, err := validateVolumeBackupConfig(cfg)
186 require.Error(t, err)
187 assert.Contains(t, err.Error(), "backups.volumes.retention.keep")
188 })
189}
190
191func TestLoadConfigSetsBackupDefaults(t *testing.T) {
192 v := viper.New()
193
194 require.NoError(t, loadConfig(v, ""))
195
196 assert.Equal(t, string(domain.ScheduleDaily), v.GetString("backups.databases.schedule"))
197 assert.Equal(t, "24h", v.GetString("backups.volumes.interval"))
198 assert.Equal(t, string(domain.VolumeBackupCompressionGzip), v.GetString("backups.volumes.compression"))
199 assert.Equal(t, 14, v.GetInt("backups.volumes.retention.keep"))
200}
201
202func TestResolveBackupSchedule(t *testing.T) {
203 t.Run("valid values", func(t *testing.T) {
204 tests := []struct {
205 input string
206 want domain.BackupSchedule
207 }{
208 {input: "hourly", want: domain.ScheduleHourly},
209 {input: "daily", want: domain.ScheduleDaily},
210 {input: "weekly", want: domain.ScheduleWeekly},
211 {input: "monthly", want: domain.ScheduleMonthly},
212 {input: " DAILY ", want: domain.ScheduleDaily},
213 }
214
215 for _, tt := range tests {
216 t.Run(tt.input, func(t *testing.T) {
217 got, err := resolveBackupSchedule(tt.input)
218 require.NoError(t, err)
219 assert.Equal(t, tt.want, got)
220 })
221 }
222 })
223
224 t.Run("invalid value", func(t *testing.T) {
225 _, err := resolveBackupSchedule("every-minute")
226 require.Error(t, err)
227 assert.Contains(t, err.Error(), "backups.databases.schedule")
228 })
229}