676 lines
18 KiB
Go
676 lines
18 KiB
Go
package apps
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func TestParseAppVersion(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
major, minor, patch, revision int
|
|
}{
|
|
{"1.24.3-1", 1, 24, 3, 1},
|
|
{"v3.4", 3, 4, 0, 0},
|
|
{"5.118.1", 5, 118, 1, 0},
|
|
{"v4.0.18-2", 4, 0, 18, 2},
|
|
{"1.0.0", 1, 0, 0, 0},
|
|
{"0.1.0-10", 0, 1, 0, 10},
|
|
{"v1.0.0-1", 1, 0, 0, 1},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.input, func(t *testing.T) {
|
|
maj, min, pat, rev := ParseAppVersion(tt.input)
|
|
if maj != tt.major || min != tt.minor || pat != tt.patch || rev != tt.revision {
|
|
t.Errorf("ParseAppVersion(%q) = (%d,%d,%d,%d), want (%d,%d,%d,%d)",
|
|
tt.input, maj, min, pat, rev, tt.major, tt.minor, tt.patch, tt.revision)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompareAppVersions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
a, b string
|
|
want int // >0, <0, or 0
|
|
}{
|
|
{"equal", "1.0.0", "1.0.0", 0},
|
|
{"major greater", "2.0.0", "1.0.0", 1},
|
|
{"major less", "1.0.0", "2.0.0", -1},
|
|
{"minor greater", "1.2.0", "1.1.0", 1},
|
|
{"minor less", "1.1.0", "1.2.0", -1},
|
|
{"patch greater", "1.0.2", "1.0.1", 1},
|
|
{"patch less", "1.0.1", "1.0.2", -1},
|
|
{"revision tiebreaker", "1.0.0-2", "1.0.0-1", 1},
|
|
{"revision less", "1.0.0-1", "1.0.0-2", -1},
|
|
{"revision equal", "1.0.0-1", "1.0.0-1", 0},
|
|
{"no revision vs revision", "1.0.0", "1.0.0-1", -1},
|
|
{"v prefix ignored", "v1.0.0", "1.0.0", 0},
|
|
{"real versions", "1.24.3-1", "1.23.0", 1},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := CompareAppVersions(tt.a, tt.b)
|
|
if (tt.want > 0 && got <= 0) || (tt.want < 0 && got >= 0) || (tt.want == 0 && got != 0) {
|
|
t.Errorf("CompareAppVersions(%q, %q) = %d, want sign %d", tt.a, tt.b, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMatchVersionConstraint(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
constraint string
|
|
version string
|
|
want bool
|
|
}{
|
|
{"gte match", ">=1.23.0", "1.24.0", true},
|
|
{"gte exact", ">=1.23.0", "1.23.0", true},
|
|
{"gte with revision", ">=1.23.0", "1.23.0-1", true},
|
|
{"gte below", ">=1.23.0", "1.22.0", false},
|
|
{"gt match", ">1.0.0", "1.0.1", true},
|
|
{"gt exact", ">1.0.0", "1.0.0", false},
|
|
{"lt match", "<2.0.0", "1.9.9", true},
|
|
{"lt exact", "<2.0.0", "2.0.0", false},
|
|
{"lt above", "<2.0.0", "2.0.1", false},
|
|
{"lte match", "<=2.0.0", "2.0.0", true},
|
|
{"lte below", "<=2.0.0", "1.9.0", true},
|
|
{"lte above", "<=2.0.0", "2.0.1", false},
|
|
{"exact match", "=1.5.0", "1.5.0", true},
|
|
{"exact no match", "=1.5.0", "1.5.1", false},
|
|
{"bare version exact", "1.5.0", "1.5.0", true},
|
|
{"bare version no match", "1.5.0", "1.5.1", false},
|
|
{"gt zero universal", ">0", "0.0.1", true},
|
|
{"gt zero any version", ">0", "99.99.99", true},
|
|
{"empty constraint", "", "1.0.0", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := MatchVersionConstraint(tt.constraint, tt.version)
|
|
if got != tt.want {
|
|
t.Errorf("MatchVersionConstraint(%q, %q) = %v, want %v",
|
|
tt.constraint, tt.version, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// writeManifest is a test helper that writes an AppManifest as YAML to a manifest.yaml file.
|
|
func writeManifest(t *testing.T, dir string, manifest AppManifest) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := yaml.Marshal(manifest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "manifest.yaml"), data, 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// writeAppMeta writes an AppMeta as app.yaml in the given app root directory.
|
|
func writeAppMeta(t *testing.T, appRoot string, meta AppMeta) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(appRoot, 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := yaml.Marshal(meta)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(appRoot, "app.yaml"), data, 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// writeVersionManifest writes a version manifest at versions/{version}/manifest.yaml.
|
|
func writeVersionManifest(t *testing.T, appRoot, version string, manifest AppManifest) {
|
|
t.Helper()
|
|
writeManifest(t, filepath.Join(appRoot, "versions", version), manifest)
|
|
}
|
|
|
|
func TestComputeUpgradePlan_AlreadyCurrent(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{Name: "myapp", Latest: "1"})
|
|
writeVersionManifest(t, appDir, "1", AppManifest{Version: "1.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(plan.Steps) != 0 {
|
|
t.Errorf("expected 0 steps for current version, got %d", len(plan.Steps))
|
|
}
|
|
if plan.Blocked {
|
|
t.Error("expected not blocked")
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_NoUpgradeBlock(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{Name: "myapp", Latest: "2"})
|
|
writeVersionManifest(t, appDir, "2", AppManifest{Version: "2.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Error("expected not blocked")
|
|
}
|
|
if len(plan.Steps) != 1 {
|
|
t.Fatalf("expected 1 step, got %d", len(plan.Steps))
|
|
}
|
|
if plan.Steps[0].FromVersion != "1.0.0" || plan.Steps[0].ToVersion != "2.0.0" {
|
|
t.Errorf("step = %s -> %s, want 1.0.0 -> 2.0.0",
|
|
plan.Steps[0].FromVersion, plan.Steps[0].ToVersion)
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_DirectUpgrade(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "2",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=1.0.0"},
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "2", AppManifest{Version: "2.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.5.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Error("expected not blocked")
|
|
}
|
|
if len(plan.Steps) != 1 {
|
|
t.Fatalf("expected 1 step, got %d", len(plan.Steps))
|
|
}
|
|
if plan.Steps[0].FromVersion != "1.5.0" || plan.Steps[0].ToVersion != "2.0.0" {
|
|
t.Errorf("step = %s -> %s, want 1.5.0 -> 2.0.0",
|
|
plan.Steps[0].FromVersion, plan.Steps[0].ToVersion)
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_Blocked(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "3",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: "<1.0.0", Blocked: true, Notes: "too old, manual migration required"},
|
|
{Version: ">=1.0.0"},
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "3", AppManifest{Version: "3.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("0.5.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !plan.Blocked {
|
|
t.Error("expected blocked")
|
|
}
|
|
if plan.Notes != "too old, manual migration required" {
|
|
t.Errorf("unexpected notes: %q", plan.Notes)
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_NoMatchingRule(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "3",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=2.0.0"}, // only allows from 2.x+
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "3", AppManifest{Version: "3.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !plan.Blocked {
|
|
t.Error("expected blocked when no rule matches")
|
|
}
|
|
if plan.Notes == "" {
|
|
t.Error("expected notes explaining no upgrade path")
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_ViaWaypoint(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// Slot "3" has version 3.0.0, slot "2" (waypoint) has version 2.0.0
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "3",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=2.0.0"}, // direct from 2.x
|
|
{Version: ">=1.0.0", Via: "2"},
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "3", AppManifest{Version: "3.0.0"})
|
|
writeVersionManifest(t, appDir, "2", AppManifest{Version: "2.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.5.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Errorf("expected not blocked, notes: %s", plan.Notes)
|
|
}
|
|
if len(plan.Steps) != 2 {
|
|
t.Fatalf("expected 2 steps, got %d", len(plan.Steps))
|
|
}
|
|
|
|
// Step 1: 1.5.0 -> 2.0.0 (version from manifest in slot "2")
|
|
if plan.Steps[0].FromVersion != "1.5.0" || plan.Steps[0].ToVersion != "2.0.0" {
|
|
t.Errorf("step 1 = %s -> %s, want 1.5.0 -> 2.0.0",
|
|
plan.Steps[0].FromVersion, plan.Steps[0].ToVersion)
|
|
}
|
|
// Step 2: 2.0.0 -> 3.0.0 (version from manifest in slot "3")
|
|
if plan.Steps[1].FromVersion != "2.0.0" || plan.Steps[1].ToVersion != "3.0.0" {
|
|
t.Errorf("step 2 = %s -> %s, want 2.0.0 -> 3.0.0",
|
|
plan.Steps[1].FromVersion, plan.Steps[1].ToVersion)
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_MultipleWaypoints(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// Slots "2", "3", "4" with versions 2.0.0, 3.0.0, 4.0.0
|
|
// Centralized rules: 1.x → via slot "2", 2.x → via slot "3", 3.x → direct to slot "4"
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "4",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=3.0.0"}, // direct from 3.x
|
|
{Version: ">=2.0.0", Via: "3"}, // 2.x must go through slot "3"
|
|
{Version: ">=1.0.0", Via: "2"}, // 1.x must go through slot "2"
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "4", AppManifest{Version: "4.0.0"})
|
|
writeVersionManifest(t, appDir, "3", AppManifest{Version: "3.0.0"})
|
|
writeVersionManifest(t, appDir, "2", AppManifest{Version: "2.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Errorf("expected not blocked, notes: %s", plan.Notes)
|
|
}
|
|
if len(plan.Steps) != 3 {
|
|
t.Fatalf("expected 3 steps, got %d", len(plan.Steps))
|
|
}
|
|
|
|
// Step 1: 1.0.0 -> 2.0.0 (version from manifest in slot "2")
|
|
if plan.Steps[0].FromVersion != "1.0.0" || plan.Steps[0].ToVersion != "2.0.0" {
|
|
t.Errorf("step 1 = %s -> %s, want 1.0.0 -> 2.0.0",
|
|
plan.Steps[0].FromVersion, plan.Steps[0].ToVersion)
|
|
}
|
|
// Step 2: 2.0.0 -> 3.0.0 (version from manifest in slot "3")
|
|
if plan.Steps[1].FromVersion != "2.0.0" || plan.Steps[1].ToVersion != "3.0.0" {
|
|
t.Errorf("step 2 = %s -> %s, want 2.0.0 -> 3.0.0",
|
|
plan.Steps[1].FromVersion, plan.Steps[1].ToVersion)
|
|
}
|
|
// Step 3: 3.0.0 -> 4.0.0 (version from manifest in slot "4")
|
|
if plan.Steps[2].FromVersion != "3.0.0" || plan.Steps[2].ToVersion != "4.0.0" {
|
|
t.Errorf("step 3 = %s -> %s, want 3.0.0 -> 4.0.0",
|
|
plan.Steps[2].FromVersion, plan.Steps[2].ToVersion)
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_CycleDetection(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// Two waypoints that bounce between each other: wp-a (1.5.0) and wp-b (1.2.0)
|
|
// 1.0.0 → wp-a (1.5.0) → wp-b (1.2.0) → wp-a (1.5.0) = cycle
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "3",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=1.5.0", Via: "wp-b"},
|
|
{Version: ">0", Via: "wp-a"},
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "3", AppManifest{Version: "3.0.0"})
|
|
writeVersionManifest(t, appDir, "wp-a", AppManifest{Version: "1.5.0"})
|
|
writeVersionManifest(t, appDir, "wp-b", AppManifest{Version: "1.2.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !plan.Blocked {
|
|
t.Error("expected blocked for circular upgrade path")
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_BackupRequired(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "2",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">0"},
|
|
},
|
|
PreUpgrade: &PreUpgradeConfig{
|
|
Backup: "required",
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "2", AppManifest{Version: "2.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Error("expected not blocked")
|
|
}
|
|
if !plan.BackupRequired {
|
|
t.Error("expected BackupRequired to be true")
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_BackupRecommended(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "2",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">0"},
|
|
},
|
|
PreUpgrade: &PreUpgradeConfig{
|
|
Backup: "recommended",
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "2", AppManifest{Version: "2.0.0"})
|
|
|
|
plan, err := ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !plan.BackupRecommended {
|
|
t.Error("expected BackupRecommended to be true")
|
|
}
|
|
if plan.BackupRequired {
|
|
t.Error("expected BackupRequired to be false")
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_WaypointMissing(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "2",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">0", Via: "1-wp"}, // waypoint slot doesn't exist
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "2", AppManifest{Version: "2.0.0"})
|
|
|
|
_, err = ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err == nil {
|
|
t.Error("expected error for missing waypoint")
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_RuleOrdering(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "3",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=2.0.0"}, // direct for 2.x+
|
|
{Version: ">=1.0.0", Blocked: true, Notes: "must be on 2.x+"}, // block for 1.x
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "3", AppManifest{Version: "3.0.0"})
|
|
|
|
// Version 2.5.0 matches first rule (direct)
|
|
plan, err := ComputeUpgradePlan("2.5.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Error("expected 2.5.0 to not be blocked")
|
|
}
|
|
|
|
// Version 1.5.0 matches second rule (blocked)
|
|
plan, err = ComputeUpgradePlan("1.5.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !plan.Blocked {
|
|
t.Error("expected 1.5.0 to be blocked")
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_BackupAggregation(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// App-level has no backup requirement, but waypoint manifest does
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeAppMeta(t, appDir, AppMeta{
|
|
Name: "myapp",
|
|
Latest: "3",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=2.0.0"},
|
|
{Version: ">=1.0.0", Via: "2"},
|
|
},
|
|
},
|
|
})
|
|
writeVersionManifest(t, appDir, "3", AppManifest{Version: "3.0.0"})
|
|
writeVersionManifest(t, appDir, "2", AppManifest{
|
|
Version: "2.0.0",
|
|
Upgrade: &UpgradeConfig{
|
|
PreUpgrade: &PreUpgradeConfig{
|
|
Backup: "required",
|
|
},
|
|
},
|
|
})
|
|
|
|
plan, err := ComputeUpgradePlan("1.0.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Errorf("expected not blocked, notes: %s", plan.Notes)
|
|
}
|
|
if !plan.BackupRequired {
|
|
t.Error("expected BackupRequired to be true (aggregated from waypoint)")
|
|
}
|
|
}
|
|
|
|
// --- Old-style backward compatibility tests ---
|
|
|
|
func TestComputeUpgradePlan_OldStyle_DirectUpgrade(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// Old-style: manifest.yaml at root, no app.yaml
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeManifest(t, appDir, AppManifest{
|
|
Name: "myapp",
|
|
Version: "2.0.0",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=1.0.0"},
|
|
},
|
|
},
|
|
})
|
|
|
|
plan, err := ComputeUpgradePlan("1.5.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Error("expected not blocked")
|
|
}
|
|
if len(plan.Steps) != 1 {
|
|
t.Fatalf("expected 1 step, got %d", len(plan.Steps))
|
|
}
|
|
if plan.Steps[0].FromVersion != "1.5.0" || plan.Steps[0].ToVersion != "2.0.0" {
|
|
t.Errorf("step = %s -> %s, want 1.5.0 -> 2.0.0",
|
|
plan.Steps[0].FromVersion, plan.Steps[0].ToVersion)
|
|
}
|
|
}
|
|
|
|
func TestComputeUpgradePlan_OldStyle_ViaWaypoint(t *testing.T) {
|
|
tmpDir, err := os.MkdirTemp("", "upgrade-test-*")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
// Old-style: root manifest + .versions/ for waypoints
|
|
appDir := filepath.Join(tmpDir, "myapp")
|
|
writeManifest(t, appDir, AppManifest{
|
|
Name: "myapp",
|
|
Version: "3.0.0",
|
|
Upgrade: &UpgradeConfig{
|
|
From: []UpgradeFromRule{
|
|
{Version: ">=2.0.0"},
|
|
{Version: ">=1.0.0", Via: "2.0.0"},
|
|
},
|
|
},
|
|
})
|
|
|
|
waypointDir := filepath.Join(appDir, ".versions", "2.0.0")
|
|
writeManifest(t, waypointDir, AppManifest{
|
|
Name: "myapp",
|
|
Version: "2.0.0",
|
|
})
|
|
|
|
plan, err := ComputeUpgradePlan("1.5.0", "myapp", tmpDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if plan.Blocked {
|
|
t.Errorf("expected not blocked, notes: %s", plan.Notes)
|
|
}
|
|
if len(plan.Steps) != 2 {
|
|
t.Fatalf("expected 2 steps, got %d", len(plan.Steps))
|
|
}
|
|
if plan.Steps[0].FromVersion != "1.5.0" || plan.Steps[0].ToVersion != "2.0.0" {
|
|
t.Errorf("step 1 = %s -> %s, want 1.5.0 -> 2.0.0",
|
|
plan.Steps[0].FromVersion, plan.Steps[0].ToVersion)
|
|
}
|
|
if plan.Steps[1].FromVersion != "2.0.0" || plan.Steps[1].ToVersion != "3.0.0" {
|
|
t.Errorf("step 2 = %s -> %s, want 2.0.0 -> 3.0.0",
|
|
plan.Steps[1].FromVersion, plan.Steps[1].ToVersion)
|
|
}
|
|
}
|