c2ca6c0b73
auto_backup_before_deploy silently did nothing — MaybeBackupBeforeDeploy's only caller was the legacy executeDeploy pipeline, removed in the workload-first cutover. Reconnect it as maybeBackupBeforeDeploy(), invoked from DispatchPlugin after the source resolves and before it runs, so the setting fires for every source kind. Fail-open: a nil backuper, a settings-load error, or a backup failure skips the snapshot without blocking the deploy. Adds predeploy_backup_test.go asserting the wiring.
108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
package deployer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/alexei/tinyforge/internal/store"
|
|
"github.com/alexei/tinyforge/internal/workload/plugin"
|
|
)
|
|
|
|
// fakeBackuper records pre-deploy backup calls so the dispatch wiring can be
|
|
// asserted. err (when set) simulates a backup failure.
|
|
type fakeBackuper struct {
|
|
count atomic.Int32
|
|
lastType atomic.Value // string
|
|
err error
|
|
}
|
|
|
|
func (f *fakeBackuper) CreateBackup(backupType string) (store.Backup, error) {
|
|
f.count.Add(1)
|
|
f.lastType.Store(backupType)
|
|
if f.err != nil {
|
|
return store.Backup{}, f.err
|
|
}
|
|
return store.Backup{ID: "b1", Filename: "tinyforge-pre-deploy.db"}, nil
|
|
}
|
|
|
|
func setAutoBackup(t *testing.T, d *Deployer, enabled bool) {
|
|
t.Helper()
|
|
s, err := d.store.GetSettings()
|
|
if err != nil {
|
|
t.Fatalf("get settings: %v", err)
|
|
}
|
|
s.AutoBackupBeforeDeploy = enabled
|
|
if err := d.store.UpdateSettings(s); err != nil {
|
|
t.Fatalf("update settings: %v", err)
|
|
}
|
|
}
|
|
|
|
// Regression: the pre-deploy backup hook was orphaned after the cutover (no
|
|
// caller on DispatchPlugin), making auto_backup_before_deploy a silent no-op.
|
|
func TestDispatchPlugin_PreDeployBackup_FiresWhenEnabled(t *testing.T) {
|
|
resetFake(t)
|
|
d := newTestDeployer(t)
|
|
b := &fakeBackuper{}
|
|
d.SetPreDeployBackuper(b)
|
|
setAutoBackup(t, d, true)
|
|
|
|
if err := d.DispatchPlugin(context.Background(), sampleWorkload(), plugin.DeploymentIntent{}); err != nil {
|
|
t.Fatalf("dispatch: %v", err)
|
|
}
|
|
if got := b.count.Load(); got != 1 {
|
|
t.Fatalf("CreateBackup called %d times, want 1", got)
|
|
}
|
|
if bt, _ := b.lastType.Load().(string); bt != "pre-deploy" {
|
|
t.Fatalf("backup type = %q, want pre-deploy", bt)
|
|
}
|
|
if got := dispatchTestSource.deployCount.Load(); got != 1 {
|
|
t.Fatalf("Deploy ran %d times, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestDispatchPlugin_PreDeployBackup_SkippedWhenDisabled(t *testing.T) {
|
|
resetFake(t)
|
|
d := newTestDeployer(t)
|
|
b := &fakeBackuper{}
|
|
d.SetPreDeployBackuper(b)
|
|
setAutoBackup(t, d, false)
|
|
|
|
if err := d.DispatchPlugin(context.Background(), sampleWorkload(), plugin.DeploymentIntent{}); err != nil {
|
|
t.Fatalf("dispatch: %v", err)
|
|
}
|
|
if got := b.count.Load(); got != 0 {
|
|
t.Fatalf("CreateBackup called %d times, want 0 (setting off)", got)
|
|
}
|
|
}
|
|
|
|
func TestDispatchPlugin_PreDeployBackup_NilBackuperNoPanic(t *testing.T) {
|
|
resetFake(t)
|
|
d := newTestDeployer(t)
|
|
setAutoBackup(t, d, true) // enabled, but no backuper wired
|
|
|
|
if err := d.DispatchPlugin(context.Background(), sampleWorkload(), plugin.DeploymentIntent{}); err != nil {
|
|
t.Fatalf("dispatch must not panic/fail with a nil backuper: %v", err)
|
|
}
|
|
if got := dispatchTestSource.deployCount.Load(); got != 1 {
|
|
t.Fatalf("Deploy ran %d times, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestDispatchPlugin_PreDeployBackup_FailOpen(t *testing.T) {
|
|
resetFake(t)
|
|
d := newTestDeployer(t)
|
|
b := &fakeBackuper{err: errors.New("disk full")}
|
|
d.SetPreDeployBackuper(b)
|
|
setAutoBackup(t, d, true)
|
|
|
|
// A failed backup is logged but must NOT block the deploy.
|
|
if err := d.DispatchPlugin(context.Background(), sampleWorkload(), plugin.DeploymentIntent{}); err != nil {
|
|
t.Fatalf("deploy must succeed when backup fails (fail-open): %v", err)
|
|
}
|
|
if got := dispatchTestSource.deployCount.Load(); got != 1 {
|
|
t.Fatalf("Deploy ran %d times, want 1 (despite backup failure)", got)
|
|
}
|
|
}
|