aboutsummaryrefslogtreecommitdiff
path: root/internal/config/dir_test.go
blob: 7ddfa2a7d59e76f9ba805c838f2fcced9ea8a0e9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// SPDX-License-Identifier: GPL-3.0-or-later

package config

import (
	"reflect"
	"testing"
)

const fullDir = `;; -*- mode: lisp -*-
(path "~/downloads")
(recursive yes)
(ignore "*.part" "*.aria2")
(ignore ".*")

(rule "acme"
  (when (type document)
        (or (content "acme ltd") (name "\bacme\b")))
  (move "Work/Acme/{mtime:%Y}")
  (stop))

(rule "photos"
  (case strict)
  (when (type image))
  (rename "{mtime:%Y-%m-%d}_{stem}{ext}")
  (move "Photos"))

(rule "old" (when (type package) (age > 30d)) (delete permanent))
(rule "keep" (when (name "^important")) (stop))
(rule "all" (copy "~/backup"))
`

func kinds(r *Rule) []ActionKind {
	var k []ActionKind
	for _, a := range r.Actions {
		k = append(k, a.Kind)
	}
	return k
}

func TestParseDir(t *testing.T) {
	t.Setenv("HOME", "/home/u")
	dir, errs := ParseDir("downloads", "downloads.conf", []byte(fullDir))
	if len(errs) > 0 {
		t.Fatal(errs)
	}
	if dir.Name != "downloads" || dir.Path != "/home/u/downloads" || dir.PathText != "~/downloads" {
		t.Errorf("dir = %+v", dir)
	}
	if !reflect.DeepEqual(dir.Ignore, []string{"*.part", "*.aria2", ".*"}) {
		t.Errorf("Ignore = %q", dir.Ignore)
	}
	if !dir.Settings.Over(Builtin()).Recursive {
		t.Error("recursive not set")
	}
	if len(dir.Rules) != 5 {
		t.Fatalf("got %d rules", len(dir.Rules))
	}
	acme := dir.Rules[0]
	if acme.Name != "acme" || !acme.Stop || !acme.HasWhen || len(acme.When) != 2 || acme.When[1].Head() != "or" {
		t.Errorf("acme = %+v", acme)
	}
	if len(acme.Actions) != 1 || acme.Actions[0].Kind != Move || acme.Actions[0].Arg != "Work/Acme/{mtime:%Y}" {
		t.Errorf("acme actions = %+v", acme.Actions)
	}
	photos := dir.Rules[1]
	if photos.Settings.Over(Builtin()).Case != CaseStrict {
		t.Error("photos: case strict not set")
	}
	if got := kinds(photos); !reflect.DeepEqual(got, []ActionKind{Rename, Move}) {
		t.Errorf("photos actions = %v", got)
	}
	if got := kinds(dir.Rules[2]); !reflect.DeepEqual(got, []ActionKind{DeletePermanent}) {
		t.Errorf("old actions = %v", got)
	}
	if keep := dir.Rules[3]; !keep.Stop || len(keep.Actions) != 0 {
		t.Errorf("keep = %+v", keep)
	}
	if all := dir.Rules[4]; all.HasWhen || all.When != nil || all.Actions[0].Arg != "~/backup" {
		t.Errorf("all = %+v", all)
	}
}

func TestActionKindString(t *testing.T) {
	want := map[ActionKind]string{Copy: "copy", Move: "move", Rename: "rename", Delete: "delete", DeletePermanent: "delete permanent"}
	for k, s := range want {
		if k.String() != s {
			t.Errorf("%d.String() = %q, want %q", k, k.String(), s)
		}
	}
}

func TestParseDirErrors(t *testing.T) {
	tests := []struct{ src, want string }{
		{``, `d.conf: no (path ...): say which directory this file sorts`},
		{`(path ~/x)`, `d.conf:1:7: path must be a string: write (path "~/x")`},
		{`(path "rel")`, `d.conf:1:7: path must be absolute or start with ~, not "rel"`},
		{`(path "/a") (path "/b")`, `d.conf:1:13: path given twice (first at line 1)`},
		{`(path "/a") (ignore part)`, `d.conf:1:21: ignore takes patterns in quotes, like "*.part"; got part`},
		{`(path "/a") (rule x (stop))`, `d.conf:1:13: rule needs a name in quotes first, like (rule "invoices" ...)`},
		{`(path "/a") (rule "x" (stop)) (rule "x" (stop))`, `d.conf:1:31: rule "x" defined twice (first at line 1)`},
		{`(path "/a") (rule "x" (when (type pdf)))`, `d.conf:1:13: rule "x" does nothing: give it an action like (move "Somewhere") or (stop)`},
		{`(path "/a") (rule "x" (when) (stop))`, `d.conf:1:23: rule "x": (when) needs a condition; leave it out to match every file`},
		{`(path "/a") (rule "x" (when pdf) (stop))`, `d.conf:1:29: rule "x": a condition is a form like (type pdf), not pdf`},
		{`(path "/a") (rule "x" (when (a)) (when (b)) (stop))`, `d.conf:1:34: rule "x": when given twice (first at line 1)`},
		{`(path "/a") (rule "x" (recursive yes) (stop))`, `d.conf:1:23: rule "x": recursive cannot be set in a rule, only case, fold and on-conflict`},
		{`(path "/a") (rule "x" (move Work))`, `d.conf:1:29: rule "x": move takes a string: write (move "Work")`},
		{`(path "/a") (rule "x" (move))`, `d.conf:1:23: rule "x": move takes one directory in quotes`},
		{`(path "/a") (rule "x" (rename "a/b"))`, `d.conf:1:31: rule "x": rename gives a new name, not a path; use move to change directory`},
		{`(path "/a") (rule "x" (delete forever))`, `d.conf:1:23: rule "x": write (delete) for the Trash or (delete permanent)`},
		{`(path "/a") (rule "x" (delete) (move "y"))`, `d.conf:1:32: rule "x": (move "y") after delete would never run`},
		{`(path "/a") (rule "x" (stop now))`, `d.conf:1:23: rule "x": stop takes nothing: write (stop)`},
		{`(path "/a") (rule "x" (fly "y"))`, `d.conf:1:23: rule "x": unknown form (fly ...); a rule has when, copy, move, rename, delete, stop, case, fold and on-conflict`},
		{`(path "/a") (sort "x")`, `d.conf:1:13: unknown form (sort ...); a directory file has path, ignore, rule and settings like (recursive yes)`},
	}
	for _, tt := range tests {
		_, errs := ParseDir("d", "d.conf", []byte(tt.src))
		if len(errs) != 1 || errs[0].Error() != tt.want {
			t.Errorf("%s:\n got  %v\n want %s", tt.src, errs, tt.want)
		}
	}
}