mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-11 18:10:23 +08:00
1. 失败自动重试:rclone Pacer 指数退避,默认 10 次底层 HTTP 重试 2. 带宽限制:配置 bandwidth_limit + Settings 运行时可调 3. 上传实时进度:progressReader + LogHub SSE 推送字节级进度/速率 4. 存储空间查询:StorageAbout 可选接口,GetUsage 返回远端真实空间 5. 全 rclone 后端:backend/all 引入 70+ 后端,新增 rclone 存储类型, API 驱动的可搜索后端选择器 + 动态配置表单
108 lines
4.6 KiB
Go
108 lines
4.6 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"backupx/server/internal/backup"
|
|
backupretention "backupx/server/internal/backup/retention"
|
|
"backupx/server/internal/config"
|
|
"backupx/server/internal/database"
|
|
"backupx/server/internal/logger"
|
|
"backupx/server/internal/model"
|
|
"backupx/server/internal/repository"
|
|
"backupx/server/internal/storage"
|
|
"backupx/server/internal/storage/codec"
|
|
storageRclone "backupx/server/internal/storage/rclone"
|
|
)
|
|
|
|
func newExecutionTestServices(t *testing.T) (*BackupExecutionService, *BackupRecordService, repository.BackupTaskRepository, repository.StorageTargetRepository, repository.BackupRecordRepository, string, string) {
|
|
t.Helper()
|
|
baseDir := t.TempDir()
|
|
storageDir := filepath.Join(baseDir, "storage")
|
|
sourceDir := filepath.Join(baseDir, "source")
|
|
if err := os.MkdirAll(sourceDir, 0o755); err != nil {
|
|
t.Fatalf("MkdirAll returned error: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(sourceDir, "index.html"), []byte("hello"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile returned error: %v", err)
|
|
}
|
|
log, err := logger.New(config.LogConfig{Level: "error"})
|
|
if err != nil {
|
|
t.Fatalf("logger.New returned error: %v", err)
|
|
}
|
|
db, err := database.Open(config.DatabaseConfig{Path: filepath.Join(baseDir, "backupx.db")}, log)
|
|
if err != nil {
|
|
t.Fatalf("database.Open returned error: %v", err)
|
|
}
|
|
cipher := codec.NewConfigCipher("execution-secret")
|
|
tasks := repository.NewBackupTaskRepository(db)
|
|
targets := repository.NewStorageTargetRepository(db)
|
|
records := repository.NewBackupRecordRepository(db)
|
|
configCiphertext, err := cipher.EncryptJSON(map[string]any{"basePath": storageDir})
|
|
if err != nil {
|
|
t.Fatalf("EncryptJSON returned error: %v", err)
|
|
}
|
|
if err := targets.Create(context.Background(), &model.StorageTarget{Name: "local", Type: string(storage.ProviderTypeLocalDisk), Enabled: true, ConfigCiphertext: configCiphertext, ConfigVersion: 1, LastTestStatus: "unknown"}); err != nil {
|
|
t.Fatalf("Create storage target returned error: %v", err)
|
|
}
|
|
if err := tasks.Create(context.Background(), &model.BackupTask{Name: "site-files", Type: "file", Enabled: true, SourcePath: sourceDir, StorageTargetID: 1, RetentionDays: 30, Compression: "gzip", MaxBackups: 10, LastStatus: "idle"}); err != nil {
|
|
t.Fatalf("Create backup task returned error: %v", err)
|
|
}
|
|
logHub := backup.NewLogHub()
|
|
runnerRegistry := backup.NewRegistry(backup.NewFileRunner(), backup.NewMySQLRunner(nil), backup.NewSQLiteRunner(), backup.NewPostgreSQLRunner(nil))
|
|
storageRegistry := storage.NewRegistry(storageRclone.NewLocalDiskFactory())
|
|
retentionService := backupretention.NewService(records)
|
|
tempDir := filepath.Join(baseDir, "tmp")
|
|
if err := os.MkdirAll(tempDir, 0o755); err != nil {
|
|
t.Fatalf("MkdirAll tempDir returned error: %v", err)
|
|
}
|
|
executionService := NewBackupExecutionService(tasks, records, targets, storageRegistry, runnerRegistry, logHub, retentionService, cipher, nil, tempDir, 2, 10, "")
|
|
recordService := NewBackupRecordService(records, executionService, logHub)
|
|
return executionService, recordService, tasks, targets, records, sourceDir, storageDir
|
|
}
|
|
|
|
func TestBackupExecutionServiceRunTaskByIDSync(t *testing.T) {
|
|
executionService, _, _, _, records, _, storageDir := newExecutionTestServices(t)
|
|
detail, err := executionService.RunTaskByIDSync(context.Background(), 1)
|
|
if err != nil {
|
|
t.Fatalf("RunTaskByIDSync returned error: %v", err)
|
|
}
|
|
if detail.Status != "success" || detail.StoragePath == "" {
|
|
t.Fatalf("unexpected record detail: %#v", detail)
|
|
}
|
|
stored, err := records.FindByID(context.Background(), detail.ID)
|
|
if err != nil {
|
|
t.Fatalf("FindByID returned error: %v", err)
|
|
}
|
|
if stored == nil || stored.Status != "success" {
|
|
t.Fatalf("unexpected stored record: %#v", stored)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(storageDir, filepath.FromSlash(detail.StoragePath))); err != nil {
|
|
t.Fatalf("expected artifact in local storage: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBackupRecordServiceRestore(t *testing.T) {
|
|
executionService, recordService, _, _, _, sourceDir, _ := newExecutionTestServices(t)
|
|
detail, err := executionService.RunTaskByIDSync(context.Background(), 1)
|
|
if err != nil {
|
|
t.Fatalf("RunTaskByIDSync returned error: %v", err)
|
|
}
|
|
if err := os.RemoveAll(sourceDir); err != nil {
|
|
t.Fatalf("RemoveAll returned error: %v", err)
|
|
}
|
|
if err := recordService.Restore(context.Background(), detail.ID); err != nil {
|
|
t.Fatalf("Restore returned error: %v", err)
|
|
}
|
|
content, err := os.ReadFile(filepath.Join(sourceDir, "index.html"))
|
|
if err != nil {
|
|
t.Fatalf("ReadFile returned error: %v", err)
|
|
}
|
|
if string(content) != "hello" {
|
|
t.Fatalf("unexpected restored content: %s", string(content))
|
|
}
|
|
}
|