Files
stalwart-migrator/internal/recovery/recovery_test.go
T
jcoffey-dev 5566eed1c8 Fix module path and restore cmd/ omitted by gitignore
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.
2026-08-22 18:23:48 -07:00

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"))
}
}