From cb8f4c9b37df0eaf5ffa7db358a65140100d7005 Mon Sep 17 00:00:00 2001 From: nkitlabs Date: Mon, 14 Oct 2024 19:45:53 +0700 Subject: [PATCH 01/10] add logic for init config --- cmd/config.go | 8 ++++++-- falcon/app.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ falcon/config.go | 12 ++++++++++++ go.mod | 2 +- 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index 5796b01..052a8ff 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -54,8 +54,12 @@ func configInitCmd(app *falcon.App) *cobra.Command { $ %s config init --home %s $ %s cfg i`, appName, defaultHome, appName)), RunE: func(cmd *cobra.Command, args []string) error { - _ = app - return nil + home, err := cmd.Flags().GetString(flagHome) + if err != nil { + return err + } + + return app.InitConfigFile(home) }, } diff --git a/falcon/app.go b/falcon/app.go index 8509f2c..956fb05 100644 --- a/falcon/app.go +++ b/falcon/app.go @@ -2,7 +2,11 @@ package falcon import ( "context" + "fmt" + "os" + "path" + "github.com/pelletier/go-toml/v2" "github.com/spf13/viper" "go.uber.org/zap" @@ -11,6 +15,11 @@ import ( "github.com/bandprotocol/falcon/falcon/chains" ) +const ( + configFolderName = "config" + configFileName = "config.toml" +) + // App is the main application struct. type App struct { Log *zap.Logger @@ -62,6 +71,48 @@ func (a *App) LoadConfigFile(ctx context.Context) error { // InitConfigFile initializes the configuration to the given path. func (a *App) InitConfigFile(homePath string) error { + cfgDir := path.Join(homePath, configFolderName) + cfgPath := path.Join(cfgDir, configFileName) + + // check if the config file already exists + // https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go + if _, err := os.Stat(cfgPath); err == nil { + return fmt.Errorf("config already exists: %s", cfgPath) + } else if !os.IsNotExist(err) { + return err + } + + // Create the home folder if doesn't exist + if _, err := os.Stat(homePath); os.IsNotExist(err) { + if err = os.Mkdir(homePath, os.ModePerm); err != nil { + return err + } + } + + // Create the config folder if doesn't exist + if _, err := os.Stat(cfgDir); os.IsNotExist(err) { + if err = os.Mkdir(cfgDir, os.ModePerm); err != nil { + return err + } + } + + // Create the file and write the default config to the given location. + f, err := os.Create(cfgPath) + if err != nil { + return err + } + defer f.Close() + + cfg := DefaultConfig() + b, err := toml.Marshal(cfg) + if err != nil { + return err + } + + if _, err = f.Write(b); err != nil { + return err + } + return nil } diff --git a/falcon/config.go b/falcon/config.go index 76d1d67..5c32127 100644 --- a/falcon/config.go +++ b/falcon/config.go @@ -20,3 +20,15 @@ type Config struct { TargetChains []TargetChainConfig `toml:"target_chains"` CheckingPacketInterval time.Duration `toml:"checking_packet_interval"` } + +// DefaultConfig returns the default configuration. +func DefaultConfig() Config { + return Config{ + BandChainConfig: band.Config{ + RpcEndpoints: []string{"http://localhost:26657"}, + Timeout: 5, + }, + TargetChains: []TargetChainConfig{}, + CheckingPacketInterval: time.Minute, + } +} diff --git a/go.mod b/go.mod index 1cab16c..34ac799 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.3 require ( github.com/cometbft/cometbft v0.38.12 github.com/jsternberg/zap-logfmt v1.3.0 + github.com/pelletier/go-toml/v2 v2.2.2 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 go.uber.org/zap v1.27.0 @@ -16,7 +17,6 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect From 606b71e61f0fd6856c1c1b7993696dfbcbde1df0 Mon Sep 17 00:00:00 2001 From: nkitlabs Date: Mon, 14 Oct 2024 20:08:37 +0700 Subject: [PATCH 02/10] add test --- falcon/app_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 +++ 2 files changed, 53 insertions(+) create mode 100644 falcon/app_test.go diff --git a/falcon/app_test.go b/falcon/app_test.go new file mode 100644 index 0000000..762e352 --- /dev/null +++ b/falcon/app_test.go @@ -0,0 +1,50 @@ +package falcon_test + +import ( + "os" + "path" + "testing" + + "github.com/pelletier/go-toml/v2" + "github.com/stretchr/testify/require" + + "github.com/bandprotocol/falcon/falcon" +) + +func TestInitConfig(t *testing.T) { + tmpDir := t.TempDir() + + // Test InitConfig + app := falcon.NewApp(nil, nil, tmpDir, false, nil) + + err := app.InitConfigFile(tmpDir) + require.NoError(t, err) + + require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) + + // read the file + b, err := os.ReadFile(path.Join(tmpDir, "config", "config.toml")) + require.NoError(t, err) + + // unmarshal data + actual := falcon.Config{} + err = toml.Unmarshal(b, &actual) + require.NoError(t, err) + + expect := falcon.DefaultConfig() + + require.Equal(t, expect, actual) +} + +func TestInitExistingConfig(t *testing.T) { + tmpDir := t.TempDir() + + // Test InitConfig + app := falcon.NewApp(nil, nil, tmpDir, false, nil) + + err := app.InitConfigFile(tmpDir) + require.NoError(t, err) + + err = app.InitConfigFile(tmpDir) + require.ErrorContains(t, err, "config already exists:") +} diff --git a/go.mod b/go.mod index 34ac799..c054a64 100644 --- a/go.mod +++ b/go.mod @@ -8,15 +8,18 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 + github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 ) require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect From 0bf1e6332f6423ae3932152cf749dd400c93476f Mon Sep 17 00:00:00 2001 From: nkitlabs Date: Mon, 14 Oct 2024 20:10:22 +0700 Subject: [PATCH 03/10] fix test --- falcon/app_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/falcon/app_test.go b/falcon/app_test.go index 762e352..8fb3c0f 100644 --- a/falcon/app_test.go +++ b/falcon/app_test.go @@ -14,7 +14,6 @@ import ( func TestInitConfig(t *testing.T) { tmpDir := t.TempDir() - // Test InitConfig app := falcon.NewApp(nil, nil, tmpDir, false, nil) err := app.InitConfigFile(tmpDir) @@ -39,12 +38,12 @@ func TestInitConfig(t *testing.T) { func TestInitExistingConfig(t *testing.T) { tmpDir := t.TempDir() - // Test InitConfig app := falcon.NewApp(nil, nil, tmpDir, false, nil) err := app.InitConfigFile(tmpDir) require.NoError(t, err) + // second time should fail err = app.InitConfigFile(tmpDir) require.ErrorContains(t, err, "config already exists:") } From dbf889d306f40c8b6e2851ed16c8d6774c3ddce2 Mon Sep 17 00:00:00 2001 From: Tanut Lertwarachai Date: Wed, 16 Oct 2024 15:19:28 +0700 Subject: [PATCH 04/10] implement config show and custom confg --- cmd/config.go | 50 +++++++++++------- cmd/flags.go | 18 +++++++ falcon/app.go | 58 +++++++++++++++++++- falcon/app_test.go | 129 +++++++++++++++++++++++++++++++++++++++++++-- falcon/config.go | 19 ++++++- 5 files changed, 248 insertions(+), 26 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index 052a8ff..1899128 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -24,44 +24,54 @@ func configCmd(app *falcon.App) *cobra.Command { return cmd } -// configShowCmd returns the commands that prints current configuration -func configShowCmd(app *falcon.App) *cobra.Command { +// configInitCmd returns the commands that for initializing an empty config at the --home location +func configInitCmd(app *falcon.App) *cobra.Command { cmd := &cobra.Command{ - Use: "show", - Aliases: []string{"s", "list", "l"}, - Short: "Display global configuration", + Use: "init", + Aliases: []string{"i"}, + Short: "Create a default configuration at home directory path defined by --home", Args: withUsage(cobra.NoArgs), Example: strings.TrimSpace(fmt.Sprintf(` -$ %s config show --home %s -$ %s cfg s`, appName, defaultHome, appName)), +$ %s config init --home %s +$ %s cfg i`, appName, defaultHome, appName)), RunE: func(cmd *cobra.Command, args []string) error { - _ = app - return nil + home, err := cmd.Flags().GetString(flagHome) + if err != nil { + return err + } + file, err := cmd.Flags().GetString(flagFile) + if err != nil { + return err + } + return app.InitConfigFile(home, file) }, } - return cmd + return configInitFlags(app.Viper, cmd) } -// configInitCmd returns the commands that for initializing an empty config at the --home location -func configInitCmd(app *falcon.App) *cobra.Command { +// Command for printing current configuration +func configShowCmd(app *falcon.App) *cobra.Command { cmd := &cobra.Command{ - Use: "init", - Aliases: []string{"i"}, - Short: "Create a default configuration at home directory path defined by --home", + Use: "show", + Aliases: []string{"s", "list", "l"}, + Short: "Prints current configuration", Args: withUsage(cobra.NoArgs), Example: strings.TrimSpace(fmt.Sprintf(` -$ %s config init --home %s -$ %s cfg i`, appName, defaultHome, appName)), +$ %s config show --home %s +$ %s cfg list`, appName, defaultHome, appName)), RunE: func(cmd *cobra.Command, args []string) error { home, err := cmd.Flags().GetString(flagHome) if err != nil { return err } - - return app.InitConfigFile(home) + out, err := app.GetConfigFile(home) + if err != nil { + return err + } + fmt.Fprintln(cmd.OutOrStdout(), out) + return nil }, } - return cmd } diff --git a/cmd/flags.go b/cmd/flags.go index 172f95e..4362dd2 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -1,5 +1,23 @@ package cmd +import ( + "github.com/spf13/viper" + "github.com/spf13/cobra" +) const ( flagHome = "home" + flagFile = "file" ) + +func configInitFlags(v *viper.Viper, cmd *cobra.Command) *cobra.Command { + fileFlag(v, cmd) + return cmd +} + +func fileFlag(v *viper.Viper, cmd *cobra.Command) *cobra.Command { + cmd.Flags().StringP(flagFile, "f", "", "fetch toml data from specified file") + if err := v.BindPFlag(flagFile, cmd.Flags().Lookup(flagFile)); err != nil { + panic(err) + } + return cmd +} \ No newline at end of file diff --git a/falcon/app.go b/falcon/app.go index 956fb05..8fcd3bf 100644 --- a/falcon/app.go +++ b/falcon/app.go @@ -64,13 +64,39 @@ func (a *App) InitLogger(configLogLevel string) error { return nil } +func (a *App) configPath() string { + return path.Join(a.HomePath, "config", "config.toml") +} + // loadConfigFile reads config file into a.Config if file is present. func (a *App) LoadConfigFile(ctx context.Context) error { + cfgPath := a.configPath() + if _, err := os.Stat(cfgPath); err != nil { + // don't return error if file doesn't exist + return nil + } + + // read the config file bytes + file, err := os.ReadFile(cfgPath) + if err != nil { + return fmt.Errorf("error reading file: %w", err) + } + + // unmarshall them into the struct + cfg := &Config{} + err = toml.Unmarshal(file, cfg) + if err != nil { + return fmt.Errorf("error unmarshalling config: %w", err) + } + + // save configuration + a.Config = cfg + return nil } // InitConfigFile initializes the configuration to the given path. -func (a *App) InitConfigFile(homePath string) error { +func (a *App) InitConfigFile(homePath string, customFilePath string) error { cfgDir := path.Join(homePath, configFolderName) cfgPath := path.Join(cfgDir, configFileName) @@ -96,6 +122,18 @@ func (a *App) InitConfigFile(homePath string) error { } } + var cfg Config + var err error + switch { + case customFilePath != "": + cfg, err = LoadConfig(customFilePath) // Initialize with CustomConfig if file is provided + if err != nil { + return fmt.Errorf("LoadConfig file %v error %v", customFilePath, err) + } + default: + cfg = DefaultConfig() // Initialize with DefaultConfig if no file is provided + } + // Create the file and write the default config to the given location. f, err := os.Create(cfgPath) if err != nil { @@ -103,7 +141,6 @@ func (a *App) InitConfigFile(homePath string) error { } defer f.Close() - cfg := DefaultConfig() b, err := toml.Marshal(cfg) if err != nil { return err @@ -116,6 +153,23 @@ func (a *App) InitConfigFile(homePath string) error { return nil } +// Get config file from given home path. +func (a *App) GetConfigFile(homePath string) (string, error) { + cfgPath := path.Join(homePath, "config", "config.toml") + if _, err := os.Stat(cfgPath); os.IsNotExist(err) { + if _, err := os.Stat(homePath); os.IsNotExist(err) { + return "", fmt.Errorf("home path does not exist: %s", homePath) + } + return "", fmt.Errorf("config does not exist: %s", cfgPath) + } + + out, err := toml.Marshal(a.Config) + if err != nil { + return "", err + } + return string(out), nil +} + // Start starts the tunnel relayer program. func (a *App) Start(ctx context.Context, tunnelIDs []uint64) error { // initialize band client diff --git a/falcon/app_test.go b/falcon/app_test.go index 8fb3c0f..3c46e41 100644 --- a/falcon/app_test.go +++ b/falcon/app_test.go @@ -1,22 +1,26 @@ package falcon_test import ( + "fmt" "os" "path" "testing" + "time" "github.com/pelletier/go-toml/v2" "github.com/stretchr/testify/require" "github.com/bandprotocol/falcon/falcon" + "github.com/bandprotocol/falcon/falcon/band" ) func TestInitConfig(t *testing.T) { tmpDir := t.TempDir() + customCfgPath := "" app := falcon.NewApp(nil, nil, tmpDir, false, nil) - err := app.InitConfigFile(tmpDir) + err := app.InitConfigFile(tmpDir, customCfgPath) require.NoError(t, err) require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) @@ -37,13 +41,132 @@ func TestInitConfig(t *testing.T) { func TestInitExistingConfig(t *testing.T) { tmpDir := t.TempDir() + customCfgPath := "" app := falcon.NewApp(nil, nil, tmpDir, false, nil) - err := app.InitConfigFile(tmpDir) + err := app.InitConfigFile(tmpDir, customCfgPath) require.NoError(t, err) // second time should fail - err = app.InitConfigFile(tmpDir) + err = app.InitConfigFile(tmpDir, customCfgPath) require.ErrorContains(t, err, "config already exists:") } + +func TestShowEmptyConfig(t *testing.T) { + tmpDir := t.TempDir() + + app := falcon.NewApp(nil, nil, tmpDir, false, nil) + + // should not have the file + require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) + + _, err := app.GetConfigFile(tmpDir) + require.ErrorContains(t, err, "config does not exist:") +} + +func TestShowConfig(t *testing.T) { + tmpDir := t.TempDir() + customCfgPath := "" + + app := falcon.NewApp(nil, nil, tmpDir, false, nil) + + // create config file + err := app.InitConfigFile(tmpDir, customCfgPath) + require.NoError(t, err) + + require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) + + // assign config + cfg := falcon.DefaultConfig() + app.Config = &(cfg) + + // read config + cfgContent, err := app.GetConfigFile(tmpDir) + require.NoError(t, err) + + require.Contains(t, cfgContent, "target_chains = []") + require.Contains(t, cfgContent, "checking_packet_interval = 60000000000") + require.Contains(t, cfgContent, "[bandchain]") + require.Contains(t, cfgContent, "rpc_endpoints = ['http://localhost:26657']") + require.Contains(t, cfgContent, "timeout = 5") +} + +func TestLoadConfig(t *testing.T) { + tmpDir := t.TempDir() + + // custom config path + customCfgPath := path.Join(tmpDir, "custom.toml") + + // create new toml config file + cfg := ` + target_chains = [] + checking_packet_interval = 60000000000 + + [bandchain] + rpc_endpoints = ['http://localhost:26657'] + timeout = 7 + ` + + // Write the file + err := os.WriteFile(customCfgPath, []byte(cfg), 0644) + require.NoError(t, err) + + app := falcon.NewApp(nil, nil, tmpDir, false, nil) + + err = app.InitConfigFile(tmpDir, customCfgPath) + require.NoError(t, err) + + // Config file should exist + require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) + + // read the file + b, err := os.ReadFile(path.Join(tmpDir, "config", "config.toml")) + require.NoError(t, err) + + // unmarshal data + actual := falcon.Config{} + err = toml.Unmarshal(b, &actual) + require.NoError(t, err) + + expect := falcon.Config{ + BandChainConfig: band.Config{ + RpcEndpoints: []string{"http://localhost:26657"}, + Timeout: 7, + }, + TargetChains: []falcon.TargetChainConfig{}, + CheckingPacketInterval: time.Minute, + } + + require.Equal(t, expect, actual) +} + +func TestLoadWrongConfig(t *testing.T) { + tmpDir := t.TempDir() + + // custom config + customCfgPath := path.Join(tmpDir, "custom.toml") + + // create new toml config file + cfg := ` + target_chains = [] + checking_packet_interval = '60000000000' + + [bandchain] + rpc_endpoints = ['http://localhost:26657'] + timeout = 7 + ` + // Write the file + err := os.WriteFile(customCfgPath, []byte(cfg), 0644) + require.NoError(t, err) + + app := falcon.NewApp(nil, nil, tmpDir, false, nil) + + err = app.InitConfigFile(tmpDir, customCfgPath) + + // should fail when try to unmarshal + require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error toml", customCfgPath)) + + // file should not be created + require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) +} diff --git a/falcon/config.go b/falcon/config.go index 5c32127..9a53ae2 100644 --- a/falcon/config.go +++ b/falcon/config.go @@ -2,9 +2,10 @@ package falcon import ( "time" - + "os" "github.com/bandprotocol/falcon/falcon/band" "github.com/bandprotocol/falcon/falcon/chains" + "github.com/pelletier/go-toml/v2" ) // TargetChainConfig is the metadata of the configured target chain. @@ -32,3 +33,19 @@ func DefaultConfig() Config { CheckingPacketInterval: time.Minute, } } + +func LoadConfig(file string) (Config, error) { + byt, err := os.ReadFile(file) + if err != nil { + return Config{}, err + } + + // unmarshall them into struct + cfgWrapper := &Config{} + err = toml.Unmarshal(byt, cfgWrapper) + if err != nil { + return Config{}, err + } + + return *cfgWrapper, nil +} \ No newline at end of file From fa2e916f3eb7b742a31974ab27a1636d8df8e12e Mon Sep 17 00:00:00 2001 From: Tanut Lertwarachai Date: Wed, 16 Oct 2024 15:56:40 +0700 Subject: [PATCH 05/10] fix golint --- cmd/config.go | 46 +++++++++++++++++++++++----------------------- cmd/flags.go | 5 +++-- falcon/app.go | 2 +- falcon/app_test.go | 10 +++++----- falcon/config.go | 8 +++++--- 5 files changed, 37 insertions(+), 34 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index 1899128..2e1e571 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -24,54 +24,54 @@ func configCmd(app *falcon.App) *cobra.Command { return cmd } -// configInitCmd returns the commands that for initializing an empty config at the --home location -func configInitCmd(app *falcon.App) *cobra.Command { +// Command for printing current configuration +func configShowCmd(app *falcon.App) *cobra.Command { cmd := &cobra.Command{ - Use: "init", - Aliases: []string{"i"}, - Short: "Create a default configuration at home directory path defined by --home", + Use: "show", + Aliases: []string{"s", "list", "l"}, + Short: "Prints current configuration", Args: withUsage(cobra.NoArgs), Example: strings.TrimSpace(fmt.Sprintf(` -$ %s config init --home %s -$ %s cfg i`, appName, defaultHome, appName)), +$ %s config show --home %s +$ %s cfg list`, appName, defaultHome, appName)), RunE: func(cmd *cobra.Command, args []string) error { home, err := cmd.Flags().GetString(flagHome) if err != nil { return err } - file, err := cmd.Flags().GetString(flagFile) + out, err := app.GetConfigFile(home) if err != nil { return err } - return app.InitConfigFile(home, file) + fmt.Fprintln(cmd.OutOrStdout(), out) + return nil }, } - - return configInitFlags(app.Viper, cmd) + return cmd } -// Command for printing current configuration -func configShowCmd(app *falcon.App) *cobra.Command { +// configInitCmd returns the commands that for initializing an empty config at the --home location +func configInitCmd(app *falcon.App) *cobra.Command { cmd := &cobra.Command{ - Use: "show", - Aliases: []string{"s", "list", "l"}, - Short: "Prints current configuration", + Use: "init", + Aliases: []string{"i"}, + Short: "Create a default configuration at home directory path defined by --home", Args: withUsage(cobra.NoArgs), Example: strings.TrimSpace(fmt.Sprintf(` -$ %s config show --home %s -$ %s cfg list`, appName, defaultHome, appName)), +$ %s config init --home %s +$ %s cfg i`, appName, defaultHome, appName)), RunE: func(cmd *cobra.Command, args []string) error { home, err := cmd.Flags().GetString(flagHome) if err != nil { return err } - out, err := app.GetConfigFile(home) + file, err := cmd.Flags().GetString(flagFile) if err != nil { return err } - fmt.Fprintln(cmd.OutOrStdout(), out) - return nil + return app.InitConfigFile(home, file) }, } - return cmd -} + + return configInitFlags(app.Viper, cmd) +} \ No newline at end of file diff --git a/cmd/flags.go b/cmd/flags.go index 4362dd2..b52fecc 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -1,9 +1,10 @@ package cmd import ( + "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/spf13/cobra" ) + const ( flagHome = "home" flagFile = "file" @@ -20,4 +21,4 @@ func fileFlag(v *viper.Viper, cmd *cobra.Command) *cobra.Command { panic(err) } return cmd -} \ No newline at end of file +} diff --git a/falcon/app.go b/falcon/app.go index 8fcd3bf..e5cbd7d 100644 --- a/falcon/app.go +++ b/falcon/app.go @@ -162,7 +162,7 @@ func (a *App) GetConfigFile(homePath string) (string, error) { } return "", fmt.Errorf("config does not exist: %s", cfgPath) } - + out, err := toml.Marshal(a.Config) if err != nil { return "", err diff --git a/falcon/app_test.go b/falcon/app_test.go index 3c46e41..7a995ad 100644 --- a/falcon/app_test.go +++ b/falcon/app_test.go @@ -81,10 +81,10 @@ func TestShowConfig(t *testing.T) { cfg := falcon.DefaultConfig() app.Config = &(cfg) - // read config + // read config cfgContent, err := app.GetConfigFile(tmpDir) require.NoError(t, err) - + require.Contains(t, cfgContent, "target_chains = []") require.Contains(t, cfgContent, "checking_packet_interval = 60000000000") require.Contains(t, cfgContent, "[bandchain]") @@ -107,9 +107,9 @@ func TestLoadConfig(t *testing.T) { rpc_endpoints = ['http://localhost:26657'] timeout = 7 ` - + // Write the file - err := os.WriteFile(customCfgPath, []byte(cfg), 0644) + err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) require.NoError(t, err) app := falcon.NewApp(nil, nil, tmpDir, false, nil) @@ -157,7 +157,7 @@ func TestLoadWrongConfig(t *testing.T) { timeout = 7 ` // Write the file - err := os.WriteFile(customCfgPath, []byte(cfg), 0644) + err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) require.NoError(t, err) app := falcon.NewApp(nil, nil, tmpDir, false, nil) diff --git a/falcon/config.go b/falcon/config.go index 9a53ae2..1b5c0ef 100644 --- a/falcon/config.go +++ b/falcon/config.go @@ -1,11 +1,13 @@ package falcon import ( - "time" "os" + "time" + + "github.com/pelletier/go-toml/v2" + "github.com/bandprotocol/falcon/falcon/band" "github.com/bandprotocol/falcon/falcon/chains" - "github.com/pelletier/go-toml/v2" ) // TargetChainConfig is the metadata of the configured target chain. @@ -48,4 +50,4 @@ func LoadConfig(file string) (Config, error) { } return *cfgWrapper, nil -} \ No newline at end of file +} From 0d83598f61a1d3b1a7aea34d5aaa3192f38da854 Mon Sep 17 00:00:00 2001 From: Tanut Lertwarachai Date: Wed, 16 Oct 2024 16:51:27 +0700 Subject: [PATCH 06/10] add validate invalid toml field logic --- falcon/app_test.go | 48 +++++++++++++++++++++++++++++++++++++++++----- falcon/config.go | 41 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/falcon/app_test.go b/falcon/app_test.go index 7a995ad..7cfa575 100644 --- a/falcon/app_test.go +++ b/falcon/app_test.go @@ -141,21 +141,56 @@ func TestLoadConfig(t *testing.T) { require.Equal(t, expect, actual) } -func TestLoadWrongConfig(t *testing.T) { +func TestInvalidTypeLoadConfig(t *testing.T) { tmpDir := t.TempDir() // custom config customCfgPath := path.Join(tmpDir, "custom.toml") + // invalid type of value + invalidType := "'600000'" // create new toml config file - cfg := ` + cfg := fmt.Sprintf(` target_chains = [] - checking_packet_interval = '60000000000' + checking_packet_interval = %v [bandchain] rpc_endpoints = ['http://localhost:26657'] timeout = 7 - ` + `, invalidType) + + // Write the file + err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) + require.NoError(t, err) + + app := falcon.NewApp(nil, nil, tmpDir, false, nil) + + err = app.InitConfigFile(tmpDir, customCfgPath) + + // should fail when try to unmarshal + require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) + + // file should not be created + require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) +} + +func TestInvalidFieldLoadConfig(t *testing.T) { + tmpDir := t.TempDir() + + // custom config + customCfgPath := path.Join(tmpDir, "custom.toml") + // invalid field name + invalidField := "checking_packet_intervalsss" + + // create new toml config file + cfg := fmt.Sprintf(` + target_chains = [] + %v = 60000000000 + + [bandchain] + rpc_endpoints = ['http://localhost:26657'] + timeout = 7 + `, invalidField) // Write the file err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) require.NoError(t, err) @@ -164,9 +199,12 @@ func TestLoadWrongConfig(t *testing.T) { err = app.InitConfigFile(tmpDir, customCfgPath) + // should fail when try to unmarshal - require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error toml", customCfgPath)) + require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) + require.ErrorContains(t, err, fmt.Sprintf("invalid field in TOML: %v", invalidField)) // file should not be created require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) } + diff --git a/falcon/config.go b/falcon/config.go index 1b5c0ef..1a8b09c 100644 --- a/falcon/config.go +++ b/falcon/config.go @@ -1,7 +1,9 @@ package falcon import ( + "fmt" "os" + "reflect" "time" "github.com/pelletier/go-toml/v2" @@ -42,12 +44,49 @@ func LoadConfig(file string) (Config, error) { return Config{}, err } - // unmarshall them into struct + // unmarshall them with Config into struct cfgWrapper := &Config{} err = toml.Unmarshal(byt, cfgWrapper) if err != nil { return Config{}, err } + // unmarshall them with raw map[string] into struct + var rawData map[string]interface{} + err = toml.Unmarshal(byt, &rawData) + if err != nil { + return Config{}, err + } + + // validate if there is invalid field in toml file + err = validateConfigFields(rawData, *cfgWrapper) + if err != nil { + return Config{}, err + } + return *cfgWrapper, nil } + +// Function to validate invalid fields in the TOML +func validateConfigFields(rawData map[string]interface{}, cfg Config) error { + // Use reflection to get the struct field names + expectedFields := make(map[string]bool) + val := reflect.ValueOf(cfg) + typ := val.Type() + + // Build a set of expected field names from the struct tags + for i := 0; i < val.NumField(); i++ { + tag := typ.Field(i).Tag.Get("toml") + if tag != "" { + expectedFields[tag] = true + } + } + + // Compare the map keys (raw TOML fields) with the expected field names + for field := range rawData { + if !expectedFields[field] { + return fmt.Errorf("invalid field in TOML: %s", field) + } + } + return nil +} From 7006d6887657ff16a6b33f8dcf5e7347de825e27 Mon Sep 17 00:00:00 2001 From: Tanut Lertwarachai Date: Wed, 16 Oct 2024 17:01:54 +0700 Subject: [PATCH 07/10] fix golint --- cmd/config.go | 2 +- falcon/app_test.go | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index 2e1e571..9e2fc14 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -74,4 +74,4 @@ $ %s cfg i`, appName, defaultHome, appName)), } return configInitFlags(app.Viper, cmd) -} \ No newline at end of file +} diff --git a/falcon/app_test.go b/falcon/app_test.go index 7cfa575..d4a1b8d 100644 --- a/falcon/app_test.go +++ b/falcon/app_test.go @@ -199,7 +199,6 @@ func TestInvalidFieldLoadConfig(t *testing.T) { err = app.InitConfigFile(tmpDir, customCfgPath) - // should fail when try to unmarshal require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) require.ErrorContains(t, err, fmt.Sprintf("invalid field in TOML: %v", invalidField)) @@ -207,4 +206,3 @@ func TestInvalidFieldLoadConfig(t *testing.T) { // file should not be created require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) } - From ee89bbd6026cecf22356037da9a222f36f7c19d2 Mon Sep 17 00:00:00 2001 From: Tanut Lertwarachai Date: Fri, 18 Oct 2024 11:36:57 +0700 Subject: [PATCH 08/10] Fix loadconfig and test --- cmd/config.go | 21 +-- cmd/flags.go | 18 --- falcon/app.go | 55 ++++---- falcon/app_test.go | 254 +++++++++++++++++++++--------------- falcon/config.go | 54 +------- falcon/config_test.go | 50 +++++++ falcon/falcontest/system.go | 59 +++++++++ 7 files changed, 301 insertions(+), 210 deletions(-) create mode 100644 falcon/config_test.go create mode 100644 falcon/falcontest/system.go diff --git a/cmd/config.go b/cmd/config.go index 9e2fc14..e14487a 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/pelletier/go-toml/v2" "github.com/spf13/cobra" "github.com/bandprotocol/falcon/falcon" @@ -24,7 +25,7 @@ func configCmd(app *falcon.App) *cobra.Command { return cmd } -// Command for printing current configuration +// configShowCmd returns the commands that prints current configuration func configShowCmd(app *falcon.App) *cobra.Command { cmd := &cobra.Command{ Use: "show", @@ -35,15 +36,17 @@ func configShowCmd(app *falcon.App) *cobra.Command { $ %s config show --home %s $ %s cfg list`, appName, defaultHome, appName)), RunE: func(cmd *cobra.Command, args []string) error { - home, err := cmd.Flags().GetString(flagHome) - if err != nil { - return err + cfg := app.Config + if cfg == nil { + return fmt.Errorf("config does not exist: %s", app.HomePath) } - out, err := app.GetConfigFile(home) + + b, err := toml.Marshal(cfg) if err != nil { return err } - fmt.Fprintln(cmd.OutOrStdout(), out) + + fmt.Fprintln(cmd.OutOrStdout(), string(b)) return nil }, } @@ -65,13 +68,15 @@ $ %s cfg i`, appName, defaultHome, appName)), if err != nil { return err } + file, err := cmd.Flags().GetString(flagFile) if err != nil { return err } + return app.InitConfigFile(home, file) }, } - - return configInitFlags(app.Viper, cmd) + cmd.Flags().StringP(flagFile, "f", "", "fetch toml data from specified file") + return cmd } diff --git a/cmd/flags.go b/cmd/flags.go index b52fecc..ea4d1ca 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -1,24 +1,6 @@ package cmd -import ( - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - const ( flagHome = "home" flagFile = "file" ) - -func configInitFlags(v *viper.Viper, cmd *cobra.Command) *cobra.Command { - fileFlag(v, cmd) - return cmd -} - -func fileFlag(v *viper.Viper, cmd *cobra.Command) *cobra.Command { - cmd.Flags().StringP(flagFile, "f", "", "fetch toml data from specified file") - if err := v.BindPFlag(flagFile, cmd.Flags().Lookup(flagFile)); err != nil { - panic(err) - } - return cmd -} diff --git a/falcon/app.go b/falcon/app.go index e5cbd7d..462a8b2 100644 --- a/falcon/app.go +++ b/falcon/app.go @@ -64,29 +64,18 @@ func (a *App) InitLogger(configLogLevel string) error { return nil } -func (a *App) configPath() string { - return path.Join(a.HomePath, "config", "config.toml") -} - // loadConfigFile reads config file into a.Config if file is present. func (a *App) LoadConfigFile(ctx context.Context) error { - cfgPath := a.configPath() + cfgPath := path.Join(a.HomePath, configFolderName, configFileName) if _, err := os.Stat(cfgPath); err != nil { // don't return error if file doesn't exist return nil } - // read the config file bytes - file, err := os.ReadFile(cfgPath) - if err != nil { - return fmt.Errorf("error reading file: %w", err) - } - - // unmarshall them into the struct - cfg := &Config{} - err = toml.Unmarshal(file, cfg) + // read the config from config path + cfg, err := LoadConfig(cfgPath) if err != nil { - return fmt.Errorf("error unmarshalling config: %w", err) + return err } // save configuration @@ -108,6 +97,25 @@ func (a *App) InitConfigFile(homePath string, customFilePath string) error { return err } + // Load config from given custom file path if exists + var cfg *Config + var err error + switch { + case customFilePath != "": + cfg, err = LoadConfig(customFilePath) // Initialize with CustomConfig if file is provided + if err != nil { + return fmt.Errorf("LoadConfig file %v error %v", customFilePath, err) + } + default: + cfg = DefaultConfig() // Initialize with DefaultConfig if no file is provided + } + + // Marshal config object into bytes + b, err := toml.Marshal(cfg) + if err != nil { + return err + } + // Create the home folder if doesn't exist if _, err := os.Stat(homePath); os.IsNotExist(err) { if err = os.Mkdir(homePath, os.ModePerm); err != nil { @@ -122,18 +130,6 @@ func (a *App) InitConfigFile(homePath string, customFilePath string) error { } } - var cfg Config - var err error - switch { - case customFilePath != "": - cfg, err = LoadConfig(customFilePath) // Initialize with CustomConfig if file is provided - if err != nil { - return fmt.Errorf("LoadConfig file %v error %v", customFilePath, err) - } - default: - cfg = DefaultConfig() // Initialize with DefaultConfig if no file is provided - } - // Create the file and write the default config to the given location. f, err := os.Create(cfgPath) if err != nil { @@ -141,11 +137,6 @@ func (a *App) InitConfigFile(homePath string, customFilePath string) error { } defer f.Close() - b, err := toml.Marshal(cfg) - if err != nil { - return err - } - if _, err = f.Write(b); err != nil { return err } diff --git a/falcon/app_test.go b/falcon/app_test.go index d4a1b8d..fb20c65 100644 --- a/falcon/app_test.go +++ b/falcon/app_test.go @@ -1,7 +1,6 @@ package falcon_test import ( - "fmt" "os" "path" "testing" @@ -30,13 +29,13 @@ func TestInitConfig(t *testing.T) { require.NoError(t, err) // unmarshal data - actual := falcon.Config{} - err = toml.Unmarshal(b, &actual) + actual := &falcon.Config{} + err = toml.Unmarshal(b, actual) require.NoError(t, err) expect := falcon.DefaultConfig() - require.Equal(t, expect, actual) + require.Equal(t, *expect, *actual) } func TestInitExistingConfig(t *testing.T) { @@ -53,62 +52,20 @@ func TestInitExistingConfig(t *testing.T) { require.ErrorContains(t, err, "config already exists:") } -func TestShowEmptyConfig(t *testing.T) { - tmpDir := t.TempDir() - - app := falcon.NewApp(nil, nil, tmpDir, false, nil) - - // should not have the file - require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) - - _, err := app.GetConfigFile(tmpDir) - require.ErrorContains(t, err, "config does not exist:") -} - -func TestShowConfig(t *testing.T) { +func TestInitCustomConfig(t *testing.T) { tmpDir := t.TempDir() - customCfgPath := "" - - app := falcon.NewApp(nil, nil, tmpDir, false, nil) - - // create config file - err := app.InitConfigFile(tmpDir, customCfgPath) - require.NoError(t, err) - - require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) - - // assign config - cfg := falcon.DefaultConfig() - app.Config = &(cfg) - - // read config - cfgContent, err := app.GetConfigFile(tmpDir) - require.NoError(t, err) - - require.Contains(t, cfgContent, "target_chains = []") - require.Contains(t, cfgContent, "checking_packet_interval = 60000000000") - require.Contains(t, cfgContent, "[bandchain]") - require.Contains(t, cfgContent, "rpc_endpoints = ['http://localhost:26657']") - require.Contains(t, cfgContent, "timeout = 5") -} - -func TestLoadConfig(t *testing.T) { - tmpDir := t.TempDir() - - // custom config path customCfgPath := path.Join(tmpDir, "custom.toml") - // create new toml config file + // Create custom config file cfg := ` target_chains = [] checking_packet_interval = 60000000000 [bandchain] - rpc_endpoints = ['http://localhost:26657'] - timeout = 7 + rpc_endpoints = ['http://localhost:26659'] + timeout = 50 ` - - // Write the file + // write file err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) require.NoError(t, err) @@ -117,7 +74,6 @@ func TestLoadConfig(t *testing.T) { err = app.InitConfigFile(tmpDir, customCfgPath) require.NoError(t, err) - // Config file should exist require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) // read the file @@ -131,8 +87,8 @@ func TestLoadConfig(t *testing.T) { expect := falcon.Config{ BandChainConfig: band.Config{ - RpcEndpoints: []string{"http://localhost:26657"}, - Timeout: 7, + RpcEndpoints: []string{"http://localhost:26659"}, + Timeout: 50, }, TargetChains: []falcon.TargetChainConfig{}, CheckingPacketInterval: time.Minute, @@ -141,68 +97,156 @@ func TestLoadConfig(t *testing.T) { require.Equal(t, expect, actual) } -func TestInvalidTypeLoadConfig(t *testing.T) { - tmpDir := t.TempDir() +// func TestShowEmptyConfig(t *testing.T) { +// tmpDir := t.TempDir() - // custom config - customCfgPath := path.Join(tmpDir, "custom.toml") - // invalid type of value - invalidType := "'600000'" +// app := falcon.NewApp(nil, nil, tmpDir, false, nil) - // create new toml config file - cfg := fmt.Sprintf(` - target_chains = [] - checking_packet_interval = %v - - [bandchain] - rpc_endpoints = ['http://localhost:26657'] - timeout = 7 - `, invalidType) +// // should not have the file +// require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) - // Write the file - err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) - require.NoError(t, err) +// _, err := app.GetConfigFile(tmpDir) +// require.ErrorContains(t, err, "config does not exist:") +// } - app := falcon.NewApp(nil, nil, tmpDir, false, nil) +// func TestShowConfig(t *testing.T) { +// tmpDir := t.TempDir() +// customCfgPath := "" - err = app.InitConfigFile(tmpDir, customCfgPath) +// app := falcon.NewApp(nil, nil, tmpDir, false, nil) - // should fail when try to unmarshal - require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) +// // create config file +// err := app.InitConfigFile(tmpDir, customCfgPath) +// require.NoError(t, err) - // file should not be created - require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) -} +// require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) -func TestInvalidFieldLoadConfig(t *testing.T) { - tmpDir := t.TempDir() +// // assign config +// cfg := falcon.DefaultConfig() +// app.Config = &(cfg) - // custom config - customCfgPath := path.Join(tmpDir, "custom.toml") - // invalid field name - invalidField := "checking_packet_intervalsss" +// // read config +// cfgContent, err := app.GetConfigFile(tmpDir) +// require.NoError(t, err) - // create new toml config file - cfg := fmt.Sprintf(` - target_chains = [] - %v = 60000000000 - - [bandchain] - rpc_endpoints = ['http://localhost:26657'] - timeout = 7 - `, invalidField) - // Write the file - err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) - require.NoError(t, err) +// require.Contains(t, cfgContent, "target_chains = []") +// require.Contains(t, cfgContent, "checking_packet_interval = 60000000000") +// require.Contains(t, cfgContent, "[bandchain]") +// require.Contains(t, cfgContent, "rpc_endpoints = ['http://localhost:26657']") +// require.Contains(t, cfgContent, "timeout = 5") +// } - app := falcon.NewApp(nil, nil, tmpDir, false, nil) +// func TestLoadConfig(t *testing.T) { +// tmpDir := t.TempDir() - err = app.InitConfigFile(tmpDir, customCfgPath) +// // custom config path +// customCfgPath := path.Join(tmpDir, "custom.toml") - // should fail when try to unmarshal - require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) - require.ErrorContains(t, err, fmt.Sprintf("invalid field in TOML: %v", invalidField)) +// // create new toml config file +// cfg := ` +// target_chains = [] +// checking_packet_interval = 60000000000 - // file should not be created - require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) -} +// [bandchain] +// rpc_endpoints = ['http://localhost:26657'] +// timeout = 7 +// ` + +// // Write the file +// err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) +// require.NoError(t, err) + +// app := falcon.NewApp(nil, nil, tmpDir, false, nil) + +// err = app.InitConfigFile(tmpDir, customCfgPath) +// require.NoError(t, err) + +// // Config file should exist +// require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) + +// // read the file +// b, err := os.ReadFile(path.Join(tmpDir, "config", "config.toml")) +// require.NoError(t, err) + +// // unmarshal data +// actual := falcon.Config{} +// err = toml.Unmarshal(b, &actual) +// require.NoError(t, err) + +// expect := falcon.Config{ +// BandChainConfig: band.Config{ +// RpcEndpoints: []string{"http://localhost:26657"}, +// Timeout: 7, +// }, +// TargetChains: []falcon.TargetChainConfig{}, +// CheckingPacketInterval: time.Minute, +// } + +// require.Equal(t, expect, actual) +// } + +// func TestInvalidTypeLoadConfig(t *testing.T) { +// tmpDir := t.TempDir() + +// // custom config +// customCfgPath := path.Join(tmpDir, "custom.toml") +// // invalid type of value +// invalidType := "'600000'" + +// // create new toml config file +// cfg := fmt.Sprintf(` +// target_chains = [] +// checking_packet_interval = %v + +// [bandchain] +// rpc_endpoints = ['http://localhost:26657'] +// timeout = 7 +// `, invalidType) + +// // Write the file +// err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) +// require.NoError(t, err) + +// app := falcon.NewApp(nil, nil, tmpDir, false, nil) + +// err = app.InitConfigFile(tmpDir, customCfgPath) + +// // should fail when try to unmarshal +// require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) + +// // file should not be created +// require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) +// } + +// func TestInvalidFieldLoadConfig(t *testing.T) { +// tmpDir := t.TempDir() + +// // custom config +// customCfgPath := path.Join(tmpDir, "custom.toml") +// // invalid field name +// invalidField := "checking_packet_intervalsss" + +// // create new toml config file +// cfg := fmt.Sprintf(` +// target_chains = [] +// %v = 60000000000 + +// [bandchain] +// rpc_endpoints = ['http://localhost:26657'] +// timeout = 7 +// `, invalidField) +// // Write the file +// err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) +// require.NoError(t, err) + +// app := falcon.NewApp(nil, nil, tmpDir, false, nil) + +// err = app.InitConfigFile(tmpDir, customCfgPath) + +// // should fail when try to unmarshal +// require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) +// require.ErrorContains(t, err, fmt.Sprintf("invalid field in TOML: %v", invalidField)) + +// // file should not be created +// require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) +// } diff --git a/falcon/config.go b/falcon/config.go index 1a8b09c..db54069 100644 --- a/falcon/config.go +++ b/falcon/config.go @@ -1,9 +1,7 @@ package falcon import ( - "fmt" "os" - "reflect" "time" "github.com/pelletier/go-toml/v2" @@ -27,8 +25,8 @@ type Config struct { } // DefaultConfig returns the default configuration. -func DefaultConfig() Config { - return Config{ +func DefaultConfig() *Config { + return &Config{ BandChainConfig: band.Config{ RpcEndpoints: []string{"http://localhost:26657"}, Timeout: 5, @@ -38,55 +36,17 @@ func DefaultConfig() Config { } } -func LoadConfig(file string) (Config, error) { - byt, err := os.ReadFile(file) +func LoadConfig(cfgPath string) (*Config, error) { + byt, err := os.ReadFile(cfgPath) if err != nil { - return Config{}, err + return &Config{}, err } // unmarshall them with Config into struct cfgWrapper := &Config{} err = toml.Unmarshal(byt, cfgWrapper) if err != nil { - return Config{}, err + return &Config{}, err } - - // unmarshall them with raw map[string] into struct - var rawData map[string]interface{} - err = toml.Unmarshal(byt, &rawData) - if err != nil { - return Config{}, err - } - - // validate if there is invalid field in toml file - err = validateConfigFields(rawData, *cfgWrapper) - if err != nil { - return Config{}, err - } - - return *cfgWrapper, nil -} - -// Function to validate invalid fields in the TOML -func validateConfigFields(rawData map[string]interface{}, cfg Config) error { - // Use reflection to get the struct field names - expectedFields := make(map[string]bool) - val := reflect.ValueOf(cfg) - typ := val.Type() - - // Build a set of expected field names from the struct tags - for i := 0; i < val.NumField(); i++ { - tag := typ.Field(i).Tag.Get("toml") - if tag != "" { - expectedFields[tag] = true - } - } - - // Compare the map keys (raw TOML fields) with the expected field names - for field := range rawData { - if !expectedFields[field] { - return fmt.Errorf("invalid field in TOML: %s", field) - } - } - return nil + return cfgWrapper, nil } diff --git a/falcon/config_test.go b/falcon/config_test.go new file mode 100644 index 0000000..58efd40 --- /dev/null +++ b/falcon/config_test.go @@ -0,0 +1,50 @@ +package falcon_test + +import ( + "path" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/bandprotocol/falcon/falcon" + falcon_test "github.com/bandprotocol/falcon/falcon/falcontest" +) + +func TestShowConfig(t *testing.T) { + sys := falcon_test.NewSystem(t) + + res := sys.RunWithInput(t, "config", "init") + require.NoError(t, res.Err) + + res = sys.RunWithInput(t, "config", "show") + require.NoError(t, res.Err) + + actual := res.Stdout.String() + expect := "target_chains = []\nchecking_packet_interval = 60000000000\n\n[bandchain]\nrpc_endpoints = ['http://localhost:26657']\ntimeout = 5\n\n" + + require.Equal(t, expect, actual) +} + +func TestShowEmptyConfig(t *testing.T) { + sys := falcon_test.NewSystem(t) + + res := sys.RunWithInput(t, "config", "show") + require.ErrorContains(t, res.Err, "config does not exist:") +} + +func TestLoadConfig(t *testing.T) { + tmpDir := t.TempDir() + customConfigPath := "" + cfgPath := path.Join(tmpDir, "config", "config.toml") + + app := falcon.NewApp(nil, nil, tmpDir, false, nil) + + // Prepare config before test + err := app.InitConfigFile(tmpDir, customConfigPath) + require.NoError(t, err) + + actual, err := falcon.LoadConfig(cfgPath) + require.NoError(t, err) + expect := falcon.DefaultConfig() + require.Equal(t, expect, actual) +} diff --git a/falcon/falcontest/system.go b/falcon/falcontest/system.go new file mode 100644 index 0000000..2fd27ed --- /dev/null +++ b/falcon/falcontest/system.go @@ -0,0 +1,59 @@ +package falcon_test + +import ( + "bytes" + "context" + "testing" + + "go.uber.org/zap/zaptest" + + "github.com/bandprotocol/falcon/cmd" +) + +// System is a system under test. +type System struct { + // Temporary directory to be injected as --home argument. + HomeDir string +} + +// NewSystem creates a new system with a home dir associated with a temp dir belonging to t. +// +// The returned System does not store a reference to t; +// some of its methods expect a *testing.T as an argument. +// This allows creating one instance of System to be shared with subtests. +func NewSystem(t *testing.T) *System { + t.Helper() + + homeDir := t.TempDir() + + return &System{ + HomeDir: homeDir, + } +} + +// RunResult is the stdout and stderr resulting from a call to (*System).Run, +// and any error that was returned. +type RunResult struct { + Stdout, Stderr bytes.Buffer + + Err error +} + +func (s *System) RunWithInput(t *testing.T, args ...string) RunResult { + rootCmd := cmd.NewRootCmd(zaptest.NewLogger(t)) + + rootCmd.SilenceUsage = true + + ctx := context.Background() + + var res RunResult + rootCmd.SetOut(&res.Stdout) + rootCmd.SetErr(&res.Stderr) + + // Prepend the system's home directory to any provided args. + args = append([]string{"--home", s.HomeDir}, args...) + rootCmd.SetArgs(args) + + res.Err = rootCmd.ExecuteContext(ctx) + return res +} From 31f55df8384022bf3000d1c3702b629e4f45ed03 Mon Sep 17 00:00:00 2001 From: Tanut Lertwarachai Date: Fri, 18 Oct 2024 13:10:09 +0700 Subject: [PATCH 09/10] del comment --- cmd/config.go | 5 +- falcon/app.go | 17 ----- falcon/app_test.go | 154 --------------------------------------------- falcon/config.go | 7 ++- 4 files changed, 6 insertions(+), 177 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index e14487a..81fe06f 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -36,12 +36,11 @@ func configShowCmd(app *falcon.App) *cobra.Command { $ %s config show --home %s $ %s cfg list`, appName, defaultHome, appName)), RunE: func(cmd *cobra.Command, args []string) error { - cfg := app.Config - if cfg == nil { + if app.Config == nil { return fmt.Errorf("config does not exist: %s", app.HomePath) } - b, err := toml.Marshal(cfg) + b, err := toml.Marshal(app.Config) if err != nil { return err } diff --git a/falcon/app.go b/falcon/app.go index 462a8b2..50d8a2c 100644 --- a/falcon/app.go +++ b/falcon/app.go @@ -144,23 +144,6 @@ func (a *App) InitConfigFile(homePath string, customFilePath string) error { return nil } -// Get config file from given home path. -func (a *App) GetConfigFile(homePath string) (string, error) { - cfgPath := path.Join(homePath, "config", "config.toml") - if _, err := os.Stat(cfgPath); os.IsNotExist(err) { - if _, err := os.Stat(homePath); os.IsNotExist(err) { - return "", fmt.Errorf("home path does not exist: %s", homePath) - } - return "", fmt.Errorf("config does not exist: %s", cfgPath) - } - - out, err := toml.Marshal(a.Config) - if err != nil { - return "", err - } - return string(out), nil -} - // Start starts the tunnel relayer program. func (a *App) Start(ctx context.Context, tunnelIDs []uint64) error { // initialize band client diff --git a/falcon/app_test.go b/falcon/app_test.go index fb20c65..89fe81a 100644 --- a/falcon/app_test.go +++ b/falcon/app_test.go @@ -96,157 +96,3 @@ func TestInitCustomConfig(t *testing.T) { require.Equal(t, expect, actual) } - -// func TestShowEmptyConfig(t *testing.T) { -// tmpDir := t.TempDir() - -// app := falcon.NewApp(nil, nil, tmpDir, false, nil) - -// // should not have the file -// require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) - -// _, err := app.GetConfigFile(tmpDir) -// require.ErrorContains(t, err, "config does not exist:") -// } - -// func TestShowConfig(t *testing.T) { -// tmpDir := t.TempDir() -// customCfgPath := "" - -// app := falcon.NewApp(nil, nil, tmpDir, false, nil) - -// // create config file -// err := app.InitConfigFile(tmpDir, customCfgPath) -// require.NoError(t, err) - -// require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) - -// // assign config -// cfg := falcon.DefaultConfig() -// app.Config = &(cfg) - -// // read config -// cfgContent, err := app.GetConfigFile(tmpDir) -// require.NoError(t, err) - -// require.Contains(t, cfgContent, "target_chains = []") -// require.Contains(t, cfgContent, "checking_packet_interval = 60000000000") -// require.Contains(t, cfgContent, "[bandchain]") -// require.Contains(t, cfgContent, "rpc_endpoints = ['http://localhost:26657']") -// require.Contains(t, cfgContent, "timeout = 5") -// } - -// func TestLoadConfig(t *testing.T) { -// tmpDir := t.TempDir() - -// // custom config path -// customCfgPath := path.Join(tmpDir, "custom.toml") - -// // create new toml config file -// cfg := ` -// target_chains = [] -// checking_packet_interval = 60000000000 - -// [bandchain] -// rpc_endpoints = ['http://localhost:26657'] -// timeout = 7 -// ` - -// // Write the file -// err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) -// require.NoError(t, err) - -// app := falcon.NewApp(nil, nil, tmpDir, false, nil) - -// err = app.InitConfigFile(tmpDir, customCfgPath) -// require.NoError(t, err) - -// // Config file should exist -// require.FileExists(t, path.Join(tmpDir, "config", "config.toml")) - -// // read the file -// b, err := os.ReadFile(path.Join(tmpDir, "config", "config.toml")) -// require.NoError(t, err) - -// // unmarshal data -// actual := falcon.Config{} -// err = toml.Unmarshal(b, &actual) -// require.NoError(t, err) - -// expect := falcon.Config{ -// BandChainConfig: band.Config{ -// RpcEndpoints: []string{"http://localhost:26657"}, -// Timeout: 7, -// }, -// TargetChains: []falcon.TargetChainConfig{}, -// CheckingPacketInterval: time.Minute, -// } - -// require.Equal(t, expect, actual) -// } - -// func TestInvalidTypeLoadConfig(t *testing.T) { -// tmpDir := t.TempDir() - -// // custom config -// customCfgPath := path.Join(tmpDir, "custom.toml") -// // invalid type of value -// invalidType := "'600000'" - -// // create new toml config file -// cfg := fmt.Sprintf(` -// target_chains = [] -// checking_packet_interval = %v - -// [bandchain] -// rpc_endpoints = ['http://localhost:26657'] -// timeout = 7 -// `, invalidType) - -// // Write the file -// err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) -// require.NoError(t, err) - -// app := falcon.NewApp(nil, nil, tmpDir, false, nil) - -// err = app.InitConfigFile(tmpDir, customCfgPath) - -// // should fail when try to unmarshal -// require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) - -// // file should not be created -// require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) -// } - -// func TestInvalidFieldLoadConfig(t *testing.T) { -// tmpDir := t.TempDir() - -// // custom config -// customCfgPath := path.Join(tmpDir, "custom.toml") -// // invalid field name -// invalidField := "checking_packet_intervalsss" - -// // create new toml config file -// cfg := fmt.Sprintf(` -// target_chains = [] -// %v = 60000000000 - -// [bandchain] -// rpc_endpoints = ['http://localhost:26657'] -// timeout = 7 -// `, invalidField) -// // Write the file -// err := os.WriteFile(customCfgPath, []byte(cfg), 0o600) -// require.NoError(t, err) - -// app := falcon.NewApp(nil, nil, tmpDir, false, nil) - -// err = app.InitConfigFile(tmpDir, customCfgPath) - -// // should fail when try to unmarshal -// require.ErrorContains(t, err, fmt.Sprintf("LoadConfig file %v error", customCfgPath)) -// require.ErrorContains(t, err, fmt.Sprintf("invalid field in TOML: %v", invalidField)) - -// // file should not be created -// require.NoFileExists(t, path.Join(tmpDir, "config", "config.toml")) -// } diff --git a/falcon/config.go b/falcon/config.go index db54069..9564d21 100644 --- a/falcon/config.go +++ b/falcon/config.go @@ -36,6 +36,7 @@ func DefaultConfig() *Config { } } +// Read config file from given path and return config object func LoadConfig(cfgPath string) (*Config, error) { byt, err := os.ReadFile(cfgPath) if err != nil { @@ -43,10 +44,10 @@ func LoadConfig(cfgPath string) (*Config, error) { } // unmarshall them with Config into struct - cfgWrapper := &Config{} - err = toml.Unmarshal(byt, cfgWrapper) + cfg := &Config{} + err = toml.Unmarshal(byt, cfg) if err != nil { return &Config{}, err } - return cfgWrapper, nil + return cfg, nil } From c2aa43f677e81468c506ea3dbe97e71c1ca8d9e9 Mon Sep 17 00:00:00 2001 From: Tanut Lertwarachai Date: Fri, 18 Oct 2024 13:14:57 +0700 Subject: [PATCH 10/10] fix comment --- falcon/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/falcon/config.go b/falcon/config.go index 9564d21..4d584b2 100644 --- a/falcon/config.go +++ b/falcon/config.go @@ -36,7 +36,7 @@ func DefaultConfig() *Config { } } -// Read config file from given path and return config object +// LoadConfig reads config file from given path and return config object func LoadConfig(cfgPath string) (*Config, error) { byt, err := os.ReadFile(cfgPath) if err != nil {