343 lines
9.0 KiB
Go
343 lines
9.0 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
package upgrade
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseVersionFromBackupName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
want string
|
|
}{
|
|
{"v1.0.6-20260401-093000", "1.0.6"},
|
|
{"v0.2.7-20260314-100523", "0.2.7"},
|
|
{"v1.0.0", "1.0.0"},
|
|
{"invalid", "unknown"},
|
|
{"v", "unknown"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := parseVersionFromBackupName(tt.name)
|
|
if got != tt.want {
|
|
t.Errorf("parseVersionFromBackupName(%q) = %q, want %q", tt.name, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRollbackManagerBackupAndList(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
// Create a fake binary to backup
|
|
fakeExe := filepath.Join(dir, "dws")
|
|
os.WriteFile(fakeExe, []byte("#!/bin/sh\necho fake"), 0755)
|
|
|
|
// ListBackups on empty dir
|
|
backups, err := rm.ListBackups()
|
|
if err != nil {
|
|
t.Fatalf("ListBackups() error = %v", err)
|
|
}
|
|
if len(backups) != 0 {
|
|
t.Errorf("ListBackups() on empty = %d, want 0", len(backups))
|
|
}
|
|
}
|
|
|
|
func TestRollbackManagerCleanup(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
// Create backup directories with proper timestamps in info.json
|
|
names := []string{"v1.0.1-20260101-010000", "v1.0.2-20260102-020000", "v1.0.3-20260103-030000"}
|
|
for i, name := range names {
|
|
backupDir := filepath.Join(dir, name)
|
|
os.MkdirAll(backupDir, 0755)
|
|
info := BackupInfo{
|
|
Path: backupDir,
|
|
Version: "1.0." + string(rune('1'+i)),
|
|
CreatedAt: mustParseTime(t, "2026-01-0"+string(rune('1'+i))+"T01:00:00Z"),
|
|
}
|
|
rm.saveBackupInfo(info)
|
|
}
|
|
|
|
// Keep only 1 (the newest)
|
|
if err := rm.Cleanup(1); err != nil {
|
|
t.Fatalf("Cleanup(1) error = %v", err)
|
|
}
|
|
|
|
entries, _ := os.ReadDir(dir)
|
|
if len(entries) != 1 {
|
|
t.Errorf("after Cleanup(1), %d entries remain, want 1", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestRollbackManagerCleanup_KeepAll(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
name := "v1.0." + string(rune('0'+i)) + "-20260101-01000" + string(rune('0'+i))
|
|
backupDir := filepath.Join(dir, name)
|
|
os.MkdirAll(backupDir, 0755)
|
|
rm.saveBackupInfo(BackupInfo{
|
|
Path: backupDir,
|
|
Version: "1.0." + string(rune('0'+i)),
|
|
CreatedAt: mustParseTime(t, "2026-01-0"+string(rune('1'+i))+"T01:00:00Z"),
|
|
})
|
|
}
|
|
|
|
if err := rm.Cleanup(10); err != nil {
|
|
t.Fatalf("Cleanup(10) error = %v", err)
|
|
}
|
|
entries, _ := os.ReadDir(dir)
|
|
if len(entries) != 3 {
|
|
t.Errorf("after Cleanup(10), %d entries remain, want 3", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestRollbackManagerCleanup_EmptyDir(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
if err := rm.Cleanup(1); err != nil {
|
|
t.Fatalf("Cleanup on empty dir error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestListBackups_WithInfoJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
backupDir := filepath.Join(dir, "v1.0.5-20260315-120000")
|
|
os.MkdirAll(filepath.Join(backupDir, "binary"), 0755)
|
|
os.WriteFile(filepath.Join(backupDir, "binary", "dws"), []byte("#!/bin/sh"), 0755)
|
|
|
|
info := BackupInfo{
|
|
Path: backupDir,
|
|
BinaryPath: filepath.Join(backupDir, "binary", "dws"),
|
|
Version: "1.0.5",
|
|
CreatedAt: mustParseTime(t, "2026-03-15T12:00:00Z"),
|
|
Size: 1234,
|
|
}
|
|
rm.saveBackupInfo(info)
|
|
|
|
backups, err := rm.ListBackups()
|
|
if err != nil {
|
|
t.Fatalf("ListBackups() error = %v", err)
|
|
}
|
|
if len(backups) != 1 {
|
|
t.Fatalf("ListBackups() len = %d, want 1", len(backups))
|
|
}
|
|
if backups[0].Version != "1.0.5" {
|
|
t.Errorf("Version = %q, want %q", backups[0].Version, "1.0.5")
|
|
}
|
|
if backups[0].Size != 1234 {
|
|
t.Errorf("Size = %d, want 1234", backups[0].Size)
|
|
}
|
|
if backups[0].BinaryPath == "" {
|
|
t.Error("BinaryPath should be set from info.json")
|
|
}
|
|
}
|
|
|
|
func TestListBackups_WithoutInfoJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
// Create a backup dir without info.json => should fallback to name parsing
|
|
backupDir := filepath.Join(dir, "v1.0.3-20260101-100000")
|
|
os.MkdirAll(backupDir, 0755)
|
|
|
|
backups, err := rm.ListBackups()
|
|
if err != nil {
|
|
t.Fatalf("ListBackups() error = %v", err)
|
|
}
|
|
if len(backups) != 1 {
|
|
t.Fatalf("len = %d, want 1", len(backups))
|
|
}
|
|
if backups[0].Version != "1.0.3" {
|
|
t.Errorf("fallback Version = %q, want 1.0.3", backups[0].Version)
|
|
}
|
|
}
|
|
|
|
func TestListBackups_SortOrder(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
timestamps := []string{
|
|
"2026-01-01T01:00:00Z",
|
|
"2026-03-15T12:00:00Z",
|
|
"2026-02-10T06:00:00Z",
|
|
}
|
|
for i, ts := range timestamps {
|
|
name := "v1.0." + string(rune('0'+i)) + "-backup"
|
|
backupDir := filepath.Join(dir, name)
|
|
os.MkdirAll(backupDir, 0755)
|
|
rm.saveBackupInfo(BackupInfo{
|
|
Path: backupDir,
|
|
Version: "1.0." + string(rune('0'+i)),
|
|
CreatedAt: mustParseTime(t, ts),
|
|
})
|
|
}
|
|
|
|
backups, err := rm.ListBackups()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(backups) != 3 {
|
|
t.Fatalf("len = %d", len(backups))
|
|
}
|
|
// Should be sorted newest first
|
|
if backups[0].Version != "1.0.1" {
|
|
t.Errorf("newest = %q, want 1.0.1 (March)", backups[0].Version)
|
|
}
|
|
if backups[2].Version != "1.0.0" {
|
|
t.Errorf("oldest = %q, want 1.0.0 (January)", backups[2].Version)
|
|
}
|
|
}
|
|
|
|
func TestListBackups_NonexistentDir(t *testing.T) {
|
|
rm := NewRollbackManagerWithDir("/tmp/nonexistent-dir-" + time.Now().Format("20060102150405"))
|
|
backups, err := rm.ListBackups()
|
|
if err != nil {
|
|
t.Fatalf("error = %v (should return nil for nonexistent dir)", err)
|
|
}
|
|
if backups != nil {
|
|
t.Errorf("should return nil for nonexistent dir, got %d entries", len(backups))
|
|
}
|
|
}
|
|
|
|
func TestListBackups_SkipsRegularFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
// Create a regular file (not a directory) — should be ignored
|
|
os.WriteFile(filepath.Join(dir, "not-a-backup.txt"), []byte("ignore"), 0644)
|
|
|
|
// Create an actual backup directory
|
|
backupDir := filepath.Join(dir, "v1.0.0-20260101-000000")
|
|
os.MkdirAll(backupDir, 0755)
|
|
rm.saveBackupInfo(BackupInfo{
|
|
Path: backupDir,
|
|
Version: "1.0.0",
|
|
CreatedAt: mustParseTime(t, "2026-01-01T00:00:00Z"),
|
|
})
|
|
|
|
backups, err := rm.ListBackups()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(backups) != 1 {
|
|
t.Errorf("len = %d, want 1 (file should be ignored)", len(backups))
|
|
}
|
|
}
|
|
|
|
func TestSaveAndLoadBackupInfo(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
backupDir := filepath.Join(dir, "v1.0.0-test")
|
|
os.MkdirAll(backupDir, 0755)
|
|
|
|
original := BackupInfo{
|
|
Path: backupDir,
|
|
BinaryPath: filepath.Join(backupDir, "binary", "dws"),
|
|
Version: "1.0.0",
|
|
CreatedAt: mustParseTime(t, "2026-06-01T10:00:00Z"),
|
|
Size: 5678,
|
|
}
|
|
rm.saveBackupInfo(original)
|
|
|
|
loaded, err := rm.loadBackupInfo(backupDir)
|
|
if err != nil {
|
|
t.Fatalf("loadBackupInfo() error = %v", err)
|
|
}
|
|
if loaded.Version != original.Version {
|
|
t.Errorf("Version = %q, want %q", loaded.Version, original.Version)
|
|
}
|
|
if loaded.Size != original.Size {
|
|
t.Errorf("Size = %d, want %d", loaded.Size, original.Size)
|
|
}
|
|
if loaded.BinaryPath != original.BinaryPath {
|
|
t.Errorf("BinaryPath = %q, want %q", loaded.BinaryPath, original.BinaryPath)
|
|
}
|
|
}
|
|
|
|
func TestLoadBackupInfo_InvalidJSON(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rm := NewRollbackManagerWithDir(dir)
|
|
|
|
backupDir := filepath.Join(dir, "v1.0.0-bad")
|
|
os.MkdirAll(backupDir, 0755)
|
|
os.WriteFile(filepath.Join(backupDir, "info.json"), []byte("{bad json"), 0644)
|
|
|
|
_, err := rm.loadBackupInfo(backupDir)
|
|
if err == nil {
|
|
t.Error("expected error for invalid JSON")
|
|
}
|
|
}
|
|
|
|
func TestNewRollbackManager(t *testing.T) {
|
|
rm := NewRollbackManager()
|
|
if rm.backupDir == "" {
|
|
t.Error("backupDir should not be empty")
|
|
}
|
|
if rm.maxBackups != defaultMaxBackups {
|
|
t.Errorf("maxBackups = %d, want %d", rm.maxBackups, defaultMaxBackups)
|
|
}
|
|
}
|
|
|
|
func TestCopyDir(t *testing.T) {
|
|
src := t.TempDir()
|
|
os.WriteFile(filepath.Join(src, "file1.txt"), []byte("content1"), 0644)
|
|
os.MkdirAll(filepath.Join(src, "sub"), 0755)
|
|
os.WriteFile(filepath.Join(src, "sub", "file2.txt"), []byte("content2"), 0644)
|
|
|
|
dst := filepath.Join(t.TempDir(), "copy")
|
|
|
|
if err := copyDir(src, dst); err != nil {
|
|
t.Fatalf("copyDir() error = %v", err)
|
|
}
|
|
|
|
got1, _ := os.ReadFile(filepath.Join(dst, "file1.txt"))
|
|
if string(got1) != "content1" {
|
|
t.Errorf("file1 = %q, want content1", string(got1))
|
|
}
|
|
got2, _ := os.ReadFile(filepath.Join(dst, "sub", "file2.txt"))
|
|
if string(got2) != "content2" {
|
|
t.Errorf("sub/file2 = %q, want content2", string(got2))
|
|
}
|
|
}
|
|
|
|
func TestCopyDir_EmptySrc(t *testing.T) {
|
|
src := t.TempDir()
|
|
dst := filepath.Join(t.TempDir(), "copy")
|
|
|
|
if err := copyDir(src, dst); err != nil {
|
|
t.Fatalf("copyDir() error = %v", err)
|
|
}
|
|
|
|
entries, _ := os.ReadDir(dst)
|
|
if len(entries) != 0 {
|
|
t.Errorf("dst should be empty, got %d entries", len(entries))
|
|
}
|
|
}
|
|
|
|
func TestCopyDir_SrcNotExist(t *testing.T) {
|
|
err := copyDir("/tmp/nonexistent-src-"+time.Now().Format("150405"), t.TempDir())
|
|
if err == nil {
|
|
t.Error("expected error for nonexistent src")
|
|
}
|
|
}
|
|
|
|
func mustParseTime(t *testing.T, s string) time.Time {
|
|
t.Helper()
|
|
ts, err := time.Parse(time.RFC3339, s)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return ts
|
|
}
|