Files
RenewCMS/test/update_helper_files_test.go

189 lines
6.4 KiB
Go

package test
import (
"RenewCMS/internal/domain/update"
infraUpdate "RenewCMS/internal/infrastructure/update"
"errors"
"os"
"path/filepath"
"testing"
"time"
)
func TestRestartRequestJSONStore(t *testing.T) {
t.Run("it round trips and replaces atomically", func(t *testing.T) {
directory := t.TempDir()
path := filepath.Join(directory, update.RestartRequestFileName)
store := infraUpdate.NewJSONRestartRequestStore(path)
if _, err := store.Read(); !errors.Is(err, update.ErrNoRestartRequest) {
t.Fatalf("an absent marker = %v", err)
}
first := update.RestartRequest{Kind: "update", Tag: downloadTag, RequestedAt: restartBaseTime, RequestedBy: 7}
second := update.RestartRequest{Kind: "restart", RequestedAt: restartBaseTime.Add(time.Minute), RequestedBy: 8}
if err := store.Write(first); err != nil {
t.Fatal(err)
}
if err := store.Write(second); err != nil {
t.Fatal(err)
}
got, err := store.Read()
if err != nil || got != second {
t.Errorf("got = %+v, err = %v", got, err)
}
if entries, _ := os.ReadDir(directory); len(entries) != 1 {
t.Errorf("no temporary file may remain, got %d entries", len(entries))
}
if err := store.Delete(); err != nil {
t.Fatal(err)
}
if err := store.Delete(); err != nil {
t.Errorf("deleting twice = %v", err)
}
if _, err := store.Read(); !errors.Is(err, update.ErrNoRestartRequest) {
t.Errorf("after delete = %v", err)
}
})
t.Run("a corrupt marker still counts as present", func(t *testing.T) {
path := filepath.Join(t.TempDir(), update.RestartRequestFileName)
_ = os.WriteFile(path, []byte("{nope"), 0o600)
_, err := infraUpdate.NewJSONRestartRequestStore(path).Read()
if err == nil || errors.Is(err, update.ErrNoRestartRequest) {
t.Errorf("err = %v", err)
}
})
}
func TestResultJSONStore(t *testing.T) {
t.Run("it round trips, keeps null finished_at and ignores corrupt or unknown files", func(t *testing.T) {
path := filepath.Join(t.TempDir(), update.ResultFileName)
store := infraUpdate.NewJSONResultStore(path)
if _, found := store.Read(); found {
t.Fatal("absent file")
}
running := update.Result{Outcome: update.OutcomeRunning, Kind: "update", Tag: downloadTag, FromVersion: helperOld, StartedAt: restartBaseTime}
if err := store.Write(running); err != nil {
t.Fatal(err)
}
got, found := store.Read()
if !found || got.Outcome != "running" || got.FinishedAt != nil || !got.StartedAt.Equal(restartBaseTime) {
t.Errorf("got = %+v", got)
}
for name, content := range map[string]string{"corrupt": "{nope", "unknown outcome": `{"outcome":"weird"}`, "empty": ""} {
_ = os.WriteFile(path, []byte(content), 0o644)
if _, found := store.Read(); found {
t.Errorf("%s must count as no result", name)
}
}
_ = store.Write(running)
if err := store.Delete(); err != nil {
t.Fatal(err)
}
if _, found := store.Read(); found {
t.Error("deleted")
}
if err := store.Delete(); err != nil {
t.Errorf("deleting twice = %v", err)
}
})
}
func TestRunningInfoJSONStore(t *testing.T) {
t.Run("it round trips and rejects missing, corrupt and incomplete files", func(t *testing.T) {
path := filepath.Join(t.TempDir(), update.RunningInfoFileName)
store := infraUpdate.NewJSONRunningInfoStore(path)
if _, found := store.Read(); found {
t.Fatal("absent file")
}
info := update.RunningInfo{Version: downloadTag, Pid: 12, StartedAt: restartBaseTime}
if err := store.Write(info); err != nil {
t.Fatal(err)
}
if got, found := store.Read(); !found || got != info {
t.Errorf("got = %+v", got)
}
for name, content := range map[string]string{"corrupt": "{nope", "no pid": `{"version":"v1.0.0"}`, "no version": `{"pid":3}`} {
_ = os.WriteFile(path, []byte(content), 0o644)
if _, found := store.Read(); found {
t.Errorf("%s must not be read as running info", name)
}
}
})
t.Run("a symlink is never read", func(t *testing.T) {
directory := t.TempDir()
target := filepath.Join(directory, "target.json")
_ = os.WriteFile(target, []byte(`{"version":"v9.9.9","pid":1,"started_at":"2026-09-30T09:50:00Z"}`), 0o644)
link := filepath.Join(directory, update.RunningInfoFileName)
if err := os.Symlink(target, link); err != nil {
t.Skipf("symlinks are not available: %v", err)
}
if _, found := infraUpdate.NewJSONRunningInfoStore(link).Read(); found {
t.Error("a symlink must not count")
}
})
}
func TestHelperStatusFile(t *testing.T) {
t.Run("it is written world readable so the app can detect it", func(t *testing.T) {
directory := t.TempDir()
writer := infraUpdate.NewJSONHelperStatusWriter()
err := writer.Write(directory, update.HelperStatus{Version: 1, Service: "renewcms", Binary: "/opt/renewcms/renewcms", DataDir: directory, InstalledAt: restartBaseTime})
path := filepath.Join(directory, update.HelperStatusFileName)
if err != nil || !infraUpdate.NewFileHelperDetector(path).Installed() {
t.Fatalf("err = %v", err)
}
if info, _ := os.Stat(path); info.Mode().Perm()&0o044 != 0o044 && filepath.Separator == '/' {
t.Errorf("mode = %v", info.Mode())
}
removed, err := writer.Remove(directory)
if err != nil || !removed {
t.Errorf("removed = %v, err = %v", removed, err)
}
removed, err = writer.Remove(directory)
if err != nil || removed {
t.Errorf("a second removal = %v, %v", removed, err)
}
})
}
func TestServiceUnitParsing(t *testing.T) {
t.Run("it reads the binary, the data directory and the environment file", func(t *testing.T) {
unit := "[Service]\nUser=renewcms\nEnvironmentFile=/etc/renewcms/renewcms.env\nExecStart=/opt/renewcms/renewcms\nProtectSystem=strict\nReadWritePaths=/var/lib/renewcms\n"
got := infraUpdate.ParseServiceUnit(unit)
if got.BinaryPath != "/opt/renewcms/renewcms" || got.DataDir != "/var/lib/renewcms" || got.EnvironmentFile != "/etc/renewcms/renewcms.env" {
t.Errorf("got = %+v", got)
}
})
t.Run("systemd prefixes are stripped", func(t *testing.T) {
unit := "ExecStart=-@/opt/renewcms/renewcms --flag\nEnvironmentFile=-/etc/x.env\nReadWritePaths=-/var/lib/renewcms /other\n"
got := infraUpdate.ParseServiceUnit(unit)
if got.BinaryPath != "/opt/renewcms/renewcms" || got.EnvironmentFile != "/etc/x.env" || got.DataDir != "/var/lib/renewcms" {
t.Errorf("got = %+v", got)
}
})
}
func TestNoFollowFilesystemExists(t *testing.T) {
var files update.HelperFilesystem = infraUpdate.NewNoFollowFilesystem()
if files == nil {
t.Fatal("the adapter must compile on every platform")
}
}