summaryrefslogtreecommitdiff
path: root/cmd/krino/matching_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/krino/matching_test.go')
-rw-r--r--cmd/krino/matching_test.go99
1 files changed, 97 insertions, 2 deletions
diff --git a/cmd/krino/matching_test.go b/cmd/krino/matching_test.go
index 614edc2..69c0eca 100644
--- a/cmd/krino/matching_test.go
+++ b/cmd/krino/matching_test.go
@@ -5,6 +5,7 @@ package main
import (
"bytes"
"encoding/json"
+ "fmt"
"os"
"path/filepath"
"strings"
@@ -148,8 +149,8 @@ func TestSortFlags(t *testing.T) {
want string
}{
{[]string{"-y", "-n"}, "-y and -n cannot be used together"},
- {nil, "applying files is not implemented yet; use -n to see what would happen"},
- {[]string{"--json"}, "--json is not implemented yet"}, // --json without -n stays an error
+ {nil, "not a terminal"}, // no -y, no -n, and the test's stdin is not one
+ {[]string{"--json"}, "--json is only valid with -n"}, // --json without -n stays an error
}
for _, tt := range tests {
if code, _, errOut := runCLI(t, tt.args...); code != 2 || !strings.Contains(errOut, tt.want) {
@@ -356,6 +357,100 @@ func TestDirectoryWarningNotCountedInWarningsField(t *testing.T) {
// not stretched further), while a short name alongside it is still padded
// out to the full 40-column cap — the layout stays a clean two-column grid
// even though one row's first cell overruns it.
+// TestApplyWithYesMovesFiles is brief 7's basic apply-path test: -y applies
+// the plan with no prompt, the file actually moves, the outcome line says
+// so, and the run is logged with both boundaries.
+func TestApplyWithYesMovesFiles(t *testing.T) {
+ h := matchingFixture(t)
+ code, out, errOut := runCLI(t, "-y")
+ if code != 0 {
+ t.Fatalf("exit %d: %s", code, errOut)
+ }
+ if _, err := os.Stat(filepath.Join(h, "dl", "Work", "Acme", "inv1.txt")); err != nil {
+ t.Errorf("inv1.txt was not filed: %v", err)
+ }
+ if !strings.Contains(out, "applied") {
+ t.Errorf("no outcome line:\n%s", out)
+ }
+ log := filepath.Join(h, ".local", "state", "krino", "krino.log")
+ b, err := os.ReadFile(log)
+ if err != nil {
+ t.Fatalf("nothing was logged: %v", err)
+ }
+ if !strings.Contains(string(b), "run-start") || !strings.Contains(string(b), "run-end") {
+ t.Errorf("log lacks run boundaries:\n%s", b)
+ }
+}
+
+// TestDryRunLogsNothing is Ruling 2: journal.Open must never be called at
+// all in dry-run mode, since it materialises both the state directory and
+// an empty log file as a side effect of merely opening it.
+func TestDryRunLogsNothing(t *testing.T) {
+ h := matchingFixture(t)
+ if code, _, errOut := runCLI(t, "-n"); code != 0 {
+ t.Fatalf("exit %d: %s", code, errOut)
+ }
+ if _, err := os.Stat(filepath.Join(h, ".local", "state", "krino", "krino.log")); !os.IsNotExist(err) {
+ t.Error("a dry run wrote to the log")
+ }
+}
+
+// TestRefusesWithoutTerminalAndWithoutFlags is spec §8.4: with neither -y
+// nor -n, and stdin not a terminal (as in every test), krino refuses rather
+// than guess.
+func TestRefusesWithoutTerminalAndWithoutFlags(t *testing.T) {
+ matchingFixture(t)
+ code, _, errOut := runCLI(t)
+ if code != 2 || !strings.Contains(errOut, "not a terminal") {
+ t.Errorf("no-flags, no-terminal: %d %q", code, errOut)
+ }
+}
+
+// TestSecondRunFailsImmediatelyWithYes is spec §11: a second krino on the
+// same directory fails immediately with -y (wait = false) rather than
+// piling up behind a stuck run, so a cron job never queues silently.
+func TestSecondRunFailsImmediatelyWithYes(t *testing.T) {
+ h := matchingFixture(t)
+ held := filepath.Join(h, ".local", "state", "krino", "dl.lock")
+ if err := os.MkdirAll(filepath.Dir(held), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ // A lock held by this very process, so it is not stale.
+ if err := os.WriteFile(held, []byte(fmt.Sprintf("pid %d\n", os.Getpid())), 0o644); err != nil {
+ t.Fatal(err)
+ }
+ code, _, errOut := runCLI(t, "-y")
+ if code != 1 || !strings.Contains(errOut, "another krino") {
+ t.Errorf("-y against a held lock: %d %q", code, errOut)
+ }
+}
+
+// TestFlagsMustPrecedeDirectoryNames is Ruling 4: Go's flag package stops
+// parsing at the first non-flag argument, so "krino dl -n" would otherwise
+// silently take "-n" as a second directory name and never honour the dry
+// run. A leftover argument starting with "-" is a usage error instead of a
+// guess.
+func TestFlagsMustPrecedeDirectoryNames(t *testing.T) {
+ home(t)
+ code, _, errOut := runCLI(t, "dl", "-n")
+ if code != 2 || !strings.Contains(errOut, "-n") {
+ t.Errorf("krino dl -n: %d %q", code, errOut)
+ }
+}
+
+// TestNoColourEscapeToNonTerminal is Ruling 7's one pinned guarantee: a plan
+// piped to a file or read by another tool must be plain text. tui.Colour(w)
+// already returns false for anything that is not a terminal *os.File, and
+// runCLI's stdout is a bytes.Buffer, so this holds end to end through the
+// real command path, not just at the helper that decides it.
+func TestNoColourEscapeToNonTerminal(t *testing.T) {
+ matchingFixture(t)
+ _, out, _ := runCLI(t, "-n")
+ if strings.ContainsRune(out, '\x1b') {
+ t.Errorf("escape byte reached a non-terminal writer:\n%q", out)
+ }
+}
+
func TestLongNameNotPaddedLayoutIntact(t *testing.T) {
h := home(t)
dl := filepath.Join(h, "dl")