textmachine/platform/cmd/tmplatformctl/backup_test.go

98 lines
4.2 KiB
Go

package main
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
)
// The read-only halves of `backup` answer WITHOUT a DSN, and that is the property the command was
// shaped around: a restore point copied onto another machine is still a restore point, and the moment
// somebody asks "what have we actually got" is not the moment to require a working database.
//
// ⚠ It is dispatched before the DSN gate in run() for exactly this, and nothing checked it. Mutation
// caught: moving the `backup` case below `config.Secret("TM_PLATFORM_DSN")` — every operator use
// still works on a configured host, and the one case this exists for stops working.
func TestListingAndVerifyingRestorePointsNeedsNoDatabase(t *testing.T) {
t.Setenv("TM_PLATFORM_DSN", "")
t.Setenv("TM_PLATFORM_DSN_FILE", "")
t.Setenv("TM_PLATFORM_BACKUP_DIR", "")
dir := t.TempDir()
var out strings.Builder
if err := run([]string{"backup", "--list", "--dir", dir}, &out); err != nil {
t.Fatalf("--list refused without a DSN: %v", err)
}
if !strings.Contains(out.String(), "no restore points") {
t.Errorf("--list on an empty directory said: %q", out.String())
}
// A directory that is not a restore point is REPORTED, not crashed on: this is the command an
// operator reaches for when things are already wrong.
point := filepath.Join(dir, "20260905T120000Z")
if err := os.MkdirAll(point, 0o750); err != nil {
t.Fatal(err)
}
out.Reset()
if err := run([]string{"backup", "--list", "--dir", dir}, &out); err != nil {
t.Fatalf("--list on an unreadable point refused: %v", err)
}
if !strings.Contains(out.String(), "UNREADABLE") {
t.Errorf("a point with no manifest was not reported as unreadable: %q", out.String())
}
// And --verify FAILS on it rather than reporting it sound.
out.Reset()
if err := run([]string{"backup", "--verify", "20260905T120000Z", "--dir", dir}, &out); err == nil {
t.Errorf("--verify called a point with no manifest sound: %q", out.String())
}
}
// Without a directory and without the variable, the command says which of the two is missing instead
// of failing somewhere deeper.
func TestBackupSaysWhenItDoesNotKnowWhereToLook(t *testing.T) {
t.Setenv("TM_PLATFORM_DSN", "")
t.Setenv("TM_PLATFORM_BACKUP_DIR", "")
var out strings.Builder
err := run([]string{"backup", "--list"}, &out)
if err == nil {
t.Fatal("listing with nowhere to look was accepted")
}
if !strings.Contains(err.Error(), "TM_PLATFORM_BACKUP_DIR") || !strings.Contains(err.Error(), "--dir") {
t.Errorf("the error names neither way to say where: %v", err)
}
}
// ⚠ THERE IS NO `restore` SUBCOMMAND, and its absence is a decision this test defends. A restore
// replaces a live database and a live library on the worst day this deployment has; a one-word
// command for that is a one-word command for destroying the current state by mistake. The procedure
// lives in deploy/README.md and inside the point's own manifest.
func TestThereIsNoOneWordRestoreCommand(t *testing.T) {
// ⚠ WITH a database, and that is not decoration: with `TM_PLATFORM_DSN` unset the dispatcher
// refuses at the DSN gate before it ever looks at the subcommand, so the test would pass over a
// `restore` command that existed. Acceptance caught exactly that — the assertion below now
// reaches the thing it is about.
t.Setenv("TM_PLATFORM_DSN", freshDB(t))
var out strings.Builder
err := run([]string{"restore"}, &out)
if err == nil {
t.Fatal("a `restore` command exists; the runbook's deliberate absence has been undone")
}
if !errors.Is(err, errUsage) {
t.Errorf("`restore` was refused for some reason other than being unknown: %v", err)
}
if !strings.Contains(usage, "backup") {
t.Error("usage does not mention the backup command at all")
}
// ⚠ Checked as a COMMAND line and not as a substring: the first version of this assertion searched
// for "restore " anywhere in the usage and matched the phrase "restore point" inside `backup`'s own
// description — a red test over correct code, which is the same class of wrong as a green test over
// broken code.
for _, line := range strings.Split(usage, "\n") {
if strings.HasPrefix(strings.TrimLeft(line, " "), "restore") {
t.Errorf("usage advertises a restore command: %q", line)
}
}
}