From 26c94eb3db62ec6eebbf8d22c11afe691d9520c4 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Sun, 13 Sep 2026 02:31:32 +0200 Subject: krino: release 0.0.1 — man pages, install, examples, cross and release, README, changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also: undo removes the directories its run created; a hardlink is never a duplicate of its own other name; a flag written before "undo" is honoured; --version prints no leading v. Duplicate conditions with different scopes not sharing an original is documented as a known limitation. --- internal/apply/apply_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'internal/apply/apply_test.go') diff --git a/internal/apply/apply_test.go b/internal/apply/apply_test.go index 64b0176..4bf6c30 100644 --- a/internal/apply/apply_test.go +++ b/internal/apply/apply_test.go @@ -250,3 +250,45 @@ func TestChainMadeIsOutermostFirstForNestedDirectories(t *testing.T) { t.Errorf("Made = %v, want %v (outermost first)", got[0].Made, want) } } + +// TestMoveFileRefusesOccupiedDestination is item 16 (fix round 2026-09-12, +// plan 5 Task 2): moveFile must refuse an occupied destination on its own, +// not merely rely on runFileStep having already checked - the exact +// arrangement that produced plan 4's Task 5 Critical, where a helper that +// replaced silently was trusted because some caller had checked. Called +// directly, bypassing runFileStep's own pre-check entirely. +func TestMoveFileRefusesOccupiedDestination(t *testing.T) { + dir := t.TempDir() + src := write(t, filepath.Join(dir, "x.pdf"), "source", 0o644) + dst := write(t, filepath.Join(dir, "y.pdf"), "already there", 0o644) + + if err := moveFile(src, dst); err == nil { + t.Fatal("moveFile overwrote an existing destination") + } + if b, err := os.ReadFile(src); err != nil || string(b) != "source" { + t.Errorf("moveFile touched its source: %q, %v", b, err) + } + if b, err := os.ReadFile(dst); err != nil || string(b) != "already there" { + t.Errorf("moveFile touched its destination: %q, %v", b, err) + } +} + +// TestRenameFileRefusesOccupiedDestination is item 16's other half: +// runFileStep's bare os.Rename call for the Rename kind was just as +// unguarded in itself as moveFile was. renameFile is the helper that now +// carries the same independent guard, called directly here. +func TestRenameFileRefusesOccupiedDestination(t *testing.T) { + dir := t.TempDir() + src := write(t, filepath.Join(dir, "x.pdf"), "source", 0o644) + dst := write(t, filepath.Join(dir, "y.pdf"), "already there", 0o644) + + if err := renameFile(src, dst); err == nil { + t.Fatal("renameFile overwrote an existing destination") + } + if b, err := os.ReadFile(src); err != nil || string(b) != "source" { + t.Errorf("renameFile touched its source: %q, %v", b, err) + } + if b, err := os.ReadFile(dst); err != nil || string(b) != "already there" { + t.Errorf("renameFile touched its destination: %q, %v", b, err) + } +} -- cgit v1.3