Rename module github.com/johnellis/stalwart-migrator -> github.com/LINUXexpert-org/stalwart-migrator to match the repository location, so the module resolves under `go get`. The initial commit's .gitignore listed the compiled binary as a bare `stalwart-migrate` pattern, which Git matches at any depth -- so it also excluded the cmd/stalwart-migrate/ source directory, and the initial commit shipped without the CLI entrypoint. Anchor the pattern to the repo root as /stalwart-migrate and add the four missing files. go build, go vet, and go test ./... all pass.
114 lines
3.6 KiB
Go
114 lines
3.6 KiB
Go
package recovery
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/LINUXexpert-org/stalwart-migrator/internal/checkpoint"
|
|
)
|
|
|
|
func TestRecoveryRunEndToEndAndResume(t *testing.T) {
|
|
port := freePort(t)
|
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
|
os.WriteFile(configPath, []byte("{}"), 0o644)
|
|
|
|
applyDir := t.TempDir()
|
|
applyLog := argsFile(t, applyDir)
|
|
withFakeExecutable(t, "stalwart-cli", fmt.Sprintf("#!/bin/sh\necho \"$@\" >> %q\nexit 0\n", applyLog))
|
|
|
|
exportFile := filepath.Join(t.TempDir(), "export.json")
|
|
os.WriteFile(exportFile, []byte("{}"), 0o644)
|
|
|
|
store := checkpoint.NewStore(t.TempDir())
|
|
rs, err := store.Create("0.15.5", "0.16.14")
|
|
if err != nil {
|
|
t.Fatalf("store.Create: %v", err)
|
|
}
|
|
|
|
opts := Options{
|
|
BinaryPath: testBinaryPath(t),
|
|
ConfigPath: configPath,
|
|
ListenURL: fmt.Sprintf("http://127.0.0.1:%d/", port),
|
|
AdminUser: "admin",
|
|
ApplyFiles: []string{exportFile},
|
|
ExtraEnv: helperProcessEnv(port),
|
|
StartupTimeout: 5 * time.Second,
|
|
StopGrace: 5 * time.Second,
|
|
}
|
|
|
|
report, err := Run(context.Background(), store, rs, opts)
|
|
if err != nil {
|
|
t.Fatalf("Run #1: %v", err)
|
|
}
|
|
if len(report.Results) != 1 || report.Results[0].Status != StatusOK {
|
|
t.Fatalf("Run #1 report = %+v, want a single OK result", report.Results)
|
|
}
|
|
if !rs.Done(checkpoint.PhaseRecovery, "recovery-cycle") {
|
|
t.Fatal("recovery-cycle should be marked done after a successful run")
|
|
}
|
|
if got := readArgsFile(t, applyLog); got == "" {
|
|
t.Fatal("stalwart-cli apply was never invoked")
|
|
}
|
|
|
|
// -- resume: break the CLI so a re-invocation would fail loudly, and use
|
|
// -- a port nothing listens on, so re-starting the process would time out.
|
|
withFakeExecutable(t, "stalwart-cli", "#!/bin/sh\necho should-not-run-again >&2\nexit 1\n")
|
|
badPort := freePort(t)
|
|
resumedOpts := opts
|
|
resumedOpts.ListenURL = fmt.Sprintf("http://127.0.0.1:%d/", badPort)
|
|
resumedOpts.ExtraEnv = helperProcessEnv(badPort)
|
|
resumedOpts.StartupTimeout = 300 * time.Millisecond
|
|
|
|
resumed, err := store.Load(rs.RunID)
|
|
if err != nil {
|
|
t.Fatalf("store.Load (resume): %v", err)
|
|
}
|
|
report2, err := Run(context.Background(), store, resumed, resumedOpts)
|
|
if err != nil {
|
|
t.Fatalf("Run #2 (resume) should succeed without redoing the cycle: %v", err)
|
|
}
|
|
if len(report2.Results) != 1 || report2.Results[0].Detail != report.Results[0].Detail {
|
|
t.Errorf("resumed report = %+v, want the cached outcome from Run #1", report2.Results)
|
|
}
|
|
}
|
|
|
|
func TestRecoveryRunFailsWhenProcessNeverBecomesHealthy(t *testing.T) {
|
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
|
os.WriteFile(configPath, []byte("{}"), 0o644)
|
|
|
|
// Nothing listens on this port - the binary never actually starts an
|
|
// HTTP server, since BinaryPath here is a script that just exits.
|
|
binDir := t.TempDir()
|
|
binPath := filepath.Join(binDir, "stalwart")
|
|
os.WriteFile(binPath, []byte("#!/bin/sh\nsleep 5\n"), 0o755)
|
|
port := freePort(t)
|
|
|
|
store := checkpoint.NewStore(t.TempDir())
|
|
rs, err := store.Create("0.15.5", "0.16.14")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
opts := Options{
|
|
BinaryPath: binPath,
|
|
ConfigPath: configPath,
|
|
ListenURL: fmt.Sprintf("http://127.0.0.1:%d/", port),
|
|
AdminUser: "admin",
|
|
ApplyFiles: []string{},
|
|
StartupTimeout: 300 * time.Millisecond,
|
|
StopGrace: 2 * time.Second,
|
|
}
|
|
|
|
_, err = Run(context.Background(), store, rs, opts)
|
|
if err == nil {
|
|
t.Fatal("Run should fail when the process never becomes healthy")
|
|
}
|
|
if rs.Status(checkpoint.PhaseRecovery, "recovery-cycle") != checkpoint.StepFailed {
|
|
t.Errorf("step status = %s, want failed (so a retry is possible)", rs.Status(checkpoint.PhaseRecovery, "recovery-cycle"))
|
|
}
|
|
}
|