Files
llmgw/internal/cli/restore_test.go

119 lines
3.9 KiB
Go

package cli
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"time"
agecrypt "filippo.io/age"
"gitea.mynodes.xyz/ilker/llmgw/internal/config"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
func TestABackupIsRestoredWhereItSays(t *testing.T) {
tmp := t.TempDir()
etc := filepath.Join(tmp, "etc")
_ = os.MkdirAll(filepath.Join(etc, "secrets"), 0o750)
dbPath := filepath.Join(tmp, "llmgw.db")
_ = os.WriteFile(filepath.Join(etc, "server.yaml"), []byte("auth:\n enabled: true\n store: "+dbPath+"\n"), 0o640)
_ = os.WriteFile(filepath.Join(etc, "secrets", "admin-token"), []byte(strings.Repeat("t", 40)), 0o600)
db, err := store.Open(t.Context(), dbPath)
if err != nil {
t.Fatal(err)
}
if _, err := db.CreateOrg(t.Context(), "acme"); err != nil {
t.Fatal(err)
}
_ = db.Close()
id, _ := agecrypt.GenerateX25519Identity()
to, _ := config.ParseBackupRecipients([]string{id.Recipient().String()})
archive, err := writeBackup(t.Context(), dbPath, etc, filepath.Join(tmp, "backups"), to)
if err != nil {
t.Fatal(err)
}
key := filepath.Join(tmp, "key.txt")
_ = os.WriteFile(key, []byte(id.String()+"\n"), 0o600)
// Onto another machine, as it were: an empty place for both.
newEtc, newDB := filepath.Join(tmp, "new-etc"), filepath.Join(tmp, "new", "llmgw.db")
var out bytes.Buffer
rs := restore{archive: archive, identities: []string{key}, etcDir: newEtc, storeAddr: newDB,
dryRun: true, out: &out, now: time.Now}
if err := rs.run(t.Context()); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(newEtc); err == nil || !strings.Contains(out.String(), "dry run") {
t.Fatalf("a dry run changed something:\n%s", out.String())
}
out.Reset()
rs.dryRun = false
if err := rs.run(t.Context()); err != nil {
t.Fatalf("%v\n%s", err, out.String())
}
if b, _ := os.ReadFile(filepath.Join(newEtc, "secrets", "admin-token")); len(b) != 40 {
t.Error("the configuration did not come back")
}
if fi, err := os.Stat(filepath.Join(newEtc, "secrets", "admin-token")); err != nil || fi.Mode().Perm() != 0o600 {
t.Errorf("modes: %v %v", fi.Mode(), err)
}
db, err = store.Open(t.Context(), newDB)
if err != nil {
t.Fatal(err)
}
defer db.Close()
if orgs, _ := db.AllOrgs(t.Context()); len(orgs) != 1 || orgs[0].Name != "acme" {
t.Errorf("the store did not come back: %+v", orgs)
}
// Again over what is there: the current one is kept aside, not lost.
out.Reset()
if err := rs.run(t.Context()); err != nil {
t.Fatal(err)
}
matches, _ := filepath.Glob(newEtc + ".before-restore-*")
if len(matches) != 1 {
t.Errorf("the configuration replaced was not kept: %v", matches)
}
// Without the key it is said, not guessed.
rs.identities = nil
if err := rs.run(t.Context()); err == nil || !strings.Contains(err.Error(), "--identity") {
t.Errorf("%v", err)
}
}
func TestANewerArchiveIsRefused(t *testing.T) {
if !newer("v0.12.0", "v0.11.1-106-gabc") || newer("v0.11.1", "v0.11.1-5-g1") || newer("dev", "v1.0.0") {
t.Error("version comparison")
}
}
func TestAnArchiveIsCopiedAndTheOldestPruned(t *testing.T) {
tmp := t.TempDir()
dest := filepath.Join(tmp, "share")
for i := range 4 {
p := filepath.Join(tmp, "llmgw-backup-h-2026100"+string(rune('1'+i))+"T000000Z.tar.gz.age")
_ = os.WriteFile(p, []byte("x"), 0o600)
_ = os.Chtimes(p, time.Now().Add(time.Duration(i)*time.Hour), time.Now().Add(time.Duration(i)*time.Hour))
if err := copyToDir(p, dest, 2); err != nil {
t.Fatal(err)
}
_ = os.Chtimes(filepath.Join(dest, filepath.Base(p)), time.Now().Add(time.Duration(i)*time.Hour), time.Now().Add(time.Duration(i)*time.Hour))
}
left, _ := filepath.Glob(filepath.Join(dest, "llmgw-backup-*"))
if len(left) != 2 {
t.Errorf("kept %v", left)
}
c := config.Default()
c.BackupCopyTo = []string{"/mnt/x"}
if err := c.Validate(); err == nil || !strings.Contains(err.Error(), "backup_recipients") {
t.Errorf("an unencrypted copy off the machine was allowed: %v", err)
}
}