Files
mesh-host/internal/apply/whole_test.go
T
jochen 3e11d720b4 A file written over is given its kept original back when undeclared (hq ADR 0118, 0102)
The host keeps the original of a file before writing over it (ADR 0102), but
removing the file's record deleted the file and never put the original back,
although ADR 0118 and the comment on meshMadeUnits say it does. A module writing
/etc/pacman.conf, logrotate.conf, locale.conf or vconsole.conf whole would, once
unassigned, leave the machine without the file.

removeWhole now decides, in order: no kept original (the mesh made it) is
removed as before; a file gone since is not brought back; a file changed since
the mesh last wrote it is left as it stands, as a block or JSON write-into stays
the machine's; an unreadable kept copy leaves the mesh's file in place. Otherwise
the original goes back atomically with the mode and owner it was found with,
now recorded beside Kept, and the outcome is "restored". None of it is fatal.
The plan says "restore" for such a file.

A kept original is carried only for the path it was kept from, and a file whose
path moved keeps the original at its new path first, so a moved file is never
given another path's original.
2026-10-04 12:54:16 +02:00

174 lines
6.4 KiB
Go

package apply
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-host/internal/store"
)
// Defends novox/hq ADR 0118 with ADR 0102: a file the host wrote whole over one it found is given
// its kept original back when it is undeclared — not deleted, which left a machine whose package
// manager's configuration a module wrote with no configuration at all once that module was
// unassigned.
const pacmanFound = "[options]\nArchitecture = auto\n\n[core]\nInclude = /etc/pacman.d/mirrorlist\n"
const pacmanMesh = "# written by the mesh\n[options]\nArchitecture = auto\nParallelDownloads = 5\n"
// writtenOver is a file found at path with content and mode, then written whole by the mesh.
func writtenOver(t *testing.T, content string, mode os.FileMode) (path string, state store.State) {
t.Helper()
path = filepath.Join(t.TempDir(), "pacman.conf")
if err := os.WriteFile(path, []byte(content), mode); err != nil {
t.Fatal(err)
}
if err := os.Chmod(path, mode); err != nil {
t.Fatal(err)
}
_, state = applyKeepingIn(t, wholeDecl(path, pacmanMesh), store.State{}, t.TempDir())
if got := readText(t, path); got != pacmanMesh {
t.Fatalf("the mesh's file was not written: %q", got)
}
return path, state
}
func TestAFileWrittenOverGetsItsKeptOriginalBackWhenUndeclared(t *testing.T) {
path, state := writtenOver(t, pacmanFound, 0o640)
rec, _ := state.Find(namesID)
if rec.Kept == "" || rec.KeptMode != "0640" {
t.Fatalf("the original and how it was found were not recorded: kept %q, mode %q", rec.Kept, rec.KeptMode)
}
if steps := Plan(somethingElse(t), state, store.OriginDeclared); !strings.Contains(verbs(steps), "restore "+namesID) {
t.Errorf("the plan did not say the original goes back: %s", verbs(steps))
}
report, after := undeclare(t, state)
if got := readText(t, path); got != pacmanFound {
t.Fatalf("undeclared, the machine did not get its original back: %q", got)
}
info, err := os.Stat(path)
if err != nil || info.Mode().Perm() != 0o640 {
t.Errorf("the original came back with mode %o, it was found 640", info.Mode().Perm())
}
o := outcomeOf(report, namesID)
if o.Action != "restored" || !strings.Contains(o.Detail, rec.Kept) {
t.Errorf("the give-back was reported as %q: %s", o.Action, o.Detail)
}
if _, still := after.Find(namesID); still {
t.Error("the record outlived its declaration")
}
if _, err := os.Stat(rec.Kept); err != nil {
t.Errorf("the kept copy went with the give-back: %v", err)
}
}
func TestAFileTheMeshMadeIsRemovedWhenUndeclared(t *testing.T) {
path := filepath.Join(t.TempDir(), "pacman.conf")
_, state := applyKeepingIn(t, wholeDecl(path, pacmanMesh), store.State{}, t.TempDir())
if rec, _ := state.Find(namesID); rec.Kept != "" {
t.Fatalf("a file that was not there recorded an original at %s", rec.Kept)
}
report, _ := undeclare(t, state)
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Errorf("a file the mesh made outlived its declaration: %v", err)
}
if got := outcomeOf(report, namesID).Action; got != "removed" {
t.Errorf("removal was reported as %q", got)
}
}
func TestAFileWrittenOverAndChangedSinceIsLeftAsItStands(t *testing.T) {
path, state := writtenOver(t, pacmanFound, 0o644)
edited := pacmanMesh + "IgnorePkg = linux\n"
if err := os.WriteFile(path, []byte(edited), 0o644); err != nil {
t.Fatal(err)
}
report, _ := undeclare(t, state)
if got := readText(t, path); got != edited {
t.Fatalf("the operator's change was clobbered: %q", got)
}
rec, _ := state.Find(namesID)
o := outcomeOf(report, namesID)
if o.Action != "kept" || !strings.Contains(o.Detail, "changed on the machine") || !strings.Contains(o.Detail, rec.Kept) {
t.Errorf("leaving it was reported as %q: %s", o.Action, o.Detail)
}
}
func TestAFileWhoseKeptOriginalIsMissingIsLeftAndSaysSo(t *testing.T) {
path, state := writtenOver(t, pacmanFound, 0o644)
rec, _ := state.Find(namesID)
if err := os.Remove(rec.Kept); err != nil {
t.Fatal(err)
}
report, _ := undeclare(t, state)
if got := readText(t, path); got != pacmanMesh {
t.Fatalf("with no original to put back, the file became %q", got)
}
o := outcomeOf(report, namesID)
if o.Action != "kept" || !strings.Contains(o.Detail, "cannot be read at "+rec.Kept) {
t.Errorf("leaving it was reported as %q: %s", o.Action, o.Detail)
}
}
func TestAFileWrittenOverAndDeletedSinceIsNotBroughtBack(t *testing.T) {
path, state := writtenOver(t, pacmanFound, 0o644)
if err := os.Remove(path); err != nil {
t.Fatal(err)
}
report, _ := undeclare(t, state)
if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Errorf("a file somebody deleted was brought back: %v", err)
}
if got := outcomeOf(report, namesID).Action; got != "forgotten" {
t.Errorf("reported as %q", got)
}
}
func TestAFileWhosePathMovedIsNeverGivenTheOldPathsOriginal(t *testing.T) {
// The old path's original stays with the old path's record; the new path keeps its own.
oldPath, state := writtenOver(t, pacmanFound, 0o644)
newPath := filepath.Join(filepath.Dir(oldPath), "pacman.d.conf")
newFound := "# the machine's own at the new path\n"
if err := os.WriteFile(newPath, []byte(newFound), 0o644); err != nil {
t.Fatal(err)
}
_, state = applyKeepingIn(t, wholeDecl(newPath, pacmanMesh), state, t.TempDir())
rec, _ := state.Find(namesID)
if rec.Kept == "" {
t.Fatal("the original at the new path was written over without being kept")
}
if kept := readText(t, rec.Kept); kept != newFound {
t.Fatalf("the new path's record names the wrong original: %q", kept)
}
// The old path is a former target, given back by the next apply; the new one by undeclaring.
undeclare(t, state)
if got := readText(t, newPath); got != newFound {
t.Errorf("undeclared, the new path holds %q", got)
}
if got := readText(t, oldPath); got != pacmanFound {
t.Errorf("the old path did not get its own original back: %q", got)
}
}
func TestARecordFromBeforeTheModeWasKeptPutsTheOriginalBackAsTheFileStands(t *testing.T) {
path, state := writtenOver(t, pacmanFound, 0o644)
for i := range state.Resources {
state.Resources[i].KeptMode, state.Resources[i].KeptOwner = "", ""
}
if err := os.Chmod(path, 0o600); err != nil {
t.Fatal(err)
}
report, _ := undeclare(t, state)
if got := readText(t, path); got != pacmanFound {
t.Fatalf("got %q", got)
}
info, _ := os.Stat(path)
if info.Mode().Perm() != 0o600 {
t.Errorf("mode %o, the file stood at 600", info.Mode().Perm())
}
if got := outcomeOf(report, namesID).Action; got != "restored" {
t.Errorf("reported as %q", got)
}
}