diff options
Diffstat (limited to 'cmd/krino/history_test.go')
| -rw-r--r-- | cmd/krino/history_test.go | 76 |
1 files changed, 74 insertions, 2 deletions
diff --git a/cmd/krino/history_test.go b/cmd/krino/history_test.go index 5d86b6f..1a9ea2b 100644 --- a/cmd/krino/history_test.go +++ b/cmd/krino/history_test.go @@ -4,6 +4,7 @@ package main import ( "bytes" + "context" "fmt" "os" "path/filepath" @@ -12,6 +13,7 @@ import ( "time" "krino/internal/engine" + "krino/internal/journal" ) func TestLogListsRunsAndUndoReverses(t *testing.T) { @@ -45,8 +47,19 @@ func TestLogListsRunsAndUndoReverses(t *testing.T) { if _, out, _ = runCLI(t, "log"); !strings.Contains(out, "undone") { t.Errorf("log does not mark the run undone:\n%s", out) } - if code, _, errOut = runCLI(t, "undo", "-y"); code == 0 { - t.Errorf("undoing an undo run succeeded: %q", errOut) + // Plain undo after an undo continues the run it undid (review M10): + // everything came back, so nothing is left and nothing moves. + if code, out, errOut := runCLI(t, "undo", "-y"); code != 0 || !strings.Contains(out, "0 applied") { + t.Errorf("undo after a complete undo: %d\n%s\n%s", code, out, errOut) + } + if _, err := os.Stat(filepath.Join(h, "dl", "inv1.txt")); err != nil { + t.Errorf("the restored file moved: %v", err) + } + // Naming the undo run itself is still refused. + _, out, _ = runCLI(t, "log") + undoRun := strings.Fields(out)[0] + if code, _, errOut = runCLI(t, "undo", "-y", undoRun); code == 0 || !strings.Contains(errOut, "itself an undo") { + t.Errorf("undoing undo run %s: exit %d %q", undoRun, code, errOut) } } @@ -394,3 +407,62 @@ func TestGlobalYesBeforeUndo(t *testing.T) { t.Error("the filed copy survived -y undo") } } + +// TestUndoWithoutRunContinuesTheLastUndo: when the most recent run is an +// undo that could not finish, plain `krino undo` offers what that undo left +// instead of refusing because the last run is an undo (review M10). +func TestUndoWithoutRunContinuesTheLastUndo(t *testing.T) { + h := home(t) + dl := filepath.Join(h, "dl") + if err := os.MkdirAll(dl, 0o755); err != nil { + t.Fatal(err) + } + old := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + p := filepath.Join(dl, "a.pdf") + if err := os.WriteFile(p, []byte("one"), 0o644); err != nil { + t.Fatal(err) + } + os.Chtimes(p, old, old) + 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(rule \"r\" (rename \"r-{name}\") (move \"Out\"))\n" + os.WriteFile(filepath.Join(h, ".config", "krino", "dirs", "dl.conf"), []byte(rules), 0o644) + if code, out, errOut := runCLI(t, "-y"); code != 0 { + t.Fatalf("sort: %d\n%s\n%s", code, out, errOut) + } + // An undo that fails part way: planned, then something takes the + // original name before it runs (driven through the engine, since the CLI + // plans and applies in one go). + e, errs := engine.Load(filepath.Join(h, ".config", "krino", "krino.conf")) + if len(errs) > 0 { + t.Fatal(errs) + } + runs, err := e.Runs(1) + if err != nil || len(runs) != 1 { + t.Fatalf("runs: %v %v", runs, err) + } + up, err := e.PlanUndo(runs[0].ID) + if err != nil { + t.Fatal(err) + } + os.WriteFile(p, []byte("in the way"), 0o644) + j, err := journal.Open(e.Config.LogFile()) + if err != nil { + t.Fatal(err) + } + time.Sleep(1100 * time.Millisecond) // run ids are per second + res, err := e.ApplyUndo(context.Background(), up, j, journal.NewRunID(time.Now())) + j.Close() + if err != nil || res.Failed != 1 { + t.Fatalf("blocked undo: %+v, %v", res, err) + } + os.Remove(p) + code, out, errOut := runCLI(t, "undo", "-n") + if code != 0 || !strings.Contains(out, "undo-rename") || strings.Contains(out, "undo-move") { + t.Fatalf("undo -n after a failed undo: exit %d\n%s\n%s", code, out, errOut) + } +} |
