Backups are .tar.gz archives of a consistent database snapshot (VACUUM INTO) plus the images folder. They run on a cron schedule, daily at 3:00 by default, and the newest N scheduled ones are kept (7 by default). Manual backups are never pruned. Settings → Backups offers schedule presets or a custom cron expression, retention, "Back up now", and download/delete for each backup. A small built-in parser handles standard 5-field cron syntax with names, ranges, steps and @daily-style shorthands. Backups go to CANON_BACKUP_DIR (default data/backups). Compose mounts a separate canon-backups volume at /backups, overridable with CANON_BACKUP_PATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
1.6 KiB
Go
51 lines
1.6 KiB
Go
package backup
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestNext(t *testing.T) {
|
|
utc := func(s string) time.Time {
|
|
v, err := time.Parse("2006-01-02 15:04", s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return v
|
|
}
|
|
cases := []struct{ expr, from, want string }{
|
|
{"0 3 * * *", "2026-09-30 02:59", "2026-09-30 03:00"},
|
|
{"0 3 * * *", "2026-09-30 03:00", "2026-10-01 03:00"},
|
|
{"@daily", "2026-12-31 23:30", "2027-01-01 00:00"},
|
|
{"*/15 * * * *", "2026-09-30 10:07", "2026-09-30 10:15"},
|
|
{"30 4 * * sun", "2026-09-30 00:00", "2026-10-04 04:30"}, // Wed → Sun
|
|
{"30 4 * * 7", "2026-09-30 00:00", "2026-10-04 04:30"},
|
|
{"0 0 1,15 * *", "2026-09-02 00:00", "2026-09-15 00:00"},
|
|
{"0 9 * * mon-fri", "2026-10-02 10:00", "2026-10-05 09:00"}, // Fri after 9 → Mon
|
|
{"0 0 29 2 *", "2026-03-01 00:00", "2028-02-29 00:00"},
|
|
{"0 12 13 * 5", "2026-09-30 00:00", "2026-10-02 12:00"}, // Friday OR the 13th
|
|
{"5/20 * * * *", "2026-09-30 10:26", "2026-09-30 10:45"},
|
|
}
|
|
for _, c := range cases {
|
|
s, err := ParseCron(c.expr)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", c.expr, err)
|
|
}
|
|
if got := s.Next(utc(c.from)); !got.Equal(utc(c.want)) {
|
|
t.Errorf("%s from %s = %s, want %s", c.expr, c.from, got.Format("2006-01-02 15:04 Mon"), c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseCronErrors(t *testing.T) {
|
|
for _, expr := range []string{"", "* * * *", "60 * * * *", "* 24 * * *", "* * 0 * *", "5-1 * * * *", "*/0 * * * *", "a * * * *"} {
|
|
if _, err := ParseCron(expr); err == nil {
|
|
t.Errorf("%q: expected an error", expr)
|
|
}
|
|
}
|
|
s, _ := ParseCron("0 0 30 2 *")
|
|
if !s.Next(time.Now()).IsZero() {
|
|
t.Error("Feb 30 should never run")
|
|
}
|
|
}
|