// SPDX-License-Identifier: AGPL-3.0-only package state import ( "bytes" "os" "path/filepath" "strings" "testing" "time" ) func TestCandidateLeaseRoundTripAndRemoval(t *testing.T) { root := filepath.Join(t.TempDir(), "service") release := filepath.Join(root, "releases", "sha256-"+strings.Repeat("a", 64)) if err := os.MkdirAll(release, 0o755); err != nil { t.Fatal(err) } path := CandidateLeasePath(filepath.Join(root, "state.json")) operation := strings.Repeat("d", 32) lease := CandidateLease{SchemaVersion: CandidateLeaseSchemaVersion, Service: "example-site", OperationID: operation, Release: release, Unit: "example-site-tend-candidate-" + operation[:12] + ".service", Address: "127.0.0.1:18092", StartedAt: time.Unix(1, 0).UTC().Format(time.RFC3339)} if err := StoreCandidateLease(path, root, lease); err != nil { t.Fatal(err) } loaded, err := LoadCandidateLease(path, root, "example-site") if err != nil || loaded != lease { t.Fatalf("loaded=%+v err=%v", loaded, err) } if err := RemoveCandidateLease(path); err != nil { t.Fatal(err) } if _, err := os.Lstat(path); !os.IsNotExist(err) { t.Fatalf("lease remains: %v", err) } } func TestCandidateLeaseRejectsCrossServiceAndSymlink(t *testing.T) { root := filepath.Join(t.TempDir(), "service") release := filepath.Join(root, "releases", "sha256-"+strings.Repeat("a", 64)) if err := os.MkdirAll(release, 0o755); err != nil { t.Fatal(err) } operation := strings.Repeat("d", 32) lease := CandidateLease{SchemaVersion: CandidateLeaseSchemaVersion, Service: "example-site", OperationID: operation, Release: release, Unit: "example-site-tend-candidate-" + operation[:12] + ".service", Address: "127.0.0.1:18092", StartedAt: time.Unix(1, 0).UTC().Format(time.RFC3339)} if err := lease.Validate(root, "other-site"); err == nil { t.Fatal("expected service mismatch") } path := CandidateLeasePath(filepath.Join(root, "state.json")) if err := os.Symlink(filepath.Join(root, "elsewhere"), path); err != nil { t.Fatal(err) } if err := StoreCandidateLease(path, root, lease); err == nil { t.Fatal("expected symlink refusal") } if err := RemoveCandidateLease(path); err == nil { t.Fatal("expected symlink removal refusal") } } func TestCandidateLeasePreservesSchemaOneDeploymentState(t *testing.T) { root := filepath.Join(t.TempDir(), "service") release := filepath.Join(root, "releases", "sha256-"+strings.Repeat("a", 64)) if err := os.MkdirAll(release, 0o755); err != nil { t.Fatal(err) } at := time.Unix(1, 0).UTC().Format(time.RFC3339) statePath := filepath.Join(root, "state.json") record := Record{SchemaVersion: SchemaVersion, Strategy: "singleton_candidate", DesiredRelease: release, CandidateRelease: release, ActiveSlot: "singleton", ActiveRelease: release, LastAttemptRelease: release, LastAttemptOutcome: "running", LastAttemptAt: at, UpdatedAt: at} if err := Store(statePath, root, record); err != nil { t.Fatal(err) } operation := strings.Repeat("d", 32) lease := CandidateLease{SchemaVersion: CandidateLeaseSchemaVersion, Service: "example-site", OperationID: operation, Release: release, Unit: "example-site-tend-candidate-" + operation[:12] + ".service", Address: "127.0.0.1:18092", StartedAt: at} if err := StoreCandidateLease(CandidateLeasePath(statePath), root, lease); err != nil { t.Fatal(err) } body, err := os.ReadFile(statePath) if err != nil { t.Fatal(err) } if !bytes.Contains(body, []byte(`"schema_version": 1`)) || bytes.Contains(body, []byte("candidate_operation_id")) || bytes.Contains(body, []byte("candidate_unit")) { t.Fatalf("deployment state contract changed: %s", body) } loaded, err := Load(statePath, root, "singleton_candidate") if err != nil || loaded.SchemaVersion != 1 || loaded.CandidateRelease != release { t.Fatalf("loaded=%+v err=%v", loaded, err) } }