mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-27 19:19:35 +08:00
test(replication): 为零覆盖的备份复制服务补齐测试 (#80)
新增 replication_service_test.go:备份→复制到目标存储(目标出现对象、源保留、终态 success)+ 同源拒绝。纯测试新增。
This commit is contained in:
154
server/internal/service/replication_service_test.go
Normal file
154
server/internal/service/replication_service_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"backupx/server/internal/backup"
|
||||
"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"
|
||||
)
|
||||
|
||||
type replicationTestHarness struct {
|
||||
repl *ReplicationService
|
||||
execution *BackupExecutionService
|
||||
records repository.BackupRecordRepository
|
||||
destDir string
|
||||
srcDir string
|
||||
}
|
||||
|
||||
func newReplicationTestHarness(t *testing.T) *replicationTestHarness {
|
||||
t.Helper()
|
||||
baseDir := t.TempDir()
|
||||
sourceData := filepath.Join(baseDir, "data")
|
||||
srcStore := filepath.Join(baseDir, "src-store")
|
||||
destStore := filepath.Join(baseDir, "dest-store")
|
||||
if err := os.MkdirAll(sourceData, 0o755); err != nil {
|
||||
t.Fatalf("mkdir data: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(sourceData, "index.html"), []byte("hello-replicate"), 0o644); err != nil {
|
||||
t.Fatalf("write data: %v", err)
|
||||
}
|
||||
log, err := logger.New(config.LogConfig{Level: "error"})
|
||||
if err != nil {
|
||||
t.Fatalf("logger.New: %v", err)
|
||||
}
|
||||
db, err := database.Open(config.DatabaseConfig{Path: filepath.Join(baseDir, "backupx.db")}, log)
|
||||
if err != nil {
|
||||
t.Fatalf("database.Open: %v", err)
|
||||
}
|
||||
cipher := codec.NewConfigCipher("replicate-secret")
|
||||
targets := repository.NewStorageTargetRepository(db)
|
||||
tasks := repository.NewBackupTaskRepository(db)
|
||||
records := repository.NewBackupRecordRepository(db)
|
||||
replications := repository.NewReplicationRecordRepository(db)
|
||||
nodes := repository.NewNodeRepository(db)
|
||||
|
||||
mkTarget := func(name, basePath string) {
|
||||
cfg, err := cipher.EncryptJSON(map[string]any{"basePath": basePath})
|
||||
if err != nil {
|
||||
t.Fatalf("EncryptJSON: %v", err)
|
||||
}
|
||||
if err := targets.Create(context.Background(), &model.StorageTarget{Name: name, Type: string(storage.ProviderTypeLocalDisk), Enabled: true, ConfigCiphertext: cfg, ConfigVersion: 1, LastTestStatus: "unknown"}); err != nil {
|
||||
t.Fatalf("create target %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
mkTarget("src", srcStore) // ID 1
|
||||
mkTarget("dest", destStore) // ID 2
|
||||
|
||||
task := &model.BackupTask{Name: "repl-test", Type: "file", Enabled: true, SourcePath: sourceData, StorageTargetID: 1, NodeID: 0, RetentionDays: 30, Compression: "gzip", MaxBackups: 10, LastStatus: "idle"}
|
||||
if err := tasks.Create(context.Background(), task); err != nil {
|
||||
t.Fatalf("create task: %v", err)
|
||||
}
|
||||
|
||||
logHub := backup.NewLogHub()
|
||||
runnerRegistry := backup.NewRegistry(backup.NewFileRunner(), backup.NewSQLiteRunner(), backup.NewMySQLRunner(nil), backup.NewPostgreSQLRunner(nil))
|
||||
storageRegistry := storage.NewRegistry(storageRclone.NewLocalDiskFactory())
|
||||
execution := NewBackupExecutionService(tasks, records, targets, storageRegistry, runnerRegistry, logHub, nil, cipher, nil, baseDir, 2, 10, "")
|
||||
repl := NewReplicationService(replications, records, targets, nodes, storageRegistry, cipher, baseDir, 2)
|
||||
|
||||
return &replicationTestHarness{repl: repl, execution: execution, records: records, destDir: destStore, srcDir: srcStore}
|
||||
}
|
||||
|
||||
func countFiles(t *testing.T, dir string) int {
|
||||
t.Helper()
|
||||
n := 0
|
||||
_ = filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
|
||||
if err == nil && info != nil && !info.IsDir() {
|
||||
n++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return n
|
||||
}
|
||||
|
||||
// TestReplicationService_MirrorsToDestTarget 覆盖正常路径:把成功备份从源存储复制到目标存储,
|
||||
// 目标出现对象、源保留(复制非移动),记录终态为 success。
|
||||
func TestReplicationService_MirrorsToDestTarget(t *testing.T) {
|
||||
h := newReplicationTestHarness(t)
|
||||
ctx := context.Background()
|
||||
backupDetail, err := h.execution.RunTaskByIDSync(ctx, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("RunTaskByIDSync: %v", err)
|
||||
}
|
||||
if backupDetail.Status != "success" {
|
||||
t.Fatalf("expected backup success, got %s", backupDetail.Status)
|
||||
}
|
||||
if countFiles(t, h.destDir) != 0 {
|
||||
t.Fatalf("dest store should be empty before replication")
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
h.repl.async = func(job func()) {
|
||||
go func() { job(); close(done) }()
|
||||
}
|
||||
summary, err := h.repl.Start(ctx, backupDetail.ID, 2, "tester")
|
||||
if err != nil {
|
||||
t.Fatalf("replication Start: %v", err)
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(15 * time.Second):
|
||||
t.Fatal("replication did not complete in time")
|
||||
}
|
||||
|
||||
final, err := h.repl.Get(ctx, summary.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
if final.Status != model.ReplicationStatusSuccess {
|
||||
t.Fatalf("expected replication success, got %s (err=%s)", final.Status, final.ErrorMessage)
|
||||
}
|
||||
if countFiles(t, h.destDir) == 0 {
|
||||
t.Fatal("dest store should contain the replicated object")
|
||||
}
|
||||
if countFiles(t, h.srcDir) == 0 {
|
||||
t.Fatal("source object must remain after replication (copy, not move)")
|
||||
}
|
||||
}
|
||||
|
||||
// TestReplicationService_RejectsSameTarget 校验:目标与源相同时同步拒绝。
|
||||
func TestReplicationService_RejectsSameTarget(t *testing.T) {
|
||||
h := newReplicationTestHarness(t)
|
||||
ctx := context.Background()
|
||||
backupDetail, err := h.execution.RunTaskByIDSync(ctx, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("RunTaskByIDSync: %v", err)
|
||||
}
|
||||
// 备份写到 target 1;以 target 1 作为复制目标应被拒绝。
|
||||
if _, err := h.repl.Start(ctx, backupDetail.ID, 1, "tester"); err == nil {
|
||||
t.Fatal("expected error when dest target equals source")
|
||||
} else if !strings.Contains(err.Error(), "目标存储无效或与源相同") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user