Files
mesh-host/internal/apply/archive_journal_test.go
T

116 lines
4.1 KiB
Go

package apply
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-host/internal/store"
)
// Defends novox/hq issue 1585: an archive's files were refused as "not put there by the mesh" when the state held
// no record of them, though the host had unpacked them itself.
// An archive the host unpacked, whose record is gone, is its own: the next version replaces its files, drops the
// ones it no longer has, and leaves a file somebody added; a file the host never unpacked, or an unrecorded file
// with other bytes where no unpack was journaled, still refuses the archive.
func TestAnArchiveWhoseRecordIsGoneIsStillTheHosts(t *testing.T) {
rows := []struct {
name string
journaled bool // the first version was unpacked by the host
foreign string
wantRefuse bool
}{
{"unpacked by the host, record lost", true, "", false},
{"unpacked by the host, somebody's file beside", true, "mine.txt", false},
{"never unpacked by the host", false, "", true},
}
for _, row := range rows {
t.Run(row.name, func(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "tools")
first, _ := archiveDecl(t, "m.tools", target, map[string]string{"bin": "one", "old": "o"}, "")
if row.journaled {
if _, _, err := Apply(context.Background(), archHost(t), declare(t, first), store.State{},
store.OriginDeclared, noServices, nil, nil); err != nil {
t.Fatal(err)
}
} else {
os.MkdirAll(target, 0o755)
os.WriteFile(filepath.Join(target, "bin"), []byte("one"), 0o644)
}
if row.foreign != "" {
os.WriteFile(filepath.Join(target, row.foreign), []byte("x"), 0o644)
}
second, _ := archiveDecl(t, "m.tools", target, map[string]string{"bin": "two"}, "")
// The state is empty: the record of the first unpack was lost.
_, _, err := Apply(context.Background(), archHost(t), declare(t, second), store.State{},
store.OriginDeclared, noServices, nil, nil)
if row.wantRefuse {
if err == nil || !strings.Contains(err.Error(), "did not put there") {
t.Fatalf("refused? %v", err)
}
return
}
if err != nil {
t.Fatal(err)
}
if got, _ := os.ReadFile(filepath.Join(target, "bin")); string(got) != "two" {
t.Fatalf("bin is %q", got)
}
if exists(filepath.Join(target, "old")) {
t.Fatal("a file the new version no longer has is still there")
}
if row.foreign != "" && !exists(filepath.Join(target, row.foreign)) {
t.Fatal("somebody's file went")
}
})
}
}
// An unrecorded file already holding exactly the archive's bytes is not a collision; one with other bytes is.
func TestAnUnrecordedFileWithTheArchivesBytesIsNotACollision(t *testing.T) {
for _, row := range []struct {
name, has string
refused bool
}{{"same bytes", "theme", false}, {"other bytes", "somebody's", true}} {
t.Run(row.name, func(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "theme")
os.MkdirAll(target, 0o755)
os.WriteFile(filepath.Join(target, "p10k.zsh"), []byte(row.has), 0o644)
archive, _ := archiveDecl(t, "shell.theme", target, map[string]string{"p10k.zsh": "theme"}, "")
_, _, err := Apply(context.Background(), archHost(t), declare(t, archive), store.State{},
store.OriginDeclared, noServices, nil, nil)
if (err != nil) != row.refused {
t.Fatalf("err = %v", err)
}
})
}
}
// Undeclaring an archive takes its journal with it, and a journal alone never makes a file the host's.
func TestTheJournalGoesWithTheArchive(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "theme")
archive, _ := archiveDecl(t, "shell.theme", target, map[string]string{"a": "a"}, "")
_, state, err := Apply(context.Background(), archHost(t), declare(t, archive), store.State{},
store.OriginDeclared, noServices, nil, nil)
if err != nil {
t.Fatal(err)
}
if !exists(journalPath(target)) {
t.Fatal("no journal after the unpack")
}
if _, _, err := Apply(context.Background(), archHost(t),
declare(t, `{"id":"other","type":"file","path":"`+dir+`/other","content":"o"}`), state,
store.OriginDeclared, noServices, nil, nil); err != nil {
t.Fatal(err)
}
if exists(journalPath(target)) {
t.Fatal("the journal outlived the archive")
}
}