// Copyright Earl Warren // Copyright Loïc Dachary // Copyright twenty-panda // SPDX-License-Identifier: MIT package hoverfly import ( "fmt" "io" "os" "os/exec" "path" "testing" "github.com/stretchr/testify/require" ) type M interface { Run() (code int) } type Hoverfly interface { Run(m M) GetOutput() io.Writer SetOutput(io.Writer) GetURL() string } const ( modeCapture = "capture" modeSimulate = "simulate" ) var ( exit = os.Exit getwd = os.Getwd ) func Run(t testing.TB, name string) func() { t.Helper() if !hoverflySingleton.active { return func() {} } httpProxyRestore := func() {} if val, ok := os.LookupEnv("http_proxy"); ok { httpProxyRestore = func() { os.Setenv("http_proxy", val) } } os.Setenv("http_proxy", hoverflySingleton.GetURL()) if *hoverflySingleton.mode == modeSimulate { require.NoError(t, hoverflySingleton.loadSimulation(name)) require.NoError(t, hoverflySingleton.hoverctl("mode", "simulate", "--matching-strategy", "first")) return func() { httpProxyRestore() } } if *hoverflySingleton.mode == modeCapture { require.NoError(t, hoverflySingleton.hoverctl("mode", "capture", "--stateful")) return func() { httpProxyRestore() require.NoError(t, hoverflySingleton.saveSimulation(name)) } } panic(fmt.Errorf("unknown mode %s", *hoverflySingleton.mode)) } func MainTest(m *testing.M) { hoverflySingleton.Run(m) } type hoverfly struct { home string httpProxy *string output io.Writer mode *string active bool } var hoverflySingleton = &hoverfly{} func GetSingleton() Hoverfly { return hoverflySingleton } func newHoverfly() Hoverfly { return &hoverfly{} } func (o *hoverfly) hoverctl(args ...string) error { hoverctl := "hoverctl" cmd := exec.Command(hoverctl, args...) cmd.Env = append( cmd.Env, "HOME="+o.home, ) if output := o.GetOutput(); output != nil { cmd.Stdout = output cmd.Stderr = output fmt.Fprintf(output, "%v: %s %v\n", cmd.Env, hoverctl, args) } if err := cmd.Run(); err != nil { return fmt.Errorf(hoverctl+"start: %w\n", err) } return nil } func (o *hoverfly) getSimulationDir() (string, error) { pwd, err := getwd() if err != nil { return "", err } p := path.Join(pwd, "hoverfly") if err := os.MkdirAll(p, 0o755); err != nil { return p, err } return p, nil } func (o *hoverfly) getSimulationPath(name string) (string, error) { dir, err := o.getSimulationDir() if err != nil { return "", err } return path.Join(dir, name+".json"), nil } func (o *hoverfly) loadSimulation(name string) error { simulation, err := o.getSimulationPath(name) if err != nil { return err } return o.hoverctl("import", simulation) } func (o *hoverfly) saveSimulation(name string) error { simulation, err := o.getSimulationPath(name) if err != nil { return err } return o.hoverctl("export", simulation) } func (o *hoverfly) setup() error { if val, ok := os.LookupEnv("HOVERFLY"); ok { o.active = true o.mode = &val } else { return nil } home, err := os.MkdirTemp(os.TempDir(), "hoverfly") if err != nil { return fmt.Errorf("TempDir: %w", err) } o.home = home return o.hoverctl("start") } func (o *hoverfly) teardown() error { if !o.active { return nil } if err := o.hoverctl("logs"); err != nil { return err } if err := o.hoverctl("stop"); err != nil { return err } if o.home != "" { return os.RemoveAll(o.home) } return nil } func (o *hoverfly) SetOutput(output io.Writer) { o.output = output } func (o *hoverfly) GetOutput() io.Writer { return o.output } func (o *hoverfly) GetURL() string { return "http://localhost:8500" } func (o *hoverfly) Run(m M) { _ = o.setup() exitCode := m.Run() _ = o.teardown() exit(exitCode) }