From fe773fe757bd4d23045e3b80f90bc63e3cb39acc Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Sat, 28 Oct 2017 11:15:22 -0700 Subject: [PATCH] Test cmd/server --- cmd/bootstrap.go | 5 +- cmd/server.go | 21 +++- cmd/server_test.go | 303 +++++++++++++++++++++++++++++++++++++++++++++ main.go | 2 +- server/server.go | 11 -- 5 files changed, 327 insertions(+), 15 deletions(-) create mode 100644 cmd/server_test.go diff --git a/cmd/bootstrap.go b/cmd/bootstrap.go index 358cb879cc..3c164c9f4c 100644 --- a/cmd/bootstrap.go +++ b/cmd/bootstrap.go @@ -1,10 +1,11 @@ package cmd import ( - "github.com/hootsuite/atlantis/bootstrap" - "github.com/spf13/cobra" "fmt" "os" + + "github.com/hootsuite/atlantis/bootstrap" + "github.com/spf13/cobra" ) // BootstrapCmd starts the bootstrap process for testing out Atlantis. diff --git a/cmd/server.go b/cmd/server.go index 69ccdc14e2..7783ca0dd3 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/hootsuite/atlantis/server" + "github.com/mitchellh/go-homedir" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -104,7 +105,7 @@ type boolFlag struct { // us to mock out starting the actual server. type ServerCmd struct { ServerCreator ServerCreator - Viper *viper.Viper + Viper *viper.Viper // SilenceOutput set to true means nothing gets printed. // Useful for testing to keep the logs clean. SilenceOutput bool @@ -140,6 +141,7 @@ func (s *ServerCmd) Init() *cobra.Command { Flags can also be set in a yaml config file (see --` + ConfigFlag + `). Config file values are overridden by environment variables which in turn are overridden by flags.`, SilenceErrors: true, + SilenceUsage: s.SilenceOutput, PreRunE: s.withErrPrint(func(cmd *cobra.Command, args []string) error { return s.preRun() }), @@ -201,6 +203,9 @@ func (s *ServerCmd) run() error { if err := setAtlantisURL(&config); err != nil { return err } + if err := setDataDir(&config); err != nil { + return err + } sanitizeGithubUser(&config) // config looks good, start the server @@ -237,6 +242,20 @@ func setAtlantisURL(config *server.ServerConfig) error { return nil } +// setDataDir checks if ~ was used in data-dir and converts it to the actual +// home directory. If we don't do this, we'll create a directory called "~" +// instead of actually using home. +func setDataDir(config *server.ServerConfig) error { + if strings.HasPrefix(config.DataDir, "~/") { + expanded, err := homedir.Expand(config.DataDir) + if err != nil { + return errors.Wrap(err, "determining home directory") + } + config.DataDir = expanded + } + return nil +} + // sanitizeGithubUser trims @ from the front of the github username if it exists. func sanitizeGithubUser(config *server.ServerConfig) { config.GithubUser = strings.TrimPrefix(config.GithubUser, "@") diff --git a/cmd/server_test.go b/cmd/server_test.go new file mode 100644 index 0000000000..14be6da6f1 --- /dev/null +++ b/cmd/server_test.go @@ -0,0 +1,303 @@ +package cmd_test + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/hootsuite/atlantis/cmd" + "github.com/hootsuite/atlantis/server" + . "github.com/hootsuite/atlantis/testing_util" + "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// passedConfig is set to whatever config ended up being passed to NewServer. +// Used for testing. +var passedConfig server.ServerConfig + +type ServerCreatorMock struct{} + +func (s *ServerCreatorMock) NewServer(config server.ServerConfig) (cmd.ServerStarter, error) { + passedConfig = config + return &ServerStarterMock{}, nil +} + +type ServerStarterMock struct{} + +func (s *ServerStarterMock) Start() error { + return nil +} + +func TestExecute_NoConfigFlag(t *testing.T) { + t.Log("If there is no config flag specified Execute should return nil") + c := setup(map[string]interface{}{ + cmd.ConfigFlag: "", + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + }) + err := c.Execute() + Ok(t, err) +} + +func TestExecute_ConfigFileExtension(t *testing.T) { + t.Log("If the config file doesn't have an extension then error") + c := setup(map[string]interface{}{ + cmd.ConfigFlag: "does-not-exist", + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + }) + err := c.Execute() + Equals(t, "invalid config: reading does-not-exist: Unsupported Config Type \"\"", err.Error()) +} + +func TestExecute_ConfigFileMissing(t *testing.T) { + t.Log("If the config file doesn't exist then error") + c := setup(map[string]interface{}{ + cmd.ConfigFlag: "does-not-exist.yaml", + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + }) + err := c.Execute() + Equals(t, "invalid config: reading does-not-exist.yaml: open does-not-exist.yaml: no such file or directory", err.Error()) +} + +func TestExecute_ConfigFileExists(t *testing.T) { + t.Log("If the config file exists then there should be no error") + tmpFile := tempFile(t, "") + defer os.Remove(tmpFile) + c := setup(map[string]interface{}{ + cmd.ConfigFlag: tmpFile, + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + }) + err := c.Execute() + Ok(t, err) +} + +func TestExecute_InvalidConfig(t *testing.T) { + t.Log("If the config file contains invalid yaml there should be an error") + tmpFile := tempFile(t, "invalidyaml") + defer os.Remove(tmpFile) + c := setup(map[string]interface{}{ + cmd.ConfigFlag: tmpFile, + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + }) + err := c.Execute() + Assert(t, strings.Contains(err.Error(), "unmarshal errors"), "should be an unmarshal error") +} + +func TestExecute_Validation(t *testing.T) { + cases := []struct { + description string + flags map[string]interface{} + expErr string + }{ + { + "should validate log level", + map[string]interface{}{ + cmd.LogLevelFlag: "invalid", + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + }, + "invalid log level: not one of debug, info, warn, error", + }, + { + "should ensure github user is set", + map[string]interface{}{ + cmd.GHTokenFlag: "token", + }, + "--gh-user must be set", + }, + { + "should ensure github token is set", + map[string]interface{}{ + cmd.GHUserFlag: "user", + }, + "--gh-token must be set", + }, + } + for _, testCase := range cases { + t.Log(testCase.description) + c := setup(testCase.flags) + err := c.Execute() + Assert(t, err != nil, "should be an error") + Equals(t, testCase.expErr, err.Error()) + } +} + +func TestExecute_Defaults(t *testing.T) { + t.Log("should set the defaults for all unspecified flags") + c := setup(map[string]interface{}{ + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + }) + err := c.Execute() + Ok(t, err) + + Equals(t, "user", passedConfig.GithubUser) + Equals(t, "token", passedConfig.GithubToken) + Equals(t, "", passedConfig.GithubWebHookSecret) + // Get our hostname since that's what gets defaulted to + hostname, err := os.Hostname() + Ok(t, err) + Equals(t, "http://"+hostname+":4141", passedConfig.AtlantisURL) + + // Get our home dir since that's what gets defaulted to + dataDir, err := homedir.Expand("~/.atlantis") + Ok(t, err) + Equals(t, dataDir, passedConfig.DataDir) + Equals(t, "github.com", passedConfig.GithubHostname) + Equals(t, "info", passedConfig.LogLevel) + Equals(t, false, passedConfig.RequireApproval) + Equals(t, 4141, passedConfig.Port) +} + +func TestExecute_ExpandHomeDir(t *testing.T) { + t.Log("should expand the ~ in the home dir to the actual home dir") + c := setup(map[string]interface{}{ + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + cmd.DataDirFlag: "~/this/is/a/path", + }) + err := c.Execute() + Ok(t, err) + + home, err := homedir.Dir() + Ok(t, err) + Equals(t, home+"/this/is/a/path", passedConfig.DataDir) +} + +func TestExecute_GithubUser(t *testing.T) { + t.Log("should remove the @ from the github username if it's passed") + c := setup(map[string]interface{}{ + cmd.GHUserFlag: "@user", + cmd.GHTokenFlag: "token", + }) + err := c.Execute() + Ok(t, err) + + Equals(t, "user", passedConfig.GithubUser) +} + +func TestExecute_Flags(t *testing.T) { + t.Log("should use all flags that are set") + c := setup(map[string]interface{}{ + cmd.AtlantisURLFlag: "url", + cmd.DataDirFlag: "path", + cmd.GHHostnameFlag: "ghhostname", + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "token", + cmd.GHWebHookSecret: "secret", + cmd.LogLevelFlag: "debug", + cmd.PortFlag: 8181, + cmd.RequireApprovalFlag: true, + }) + err := c.Execute() + Ok(t, err) + + Equals(t, "url", passedConfig.AtlantisURL) + Equals(t, "path", passedConfig.DataDir) + Equals(t, "ghhostname", passedConfig.GithubHostname) + Equals(t, "user", passedConfig.GithubUser) + Equals(t, "token", passedConfig.GithubToken) + Equals(t, "secret", passedConfig.GithubWebHookSecret) + Equals(t, "debug", passedConfig.LogLevel) + Equals(t, 8181, passedConfig.Port) + Equals(t, true, passedConfig.RequireApproval) +} + +func TestExecute_ConfigFile(t *testing.T) { + t.Log("Should use all the values from the config file") + tmpFile := tempFile(t, `--- +atlantis-url: "url" +data-dir: "path" +gh-hostname: "ghhostname" +gh-user: "user" +gh-token: "token" +gh-webhook-secret: "secret" +log-level: "debug" +port: 8181 +require-approval: true`) + defer os.Remove(tmpFile) + c := setup(map[string]interface{}{ + cmd.ConfigFlag: tmpFile, + }) + + err := c.Execute() + Ok(t, err) + Equals(t, "url", passedConfig.AtlantisURL) + Equals(t, "path", passedConfig.DataDir) + Equals(t, "ghhostname", passedConfig.GithubHostname) + Equals(t, "user", passedConfig.GithubUser) + Equals(t, "token", passedConfig.GithubToken) + Equals(t, "secret", passedConfig.GithubWebHookSecret) + Equals(t, "debug", passedConfig.LogLevel) + Equals(t, 8181, passedConfig.Port) + Equals(t, true, passedConfig.RequireApproval) +} + +func TestExecute_EnvironmentOverride(t *testing.T) { + t.Log("Environment variables should override config file flags") + tmpFile := tempFile(t, "gh-user: config\ngh-token: config2") + defer os.Remove(tmpFile) + os.Setenv("ATLANTIS_GH_TOKEN", "override") + c := setup(map[string]interface{}{ + cmd.ConfigFlag: tmpFile, + }) + err := c.Execute() + Ok(t, err) + Equals(t, "override", passedConfig.GithubToken) +} + +func TestExecute_FlagConfigOverride(t *testing.T) { + t.Log("Flags should override config file flags") + os.Setenv("ATLANTIS_GH_TOKEN", "env-var") + c := setup(map[string]interface{}{ + cmd.GHUserFlag: "user", + cmd.GHTokenFlag: "override", + }) + err := c.Execute() + Ok(t, err) + Equals(t, "override", passedConfig.GithubToken) +} + +func TestExecute_FlagEnvVarOverride(t *testing.T) { + t.Log("Flags should override environment variables") + tmpFile := tempFile(t, "gh-user: config\ngh-token: config2") + defer os.Remove(tmpFile) + c := setup(map[string]interface{}{ + cmd.ConfigFlag: tmpFile, + cmd.GHTokenFlag: "override", + }) + + err := c.Execute() + Ok(t, err) + Equals(t, "override", passedConfig.GithubToken) +} + +func setup(flags map[string]interface{}) *cobra.Command { + viper := viper.New() + for k, v := range flags { + viper.Set(k, v) + } + c := &cmd.ServerCmd{ + ServerCreator: &ServerCreatorMock{}, + Viper: viper, + SilenceOutput: true, + } + return c.Init() +} + +func tempFile(t *testing.T, contents string) string { + f, err := ioutil.TempFile("", "") + Ok(t, err) + newName := f.Name() + ".yaml" + err = os.Rename(f.Name(), newName) + Ok(t, err) + ioutil.WriteFile(newName, []byte(contents), 0644) + return newName +} diff --git a/main.go b/main.go index 99de125102..6b77c89dfd 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ func main() { server := &cmd.ServerCmd{ ServerCreator: &cmd.DefaultServerCreator{}, - Viper: v, + Viper: v, } version := &cmd.VersionCmd{Viper: v} bootstrap := &cmd.BootstrapCmd{} diff --git a/server/server.go b/server/server.go index f42ea97c0c..92da190ee1 100644 --- a/server/server.go +++ b/server/server.go @@ -21,7 +21,6 @@ import ( "github.com/hootsuite/atlantis/server/events/terraform" "github.com/hootsuite/atlantis/server/logging" "github.com/hootsuite/atlantis/server/static" - "github.com/mitchellh/go-homedir" "github.com/pkg/errors" "github.com/urfave/cli" "github.com/urfave/negroni" @@ -55,16 +54,6 @@ type ServerConfig struct { } func NewServer(config ServerConfig) (*Server, error) { - // if ~ was used in data-dir convert that to actual home directory otherwise we'll - // create a directory call "~" instead of actually using home - if strings.HasPrefix(config.DataDir, "~/") { - expanded, err := homedir.Expand(config.DataDir) - if err != nil { - return nil, errors.Wrap(err, "determining home directory") - } - config.DataDir = expanded - } - githubClient, err := github.NewClient(config.GithubHostname, config.GithubUser, config.GithubToken) if err != nil { return nil, err