aboutsummaryrefslogtreecommitdiff
path: root/internal/engine/undo_identity_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 22:39:27 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 22:39:27 +0200
commit9e65644f473d75ceb7e3ef67302189eeaba0f922 (patch)
tree6cde9486fb55e035f5e9041f5ba2cd579c9c8ff2 /internal/engine/undo_identity_test.go
parent8227de6a887c8600746b06d1287cb2b00de77e28 (diff)
downloadkrino-9e65644f473d75ceb7e3ef67302189eeaba0f922.tar.gz
krino-9e65644f473d75ceb7e3ef67302189eeaba0f922.zip
plan 10: missing and weak tests (per-step logging, trash path, fuzz oracle, w after error)
Diffstat (limited to 'internal/engine/undo_identity_test.go')
-rw-r--r--internal/engine/undo_identity_test.go60
1 files changed, 60 insertions, 0 deletions
diff --git a/internal/engine/undo_identity_test.go b/internal/engine/undo_identity_test.go
index 7de4eb9..f9d6307 100644
--- a/internal/engine/undo_identity_test.go
+++ b/internal/engine/undo_identity_test.go
@@ -432,3 +432,63 @@ func TestUndoDoesNotOfferOnlyADirectoryRemoval(t *testing.T) {
t.Errorf("after a complete undo, still offered: %+v", again.Files)
}
}
+
+// TestApplyLogsEachStepAsItCompletes: the first step's log entry is written
+// before the second step runs - observed from the clock the log asks for
+// each entry's time - so a run killed mid-chain leaves what it did undoable
+// (review M9).
+func TestApplyLogsEachStepAsItCompletes(t *testing.T) {
+ h := sandbox(t)
+ p := filepath.Join(h, "dl", "a.pdf")
+ os.MkdirAll(filepath.Dir(p), 0o755)
+ os.WriteFile(p, []byte("one"), 0o644)
+ old := time.Now().Add(-2 * time.Hour)
+ os.Chtimes(p, old, old)
+ main := writeConfig(t, h, `(include "dl")`, map[string]string{"dl": "(path \"~/dl\")\n(rule \"r\" (rename \"r-{name}\") (move \"Out\"))\n"})
+ 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)
+ }
+ j, err := journal.Open(filepath.Join(h, "state", "krino.log"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer j.Close()
+ calls := 0
+ e.Now = func() time.Time {
+ calls++
+ if calls == 2 { // the rename's own entry is about to be written
+ if _, err := os.Lstat(filepath.Join(h, "dl", "Out", "r-a.pdf")); err == nil {
+ t.Error("the move had already run when the rename was logged: steps are logged after the whole chain")
+ }
+ }
+ return time.Now()
+ }
+ if _, err := e.Apply(context.Background(), dp, map[string]bool{"a.pdf": true}, j, "R"); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// TestUndoRefusesATrashEntryRecordedForAnotherPath: a trash entry with the
+// size and mtime the run logged, whose trashinfo now names another original
+// path, belongs to another file and is refused (review M2).
+func TestUndoRefusesATrashEntryRecordedForAnotherPath(t *testing.T) {
+ e, run, h, _ := appliedRun(t, map[string]map[string]string{"dl": {"a.pdf": "one"}},
+ map[string]string{"dl": "(path \"~/dl\")\n(rule \"r\" (delete))\n"})
+ info := filepath.Join(trash.Dir(), "info", "a.pdf.trashinfo")
+ body := "[Trash Info]\nPath=" + filepath.Join(h, "elsewhere", "a.pdf") + "\nDeletionDate=2026-09-14T10:00:00\n"
+ if err := os.WriteFile(info, []byte(body), 0o600); err != nil {
+ t.Fatal(err)
+ }
+ up, err := e.PlanUndo(run)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if f := undoFileNamed(t, up, "dl", "a.pdf"); !strings.Contains(f.Refused, "belongs to another file") {
+ t.Errorf("Refused = %q; want the trash entry named as another file's", f.Refused)
+ }
+}