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
|
// SPDX-License-Identifier: GPL-3.0-or-later
package engine
import (
"context"
"path/filepath"
"reflect"
"strings"
"testing"
"krino/internal/plan"
)
// TestExplainReportsCapturesAndChain: explain returns what each matching
// rule captured and the steps the file would get, so a GUI can show
// "{1}=2026" and the destination without planning the whole directory
// (GUI design §1.3).
func TestExplainReportsCapturesAndChain(t *testing.T) {
h, dl := excludeTree(t, map[string]string{"Screenshot_2026-09-01.png": "x"})
main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": `
(path "~/dl")
(rule "shots" (when (name "^Screenshot_(\d{4})-(\d{2})")) (move "Pictures/{1}-{2}") (stop))
`})
e, errs := Load(main)
if len(errs) > 0 {
t.Fatal(errs)
}
x, err := e.Explain(context.Background(), filepath.Join(dl, "Screenshot_2026-09-01.png"))
if err != nil {
t.Fatal(err)
}
if len(x.Rules) != 1 || !reflect.DeepEqual(x.Rules[0].Captures, []string{"Screenshot_2026-09", "2026", "09"}) {
t.Fatalf("captures = %+v", x.Rules)
}
if len(x.Chain) != 1 || x.Chain[0].Kind != plan.Move || !strings.HasSuffix(x.Chain[0].Dst, "/dl/Pictures/2026-09/Screenshot_2026-09-01.png") {
t.Fatalf("chain = %+v", x.Chain)
}
}
// TestExplainChainMatchesThePlan: the chain explain reports is the one the
// directory's plan gives the same file, conflicts and skips included.
func TestExplainChainMatchesThePlan(t *testing.T) {
h, dl := excludeTree(t, map[string]string{"a.pdf": "one", "Out/a.pdf": "taken"})
main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": `
(path "~/dl")
(ignore "Out/")
(rule "r" (when (name "^a")) (rename "b.pdf") (move "Out"))
`})
e, errs := Load(main)
if len(errs) > 0 {
t.Fatal(errs)
}
dp, err := e.Plan(context.Background(), e.Dirs[0], plan.NewClaims())
if err != nil {
t.Fatal(err)
}
x, err := e.Explain(context.Background(), filepath.Join(dl, "a.pdf"))
if err != nil {
t.Fatal(err)
}
if len(dp.Chains) != 1 || !reflect.DeepEqual(x.Chain, dp.Chains[0].Steps) {
t.Errorf("explain chain %+v\nplan chain %+v", x.Chain, dp.Chains[0].Steps)
}
}
|