//go:build linux
package apply
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-host/internal/declaration"
"github.com/novox/mesh-host/internal/store"
)
// The replay of novox/hq issue 358, written only with what the node-engine had before its fix, so it can be laid
// over the commit before (mesh-lab's replays register): a mesh directory outside every home was given its mode
// and owner by path, so a parent swapped for a link between two applies carried root's chmod to the link's target.
// aMeshDirectory applies a directory declared at /parent/state once, as the mesh's own, and answers it
// with the record a second apply would read.
func aMeshDirectory(t *testing.T) (base string, r *declaration.Directory, previous store.Applied) {
t.Helper()
base = t.TempDir()
r = &declaration.Directory{ID: "mod.state", Path: filepath.Join(base, "parent", "state"), Mode: "0700"}
out, err := applyDirectory(r, store.Applied{}, false)
if err != nil {
t.Fatalf("the first apply: %v", err)
}
if out.Action != "created" {
t.Fatalf("the first apply said %q, want created", out.Action)
}
return base, r, store.Applied{ID: r.ID, Type: "directory", Target: r.Path}
}
// swapParentForLink moves /parent away and puts a link to elsewhere (as written) in its place, where
// elsewhere holds a directory state of mode 0755: what an account that can write does between two applies.
func swapParentForLink(t *testing.T, base, to string) (target string) {
t.Helper()
elsewhere := filepath.Join(base, "elsewhere")
target = filepath.Join(elsewhere, "state")
if err := os.MkdirAll(target, 0o755); err != nil {
t.Fatal(err)
}
if err := os.Chmod(target, 0o755); err != nil {
t.Fatal(err)
}
if err := os.Rename(filepath.Join(base, "parent"), filepath.Join(base, "moved")); err != nil {
t.Fatal(err)
}
if err := os.Symlink(to, filepath.Join(base, "parent")); err != nil {
t.Fatal(err)
}
return target
}
func permOf(t *testing.T, path string) os.FileMode {
t.Helper()
info, err := os.Lstat(path)
if err != nil {
t.Fatal(err)
}
return info.Mode().Perm()
}
// The acceptance of issue 358: a parent swapped for a link after an apply leaves the link's target untouched, and
// the refusal names the link and who could have made it.
func TestAParentSwappedForALinkLeavesItsTargetUntouched(t *testing.T) {
base, r, previous := aMeshDirectory(t)
target := swapParentForLink(t, base, filepath.Join(base, "elsewhere"))
_, err := applyDirectory(r, previous, true)
var refusal *PlacementRefusedError
if !errors.As(err, &refusal) {
t.Fatalf("the directory reached through a link was not refused: %v", err)
}
if !strings.Contains(err.Error(), filepath.Join(base, "parent")) || !strings.Contains(err.Error(), "could replace") {
t.Errorf("the refusal does not name the link and who could replace it: %v", err)
}
if got := permOf(t, target); got != 0o755 {
t.Errorf("the link's target was given mode %o (was 755): root's chmod followed the link", got)
}
}