306 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			306 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Go
		
	
	
	
package options
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	. "github.com/onsi/ginkgo"
 | 
						|
	. "github.com/onsi/ginkgo/extensions/table"
 | 
						|
	. "github.com/onsi/gomega"
 | 
						|
	"github.com/spf13/pflag"
 | 
						|
)
 | 
						|
 | 
						|
func TestOptionsSuite(t *testing.T) {
 | 
						|
	RegisterFailHandler(Fail)
 | 
						|
	RunSpecs(t, "Options Suite")
 | 
						|
}
 | 
						|
 | 
						|
var _ = Describe("Load", func() {
 | 
						|
	Context("with a testOptions structure", func() {
 | 
						|
		type TestOptionSubStruct struct {
 | 
						|
			StringSliceOption []string `flag:"string-slice-option" cfg:"string_slice_option"`
 | 
						|
		}
 | 
						|
 | 
						|
		type TestOptions struct {
 | 
						|
			StringOption string              `flag:"string-option" cfg:"string_option"`
 | 
						|
			Sub          TestOptionSubStruct `cfg:",squash"`
 | 
						|
			// Check exported but internal fields do not break loading
 | 
						|
			Internal *string `cfg:",internal"`
 | 
						|
			// Check unexported fields do not break loading
 | 
						|
			unexported string
 | 
						|
		}
 | 
						|
 | 
						|
		type MissingSquashTestOptions struct {
 | 
						|
			StringOption string `flag:"string-option" cfg:"string_option"`
 | 
						|
			Sub          TestOptionSubStruct
 | 
						|
		}
 | 
						|
 | 
						|
		type MissingCfgTestOptions struct {
 | 
						|
			StringOption string              `flag:"string-option"`
 | 
						|
			Sub          TestOptionSubStruct `cfg:",squash"`
 | 
						|
		}
 | 
						|
 | 
						|
		type MissingFlagTestOptions struct {
 | 
						|
			StringOption string              `cfg:"string_option"`
 | 
						|
			Sub          TestOptionSubStruct `cfg:",squash"`
 | 
						|
		}
 | 
						|
 | 
						|
		var testOptionsConfigBytes = []byte(`
 | 
						|
			string_option="foo"
 | 
						|
			string_slice_option="a,b,c,d"
 | 
						|
		`)
 | 
						|
 | 
						|
		var testOptionsFlagSet *pflag.FlagSet
 | 
						|
 | 
						|
		type testOptionsTableInput struct {
 | 
						|
			env            map[string]string
 | 
						|
			args           []string
 | 
						|
			configFile     []byte
 | 
						|
			flagSet        func() *pflag.FlagSet
 | 
						|
			expectedErr    error
 | 
						|
			input          interface{}
 | 
						|
			expectedOutput interface{}
 | 
						|
		}
 | 
						|
 | 
						|
		BeforeEach(func() {
 | 
						|
			testOptionsFlagSet = pflag.NewFlagSet("testFlagSet", pflag.ExitOnError)
 | 
						|
			testOptionsFlagSet.String("string-option", "default", "")
 | 
						|
			testOptionsFlagSet.StringSlice("string-slice-option", []string{"a", "b"}, "")
 | 
						|
		})
 | 
						|
 | 
						|
		DescribeTable("Load",
 | 
						|
			func(o *testOptionsTableInput) {
 | 
						|
				var configFileName string
 | 
						|
 | 
						|
				if o.configFile != nil {
 | 
						|
					By("Creating a config file")
 | 
						|
					configFile, err := ioutil.TempFile("", "oauth2-proxy-test-legacy-config-file")
 | 
						|
					Expect(err).ToNot(HaveOccurred())
 | 
						|
					defer configFile.Close()
 | 
						|
 | 
						|
					_, err = configFile.Write(o.configFile)
 | 
						|
					Expect(err).ToNot(HaveOccurred())
 | 
						|
					defer os.Remove(configFile.Name())
 | 
						|
 | 
						|
					configFileName = configFile.Name()
 | 
						|
				}
 | 
						|
 | 
						|
				if len(o.env) > 0 {
 | 
						|
					By("Setting environment variables")
 | 
						|
					for k, v := range o.env {
 | 
						|
						os.Setenv(k, v)
 | 
						|
						defer os.Unsetenv(k)
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				Expect(o.flagSet).ToNot(BeNil())
 | 
						|
				flagSet := o.flagSet()
 | 
						|
				Expect(flagSet).ToNot(BeNil())
 | 
						|
 | 
						|
				if len(o.args) > 0 {
 | 
						|
					By("Parsing flag arguments")
 | 
						|
					Expect(flagSet.Parse(o.args)).To(Succeed())
 | 
						|
				}
 | 
						|
 | 
						|
				var input interface{}
 | 
						|
				if o.input != nil {
 | 
						|
					input = o.input
 | 
						|
				} else {
 | 
						|
					input = &TestOptions{}
 | 
						|
				}
 | 
						|
				err := Load(configFileName, flagSet, input)
 | 
						|
				if o.expectedErr != nil {
 | 
						|
					Expect(err).To(MatchError(o.expectedErr.Error()))
 | 
						|
				} else {
 | 
						|
					Expect(err).ToNot(HaveOccurred())
 | 
						|
				}
 | 
						|
				Expect(input).To(Equal(o.expectedOutput))
 | 
						|
			},
 | 
						|
			Entry("with just a config file", &testOptionsTableInput{
 | 
						|
				configFile: testOptionsConfigBytes,
 | 
						|
				flagSet:    func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "foo",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b", "c", "d"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("when setting env variables", &testOptionsTableInput{
 | 
						|
				configFile: testOptionsConfigBytes,
 | 
						|
				env: map[string]string{
 | 
						|
					"OAUTH2_PROXY_STRING_OPTION":       "bar",
 | 
						|
					"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
 | 
						|
				},
 | 
						|
				flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "bar",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b", "c"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("when setting flags", &testOptionsTableInput{
 | 
						|
				configFile: testOptionsConfigBytes,
 | 
						|
				env: map[string]string{
 | 
						|
					"OAUTH2_PROXY_STRING_OPTION":       "bar",
 | 
						|
					"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
 | 
						|
				},
 | 
						|
				args: []string{
 | 
						|
					"--string-option", "baz",
 | 
						|
					"--string-slice-option", "a,b,c,d,e",
 | 
						|
				},
 | 
						|
				flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "baz",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b", "c", "d", "e"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("when setting flags multiple times", &testOptionsTableInput{
 | 
						|
				configFile: testOptionsConfigBytes,
 | 
						|
				env: map[string]string{
 | 
						|
					"OAUTH2_PROXY_STRING_OPTION":       "bar",
 | 
						|
					"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
 | 
						|
				},
 | 
						|
				args: []string{
 | 
						|
					"--string-option", "baz",
 | 
						|
					"--string-slice-option", "x",
 | 
						|
					"--string-slice-option", "y",
 | 
						|
					"--string-slice-option", "z",
 | 
						|
				},
 | 
						|
				flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "baz",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"x", "y", "z"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("when setting env variables without a config file", &testOptionsTableInput{
 | 
						|
				env: map[string]string{
 | 
						|
					"OAUTH2_PROXY_STRING_OPTION":       "bar",
 | 
						|
					"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
 | 
						|
				},
 | 
						|
				flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "bar",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b", "c"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("when setting flags without a config file", &testOptionsTableInput{
 | 
						|
				env: map[string]string{
 | 
						|
					"OAUTH2_PROXY_STRING_OPTION":       "bar",
 | 
						|
					"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
 | 
						|
				},
 | 
						|
				args: []string{
 | 
						|
					"--string-option", "baz",
 | 
						|
					"--string-slice-option", "a,b,c,d,e",
 | 
						|
				},
 | 
						|
				flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "baz",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b", "c", "d", "e"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("when setting flags without a config file", &testOptionsTableInput{
 | 
						|
				env: map[string]string{
 | 
						|
					"OAUTH2_PROXY_STRING_OPTION":       "bar",
 | 
						|
					"OAUTH2_PROXY_STRING_SLICE_OPTION": "a,b,c",
 | 
						|
				},
 | 
						|
				args: []string{
 | 
						|
					"--string-option", "baz",
 | 
						|
					"--string-slice-option", "a,b,c,d,e",
 | 
						|
				},
 | 
						|
				flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "baz",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b", "c", "d", "e"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("when nothing is set it should use flag defaults", &testOptionsTableInput{
 | 
						|
				flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "default",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("with an invalid config file", &testOptionsTableInput{
 | 
						|
				configFile:     []byte(`slice_option = foo`),
 | 
						|
				flagSet:        func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedErr:    fmt.Errorf("unable to load config file: While parsing config: (1, 16): never reached"),
 | 
						|
				expectedOutput: &TestOptions{},
 | 
						|
			}),
 | 
						|
			Entry("with an invalid flagset", &testOptionsTableInput{
 | 
						|
				flagSet: func() *pflag.FlagSet {
 | 
						|
					// Missing a flag
 | 
						|
					f := pflag.NewFlagSet("testFlagSet", pflag.ExitOnError)
 | 
						|
					f.String("string-option", "default", "")
 | 
						|
					return f
 | 
						|
				},
 | 
						|
				expectedErr:    fmt.Errorf("unable to register flags: field \"string-slice-option\" does not have a registered flag"),
 | 
						|
				expectedOutput: &TestOptions{},
 | 
						|
			}),
 | 
						|
			Entry("with an struct is missing the squash tag", &testOptionsTableInput{
 | 
						|
				flagSet:        func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedErr:    fmt.Errorf("unable to register flags: field \".Sub\" does not have required cfg tag: `,squash`"),
 | 
						|
				input:          &MissingSquashTestOptions{},
 | 
						|
				expectedOutput: &MissingSquashTestOptions{},
 | 
						|
			}),
 | 
						|
			Entry("with a field is missing the cfg tag", &testOptionsTableInput{
 | 
						|
				flagSet:        func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedErr:    fmt.Errorf("unable to register flags: field \".StringOption\" does not have required tags (cfg, flag)"),
 | 
						|
				input:          &MissingCfgTestOptions{},
 | 
						|
				expectedOutput: &MissingCfgTestOptions{},
 | 
						|
			}),
 | 
						|
			Entry("with a field is missing the flag tag", &testOptionsTableInput{
 | 
						|
				flagSet:        func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedErr:    fmt.Errorf("unable to register flags: field \".StringOption\" does not have required tags (cfg, flag)"),
 | 
						|
				input:          &MissingFlagTestOptions{},
 | 
						|
				expectedOutput: &MissingFlagTestOptions{},
 | 
						|
			}),
 | 
						|
			Entry("with existing unexported fields", &testOptionsTableInput{
 | 
						|
				flagSet: func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				input: &TestOptions{
 | 
						|
					unexported: "unexported",
 | 
						|
				},
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "default",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b"},
 | 
						|
					},
 | 
						|
					unexported: "unexported",
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("with an unknown option in the config file", &testOptionsTableInput{
 | 
						|
				configFile:  []byte(`unknown_option="foo"`),
 | 
						|
				flagSet:     func() *pflag.FlagSet { return testOptionsFlagSet },
 | 
						|
				expectedErr: fmt.Errorf("error unmarshalling config: 1 error(s) decoding:\n\n* '' has invalid keys: unknown_option"),
 | 
						|
				// Viper will unmarshal before returning the error, so this is the default output
 | 
						|
				expectedOutput: &TestOptions{
 | 
						|
					StringOption: "default",
 | 
						|
					Sub: TestOptionSubStruct{
 | 
						|
						StringSliceOption: []string{"a", "b"},
 | 
						|
					},
 | 
						|
				},
 | 
						|
			}),
 | 
						|
			Entry("with an empty Options struct, should return default values", &testOptionsTableInput{
 | 
						|
				flagSet:        NewFlagSet,
 | 
						|
				input:          &Options{},
 | 
						|
				expectedOutput: NewOptions(),
 | 
						|
			}),
 | 
						|
		)
 | 
						|
	})
 | 
						|
})
 |