From f0b77b2b53bf02c533893b98ff75daf53decce09 Mon Sep 17 00:00:00 2001 From: shahidhk Date: Wed, 27 Jun 2018 14:28:07 +0530 Subject: [PATCH 1/5] add test for init command --- cli/commands/init.go | 80 +++++++++++---------------------------- cli/commands/init_test.go | 53 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 58 deletions(-) create mode 100644 cli/commands/init_test.go diff --git a/cli/commands/init.go b/cli/commands/init.go index 0f59701c576e3..129408b986e51 100644 --- a/cli/commands/init.go +++ b/cli/commands/init.go @@ -20,6 +20,7 @@ const ( MANIFESTS_DIR = "install-scripts" ) +// NewInitCmd is the definition for init command func NewInitCmd(ec *cli.ExecutionContext) *cobra.Command { opts := &initOptions{ EC: ec, @@ -36,11 +37,10 @@ func NewInitCmd(ec *cli.ExecutionContext) *cobra.Command { # See https://docs.hasura.io/0.15/graphql/manual/getting-started for more details`, SilenceUsage: true, + PreRunE: func(cmd *cobra.Command, args []string) error { + return ec.Prepare() + }, RunE: func(cmd *cobra.Command, args []string) error { - err := ec.Prepare() - if err != nil { - return errors.Wrap(err, "cmd prep failed") - } return opts.Run() }, } @@ -65,6 +65,7 @@ func (o *initOptions) Run() error { o.EC.ExecutionDirectory = o.InitDir } + // prompt for init directory if it's not set already if len(o.InitDir) == 0 { p := promptui.Prompt{ Label: "Name of project directory ", @@ -82,6 +83,7 @@ func (o *initOptions) Run() error { } var infoMsg string + // If endpoint is not provided, download installation manifests if len(o.Endpoint) == 0 { o.EC.Spin("Downloading install manifests... ") defer o.EC.Spinner.Stop() @@ -107,13 +109,17 @@ func (o *initOptions) Run() error { return errors.Wrap(err, "error creating setup directories") } + // copy manifests to manifests directory (dst) err = util.CopyDir(src, dst) if err != nil { return errors.Wrap(err, "error copying files") } o.EC.Spinner.Stop() infoMsg = fmt.Sprintf("manifests created at [%s]", dst) + } else { + + // if endpoint is set, just create the execution directory if _, err := os.Stat(o.EC.ExecutionDirectory); err == nil { return errors.Errorf("directory '%s' already exist", o.EC.ExecutionDirectory) } @@ -128,72 +134,46 @@ func (o *initOptions) Run() error { `, o.EC.ExecutionDirectory, o.EC.CMDName) } + // create other required files, like config.yaml, migrations directory err := o.createFiles() if err != nil { return err } - o.EC.Logger.Infof(infoMsg) + o.EC.Logger.Info(infoMsg) return nil } +// createFiles creates files required by the CLI in the ExecutionDirectory func (o *initOptions) createFiles() error { + // create the directory err := os.MkdirAll(filepath.Dir(o.EC.ExecutionDirectory), os.ModePerm) if err != nil { return errors.Wrap(err, "error creating setup directories") } + // set config object config := &cli.HasuraGraphQLConfig{ Endpoint: "http://localhost:8080", } - - o.EC.ConfigFile = filepath.Join(o.EC.ExecutionDirectory, "config.yaml") - - // if o.Endpoint == "" { - // p := promptui.Prompt{ - // Label: "Hasura GraphQL Engine endpoint", - // Default: config.Endpoint, - // } - // r, err := p.Run() - // if err != nil { - // return handlePromptError(err) - // } - // o.Endpoint = r - // } - if o.Endpoint == "" { - o.Endpoint = config.Endpoint - } else { + if o.Endpoint != "" { config.Endpoint = o.Endpoint } + if o.AccessKey != "" { + config.AccessKey = o.AccessKey + } + // write the config file data, err := yaml.Marshal(config) if err != nil { return errors.Wrap(err, "cannot convert to yaml") } + o.EC.ConfigFile = filepath.Join(o.EC.ExecutionDirectory, "config.yaml") err = ioutil.WriteFile(o.EC.ConfigFile, data, 0644) if err != nil { return errors.Wrap(err, "cannot write config file") } - // if o.AccessKey == "" { - // p := promptui.Prompt{ - // Label: "Hasura GraphQL Engine access key", - // Default: "", - // } - // r, err := p.Run() - // if err != nil { - // return handlePromptError(err) - // } - // o.AccessKey = r - // } - if o.AccessKey == "" { - o.AccessKey = "" - } - err = ioutil.WriteFile(filepath.Join(o.EC.ExecutionDirectory, ".env"), - []byte(fmt.Sprintf("%s=%s", cli.ENV_ACCESS_KEY, o.AccessKey)), 0644) - if err != nil { - return errors.Wrap(err, "cannot write dotenv file") - } - + // create migrations directory o.EC.MigrationDir = filepath.Join(o.EC.ExecutionDirectory, "migrations") err = os.MkdirAll(o.EC.MigrationDir, os.ModePerm) if err != nil { @@ -203,22 +183,6 @@ func (o *initOptions) createFiles() error { return nil } -func getInstallManifests(url, target string) error { - err := util.Download(url, target+".zip") - if err != nil { - return errors.Wrap(err, "failed downloading manifests") - } - err = os.RemoveAll(target) - if err != nil { - return errors.Wrap(err, "failed cleaning manifests") - } - err = util.Unzip(target+".zip", filepath.Dir(target)) - if err != nil { - return errors.Wrap(err, "failed extracting manifests") - } - return nil -} - func handlePromptError(err error) error { if err == promptui.ErrInterrupt { return errors.New("cancelled by user") diff --git a/cli/commands/init_test.go b/cli/commands/init_test.go new file mode 100644 index 0000000000000..0cff3a9a7581d --- /dev/null +++ b/cli/commands/init_test.go @@ -0,0 +1,53 @@ +package commands + +import ( + "math/rand" + "os" + "path/filepath" + "strconv" + "testing" + "time" + + "github.com/hasura/graphql-engine/cli" +) + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func TestInitCmd(t *testing.T) { + tt := []struct { + name string + opts *initOptions + err error + }{ + {"only-init-dir", &initOptions{ + EC: &cli.ExecutionContext{}, + Endpoint: "", + AccessKey: "", + InitDir: filepath.Join(os.TempDir(), "hasura-cli-test-"+strconv.Itoa(rand.Intn(1000))), + }, nil}, + {"with-endpoint-flag", &initOptions{ + EC: &cli.ExecutionContext{}, + Endpoint: "https://localhost:8080", + AccessKey: "", + InitDir: filepath.Join(os.TempDir(), "hasura-cli-test-"+strconv.Itoa(rand.Intn(1000))), + }, nil}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.EC.Prepare() + if err != nil { + t.Fatalf("%s: prep failed: %v", tc.name, err) + } + err = tc.opts.Run() + if err != tc.err { + t.Fatalf("%s: expected %v, got %v", tc.name, tc.err, err) + } else { + // TODO: (shahidhk) need to verify the contents of the spec generated + os.RemoveAll(tc.opts.InitDir) + } + }) + } +} From 32e29ac9b76677338b85d84f0e164877c28ed2f6 Mon Sep 17 00:00:00 2001 From: shahidhk Date: Wed, 27 Jun 2018 14:28:34 +0530 Subject: [PATCH 2/5] minor refactor --- cli/commands/commands.go | 3 --- cli/commands/root.go | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 cli/commands/commands.go diff --git a/cli/commands/commands.go b/cli/commands/commands.go deleted file mode 100644 index f5e84d0152bbc..0000000000000 --- a/cli/commands/commands.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package commands contains the definition for all the commands present in -// Hasura CLI. -package commands diff --git a/cli/commands/root.go b/cli/commands/root.go index 80ac09015576d..bab1f943c42e9 100644 --- a/cli/commands/root.go +++ b/cli/commands/root.go @@ -1,3 +1,5 @@ +// Package commands contains the definition for all the commands present in +// Hasura CLI. package commands import ( @@ -5,6 +7,7 @@ import ( "github.com/spf13/cobra" ) +// rootCmd is the main "hasura" command var rootCmd = &cobra.Command{ Use: "hasura", Short: "Hasura GraphQL Engine command line tool", @@ -24,6 +27,7 @@ func init() { f.StringVar(&ec.LogLevel, "log-level", "INFO", "log level (DEBUG, INFO, WARN, ERROR, FATAL)") } +// Execute executes the command and returns the error func Execute() error { return rootCmd.Execute() } From c1585755407f1e2ed56628702e3674c0b886e518 Mon Sep 17 00:00:00 2001 From: shahidhk Date: Wed, 27 Jun 2018 14:28:51 +0530 Subject: [PATCH 3/5] add basic test for console command --- cli/commands/console.go | 20 ++++++++++++------ cli/commands/console_test.go | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 cli/commands/console_test.go diff --git a/cli/commands/console.go b/cli/commands/console.go index 864c0b26effbc..f796d9aaa8377 100644 --- a/cli/commands/console.go +++ b/cli/commands/console.go @@ -7,7 +7,6 @@ import ( "path/filepath" "runtime" "sync" - "time" "github.com/fatih/color" "github.com/gin-contrib/cors" @@ -49,6 +48,7 @@ func NewConsoleCmd(ec *cli.ExecutionContext) *cobra.Command { f.StringVar(&opts.APIPort, "api-port", "9693", "port for serving migrate api") f.StringVar(&opts.ConsolePort, "console-port", "9695", "port for serving console") f.StringVar(&opts.Address, "address", "localhost", "address to use") + f.BoolVar(&opts.DontOpenBrowser, "no-browser", false, "do not automatically open console in browser") f.String("endpoint", "", "http(s) endpoint for Hasura GraphQL Engine") f.String("access-key", "", "access key for Hasura GraphQL Engine") @@ -65,6 +65,10 @@ type consoleOptions struct { APIPort string ConsolePort string Address string + + DontOpenBrowser bool + + WG *sync.WaitGroup } func (o *consoleOptions) Run() error { @@ -110,6 +114,7 @@ func (o *consoleOptions) Run() error { // Create WaitGroup for running 3 servers wg := &sync.WaitGroup{} + o.WG = wg wg.Add(1) go func() { err = router.Run(o.Address + ":" + o.APIPort) @@ -129,15 +134,16 @@ func (o *consoleOptions) Run() error { consoleURL := fmt.Sprintf("http://%s:%s", o.Address, o.ConsolePort) - o.EC.Spin(color.CyanString("Opening console using default browser...")) - defer o.EC.Spinner.Stop() + if !o.DontOpenBrowser { + o.EC.Spin(color.CyanString("Opening console using default browser...")) + defer o.EC.Spinner.Stop() - err = open.Run(consoleURL) - if err != nil { - o.EC.Logger.WithError(err).Warn("Error opening browser, try to open the url manually?") + err = open.Run(consoleURL) + if err != nil { + o.EC.Logger.WithError(err).Warn("Error opening browser, try to open the url manually?") + } } - time.Sleep(2 * time.Second) o.EC.Spinner.Stop() log.Infof("console running at: %s", consoleURL) diff --git a/cli/commands/console_test.go b/cli/commands/console_test.go new file mode 100644 index 0000000000000..d9420fb223eac --- /dev/null +++ b/cli/commands/console_test.go @@ -0,0 +1,41 @@ +package commands + +import ( + "testing" + "time" + + "github.com/briandowns/spinner" + "github.com/hasura/graphql-engine/cli" + "github.com/sirupsen/logrus" +) + +func TestConsoleCmd(t *testing.T) { + opts := &consoleOptions{ + EC: &cli.ExecutionContext{ + Logger: logrus.New(), + Spinner: spinner.New(spinner.CharSets[7], 100*time.Millisecond), + Config: &cli.HasuraGraphQLConfig{ + Endpoint: "http://localhost:8080", + AccessKey: "", + }, + }, + APIPort: "9693", + ConsolePort: "9695", + Address: "localhost", + DontOpenBrowser: true, + } + + go func() { + t.Log("waiting for console to start") + for opts.WG == nil { + time.Sleep(1 * time.Second) + } + opts.WG.Done() + opts.WG.Done() + }() + err := opts.Run() + if err != nil { + t.Fatalf("failed running console: %v", err) + } + // TODO: (shahidhk) curl the console endpoint for 200 response +} From ee28bf91f07e0774c01aafb01382f7a5aee420da Mon Sep 17 00:00:00 2001 From: shahidhk Date: Wed, 27 Jun 2018 15:03:30 +0530 Subject: [PATCH 4/5] add tests for cli package --- cli/cli.go | 26 +++++++++++------ cli/cli_test.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 cli/cli_test.go diff --git a/cli/cli.go b/cli/cli.go index 87d7c0a40917c..7c7af41ae0e9b 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -35,6 +35,14 @@ const ( ENV_ACCESS_KEY = "HASURA_GRAPHQL_ACCESS_KEY" ) +// Other constants used in the package +const ( + // Name of the global configuration directory + GLOBAL_CONFIG_DIR_NAME = ".hasura-graphql" + // Name of the global configuration file + GLOBAL_CONFIG_FILE_NAME = "config.json" +) + // version is set at build time, denotes the CLI version. var version string @@ -96,13 +104,6 @@ type ExecutionContext struct { LogLevel string } -// Spin stops any existing spinner and starts a new one with the given message. -func (ec *ExecutionContext) Spin(message string) { - ec.Spinner.Stop() - ec.Spinner.Prefix = message - ec.Spinner.Start() -} - // Prepare as the name suggests, prepares the ExecutionContext ec by // initializing most of the variables to sensible defaults, if it is not already // set. @@ -207,6 +208,13 @@ func (ec *ExecutionContext) setupSpinner() { } } +// Spin stops any existing spinner and starts a new one with the given message. +func (ec *ExecutionContext) Spin(message string) { + ec.Spinner.Stop() + ec.Spinner.Prefix = message + ec.Spinner.Start() +} + // setupLogger creates a default logger if context does not have one set. func (ec *ExecutionContext) setupLogger() { if ec.Logger == nil { @@ -237,14 +245,14 @@ func (ec *ExecutionContext) setupGlobalConfigDir() error { if err != nil { return errors.Wrap(err, "cannot get home directory") } - globalConfigDir := filepath.Join(home, ".hasura-graphql") + globalConfigDir := filepath.Join(home, GLOBAL_CONFIG_DIR_NAME) ec.GlobalConfigDir = globalConfigDir } err := os.MkdirAll(ec.GlobalConfigDir, os.ModePerm) if err != nil { return errors.Wrap(err, "cannot create config directory") } - ec.GlobalConfigFile = filepath.Join(ec.GlobalConfigDir, "config.json") + ec.GlobalConfigFile = filepath.Join(ec.GlobalConfigDir, GLOBAL_CONFIG_FILE_NAME) return nil } diff --git a/cli/cli_test.go b/cli/cli_test.go new file mode 100644 index 0000000000000..fc0774696b90f --- /dev/null +++ b/cli/cli_test.go @@ -0,0 +1,74 @@ +package cli_test + +import ( + "math/rand" + "os" + "path/filepath" + "strconv" + "testing" + "time" + + "github.com/hasura/graphql-engine/cli" + "github.com/hasura/graphql-engine/cli/commands" + "github.com/spf13/viper" +) + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func TestPrepare(t *testing.T) { + ec := &cli.ExecutionContext{} + err := ec.Prepare() + if err != nil { + t.Fatalf("prepare failed: %v", err) + } + if ec.CMDName == "" { + t.Fatalf("expected CMDName, got: %v", ec.CMDName) + } + if ec.Spinner == nil { + t.Fatal("got spinner empty") + } + if ec.Logger == nil { + t.Fatal("got empty logger") + } + if ec.GlobalConfigDir == "" { + t.Fatalf("global config dir: expected $HOME/%s, got %s", cli.GLOBAL_CONFIG_DIR_NAME, ec.GlobalConfigDir) + } + if ec.GlobalConfigFile == "" { + t.Fatalf("global config file: expected $HOME/%s/%s, got %s", cli.GLOBAL_CONFIG_DIR_NAME, cli.GLOBAL_CONFIG_FILE_NAME, ec.GlobalConfigFile) + } + if ec.Config == nil { + t.Fatal("nil HasuraGraphQLConfig") + } +} + +func TestValidate(t *testing.T) { + ec := &cli.ExecutionContext{} + ec.ExecutionDirectory = filepath.Join(os.TempDir(), "hasura-gql-tests-"+strconv.Itoa(rand.Intn(1000))) + ec.Viper = viper.New() + + // validate a directory created by init + initCmd := commands.NewInitCmd(ec) + initCmd.Flags().Set("directory", ec.ExecutionDirectory) + err := initCmd.Execute() + if err != nil { + t.Fatalf("execution failed: %v", err) + } + err = ec.Validate() + if err != nil { + t.Fatalf("validate failed: %v", err) + } + + // remove config.yaml and validate, should result in an error + err = os.Remove(filepath.Join(ec.ExecutionDirectory, "config.yaml")) + if err != nil { + t.Fatalf("remove failed: %v", err) + } + err = ec.Validate() + if err == nil { + t.Fatal("validate succeeded with no config.yaml") + } + + os.RemoveAll(ec.ExecutionDirectory) +} From e30925fab22cd0b947ab6a186eba2802d922d28a Mon Sep 17 00:00:00 2001 From: shahidhk Date: Wed, 27 Jun 2018 16:19:23 +0530 Subject: [PATCH 5/5] add test for install_manifest --- cli/util/install_manifest.go | 10 --------- cli/util/install_manifest_test.go | 35 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 cli/util/install_manifest_test.go diff --git a/cli/util/install_manifest.go b/cli/util/install_manifest.go index 907d887e0b93c..7c988be01b97e 100644 --- a/cli/util/install_manifest.go +++ b/cli/util/install_manifest.go @@ -67,13 +67,3 @@ func (i *InstallManifestsRepo) Download() (dir string, err error) { func (i *InstallManifestsRepo) ZipExtractedDirectory() string { return fmt.Sprintf("%s-master", i.Name) } - -// ReadmeURL returns the GitHub URL to README.md file in the repo root. -func (i *InstallManifestsRepo) ReadmeURL() string { - u := url.URL{ - Scheme: "https", - Host: "github.com", - Path: fmt.Sprintf("%s/%s/blob/master/README.md", i.Namespace, i.Name), - } - return u.String() -} diff --git a/cli/util/install_manifest_test.go b/cli/util/install_manifest_test.go new file mode 100644 index 0000000000000..70842dbc9ebb0 --- /dev/null +++ b/cli/util/install_manifest_test.go @@ -0,0 +1,35 @@ +package util + +import ( + "os" + "testing" +) + +var i = &InstallManifestsRepo{ + Namespace: "hasura", + Name: "graphql-engine-install-manifests", +} + +func TestZipURL(t *testing.T) { + expected := "https://github.com/hasura/graphql-engine-install-manifests/archive/master.zip" + u := i.ZipURL() + if u != expected { + t.Fatalf("expected: %s, got %s", expected, u) + } +} + +func TestZipExtractedDirectory(t *testing.T) { + expected := "graphql-engine-install-manifests-master" + got := i.ZipExtractedDirectory() + if got != expected { + t.Fatalf("expected: %s, got %s", expected, got) + } +} + +func TestDownload(t *testing.T) { + dir, err := i.Download() + if err != nil { + t.Fatalf("download failed: %v", err) + } + os.RemoveAll(dir) +}