From 95d29c3e954db073237f4898b57cc0d0f23ce815 Mon Sep 17 00:00:00 2001 From: Todd Knight Date: Tue, 19 May 2020 14:54:35 -0700 Subject: [PATCH] Allow the TestController to be referenced externally (#65) * Allow the TestController to be used outside of our go module. * Adding package comment explaining this testing package is for internal use only. --- internal/servers/controller/testing.go | 14 +++- testing/controller/controller.go | 101 +++++++++++++++++++++++++ testing/controller/docs.go | 2 + 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 testing/controller/controller.go create mode 100644 testing/controller/docs.go diff --git a/internal/servers/controller/testing.go b/internal/servers/controller/testing.go index 231fda56e6..894bdd5e7f 100644 --- a/internal/servers/controller/testing.go +++ b/internal/servers/controller/testing.go @@ -19,6 +19,7 @@ type TestController struct { b *base.Server c *Controller t *testing.T + addr string // The address the Controller API is listening on client *api.Client ctx context.Context cancel context.CancelFunc @@ -41,7 +42,10 @@ func (tc *TestController) Cancel() { tc.cancel() } -func (tc *TestController) buildClient() { +func (tc *TestController) ApiAddress() string { + if tc.addr != "" { + return tc.addr + } var apiLn *base.ServerListener for _, listener := range tc.b.Listeners { if listener.Config.Purpose[0] == "api" { @@ -57,12 +61,16 @@ func (tc *TestController) buildClient() { if !ok { tc.t.Fatal("could not parse address as a TCP addr") } - addr := fmt.Sprintf("http://%s:%d", tcpAddr.IP.String(), tcpAddr.Port) + tc.addr = fmt.Sprintf("http://%s:%d", tcpAddr.IP.String(), tcpAddr.Port) + return tc.addr +} + +func (tc *TestController) buildClient() { client, err := api.NewClient(nil) if err != nil { tc.t.Fatal(fmt.Errorf("error creating client: %w", err)) } - if err := client.SetAddr(addr); err != nil { + if err := client.SetAddr(tc.ApiAddress()); err != nil { tc.t.Fatal(fmt.Errorf("error setting client address: %w", err)) } diff --git a/testing/controller/controller.go b/testing/controller/controller.go new file mode 100644 index 0000000000..88e4c5bb6c --- /dev/null +++ b/testing/controller/controller.go @@ -0,0 +1,101 @@ +package controller + +import ( + "fmt" + "testing" + + "github.com/hashicorp/watchtower/internal/cmd/config" + "github.com/hashicorp/watchtower/internal/servers/controller" +) + +func getOpts(opt ...Option) (*controller.TestControllerOpts, error) { + opts := &option{ + tcOptions: &controller.TestControllerOpts{}, + } + for _, o := range opt { + if err := o(opts); err != nil { + return nil, err + } + } + if opts.setWithConfigFile && opts.setWithConfigText { + return nil, fmt.Errorf("Cannot provide both WithConfigFile and WithConfigText") + } + return opts.tcOptions, nil +} + +type option struct { + tcOptions *controller.TestControllerOpts + setWithConfigFile bool + setWithConfigText bool + setDisableDatabaseCreation bool + setDefaultOrgId bool +} + +type Option func(*option) error + +// WithConfigFile provides the given ConfigFile to the built TestController. This option can not be used if WithConfigText is used. +func WithConfigFile(f string) Option { + return func(c *option) error { + if c.setWithConfigFile { + return fmt.Errorf("WithConfigFile provided more than once.") + } + c.setWithConfigFile = true + cfg, err := config.LoadFile(f) + if err != nil { + return err + } + c.tcOptions.Config = cfg + return nil + } +} + +// WithConfigText configures the TestController sets up the Controller using the provided config text. This option cannot be used if WithConfigFile is used. +func WithConfigText(ct string) Option { + return func(c *option) error { + if c.setWithConfigText { + return fmt.Errorf("WithConfigText provided more than once.") + } + c.setWithConfigText = true + cfg, err := config.Parse(ct) + if err != nil { + return err + } + c.tcOptions.Config = cfg + return nil + } +} + +// DisableDatabaseCreation skips creating a database in docker and allows one to be provided through a tcOptions. +func DisableDatabaseCreation() Option { + return func(c *option) error { + if c.setDisableDatabaseCreation { + return fmt.Errorf("DisableDatabaseCreation provided more than once.") + } + c.setDisableDatabaseCreation = true + c.tcOptions.DisableDatabaseCreation = true + return nil + } +} + +// WithDefaultOrgId sets the org id to the one provided here. +func WithDefaultOrgId(id string) Option { + return func(c *option) error { + if c.setDefaultOrgId { + return fmt.Errorf("WithDefaultOrgId provided more than once.") + } + c.setDefaultOrgId = true + c.tcOptions.DefaultOrgId = id + return nil + } +} + +// NewTestController blocks until a new TestController is created, returns the url for the TestController and a function +// that can be called to tear down the controller after it has been used for testing. +func NewTestController(t *testing.T, opt ...Option) (string, func()) { + conf, err := getOpts(opt...) + if err != nil { + t.Fatal("Couldn't create TestController: %v", err) + } + tc := controller.NewTestController(t, conf) + return tc.ApiAddress(), tc.Shutdown +} diff --git a/testing/controller/docs.go b/testing/controller/docs.go new file mode 100644 index 0000000000..b316b0a17f --- /dev/null +++ b/testing/controller/docs.go @@ -0,0 +1,2 @@ +// controller is a package meant for internal testing only. The interfaces may change or be removed at any time without warning. +package controller