// 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) } }