aboutsummaryrefslogtreecommitdiff
path: root/cmd/krino/sort_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-12 20:14:47 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-12 20:14:47 +0200
commit3f8679be9373ee7508d512dfdfc1dda0839c7f90 (patch)
treeec02eb075f6c4e90f21baa2fe674e86a2f7f6a62 /cmd/krino/sort_test.go
parent24a84671ace373ae331fa83a1ff484990f4dff0e (diff)
downloadkrino-3f8679be9373ee7508d512dfdfc1dda0839c7f90.tar.gz
krino-3f8679be9373ee7508d512dfdfc1dda0839c7f90.zip
krino: acting — trash, journal, apply, lock, review, undo
Diffstat (limited to 'cmd/krino/sort_test.go')
-rw-r--r--cmd/krino/sort_test.go91
1 files changed, 90 insertions, 1 deletions
diff --git a/cmd/krino/sort_test.go b/cmd/krino/sort_test.go
index a646503..e482f44 100644
--- a/cmd/krino/sort_test.go
+++ b/cmd/krino/sort_test.go
@@ -2,7 +2,16 @@
package main
-import "testing"
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "krino/internal/plan"
+ "krino/internal/scan"
+)
// TestRelWidthAndPadCellCountRunes: C4. relWidth and padCell must measure
// column width in runes, not bytes, or a name carrying diacritics
@@ -21,3 +30,83 @@ func TestRelWidthAndPadCellCountRunes(t *testing.T) {
t.Fatalf("padCell(%q, 9) = %q, want %q (already at width: no padding)", "próba.txt", got, want)
}
}
+
+// TestActionableChainsAgreesWithCountActing is fix wave item 4 / Minor 5:
+// countActing (render.go) and actionableChains used to disagree over a
+// chain every one of whose steps is skipped (len(Steps) > 0, but every
+// step's own Skip is set) - countActing already excluded it from "to act
+// on", while actionableChains's own len(Steps) > 0 check still offered it
+// for approval, so a directory could print "N scanned · 0 to act on" and
+// then still ask the user to approve a file it had just said there were
+// none of. Converged on chainActing (render.go), both must now agree.
+func TestActionableChainsAgreesWithCountActing(t *testing.T) {
+ chains := []plan.Chain{
+ {File: scan.File{Rel: "a.txt"}, Steps: []plan.Step{{Kind: plan.Move, Skip: "target exists"}}},
+ {File: scan.File{Rel: "b.txt"}, Steps: []plan.Step{{Kind: plan.Move, Dst: "/r/W/b.txt"}}},
+ }
+ if got := countActing(chains); got != 1 {
+ t.Errorf("countActing = %d, want 1 (a.txt is all-skipped)", got)
+ }
+ actionable := actionableChains(chains)
+ if len(actionable) != 1 || actionable[0].File.Rel != "b.txt" {
+ t.Errorf("actionableChains = %+v, want only b.txt - an all-skipped chain must never be offered for approval", actionable)
+ }
+}
+
+// TestAllSkippedDirectoryReportsZeroAndLogsNothing is fix wave item 4 /
+// Minor 5 and 6, end to end. Before the fix: a directory whose one file
+// matches a rule under (on-conflict skip) - so its single step's own Skip
+// is set ("target exists") - printed "0 to act on" (countActing) and then,
+// with -y, still ran that chain through Apply anyway (actionableChains'
+// own len(Steps) > 0 check approved it regardless), logging a
+// run-start/run-end pair holding only a "skipped" entry while the outcome
+// line read "0 applied · 0 failed · 0 declined" for a file that had just
+// been silently processed. After the fix, the chain is never offered for
+// approval, Apply is never even called for this directory, and the journal
+// gains nothing at all.
+func TestAllSkippedDirectoryReportsZeroAndLogsNothing(t *testing.T) {
+ h := home(t)
+ dl := filepath.Join(h, "dl")
+ if err := os.MkdirAll(filepath.Join(dl, "Out"), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ old := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
+ for _, p := range []string{filepath.Join(dl, "a.txt"), filepath.Join(dl, "Out", "a.txt")} {
+ if err := os.WriteFile(p, []byte("x"), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ if err := os.Chtimes(p, old, old); err != nil {
+ t.Fatal(err)
+ }
+ }
+ if code, _, errOut := runCLI(t, "init"); code != 0 {
+ t.Fatal(errOut)
+ }
+ if code, _, errOut := runCLI(t, "new", "dl", dl); code != 0 {
+ t.Fatal(errOut)
+ }
+ rules := "(path \"~/dl\")\n(min-age 0s)\n(on-conflict skip)\n(rule \"r\" (when (type text)) (move \"Out\"))\n"
+ if err := os.WriteFile(filepath.Join(h, ".config", "krino", "dirs", "dl.conf"), []byte(rules), 0o644); err != nil {
+ t.Fatal(err)
+ }
+
+ code, out, errOut := runCLI(t, "-y")
+ if code != 0 {
+ t.Fatalf("run: %d %s", code, errOut)
+ }
+ if !strings.Contains(out, "1 scanned · 0 to act on") {
+ t.Errorf("output = %q, want \"0 to act on\"", out)
+ }
+ if !strings.Contains(out, zeroOutcome) {
+ t.Errorf("output = %q, want the honest zero outcome %q", out, zeroOutcome)
+ }
+
+ logPath := filepath.Join(h, ".local", "state", "krino", "krino.log")
+ data, err := os.ReadFile(logPath)
+ if err != nil {
+ t.Fatalf("reading the journal: %v", err)
+ }
+ if len(data) != 0 {
+ t.Errorf("journal gained entries for a directory with nothing to act on:\n%s", data)
+ }
+}