package config import ( "context" "fmt" "github.com/Masterminds/semver" "github.com/spf13/afero" "os" filepath2 "path/filepath" "strconv" "strings" "testing" "github.com/cucumber/godog" "github.com/cucumber/godog/colors" "github.com/spf13/pflag" a "github.com/stretchr/testify/assert" ) var ( SupportedDataTypes = [4]string{"string", "integer", "float", "boolean"} assertTest *a.Assertions assertError = fmt.Errorf("assert error") opts = godog.Options{ Output: colors.Colored(os.Stdout), Format: "pretty", Paths: []string{"features"}, } originalCommandLine = os.Args ) const ( StringDefaultValue = "default value" StringConfigValue = "config file value" StringEnvironmentValue = "environment variable value" StringFlagValue = "command line value" IntegerDefaultValue = 123 IntegerConfigValue = 234 IntegerEnvironmentValue = "345" IntegerFlagValue = 456 FloatDefaultValue = 1.99 FloatConfigValue = 2.99 FloatEnvironmentValue = "3.99" FloatFlagValue = 4.99 BoolDefaultValue = false BoolConfigValue = true BoolEnvironmentValue = "false" BoolFlagValue = true ) func init() { godog.BindCommandLineFlags("godog.", &opts) configFS = afero.NewMemMapFs() } func TestCucumber(t *testing.T) { pflag.Parse() assertTest = a.New(t) opts.Paths = pflag.Args() opts.TestingT = t suite := godog.TestSuite{ Name: "pretty", ScenarioInitializer: InitializeScenario, TestSuiteInitializer: InitializeTestSuite, Options: &opts, } if suite.Run() != 0 { t.Fatal("cucumber tests failed") } } func InitializeTestSuite(ctx *godog.TestSuiteContext) { ctx.BeforeSuite(func() { os.Args = []string{originalCommandLine[0]} }) ctx.AfterSuite(func() { os.Args = originalCommandLine }) } func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Given(`^an application with a name of "([^"]*)" and a prefix of "([^"]*)"$`, setAppNameAndPrefix) ctx.Given(`^a version of "([^"]*)"$`, setAppVersion) ctx.Given(`^a "([^"]*)" configuration item defines as$`, setupConfigItem) ctx.When(`^I call GetConfig$`, callGetConfig) ctx.Then(`^the "([^"]*)" result should return "([^"]*)"$`, checkConfigValue) ctx.Then(`^the app name should return "([^"]*)"$`, theAppNameShouldReturn) ctx.Then(`^the app prefix should return "([^"]*)"$`, theAppPrefixShouldReturn) ctx.Then(`^the app version should pass the following constraint: "([^"]*)"$`, theAppVersionShouldPassTheFollowingConstraint) } // Given Callbacks func setAppNameAndPrefix(ctx context.Context, name, prefix string) context.Context { SetAppName(name, prefix) return context.WithValue(ctx, "appPrefix", prefix) } func setAppVersion(_ context.Context, versionString string) { SetAppVersion(versionString) } func setupConfigItem(ctx context.Context, itemType string, itemDefinition *godog.Table) (context.Context, error) { if assertTest.Containsf(SupportedDataTypes, itemType, "config items of type '%s' are not yet supported", itemType) { var ( data = make(map[string]string) name string shortFlag = "" helpText = "" fileContents = "" defVal any configVal any envVal string flagVal any ) for idx, key := range itemDefinition.Rows[0].Cells { data[key.Value] = itemDefinition.Rows[1].Cells[idx].Value } ctx = context.WithValue(ctx, "data", data) name = data["name"] switch itemType { case "string": defVal = StringDefaultValue configVal = StringConfigValue envVal = StringEnvironmentValue flagVal = StringFlagValue helpText = "String Config Item Test" case "integer": defVal = IntegerDefaultValue configVal = IntegerConfigValue envVal = IntegerEnvironmentValue flagVal = IntegerFlagValue helpText = "Integer Config Item Test" case "float": defVal = FloatDefaultValue configVal = FloatConfigValue envVal = FloatEnvironmentValue flagVal = FloatFlagValue helpText = "Float Config Item Test" case "boolean": defVal = BoolDefaultValue configVal = BoolConfigValue envVal = BoolEnvironmentValue flagVal = BoolFlagValue helpText = "Bool Config Item Test" } if data["has_file_entry"] == "true" { var dirPath = filepath2.Join("/", "etc", Config.GetString("app.prefix")) var filename = filepath2.Join(dirPath, "config.yaml") fileContents = fmt.Sprintf("---\n%s: %v\n", name, configVal) err := configFS.MkdirAll(dirPath, 0755) if !assertTest.NoError(err, "unable to create test config folder") { if !assertTest.DirExists(dirPath, "unable to create test config folder") { return ctx, assertError } } err = afero.WriteFile(configFS, filename, []byte(fileContents), 0755) if !assertTest.NoError(err, "unable to write file contents") { return ctx, fmt.Errorf(err.Error()) } } if data["has_env_var"] == "true" { if data["has_env_var"] == "true" { key := strings.ToUpper( strings.ReplaceAll( strings.ReplaceAll( fmt.Sprintf("test_%s", name), "-", "_", ), ".", "_", ), ) _ = os.Setenv(key, envVal) } } if data["commandline_flag"] != "" { os.Args = append(os.Args, fmt.Sprintf("%s=%v", data["commandline_flag"], flagVal)) tagName := strings.Trim(data["commandline_flag"], "-") if len(tagName) == 1 { shortFlag = tagName } } DefineConfigItem(name, shortFlag, defVal, helpText) return ctx, nil } else { return ctx, assertError } } // When Callbacks func callGetConfig(_ context.Context) { GetConfigs() } // Then Callbacks func checkConfigValue(ctx context.Context, itemType, expectedString string) error { var ( // key = ctx.Value("itemName").(string) data = ctx.Value("data").(map[string]string) key = data["name"] err error ) if assertTest.Containsf(SupportedDataTypes, itemType, "config items of type '%s' are not yet supported", itemType) { var actual, expected interface{} switch itemType { case "string": actual = strings.Trim(Config.GetString(key), "\"") expected = expectedString case "integer": actual = Config.GetInt64(key) expected, _ = strconv.ParseInt(expectedString, 10, 64) case "float": actual = Config.GetFloat64(key) expected, _ = strconv.ParseFloat(expectedString, 64) case "boolean": actual = Config.GetBool(key) expected, _ = strconv.ParseBool(expectedString) } if !assertTest.Equal(expected, actual, "config item should equal expected", os.Args, data) { err = assertError } } else { err = assertError } return err } func theAppNameShouldReturn(expected string) error { if !assertTest.Equal(expected, Config.GetString("app.name")) { return assertError } return nil } func theAppPrefixShouldReturn(expected string) error { if !assertTest.Equal(expected, Config.GetString("app.prefix")) { return assertError } return nil } func theAppVersionShouldPassTheFollowingConstraint(constraintString string) error { var ( constraint, _ = semver.NewConstraint(constraintString) actual = Config.Get("app.version").(*semver.Version) ) if !assertTest.True(constraint.Check(actual), "version does not pass check") { return assertError } return nil }