mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-30 09:32:05 -04:00
316 lines
7.6 KiB
Go
316 lines
7.6 KiB
Go
package trash
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func setupHomeTrash(t *testing.T) (homeRoot string, trashDir string) {
|
|
t.Helper()
|
|
homeRoot = t.TempDir()
|
|
xdg := filepath.Join(homeRoot, ".local", "share")
|
|
if err := os.MkdirAll(xdg, 0o700); err != nil {
|
|
t.Fatalf("mkdir xdg: %v", err)
|
|
}
|
|
t.Setenv("XDG_DATA_HOME", xdg)
|
|
t.Setenv("HOME", homeRoot)
|
|
trashDir = filepath.Join(xdg, "Trash")
|
|
return homeRoot, trashDir
|
|
}
|
|
|
|
func writeFile(t *testing.T, path, content string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
t.Fatalf("write %s: %v", path, err)
|
|
}
|
|
}
|
|
|
|
func TestPutHomeTrashAbsolutePath(t *testing.T) {
|
|
homeRoot, trashDir := setupHomeTrash(t)
|
|
|
|
src := filepath.Join(homeRoot, "doc.txt")
|
|
writeFile(t, src, "hi")
|
|
|
|
entry, err := Put(src)
|
|
if err != nil {
|
|
t.Fatalf("Put: %v", err)
|
|
}
|
|
|
|
if entry.Name != "doc.txt" {
|
|
t.Errorf("name = %q, want doc.txt", entry.Name)
|
|
}
|
|
if entry.OriginalPath != src {
|
|
t.Errorf("originalPath = %q, want %q", entry.OriginalPath, src)
|
|
}
|
|
if entry.TrashDir != trashDir {
|
|
t.Errorf("trashDir = %q, want %q", entry.TrashDir, trashDir)
|
|
}
|
|
if _, err := os.Stat(src); !os.IsNotExist(err) {
|
|
t.Errorf("source still exists: %v", err)
|
|
}
|
|
|
|
body, err := os.ReadFile(filepath.Join(trashDir, "info", "doc.txt.trashinfo"))
|
|
if err != nil {
|
|
t.Fatalf("read trashinfo: %v", err)
|
|
}
|
|
if !strings.HasPrefix(string(body), "[Trash Info]\n") {
|
|
t.Errorf("trashinfo missing header: %q", body)
|
|
}
|
|
if !strings.Contains(string(body), "Path="+src+"\n") {
|
|
t.Errorf("Path key missing or wrong: %q", body)
|
|
}
|
|
if !strings.Contains(string(body), "DeletionDate=") {
|
|
t.Errorf("DeletionDate missing: %q", body)
|
|
}
|
|
}
|
|
|
|
func TestPutPercentEncodesPath(t *testing.T) {
|
|
homeRoot, trashDir := setupHomeTrash(t)
|
|
|
|
name := "spaces & %.txt"
|
|
src := filepath.Join(homeRoot, name)
|
|
writeFile(t, src, "x")
|
|
|
|
if _, err := Put(src); err != nil {
|
|
t.Fatalf("Put: %v", err)
|
|
}
|
|
|
|
body, err := os.ReadFile(filepath.Join(trashDir, "info", name+".trashinfo"))
|
|
if err != nil {
|
|
t.Fatalf("read: %v", err)
|
|
}
|
|
want := "Path=" + filepath.Dir(src) + "/spaces%20&%20%25.txt"
|
|
if !strings.Contains(string(body), want) {
|
|
t.Errorf("expected %q in %q", want, body)
|
|
}
|
|
}
|
|
|
|
func TestPutCollisionGetsUniqueName(t *testing.T) {
|
|
homeRoot, trashDir := setupHomeTrash(t)
|
|
|
|
for i := range 3 {
|
|
src := filepath.Join(homeRoot, "dup.txt")
|
|
writeFile(t, src, "x")
|
|
if _, err := Put(src); err != nil {
|
|
t.Fatalf("Put #%d: %v", i, err)
|
|
}
|
|
}
|
|
|
|
want := []string{"dup.txt", "dup.2.txt", "dup.3.txt"}
|
|
for _, n := range want {
|
|
if _, err := os.Stat(filepath.Join(trashDir, "files", n)); err != nil {
|
|
t.Errorf("expected %s in trash: %v", n, err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(trashDir, "info", n+".trashinfo")); err != nil {
|
|
t.Errorf("expected %s.trashinfo: %v", n, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestListAndCount(t *testing.T) {
|
|
homeRoot, _ := setupHomeTrash(t)
|
|
|
|
if n, _ := Count(); n != 0 {
|
|
t.Errorf("initial count = %d, want 0", n)
|
|
}
|
|
entries, _ := List()
|
|
if len(entries) != 0 {
|
|
t.Errorf("initial list len = %d, want 0", len(entries))
|
|
}
|
|
|
|
for _, n := range []string{"a.txt", "b.txt", "c.log"} {
|
|
src := filepath.Join(homeRoot, n)
|
|
writeFile(t, src, n)
|
|
if _, err := Put(src); err != nil {
|
|
t.Fatalf("Put %s: %v", n, err)
|
|
}
|
|
}
|
|
|
|
got, _ := Count()
|
|
if got != 3 {
|
|
t.Errorf("count = %d, want 3", got)
|
|
}
|
|
entries, _ = List()
|
|
if len(entries) != 3 {
|
|
t.Errorf("list len = %d, want 3", len(entries))
|
|
}
|
|
for _, e := range entries {
|
|
if e.OriginalPath == "" {
|
|
t.Errorf("entry %s: empty OriginalPath", e.Name)
|
|
}
|
|
if _, err := time.Parse("2006-01-02T15:04:05", e.DeletionDate); err != nil {
|
|
t.Errorf("entry %s: bad DeletionDate %q: %v", e.Name, e.DeletionDate, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEmptyClearsAll(t *testing.T) {
|
|
homeRoot, trashDir := setupHomeTrash(t)
|
|
|
|
for _, n := range []string{"x", "y", "z"} {
|
|
src := filepath.Join(homeRoot, n)
|
|
writeFile(t, src, n)
|
|
if _, err := Put(src); err != nil {
|
|
t.Fatalf("Put: %v", err)
|
|
}
|
|
}
|
|
if n, _ := Count(); n != 3 {
|
|
t.Fatalf("pre-empty count = %d", n)
|
|
}
|
|
|
|
if err := Empty(); err != nil {
|
|
t.Fatalf("Empty: %v", err)
|
|
}
|
|
|
|
if n, _ := Count(); n != 0 {
|
|
t.Errorf("post-empty count = %d, want 0", n)
|
|
}
|
|
for _, sub := range []string{"files", "info"} {
|
|
ents, err := os.ReadDir(filepath.Join(trashDir, sub))
|
|
if err != nil {
|
|
t.Fatalf("readdir %s: %v", sub, err)
|
|
}
|
|
if len(ents) != 0 {
|
|
t.Errorf("%s/ has %d entries, want 0", sub, len(ents))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRestoreToOriginalPath(t *testing.T) {
|
|
homeRoot, trashDir := setupHomeTrash(t)
|
|
|
|
src := filepath.Join(homeRoot, "sub", "dir", "thing.txt")
|
|
writeFile(t, src, "payload")
|
|
|
|
entry, err := Put(src)
|
|
if err != nil {
|
|
t.Fatalf("Put: %v", err)
|
|
}
|
|
|
|
os.RemoveAll(filepath.Join(homeRoot, "sub"))
|
|
|
|
if err := Restore(entry.Name, trashDir); err != nil {
|
|
t.Fatalf("Restore: %v", err)
|
|
}
|
|
|
|
body, err := os.ReadFile(src)
|
|
if err != nil {
|
|
t.Fatalf("read restored: %v", err)
|
|
}
|
|
if string(body) != "payload" {
|
|
t.Errorf("restored content = %q, want %q", body, "payload")
|
|
}
|
|
|
|
if _, err := os.Stat(entry.InfoPath); !os.IsNotExist(err) {
|
|
t.Errorf("info file still present: %v", err)
|
|
}
|
|
if _, err := os.Stat(entry.FilesPath); !os.IsNotExist(err) {
|
|
t.Errorf("files entry still present: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRestoreRefusesToOverwrite(t *testing.T) {
|
|
homeRoot, trashDir := setupHomeTrash(t)
|
|
|
|
src := filepath.Join(homeRoot, "keep.txt")
|
|
writeFile(t, src, "v1")
|
|
|
|
entry, err := Put(src)
|
|
if err != nil {
|
|
t.Fatalf("Put: %v", err)
|
|
}
|
|
|
|
writeFile(t, src, "v2-blocking")
|
|
|
|
err = Restore(entry.Name, trashDir)
|
|
if err == nil {
|
|
t.Fatalf("expected error on conflicting restore, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "exists") {
|
|
t.Errorf("error %q does not mention conflict", err)
|
|
}
|
|
|
|
body, _ := os.ReadFile(src)
|
|
if string(body) != "v2-blocking" {
|
|
t.Errorf("blocking file altered: %q", body)
|
|
}
|
|
}
|
|
|
|
func TestPutDirectory(t *testing.T) {
|
|
homeRoot, trashDir := setupHomeTrash(t)
|
|
|
|
dir := filepath.Join(homeRoot, "myfolder")
|
|
writeFile(t, filepath.Join(dir, "child.txt"), "inside")
|
|
|
|
entry, err := Put(dir)
|
|
if err != nil {
|
|
t.Fatalf("Put dir: %v", err)
|
|
}
|
|
if !entry.IsDir {
|
|
t.Errorf("IsDir = false, want true")
|
|
}
|
|
|
|
moved := filepath.Join(trashDir, "files", "myfolder", "child.txt")
|
|
body, err := os.ReadFile(moved)
|
|
if err != nil {
|
|
t.Fatalf("read moved child: %v", err)
|
|
}
|
|
if string(body) != "inside" {
|
|
t.Errorf("child content = %q", body)
|
|
}
|
|
}
|
|
|
|
func TestIsValidSharedTrashRejectsSymlink(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
target := filepath.Join(tmp, "real")
|
|
if err := os.MkdirAll(target, os.ModeSticky|0o777); err != nil {
|
|
t.Fatalf("mkdir target: %v", err)
|
|
}
|
|
|
|
link := filepath.Join(tmp, ".Trash")
|
|
if err := os.Symlink(target, link); err != nil {
|
|
t.Fatalf("symlink: %v", err)
|
|
}
|
|
if isValidSharedTrash(link) {
|
|
t.Errorf("symlinked .Trash accepted; spec requires rejection")
|
|
}
|
|
}
|
|
|
|
func TestIsValidSharedTrashRequiresStickyBit(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
dir := filepath.Join(tmp, ".Trash")
|
|
if err := os.MkdirAll(dir, 0o777); err != nil {
|
|
t.Fatalf("mkdir: %v", err)
|
|
}
|
|
if isValidSharedTrash(dir) {
|
|
t.Errorf(".Trash without sticky bit accepted; spec requires rejection")
|
|
}
|
|
|
|
if err := os.Chmod(dir, os.ModeSticky|0o777); err != nil {
|
|
t.Fatalf("chmod: %v", err)
|
|
}
|
|
if !isValidSharedTrash(dir) {
|
|
t.Errorf(".Trash with sticky bit rejected; spec accepts it")
|
|
}
|
|
}
|
|
|
|
func TestPathEncodeRoundTrip(t *testing.T) {
|
|
cases := []string{
|
|
"/home/u/file.txt",
|
|
"/path with spaces/and-symbols & %.txt",
|
|
"relative/path/é unicode.md",
|
|
}
|
|
for _, in := range cases {
|
|
got := pathDecode(pathEncode(in))
|
|
if got != in {
|
|
t.Errorf("round-trip %q -> %q", in, got)
|
|
}
|
|
}
|
|
}
|