github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/config/config_test.go (about)

     1  package config
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"reflect"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/go-playground/validator/v10"
    12  	"github.com/spf13/pflag"
    13  	"github.com/spf13/viper"
    14  	"github.com/stretchr/testify/require"
    15  	"gopkg.in/yaml.v2"
    16  
    17  	"github.com/onflow/flow-go/network/netconf"
    18  	"github.com/onflow/flow-go/utils/unittest"
    19  )
    20  
    21  // TestBindPFlags ensures configuration is bound to the pflag set as expected and configuration values are overridden when set with CLI flags.
    22  func TestBindPFlags(t *testing.T) {
    23  	t.Run("should override config values when any flag is set", func(t *testing.T) {
    24  		c := defaultConfig(t)
    25  		flags := testFlagSet(c)
    26  		err := flags.Set("networking-connection-pruning", "false")
    27  		require.NoError(t, err)
    28  		require.NoError(t, flags.Parse(nil))
    29  
    30  		configFileUsed, err := BindPFlags(c, flags)
    31  		require.NoError(t, err)
    32  		require.False(t, configFileUsed)
    33  		require.False(t, c.NetworkConfig.NetworkConnectionPruning)
    34  	})
    35  	t.Run("should return an error if flags are not parsed", func(t *testing.T) {
    36  		c := defaultConfig(t)
    37  		flags := testFlagSet(c)
    38  		configFileUsed, err := BindPFlags(&FlowConfig{}, flags)
    39  		require.False(t, configFileUsed)
    40  		require.Error(t, err)
    41  		require.True(t, errors.Is(err, errPflagsNotParsed))
    42  	})
    43  }
    44  
    45  // TestDefaultConfig ensures the default Flow config is created and returned without errors.
    46  func TestDefaultConfig(t *testing.T) {
    47  	c := defaultConfig(t)
    48  	require.Equalf(t, "./default-config.yml", c.ConfigFile, "expected default config file to be used")
    49  	require.NoErrorf(t, c.Validate(), "unexpected error encountered validating default config")
    50  	unittest.IdentifierFixture()
    51  }
    52  
    53  // TestFlowConfig_Validate ensures the Flow validate returns the expected number of validator.ValidationErrors when incorrect
    54  // fields are set.
    55  func TestFlowConfig_Validate(t *testing.T) {
    56  	c := defaultConfig(t)
    57  	// set invalid config values
    58  	c.NetworkConfig.Unicast.RateLimiter.MessageRateLimit = -100
    59  	c.NetworkConfig.Unicast.RateLimiter.BandwidthRateLimit = -100
    60  	err := c.Validate()
    61  	require.Error(t, err)
    62  	errs, ok := errors.Unwrap(err).(validator.ValidationErrors)
    63  	require.True(t, ok)
    64  	require.Len(t, errs, 2)
    65  }
    66  
    67  // TestUnmarshall_UnsetFields ensures that if the config store has any missing config values an error is returned when the config is decoded into a Flow config.
    68  func TestUnmarshall_UnsetFields(t *testing.T) {
    69  	conf = viper.New()
    70  	c := &FlowConfig{}
    71  	err := Unmarshall(c)
    72  	require.True(t, strings.Contains(err.Error(), "has unset fields"))
    73  }
    74  
    75  // Test_overrideConfigFile ensures configuration values can be overridden via the --config-file flag.
    76  func Test_overrideConfigFile(t *testing.T) {
    77  	t.Run("should override the default config if --config-file is set", func(t *testing.T) {
    78  		file, err := os.CreateTemp("", "config-*.yml")
    79  		require.NoError(t, err)
    80  		defer os.Remove(file.Name())
    81  
    82  		var data = fmt.Sprintf(`config-file: "%s"
    83  network-config:
    84   networking-connection-pruning: false
    85  `, file.Name())
    86  		_, err = file.Write([]byte(data))
    87  		require.NoError(t, err)
    88  		c := defaultConfig(t)
    89  		flags := testFlagSet(c)
    90  		err = flags.Set(configFileFlagName, file.Name())
    91  
    92  		require.NoError(t, err)
    93  		overridden, err := overrideConfigFile(flags)
    94  		require.NoError(t, err)
    95  		require.True(t, overridden)
    96  
    97  		// ensure config values overridden with values from our inline config
    98  		require.Equal(t, conf.GetString(configFileFlagName), file.Name())
    99  		require.False(t, conf.GetBool("networking-connection-pruning"))
   100  	})
   101  	t.Run("should return an error for missing --config file", func(t *testing.T) {
   102  		c := defaultConfig(t)
   103  		flags := testFlagSet(c)
   104  		err := flags.Set(configFileFlagName, "./missing-config.yml")
   105  		require.NoError(t, err)
   106  		overridden, err := overrideConfigFile(flags)
   107  		require.Error(t, err)
   108  		require.False(t, overridden)
   109  	})
   110  	t.Run("should not attempt to override config if --config-file is not set", func(t *testing.T) {
   111  		c := defaultConfig(t)
   112  		flags := testFlagSet(c)
   113  		overridden, err := overrideConfigFile(flags)
   114  		require.NoError(t, err)
   115  		require.False(t, overridden)
   116  	})
   117  	t.Run("should return an error for file types other than .yml", func(t *testing.T) {
   118  		file, err := os.CreateTemp("", "config-*.json")
   119  		require.NoError(t, err)
   120  		defer os.Remove(file.Name())
   121  		c := defaultConfig(t)
   122  		flags := testFlagSet(c)
   123  		err = flags.Set(configFileFlagName, file.Name())
   124  		require.NoError(t, err)
   125  		overridden, err := overrideConfigFile(flags)
   126  		require.Error(t, err)
   127  		require.False(t, overridden)
   128  	})
   129  }
   130  
   131  // defaultConfig resets the config store gets the default Flow config.
   132  func defaultConfig(t *testing.T) *FlowConfig {
   133  	initialize()
   134  	c, err := DefaultConfig()
   135  	require.NoError(t, err)
   136  	return c
   137  }
   138  
   139  func testFlagSet(c *FlowConfig) *pflag.FlagSet {
   140  	flags := pflag.NewFlagSet("test", pflag.PanicOnError)
   141  	// initialize default flags
   142  	InitializePFlagSet(flags, c)
   143  	return flags
   144  }
   145  
   146  // getAllYAMLKeys is a helper function that recursively extracts all keys from the YAML data.
   147  // The keys are returned in the format "prefix-key1-key2-...-keyN".
   148  // For example, if the YAML data is:
   149  //
   150  //	key1:
   151  //	  key2:
   152  //	    key3: value
   153  //	key4:
   154  //	  key5: value
   155  //
   156  // the function will return ["key1-key2-key3", "key4-key5"].
   157  // Args:
   158  // - data: the YAML data.
   159  // - prefix: the prefix to prepend to the keys.
   160  // Returns:
   161  // - the list of keys extracted from the YAML data.
   162  func getAllYAMLKeys(data interface{}, prefix string) []string {
   163  	var keys []string
   164  
   165  	switch v := data.(type) {
   166  	case map[interface{}]interface{}:
   167  		for key, value := range v {
   168  			fullKey := prefix + "-" + key.(string)
   169  			keys = append(keys, getAllYAMLKeys(value, fullKey)...)
   170  		}
   171  	case []interface{}:
   172  		for i, value := range v {
   173  			fullKey := prefix + "-" + strings.ToLower(strings.ReplaceAll(reflect.TypeOf(value).Name(), "_", "-"))
   174  			keys = append(keys, getAllYAMLKeys(value, fullKey+string(rune('a'+i)))...)
   175  		}
   176  	default:
   177  		keys = append(keys, strings.TrimRight(prefix, "-"))
   178  	}
   179  
   180  	return keys
   181  }
   182  
   183  // allResourceManagerFlagNames is a helper function that returns all libp2p-resource-manager flag names from default-config.yml.
   184  func allResourceManagerFlagNames(t *testing.T) []string {
   185  	yamlFile, err := os.ReadFile("default-config.yml")
   186  	require.NoError(t, err, "failed to read YAML file")
   187  
   188  	var config map[string]interface{}
   189  	err = yaml.Unmarshal(yamlFile, &config)
   190  	require.NoError(t, err, "failed to unmarshal YAML file")
   191  
   192  	networkConfig, exists := config["network-config"].(map[interface{}]interface{})
   193  	require.True(t, exists, "the key 'network-config' does not exist in the YAML file")
   194  
   195  	resourceManagerConfig, exists := networkConfig["libp2p-resource-manager"].(map[interface{}]interface{})
   196  	require.True(t, exists, "the key 'libp2p-resource-manager' does not exist in the YAML file")
   197  
   198  	return getAllYAMLKeys(resourceManagerConfig, "libp2p-resource-manager")
   199  }
   200  
   201  // TestAllFlagNames_AllResourceManagerFlags validates that AllFlagNames returns a complete list of flag names from default-config.yml that includes all libp2p-resource-manager flags.
   202  func TestAllFlagNames_AllResourceManagerFlags(t *testing.T) {
   203  	allFlags := netconf.AllFlagNames()
   204  	for _, f := range allResourceManagerFlagNames(t) {
   205  		require.Containsf(t, allFlags, f, "the flag '%s' is missing from the list of all flags", f)
   206  	}
   207  }
   208  
   209  // TestLoadLibP2PResourceManagerFlags validates that all libp2p-resource-manager flags from default-config.yml are settable by LoadLibP2PResourceManagerFlags.
   210  func TestLoadLibP2PResourceManagerFlags(t *testing.T) {
   211  	// create an instance of Config to pass to the loader function
   212  	var config netconf.Config
   213  
   214  	// initialize the FlagSet
   215  	flags := pflag.NewFlagSet(t.Name(), pflag.ContinueOnError)
   216  
   217  	// load the flags using your function
   218  	netconf.LoadLibP2PResourceManagerFlags(flags, &config)
   219  
   220  	// retrieve all flag names
   221  	flagNames := allResourceManagerFlagNames(t)
   222  
   223  	// iterate over the flag names to ensure each one is settable
   224  	for _, flagName := range flagNames {
   225  		require.NotNil(t, flags.Lookup(flagName), "flag '%s' is not settable by LoadLibP2PResourceManagerFlags", flagName)
   226  	}
   227  }