package storage import ( "os" "path/filepath" "testing" "time" ) func TestLocalRemoveSymlinkDoesNotDeleteTarget(t *testing.T) { root := t.TempDir() outside := t.TempDir() target := filepath.Join(outside, "secret.txt") if err := os.WriteFile(target, []byte("keep"), 0o644); err != nil { t.Fatal(err) } link := filepath.Join(root, "link") if err := os.Symlink(target, link); err != nil { t.Skipf("symlink unavailable: %v", err) } store := Local{Root: root} if err := store.Remove("link"); err != nil { t.Fatalf("remove symlink: %v", err) } if _, err := os.Stat(target); err != nil { t.Fatalf("symlink removal must not delete target: %v", err) } if _, err := os.Lstat(link); !os.IsNotExist(err) { t.Fatalf("symlink should be removed, got %v", err) } } func TestLocalRejectsSymlinkEscapeAndAllowsNestedCreate(t *testing.T) { root := t.TempDir() outside := t.TempDir() if err := os.WriteFile(filepath.Join(outside, "secret.txt"), []byte("secret"), 0o644); err != nil { t.Fatal(err) } if err := os.Symlink(outside, filepath.Join(root, "escape")); err != nil { t.Fatal(err) } s := Local{Root: root} if _, err := s.Open("escape/secret.txt"); err == nil { t.Fatal("expected symlink escape to be rejected") } f, err := s.Create("a/b/c.txt") if err != nil { t.Fatalf("nested create failed: %v", err) } _ = f.Close() } func TestMoveToTrashPreservesRelativePathAndCleansOldEntries(t *testing.T) { root := t.TempDir() trash := t.TempDir() s := Local{Root: root} if err := os.MkdirAll(filepath.Join(root, "one", "two"), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "one", "two", "same.txt"), []byte("x"), 0o644); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(trash, "old"), 0o755); err != nil { t.Fatal(err) } old := filepath.Join(trash, "old", "stale.txt") if err := os.WriteFile(old, []byte("old"), 0o644); err != nil { t.Fatal(err) } oldTime := time.Now().Add(-31 * 24 * time.Hour) if err := os.Chtimes(old, oldTime, oldTime); err != nil { t.Fatal(err) } if err := s.MoveToTrash("one/two/same.txt", trash); err != nil { t.Fatal(err) } if _, err := os.Stat(filepath.Join(trash, "one", "two", "same.txt")); err != nil { t.Fatalf("trash path not preserved: %v", err) } if _, err := os.Stat(old); !os.IsNotExist(err) { t.Fatalf("stale trash entry was not cleaned: %v", err) } } func TestCopyPathPreservesSymlink(t *testing.T) { src := filepath.Join(t.TempDir(), "link") dst := filepath.Join(t.TempDir(), "link") if err := os.Symlink("target", src); err != nil { t.Fatal(err) } if err := copyPath(src, dst); err != nil { t.Fatal(err) } got, err := os.Readlink(dst) if err != nil { t.Fatal(err) } if got != "target" { t.Fatalf("got symlink target %q", got) } }