98 lines
4.2 KiB
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)
|
|
}
|
|
}
|
|
}
|