github.com/github/skeema@v1.2.6/util/config_test.go (about)

     1  package util
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/skeema/mybase"
    11  )
    12  
    13  func TestAddGlobalConfigFiles(t *testing.T) {
    14  	cmdSuite := mybase.NewCommandSuite("skeematest", "", "")
    15  	AddGlobalOptions(cmdSuite)
    16  	cmd := mybase.NewCommand("diff", "", "", nil)
    17  	cmd.AddArg("environment", "production", false)
    18  	cmdSuite.AddSubCommand(cmd)
    19  
    20  	// Expectation: global config files not existing isn't fatal
    21  	cfg := mybase.ParseFakeCLI(t, cmdSuite, "skeema diff")
    22  	AddGlobalConfigFiles(cfg)
    23  	if cfg.Supplied("password") || cfg.Changed("password") {
    24  		t.Errorf("Expected password to be unsupplied and unchanged from default; instead found %q", cfg.GetRaw("password"))
    25  	}
    26  
    27  	os.MkdirAll("fake-etc", 0777)
    28  	os.MkdirAll("fake-home", 0777)
    29  	ioutil.WriteFile("fake-etc/skeema", []byte("user=one\npassword=foo\n"), 0777)
    30  	ioutil.WriteFile("fake-home/.my.cnf", []byte("doesnt-exist\nuser=two\nhost=uhoh\n"), 0777)
    31  	defer func() {
    32  		os.RemoveAll("fake-etc")
    33  		os.RemoveAll("fake-home")
    34  	}()
    35  
    36  	// Expectation: both global option files get applied; the one in home
    37  	// overrides the one in etc; undefined options don't cause problems for
    38  	// a file ending in .my.cnf; host is ignored in .my.cnf
    39  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff")
    40  	AddGlobalConfigFiles(cfg)
    41  	if actualUser := cfg.Get("user"); actualUser != "two" {
    42  		t.Errorf("Expected user in fake-home/.my.cnf to take precedence; instead found %s", actualUser)
    43  	}
    44  	if actualPassword := cfg.Get("password"); actualPassword != "foo" {
    45  		t.Errorf("Expected password to come from fake-etc/skeema; instead found %s", actualPassword)
    46  	}
    47  	if cfg.Supplied("host") {
    48  		t.Error("Expected host to be ignored in .my.cnf, but it was parsed anyway")
    49  	}
    50  
    51  	// Test --skip-my-cnf to avoid parsing .my.cnf
    52  	// Expectation: both only the skeema file in etc gets used due to the override option
    53  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --skip-my-cnf")
    54  	AddGlobalConfigFiles(cfg)
    55  	if actualUser := cfg.Get("user"); actualUser != "one" {
    56  		t.Errorf("Expected user in fake-home/.my.cnf to be skipped; instead found %s", actualUser)
    57  	}
    58  
    59  	// Introduce an invalid option into fake-etc/skeema. Expectation: the file
    60  	// is no longer used as a source, even for options declared above the invalid
    61  	// one.
    62  	ioutil.WriteFile("fake-etc/skeema", []byte("user=one\npassword=foo\nthis will not parse"), 0777)
    63  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff")
    64  	AddGlobalConfigFiles(cfg)
    65  	if actualUser := cfg.Get("user"); actualUser != "two" {
    66  		t.Errorf("Expected user in fake-home/.my.cnf to take precedence; instead found %s", actualUser)
    67  	}
    68  	if cfg.Supplied("password") || cfg.Changed("password") {
    69  		t.Errorf("Expected password to be unsupplied and unchanged from default; instead found %q", cfg.GetRaw("password"))
    70  	}
    71  }
    72  
    73  func TestPasswordOption(t *testing.T) {
    74  	cmdSuite := mybase.NewCommandSuite("skeematest", "", "")
    75  	AddGlobalOptions(cmdSuite)
    76  	cmdSuite.AddSubCommand(mybase.NewCommand("diff", "", "", nil))
    77  
    78  	// No MYSQL_PWD env, no password option set on CLI: should stay default
    79  	os.Unsetenv("MYSQL_PWD")
    80  	cfg := mybase.ParseFakeCLI(t, cmdSuite, "skeema diff")
    81  	if err := ProcessSpecialGlobalOptions(cfg); err != nil {
    82  		t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err)
    83  	}
    84  	if cfg.Changed("password") {
    85  		t.Errorf("Expected password to remain default, instead it is set to %s", cfg.Get("password"))
    86  	}
    87  
    88  	// Password set in env but to a blank string: should be same as specifying
    89  	// nothing at all
    90  	os.Setenv("MYSQL_PWD", "")
    91  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff")
    92  	if err := ProcessSpecialGlobalOptions(cfg); err != nil {
    93  		t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err)
    94  	}
    95  	if cfg.Changed("password") {
    96  		t.Errorf("Expected password to remain default, instead it is set to %s", cfg.Get("password"))
    97  	}
    98  
    99  	// Password set in env only, to a non-blank string
   100  	os.Setenv("MYSQL_PWD", "helloworld")
   101  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff")
   102  	if err := ProcessSpecialGlobalOptions(cfg); err != nil {
   103  		t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err)
   104  	}
   105  	if cfg.Get("password") != "helloworld" {
   106  		t.Errorf("Expected password to be helloworld, instead found %s", cfg.Get("password"))
   107  	}
   108  
   109  	// Password set on CLI and in env: CLI should win out
   110  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password=heyearth")
   111  	if err := ProcessSpecialGlobalOptions(cfg); err != nil {
   112  		t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err)
   113  	}
   114  	if cfg.Get("password") != "heyearth" {
   115  		t.Errorf("Expected password to be heyearth, instead found %s", cfg.Get("password"))
   116  	}
   117  
   118  	// Password set in file and env: file should win out
   119  	fakeFileSource := mybase.SimpleSource(map[string]string{
   120  		"password": "howdyplanet",
   121  	})
   122  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff", fakeFileSource)
   123  	if err := ProcessSpecialGlobalOptions(cfg); err != nil {
   124  		t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %s", err)
   125  	}
   126  	if cfg.Get("password") != "howdyplanet" {
   127  		t.Errorf("Expected password to be howdyplanet, instead found %s", cfg.Get("password"))
   128  	}
   129  
   130  	// ProcessSpecialGlobalOptions should error with valueless password if STDIN
   131  	// isn't a TTY. Test bare "password" (no =) on both CLI and config file.
   132  	oldStdin := os.Stdin
   133  	defer func() {
   134  		os.Stdin = oldStdin
   135  	}()
   136  	var err error
   137  	if os.Stdin, err = os.Open("../testdata/setup.sql"); err != nil {
   138  		t.Fatalf("Unable to open ../testdata/setup.sql: %s", err)
   139  	}
   140  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password")
   141  	if err := ProcessSpecialGlobalOptions(cfg); err == nil {
   142  		t.Error("Expected ProcessSpecialGlobalOptions to return an error for non-TTY STDIN, but it did not")
   143  	}
   144  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password", fakeFileSource)
   145  	if err := ProcessSpecialGlobalOptions(cfg); err == nil {
   146  		t.Error("Expected ProcessSpecialGlobalOptions to return an error for non-TTY STDIN, but it did not")
   147  	}
   148  	fakeFileSource["password"] = ""
   149  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff", fakeFileSource)
   150  	if err := ProcessSpecialGlobalOptions(cfg); err == nil {
   151  		t.Error("Expected ProcessSpecialGlobalOptions to return an error for non-TTY STDIN, but it did not")
   152  	}
   153  
   154  	// Setting password to an empty string explicitly should not trigger TTY prompt
   155  	// (note: STDIN intentionally still points to a file here, from test above)
   156  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password=")
   157  	if err := ProcessSpecialGlobalOptions(cfg); err != nil {
   158  		t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %v", err)
   159  	}
   160  	if cfg.Changed("password") {
   161  		t.Error("Password unexpectedly considered changed from default")
   162  	}
   163  	cfg = mybase.ParseFakeCLI(t, cmdSuite, "skeema diff --password=''")
   164  	if err := ProcessSpecialGlobalOptions(cfg); err != nil {
   165  		t.Errorf("Unexpected error from ProcessSpecialGlobalOptions: %v", err)
   166  	}
   167  }
   168  
   169  func TestSplitConnectOptions(t *testing.T) {
   170  	assertConnectOpts := func(connectOptions string, expectedPair ...string) {
   171  		result, err := SplitConnectOptions(connectOptions)
   172  		if err != nil {
   173  			t.Errorf("Unexpected error from SplitConnectOptions(\"%s\"): %s", connectOptions, err)
   174  		}
   175  		expected := make(map[string]string, len(expectedPair))
   176  		for _, pair := range expectedPair {
   177  			tokens := strings.SplitN(pair, "=", 2)
   178  			expected[tokens[0]] = tokens[1]
   179  		}
   180  		if !reflect.DeepEqual(expected, result) {
   181  			t.Errorf("Expected SplitConnectOptions(\"%s\") to return %v, instead received %v", connectOptions, expected, result)
   182  		}
   183  	}
   184  	assertConnectOpts("")
   185  	assertConnectOpts("foo='bar'", "foo='bar'")
   186  	assertConnectOpts("bool=true,quotes='yes,no'", "bool=true", "quotes='yes,no'")
   187  	assertConnectOpts(`escaped=we\'re ok`, `escaped=we\'re ok`)
   188  	assertConnectOpts(`escquotes='we\'re still quoted',this=that`, `escquotes='we\'re still quoted'`, "this=that")
   189  
   190  	expectError := []string{
   191  		"foo=bar,'bip'=bap",
   192  		"flip=flap=flarb",
   193  		"foo=,yes=no",
   194  		"too_many_commas=1,,between_these='yeah'",
   195  		"one=true,two=false,",
   196  		",bad=true",
   197  		",",
   198  		"unterminated='yep",
   199  		"trailingBackSlash=true\\",
   200  		"bareword",
   201  		"twice=true,bool=true,twice=true",
   202  		"start=1,bareword",
   203  	}
   204  	for _, connOpts := range expectError {
   205  		if _, err := SplitConnectOptions(connOpts); err == nil {
   206  			t.Errorf("Did not get expected error from SplitConnectOptions(\"%s\")", connOpts)
   207  		}
   208  	}
   209  }
   210  
   211  func TestRealConnectOptions(t *testing.T) {
   212  	assertResult := func(input, expected string) {
   213  		actual, err := RealConnectOptions(input)
   214  		if err != nil {
   215  			t.Errorf("Unexpected error result from RealConnectOptions(\"%s\"): %s", input, err)
   216  		} else if actual != expected {
   217  			t.Errorf("Expected RealConnectOptions(\"%s\") to return \"%s\", instead found \"%s\"", input, expected, actual)
   218  		}
   219  	}
   220  	assertResult("", "")
   221  	assertResult("foo=1", "foo=1")
   222  	assertResult("allowAllFiles=true", "")
   223  	assertResult("foo='ok,cool',multiStatements=true", "foo='ok,cool'")
   224  	assertResult("timeout=1s,bar=123", "bar=123")
   225  	assertResult("strict=1,foo=2,charset='utf8mb4,utf8'", "foo=2")
   226  	assertResult("timeout=10ms,TIMEOUT=20ms,timeOut=30ms", "")
   227  
   228  	// Ensure errors from SplitConnectOptions are passed through
   229  	if _, err := RealConnectOptions("foo='ok,cool',multiStatements=true,bareword"); err == nil {
   230  		t.Error("Expected error from SplitConnectOptions to be passed through to RealConnectOptions, but err is nil")
   231  	}
   232  }