package lifecycle import ( "errors" "os" "path/filepath" "testing" "github.com/aritmolab/thothii/tools/tht/internal/config" "github.com/aritmolab/thothii/tools/tht/internal/safeio" ) func TestLifecycleLockIsExclusivePerInstallationAndReusableAfterRelease(t *testing.T) { root := lifecycleTestRoot(t) installation := config.Installation{ProjectDirectory: root, Path: filepath.Join(root, "thothii-installation.yaml")} if err := os.MkdirAll(installation.ControlDirectory(), 0o755); err != nil { t.Fatal(err) } first, err := Acquire(installation) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = first.Release() }) for name, path := range map[string]string{ "lifecycle parent": filepath.Dir(installation.ControlDirectory()), "lifecycle directory": installation.ControlDirectory(), "owner file": first.Path(), } { t.Run(name, func(t *testing.T) { var err error if name == "owner file" { err = safeio.ValidatePrivateRegular(path) } else { err = safeio.ValidatePrivateDirectory(path) } if err != nil { t.Fatalf("%s protection = %v", name, err) } }) } if _, err := Acquire(installation); !errors.Is(err, ErrLocked) { t.Fatalf("second Acquire() error = %v, want ErrLocked", err) } if err := first.Release(); err != nil { t.Fatal(err) } second, err := Acquire(installation) if err != nil { t.Fatalf("Acquire() after release: %v", err) } if err := second.Release(); err != nil { t.Fatal(err) } } func TestLifecycleLockReleaseDoesNotRemoveAnotherOwnersFile(t *testing.T) { root := lifecycleTestRoot(t) installation := config.Installation{ProjectDirectory: root, Path: filepath.Join(root, "thothii-installation.yaml")} lock, err := Acquire(installation) if err != nil { t.Fatal(err) } if err := os.WriteFile(lock.Path(), []byte(`{"token":"different-owner"}`), 0o600); err != nil { t.Fatal(err) } if err := lock.Release(); !errors.Is(err, ErrOwnership) { t.Fatalf("Release() error = %v, want ErrOwnership", err) } if _, err := os.Stat(lock.Path()); err != nil { t.Fatalf("foreign lock was removed: %v", err) } } func TestTransactionCapabilityIsInstallationBoundAndExpiresOnRelease(t *testing.T) { root := lifecycleTestRoot(t) installation := config.Installation{ProjectDirectory: root, Path: filepath.Join(root, "thothii-installation.yaml")} transaction, err := AcquireTransaction(installation) if err != nil { t.Fatal(err) } if err := transaction.Verify(installation); err != nil { t.Fatalf("Verify() active capability error = %v", err) } otherRoot := lifecycleTestRoot(t) other := config.Installation{ProjectDirectory: otherRoot, Path: filepath.Join(otherRoot, "thothii-installation.yaml")} if err := transaction.Verify(other); !errors.Is(err, ErrTransactionInstallation) { t.Fatalf("Verify() for another installation error = %v, want ErrTransactionInstallation", err) } if err := transaction.Release(); err != nil { t.Fatal(err) } if err := transaction.Verify(installation); !errors.Is(err, ErrTransactionInactive) { t.Fatalf("Verify() after Release() error = %v, want ErrTransactionInactive", err) } } func lifecycleTestRoot(t *testing.T) string { t.Helper() root, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatal(err) } return root }