// SPDX-License-Identifier: AGPL-3.0-only package state import ( "os" "path/filepath" "testing" "time" ) func TestStoreLoadRoundTripAndRejectSymlink(t *testing.T) { root := filepath.Join(t.TempDir(), "service") release := filepath.Join(root, "releases", "sha256-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") if err := os.MkdirAll(release, 0o755); err != nil { t.Fatal(err) } path := filepath.Join(root, "state.json") at := time.Unix(1, 0).UTC().Format(time.RFC3339) record := Record{SchemaVersion: 1, Strategy: "singleton_candidate", DesiredRelease: release, ActiveSlot: "singleton", ActiveRelease: release, LastAttemptRelease: release, LastAttemptOutcome: "succeeded", LastAttemptAt: at, UpdatedAt: at} if err := Store(path, root, record); err != nil { t.Fatal(err) } loaded, err := Load(path, root, "singleton_candidate") if err != nil { t.Fatal(err) } if loaded.ActiveRelease != release || loaded.DesiredRelease != release || loaded.LastAttemptOutcome != "succeeded" { t.Fatalf("state=%+v", loaded) } if err := os.Remove(path); err != nil { t.Fatal(err) } if err := os.Symlink(filepath.Join(root, "elsewhere"), path); err != nil { t.Fatal(err) } if err := Store(path, root, record); err == nil { t.Fatal("expected symlink refusal") } } func TestRecordRequiresCandidateForRunningAttempt(t *testing.T) { root := filepath.Join(t.TempDir(), "service") release := filepath.Join(root, "releases", "sha256-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") record := Record{SchemaVersion: 1, Strategy: "singleton_candidate", DesiredRelease: release, ActiveSlot: "singleton", ActiveRelease: release, LastAttemptRelease: release, LastAttemptOutcome: "running", LastAttemptAt: time.Unix(1, 0).UTC().Format(time.RFC3339), UpdatedAt: time.Unix(1, 0).UTC().Format(time.RFC3339)} if err := record.Validate(root, "singleton_candidate"); err == nil { t.Fatal("expected missing candidate rejection") } } func TestRecordRejectsReleaseOutsideRoot(t *testing.T) { record := Record{SchemaVersion: 1, Strategy: "singleton_candidate", ActiveSlot: "singleton", ActiveRelease: "/tmp/other/release", UpdatedAt: time.Unix(1, 0).UTC().Format(time.RFC3339)} if err := record.Validate("/opt/example", "singleton_candidate"); err == nil { t.Fatal("expected path refusal") } }