textmachine/platform/internal/config/backup_test.go

100 lines
4.3 KiB
Go

package config
import (
"testing"
"time"
)
// Backups are OFF unless an operator says where, and the deployment says so loudly at boot rather
// than inventing a directory of its own. A default path would land beside the books and the database
// — the one failure a backup exists to survive.
func TestBackupsAreOffUntilAnOperatorSaysWhere(t *testing.T) {
c, err := Load()
if err != nil {
t.Fatal(err)
}
if c.BackupEnabled() || c.Backup.Dir != "" {
t.Fatalf("a backup directory was invented: %q", c.Backup.Dir)
}
// The defaults that apply once it IS on. Six hours and fourteen points is four days of history,
// which is what covers a fault discovered after a weekend.
if c.Backup.Every != 6*time.Hour || c.Backup.Keep != 14 {
t.Errorf("schedule defaults: every=%s keep=%d", c.Backup.Every, c.Backup.Keep)
}
if c.Backup.PgDumpBin != "pg_dump" || c.Backup.PgRestoreBin != "pg_restore" {
t.Errorf("the standard tools are not the default: %q %q", c.Backup.PgDumpBin, c.Backup.PgRestoreBin)
}
}
// A relative backup directory is refused at boot, like the state directory and for a sharper reason:
// it would put the deployment's only copy of itself wherever the process happened to be started.
func TestARelativeBackupDirectoryIsRefusedAtBoot(t *testing.T) {
t.Setenv("TM_PLATFORM_BACKUP_DIR", "backups")
if _, err := Load(); err == nil {
t.Fatal("a relative backup directory was accepted")
}
t.Setenv("TM_PLATFORM_BACKUP_DIR", "/srv/backups")
c, err := Load()
if err != nil || !c.BackupEnabled() {
t.Fatalf("an absolute one was not: %v", err)
}
}
// Two settings that would quietly destroy what they configure: a schedule of zero takes a full copy
// of the deployment on every sweep tick, and keeping zero points deletes the one the pass has just
// written. Both are refused — by the LOADER, which takes only positive durations and counts, so
// there is one definition of "positive" rather than a per-knob one.
//
// ⚠ This test is why the second, per-knob guard is not in the code. It was written first, gated on
// the backup directory being set, and this test — which asserts the refusal happens either way —
// showed the guard could never fire: the loader had already refused. A check whose message no
// operator can ever read is not a safety net, it is a claim about behaviour that is false.
func TestABackupScheduleThatWouldEatItselfIsRefused(t *testing.T) {
for _, dir := range []string{"/srv/backups", ""} {
t.Setenv("TM_PLATFORM_BACKUP_DIR", dir)
t.Setenv("TM_PLATFORM_BACKUP_EVERY", "0s")
t.Setenv("TM_PLATFORM_BACKUP_KEEP", "14")
if _, err := Load(); err == nil {
t.Errorf("dir=%q: a zero interval was accepted: a restore point per sweep tick", dir)
}
t.Setenv("TM_PLATFORM_BACKUP_EVERY", "1h")
t.Setenv("TM_PLATFORM_BACKUP_KEEP", "0")
if _, err := Load(); err == nil {
t.Errorf("dir=%q: keeping zero points was accepted: the pass would delete its own work", dir)
}
t.Setenv("TM_PLATFORM_BACKUP_KEEP", "1")
if _, err := Load(); err != nil {
t.Errorf("dir=%q: keeping one point was refused: %v", dir, err)
}
}
}
// The session policy has ONE definition, and `tmplatformctl token issue` reads it through this
// function rather than keeping its own defaults. Two readers is how a token minted by the operator
// comes to outlive the sessions the deployment says it issues.
func TestTheSessionPolicyHasOneDefinitionForBothReaders(t *testing.T) {
idle, maxAge, err := SessionTTLs()
if err != nil {
t.Fatal(err)
}
c, err := Load()
if err != nil {
t.Fatal(err)
}
if idle != c.SessionIdleTTL || maxAge != c.SessionMaxAge {
t.Fatalf("the CLI and the daemon read different clocks: %s/%s vs %s/%s",
idle, maxAge, c.SessionIdleTTL, c.SessionMaxAge)
}
t.Setenv("TM_PLATFORM_SESSION_IDLE", "72h")
t.Setenv("TM_PLATFORM_SESSION_MAX_AGE", "240h")
idle, maxAge, err = SessionTTLs()
if err != nil || idle != 72*time.Hour || maxAge != 240*time.Hour {
t.Fatalf("the operator's own policy did not reach the CLI: %s/%s (%v)", idle, maxAge, err)
}
// The same refusal the daemon makes: an idle window longer than the absolute one is a policy
// that cannot mean anything.
t.Setenv("TM_PLATFORM_SESSION_IDLE", "500h")
if _, _, err := SessionTTLs(); err == nil {
t.Error("an idle TTL beyond the absolute ceiling was accepted by the CLI's reader")
}
}