- go.mod: 将非法版本号 go 1.26.4 改为 go 1.26 - engine: 压缩失败时删除残留的半截 tar.gz 文件 - config: 支持仅按天数保留(count 和 days 均未设时才用默认 count=7) - engine: docker compose stop 失败时也尝试重启,避免容器停摆 - list: 仅显示真正的备份归档文件(新增导出 IsBackupArchive) - systemd: 移除 PrivateTmp,暂存目录改到安装目录内,避免私有 /tmp 被大目录撑爆
147 lines
3.2 KiB
Go
147 lines
3.2 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestLoadValid(t *testing.T) {
|
|
yaml := `
|
|
global:
|
|
backup_dir: /tmp/backups
|
|
projects:
|
|
- name: testapp
|
|
path: /opt/testapp
|
|
`
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
if err := os.WriteFile(path, []byte(yaml), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
if cfg.Global.BackupDir != "/tmp/backups" {
|
|
t.Errorf("backup_dir = %q", cfg.Global.BackupDir)
|
|
}
|
|
if len(cfg.Projects) != 1 {
|
|
t.Fatalf("expected 1 project, got %d", len(cfg.Projects))
|
|
}
|
|
p := cfg.Projects[0]
|
|
if p.Name != "testapp" {
|
|
t.Errorf("name = %q", p.Name)
|
|
}
|
|
if p.ComposeFile != "docker-compose.yml" {
|
|
t.Errorf("compose_file default = %q", p.ComposeFile)
|
|
}
|
|
if p.Retention.Count != DefaultRetentionCount {
|
|
t.Errorf("retention count default = %d", p.Retention.Count)
|
|
}
|
|
if cfg.HasRemote() {
|
|
t.Error("HasRemote should be false")
|
|
}
|
|
}
|
|
|
|
func TestLoadDaysOnlyRetention(t *testing.T) {
|
|
yaml := `
|
|
global:
|
|
backup_dir: /tmp/backups
|
|
projects:
|
|
- name: testapp
|
|
path: /opt/testapp
|
|
retention:
|
|
days: 30
|
|
`
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
if err := os.WriteFile(path, []byte(yaml), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
p := cfg.Projects[0]
|
|
if p.Retention.Count != 0 {
|
|
t.Errorf("count should stay 0 for days-only retention, got %d", p.Retention.Count)
|
|
}
|
|
if p.Retention.Days != 30 {
|
|
t.Errorf("days = %d", p.Retention.Days)
|
|
}
|
|
}
|
|
|
|
func TestLoadMissingBackupDir(t *testing.T) {
|
|
yaml := `
|
|
projects:
|
|
- name: x
|
|
path: /x
|
|
`
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
os.WriteFile(path, []byte(yaml), 0644)
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for missing backup_dir")
|
|
}
|
|
}
|
|
|
|
func TestLoadNoProjects(t *testing.T) {
|
|
yaml := `
|
|
global:
|
|
backup_dir: /tmp
|
|
`
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
os.WriteFile(path, []byte(yaml), 0644)
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error for no projects")
|
|
}
|
|
}
|
|
|
|
func TestHasCron(t *testing.T) {
|
|
cfg := &Config{
|
|
Projects: []ProjectConfig{
|
|
{Name: "a", Cron: ""},
|
|
{Name: "b", Cron: "0 3 * * *"},
|
|
},
|
|
}
|
|
if !cfg.HasCron() {
|
|
t.Error("HasCron should be true")
|
|
}
|
|
cfg2 := &Config{
|
|
Projects: []ProjectConfig{
|
|
{Name: "a", Cron: ""},
|
|
},
|
|
}
|
|
if cfg2.HasCron() {
|
|
t.Error("HasCron should be false")
|
|
}
|
|
}
|
|
|
|
func TestValidateWebDAVRequiresURL(t *testing.T) {
|
|
cfg := &Config{
|
|
Global: GlobalConfig{BackupDir: "/tmp/backups"},
|
|
Projects: []ProjectConfig{{Name: "app", Path: "/tmp/app"}},
|
|
Remote: &RemoteConfig{WebDAV: &WebDAVConfig{}},
|
|
}
|
|
err := cfg.Validate()
|
|
if err == nil || !strings.Contains(err.Error(), "remote.webdav.url is required") {
|
|
t.Fatalf("expected webdav url error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateS3RequiresFields(t *testing.T) {
|
|
cfg := &Config{
|
|
Global: GlobalConfig{BackupDir: "/tmp/backups"},
|
|
Projects: []ProjectConfig{{Name: "app", Path: "/tmp/app"}},
|
|
Remote: &RemoteConfig{S3: &S3Config{}},
|
|
}
|
|
err := cfg.Validate()
|
|
if err == nil || !strings.Contains(err.Error(), "remote.s3.bucket is required") {
|
|
t.Fatalf("expected s3 bucket error, got %v", err)
|
|
}
|
|
} |