aboutsummaryrefslogtreecommitdiff
path: root/internal/plan/hostile_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:24:22 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 21:24:22 +0200
commit8c6afca96a9f0af41c5a9d05beb7f1dedb994740 (patch)
tree8ad65b02ced85ac1cecf6fdaf53269a602cb1666 /internal/plan/hostile_test.go
parenta92ce86c0dff7ce8f1de4113f20edd76c249371e (diff)
downloadkrino-8c6afca96a9f0af41c5a9d05beb7f1dedb994740.tar.gz
krino-8c6afca96a9f0af41c5a9d05beb7f1dedb994740.zip
plan 9: placeholders stay inside the directory the rule names
Diffstat (limited to 'internal/plan/hostile_test.go')
-rw-r--r--internal/plan/hostile_test.go38
1 files changed, 37 insertions, 1 deletions
diff --git a/internal/plan/hostile_test.go b/internal/plan/hostile_test.go
index c22fb24..a2f2bfe 100644
--- a/internal/plan/hostile_test.go
+++ b/internal/plan/hostile_test.go
@@ -39,7 +39,7 @@ func TestBuildRefusesDotDotFromPlaceholder(t *testing.T) {
}}
return Build("/r", in, time.Now(), NoDisk{}, NewClaims())[0].Steps[0]
}
- if s := step("/w/{1}/in"); s.Skip != `destination "/w/../in" leaves its directory through a placeholder` {
+ if s := step("/w/{1}/in"); s.Skip != `destination "/w/../in" leaves /w through a placeholder` {
t.Errorf("placeholder ..: Skip = %q, Dst = %q", s.Skip, s.Dst)
}
if s := step("/w/../in"); s.Skip != "" || s.Dst != "/in/x.pdf" {
@@ -49,3 +49,39 @@ func TestBuildRefusesDotDotFromPlaceholder(t *testing.T) {
t.Errorf("..x: Skip = %q, Dst = %q", s.Skip, s.Dst)
}
}
+
+// TestBuildKeepsPlaceholdersInsideTheirDirectory: a capture of "~", an
+// empty capture, or an extension-less {ext} at the start of a destination
+// used to make it $HOME or absolute (review M1). The resolved destination
+// must stay under the directory the rule's text names before its first
+// placeholder; a destination with no placeholder is the rule's own
+// business.
+func TestBuildKeepsPlaceholdersInsideTheirDirectory(t *testing.T) {
+ t.Setenv("HOME", "/home/x")
+ step := func(name, dest string, caps ...string) Step {
+ in := []Input{{File: file("/r", name), Rules: []RuleMatch{{Name: "a", Captures: caps, Actions: []config.Action{act(config.Move, dest)}}}}}
+ return Build("/r", in, time.Now(), NoDisk{}, NewClaims())[0].Steps[0]
+ }
+ for _, tc := range []struct {
+ name, dest string
+ caps []string
+ skip bool
+ dst string
+ }{
+ {"~_i.pdf", "{1}/Filed", []string{"~_", "~"}, true, ""},
+ {"_i.pdf", "{1}/Filed", []string{"_", ""}, true, ""},
+ {"README", "{ext}/tmp", nil, true, ""},
+ {"a.pdf", "Out/{1}", []string{"a", "~"}, false, "/r/Out/~/a.pdf"},
+ {"a.pdf", "~/docs/{1}", []string{"a", "x"}, false, "/home/x/docs/x/a.pdf"},
+ {"a.pdf", "~/docs/{1}", []string{"a", ".."}, true, ""},
+ {"a.pdf", "Work/Ac{1}", []string{"a", "me"}, false, "/r/Work/Acme/a.pdf"},
+ {"a.pdf", "/w/{1}/in", []string{"a", ".."}, true, ""},
+ {"a.pdf", "/w/../in", nil, false, "/in/a.pdf"},
+ {"a.pdf", "{mtime:%Y}", nil, false, "/r/2026/a.pdf"},
+ } {
+ s := step(tc.name, tc.dest, tc.caps...)
+ if tc.skip != (s.Skip != "") || (!tc.skip && s.Dst != tc.dst) {
+ t.Errorf("%s -> %s %q: Skip %q Dst %q; want skip=%v dst=%q", tc.name, tc.dest, tc.caps, s.Skip, s.Dst, tc.skip, tc.dst)
+ }
+ }
+}