mirror of https://github.com/hashicorp/boundary
Add TestController (#50)
This can be used in tests to get access to a controller that behaves just like a dev controller but doesn't require the normal Command bits (UI, etc). This can be enhanced over time as needed; for now it's pretty simple but it's good enough to be used for API tests. A simple project create/read API test is added as well, which serves to show the programmatic model (as well as test those functions at the API level).pull/60/head
parent
2aca5fff03
commit
f8b456f3a7
@ -0,0 +1,41 @@
|
||||
package scopes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/watchtower/api"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProjects_Crud(t *testing.T) {
|
||||
tc := controller.NewTestController(t, nil)
|
||||
defer tc.Shutdown()
|
||||
|
||||
client := tc.Client()
|
||||
org := &Organization{
|
||||
Client: client,
|
||||
}
|
||||
|
||||
name := "foo"
|
||||
|
||||
checkProject := func(step string, p *Project, apiErr *api.Error, err error) {
|
||||
assert := assert.New(t)
|
||||
assert.NoError(err, step)
|
||||
assert.Nil(apiErr, step)
|
||||
assert.NotNil(p, "returned project", step)
|
||||
assert.NotNil(p, "returned project name", step)
|
||||
assert.Equal(name, *p.Name, step)
|
||||
}
|
||||
|
||||
p, apiErr, err := org.CreateProject(tc.Context(), &Project{Name: api.String(name)})
|
||||
checkProject("create", p, apiErr, err)
|
||||
|
||||
p, apiErr, err = org.ReadProject(tc.Context(), &Project{Id: p.Id})
|
||||
checkProject("read", p, apiErr, err)
|
||||
|
||||
// TODO: Update and Delete
|
||||
|
||||
// TODO: Error conditions once the proper errors are being returned.
|
||||
// Probably as parallel subtests against the same DB.
|
||||
}
|
||||
@ -0,0 +1,190 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/watchtower/api"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/base"
|
||||
"github.com/hashicorp/watchtower/internal/cmd/config"
|
||||
)
|
||||
|
||||
// TestController wraps a base.Server and Controller to provide a
|
||||
// fully-programmatic controller for tests. Error checking (for instance, for
|
||||
// valid config) is not stringent at the moment.
|
||||
type TestController struct {
|
||||
b *base.Server
|
||||
c *Controller
|
||||
t *testing.T
|
||||
client *api.Client
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// Controller returns the underlying controller
|
||||
func (tc *TestController) Controller() *Controller {
|
||||
return tc.c
|
||||
}
|
||||
|
||||
func (tc *TestController) Client() *api.Client {
|
||||
return tc.client
|
||||
}
|
||||
|
||||
func (tc *TestController) Context() context.Context {
|
||||
return tc.ctx
|
||||
}
|
||||
|
||||
func (tc *TestController) Cancel() {
|
||||
tc.cancel()
|
||||
}
|
||||
|
||||
func (tc *TestController) buildClient() {
|
||||
var apiLn *base.ServerListener
|
||||
for _, listener := range tc.b.Listeners {
|
||||
if listener.Config.Purpose[0] == "api" {
|
||||
apiLn = listener
|
||||
break
|
||||
}
|
||||
}
|
||||
if apiLn == nil {
|
||||
tc.t.Fatal("could not find api listener")
|
||||
}
|
||||
|
||||
tcpAddr, ok := apiLn.Mux.Addr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
tc.t.Fatal("could not parse address as a TCP addr")
|
||||
}
|
||||
addr := fmt.Sprintf("http://%s:%d", tcpAddr.IP.String(), tcpAddr.Port)
|
||||
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 {
|
||||
tc.t.Fatal(fmt.Errorf("error setting client address: %w", err))
|
||||
}
|
||||
|
||||
tc.client = client
|
||||
}
|
||||
|
||||
// Shutdown runs any cleanup functions; be sure to run this after your test is
|
||||
// done
|
||||
func (tc *TestController) Shutdown() {
|
||||
if tc.b != nil {
|
||||
close(tc.b.ShutdownCh)
|
||||
}
|
||||
|
||||
tc.cancel()
|
||||
|
||||
if tc.c != nil {
|
||||
if err := tc.c.Shutdown(); err != nil {
|
||||
tc.t.Error(err)
|
||||
}
|
||||
}
|
||||
if tc.b != nil {
|
||||
if err := tc.b.RunShutdownFuncs(); err != nil {
|
||||
tc.t.Error(err)
|
||||
}
|
||||
if tc.b.DestroyDevDatabase() != nil {
|
||||
if err := tc.b.DestroyDevDatabase(); err != nil {
|
||||
tc.t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TestControllerOpts struct {
|
||||
// Config; if not provided a dev one will be created
|
||||
Config *config.Config
|
||||
|
||||
// DefaultOrgId is the default org ID to use, if set. Can also be provided
|
||||
// in the normal config.
|
||||
DefaultOrgId string
|
||||
|
||||
// DisableDatabaseCreation can be set true to disable creating a dev
|
||||
// database
|
||||
DisableDatabaseCreation bool
|
||||
}
|
||||
|
||||
func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
tc := &TestController{
|
||||
t: t,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
if opts == nil {
|
||||
opts = new(TestControllerOpts)
|
||||
}
|
||||
|
||||
// Base server
|
||||
tc.b = base.NewServer(nil)
|
||||
tc.b.Command = &base.Command{
|
||||
ShutdownCh: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Get dev config, or use a provided one
|
||||
var err error
|
||||
if opts.Config == nil {
|
||||
opts.Config, err = config.DevController()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set default org ID, preferring one passed in from opts over config
|
||||
if opts.Config.DefaultOrgId != "" {
|
||||
tc.b.DefaultOrgId = opts.Config.DefaultOrgId
|
||||
}
|
||||
if opts.DefaultOrgId != "" {
|
||||
tc.b.DefaultOrgId = opts.DefaultOrgId
|
||||
}
|
||||
|
||||
// Start a logger
|
||||
tc.b.Logger = hclog.New(&hclog.LoggerOptions{
|
||||
Level: hclog.Trace,
|
||||
})
|
||||
|
||||
// Set up KMSes
|
||||
if err := tc.b.SetupKMSes(nil, opts.Config.SharedConfig, []string{"controller", "worker-auth"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure the listeners use random port allocation
|
||||
for _, listener := range opts.Config.Listeners {
|
||||
listener.RandomPort = true
|
||||
}
|
||||
if err := tc.b.SetupListeners(nil, opts.Config.SharedConfig); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !opts.DisableDatabaseCreation {
|
||||
if err := tc.b.CreateDevDatabase("postgres"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
conf := &Config{
|
||||
RawConfig: opts.Config,
|
||||
Server: tc.b,
|
||||
}
|
||||
|
||||
tc.c, err = New(conf)
|
||||
if err != nil {
|
||||
tc.Shutdown()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tc.buildClient()
|
||||
|
||||
if err := tc.c.Start(); err != nil {
|
||||
tc.Shutdown()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_TestController(t *testing.T) {
|
||||
t.Run("startup and shutdown", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tc := NewTestController(t, nil)
|
||||
defer tc.Shutdown()
|
||||
})
|
||||
}
|
||||
Loading…
Reference in new issue