Files
dingtalk-workspace-cli/internal/upgrade/rollback_test.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
}