| package config // import "github.com/docker/docker/daemon/config" |
| |
| import ( |
| "os" |
| "path/filepath" |
| "reflect" |
| "strings" |
| "testing" |
| |
| "github.com/docker/docker/libnetwork/ipamutils" |
| "github.com/docker/docker/opts" |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| "github.com/imdario/mergo" |
| "github.com/spf13/pflag" |
| "golang.org/x/text/encoding" |
| "golang.org/x/text/encoding/unicode" |
| "gotest.tools/v3/assert" |
| is "gotest.tools/v3/assert/cmp" |
| "gotest.tools/v3/skip" |
| ) |
| |
| func makeConfigFile(t *testing.T, content string) string { |
| t.Helper() |
| name := filepath.Join(t.TempDir(), "daemon.json") |
| err := os.WriteFile(name, []byte(content), 0666) |
| assert.NilError(t, err) |
| return name |
| } |
| |
| func TestDaemonConfigurationNotFound(t *testing.T) { |
| _, err := MergeDaemonConfigurations(&Config{}, nil, "/tmp/foo-bar-baz-docker") |
| assert.Check(t, os.IsNotExist(err), "got: %[1]T: %[1]v", err) |
| } |
| |
| func TestDaemonBrokenConfiguration(t *testing.T) { |
| configFile := makeConfigFile(t, `{"Debug": tru`) |
| |
| _, err := MergeDaemonConfigurations(&Config{}, nil, configFile) |
| assert.ErrorContains(t, err, `invalid character ' ' in literal true`) |
| } |
| |
| // TestDaemonConfigurationUnicodeVariations feeds various variations of Unicode into the JSON parser, ensuring that we |
| // respect a BOM and otherwise default to UTF-8. |
| func TestDaemonConfigurationUnicodeVariations(t *testing.T) { |
| jsonData := `{"debug": true}` |
| |
| testCases := []struct { |
| name string |
| encoding encoding.Encoding |
| }{ |
| { |
| name: "UTF-8", |
| encoding: unicode.UTF8, |
| }, |
| { |
| name: "UTF-8 (with BOM)", |
| encoding: unicode.UTF8BOM, |
| }, |
| { |
| name: "UTF-16 (BE with BOM)", |
| encoding: unicode.UTF16(unicode.BigEndian, unicode.UseBOM), |
| }, |
| { |
| name: "UTF-16 (LE with BOM)", |
| encoding: unicode.UTF16(unicode.LittleEndian, unicode.UseBOM), |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| encodedJson, err := tc.encoding.NewEncoder().String(jsonData) |
| assert.NilError(t, err) |
| configFile := makeConfigFile(t, encodedJson) |
| _, err = MergeDaemonConfigurations(&Config{}, nil, configFile) |
| assert.NilError(t, err) |
| }) |
| } |
| } |
| |
| // TestDaemonConfigurationInvalidUnicode ensures that the JSON parser returns a useful error message if malformed UTF-8 |
| // is provided. |
| func TestDaemonConfigurationInvalidUnicode(t *testing.T) { |
| configFileBOM := makeConfigFile(t, "\xef\xbb\xbf{\"debug\": true}\xff") |
| _, err := MergeDaemonConfigurations(&Config{}, nil, configFileBOM) |
| assert.ErrorIs(t, err, encoding.ErrInvalidUTF8) |
| |
| configFileNoBOM := makeConfigFile(t, "{\"debug\": true}\xff") |
| _, err = MergeDaemonConfigurations(&Config{}, nil, configFileNoBOM) |
| assert.ErrorIs(t, err, encoding.ErrInvalidUTF8) |
| } |
| |
| func TestFindConfigurationConflicts(t *testing.T) { |
| config := map[string]interface{}{"authorization-plugins": "foobar"} |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| |
| flags.String("authorization-plugins", "", "") |
| assert.Check(t, flags.Set("authorization-plugins", "asdf")) |
| assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "authorization-plugins: (from flag: asdf, from file: foobar)")) |
| } |
| |
| func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) { |
| config := map[string]interface{}{"hosts": []string{"qwer"}} |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| |
| var hosts []string |
| flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), "host", "H", "Daemon socket(s) to connect to") |
| assert.Check(t, flags.Set("host", "tcp://127.0.0.1:4444")) |
| assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock")) |
| assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "hosts")) |
| } |
| |
| func TestDaemonConfigurationMergeConflicts(t *testing.T) { |
| configFile := makeConfigFile(t, `{"debug": true}`) |
| |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.Bool("debug", false, "") |
| assert.Check(t, flags.Set("debug", "false")) |
| |
| _, err := MergeDaemonConfigurations(&Config{}, flags, configFile) |
| if err == nil { |
| t.Fatal("expected error, got nil") |
| } |
| if !strings.Contains(err.Error(), "debug") { |
| t.Fatalf("expected debug conflict, got %v", err) |
| } |
| } |
| |
| func TestDaemonConfigurationMergeConcurrent(t *testing.T) { |
| configFile := makeConfigFile(t, `{"max-concurrent-downloads": 1}`) |
| |
| _, err := MergeDaemonConfigurations(&Config{}, nil, configFile) |
| assert.NilError(t, err) |
| } |
| |
| func TestDaemonConfigurationMergeConcurrentError(t *testing.T) { |
| configFile := makeConfigFile(t, `{"max-concurrent-downloads": -1}`) |
| |
| _, err := MergeDaemonConfigurations(&Config{}, nil, configFile) |
| assert.ErrorContains(t, err, `invalid max concurrent downloads: -1`) |
| } |
| |
| func TestDaemonConfigurationMergeConflictsWithInnerStructs(t *testing.T) { |
| configFile := makeConfigFile(t, `{"tlscacert": "/etc/certificates/ca.pem"}`) |
| |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.String("tlscacert", "", "") |
| assert.Check(t, flags.Set("tlscacert", "~/.docker/ca.pem")) |
| |
| _, err := MergeDaemonConfigurations(&Config{}, flags, configFile) |
| assert.ErrorContains(t, err, `the following directives are specified both as a flag and in the configuration file: tlscacert`) |
| } |
| |
| // TestDaemonConfigurationMergeDefaultAddressPools is a regression test for #40711. |
| func TestDaemonConfigurationMergeDefaultAddressPools(t *testing.T) { |
| emptyConfigFile := makeConfigFile(t, `{}`) |
| configFile := makeConfigFile(t, `{"default-address-pools":[{"base": "10.123.0.0/16", "size": 24 }]}`) |
| |
| expected := []*ipamutils.NetworkToSplit{{Base: "10.123.0.0/16", Size: 24}} |
| |
| t.Run("empty config file", func(t *testing.T) { |
| var conf = Config{} |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") |
| assert.Check(t, flags.Set("default-address-pool", "base=10.123.0.0/16,size=24")) |
| |
| config, err := MergeDaemonConfigurations(&conf, flags, emptyConfigFile) |
| assert.NilError(t, err) |
| assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected) |
| }) |
| |
| t.Run("config file", func(t *testing.T) { |
| var conf = Config{} |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") |
| |
| config, err := MergeDaemonConfigurations(&conf, flags, configFile) |
| assert.NilError(t, err) |
| assert.DeepEqual(t, config.DefaultAddressPools.Value(), expected) |
| }) |
| |
| t.Run("with conflicting options", func(t *testing.T) { |
| var conf = Config{} |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.Var(&conf.NetworkConfig.DefaultAddressPools, "default-address-pool", "") |
| assert.Check(t, flags.Set("default-address-pool", "base=10.123.0.0/16,size=24")) |
| |
| _, err := MergeDaemonConfigurations(&conf, flags, configFile) |
| assert.ErrorContains(t, err, "the following directives are specified both as a flag and in the configuration file") |
| assert.ErrorContains(t, err, "default-address-pools") |
| }) |
| } |
| |
| func TestFindConfigurationConflictsWithUnknownKeys(t *testing.T) { |
| config := map[string]interface{}{"tls-verify": "true"} |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| |
| flags.Bool("tlsverify", false, "") |
| err := findConfigurationConflicts(config, flags) |
| assert.ErrorContains(t, err, "the following directives don't match any configuration option: tls-verify") |
| } |
| |
| func TestFindConfigurationConflictsWithMergedValues(t *testing.T) { |
| var hosts []string |
| config := map[string]interface{}{"hosts": "tcp://127.0.0.1:2345"} |
| flags := pflag.NewFlagSet("base", pflag.ContinueOnError) |
| flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, nil), "host", "H", "") |
| |
| err := findConfigurationConflicts(config, flags) |
| assert.NilError(t, err) |
| |
| assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock")) |
| err = findConfigurationConflicts(config, flags) |
| assert.ErrorContains(t, err, "hosts: (from flag: [unix:///var/run/docker.sock], from file: tcp://127.0.0.1:2345)") |
| } |
| |
| func TestValidateConfigurationErrors(t *testing.T) { |
| testCases := []struct { |
| name string |
| field string |
| config *Config |
| expectedErr string |
| }{ |
| { |
| name: "single label without value", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| Labels: []string{"one"}, |
| }, |
| }, |
| expectedErr: "bad attribute format: one", |
| }, |
| { |
| name: "multiple label without value", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| Labels: []string{"foo=bar", "one"}, |
| }, |
| }, |
| expectedErr: "bad attribute format: one", |
| }, |
| { |
| name: "single DNS, invalid IP-address", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| DNSConfig: DNSConfig{ |
| DNS: []string{"1.1.1.1o"}, |
| }, |
| }, |
| }, |
| expectedErr: "1.1.1.1o is not an ip address", |
| }, |
| { |
| name: "multiple DNS, invalid IP-address", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| DNSConfig: DNSConfig{ |
| DNS: []string{"2.2.2.2", "1.1.1.1o"}, |
| }, |
| }, |
| }, |
| expectedErr: "1.1.1.1o is not an ip address", |
| }, |
| { |
| name: "single DNSSearch", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| DNSConfig: DNSConfig{ |
| DNSSearch: []string{"123456"}, |
| }, |
| }, |
| }, |
| expectedErr: "123456 is not a valid domain", |
| }, |
| { |
| name: "multiple DNSSearch", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| DNSConfig: DNSConfig{ |
| DNSSearch: []string{"a.b.c", "123456"}, |
| }, |
| }, |
| }, |
| expectedErr: "123456 is not a valid domain", |
| }, |
| { |
| name: "negative MTU", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| Mtu: -10, |
| }, |
| }, |
| expectedErr: "invalid default MTU: -10", |
| }, |
| { |
| name: "negative max-concurrent-downloads", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| MaxConcurrentDownloads: -10, |
| }, |
| }, |
| expectedErr: "invalid max concurrent downloads: -10", |
| }, |
| { |
| name: "negative max-concurrent-uploads", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| MaxConcurrentUploads: -10, |
| }, |
| }, |
| expectedErr: "invalid max concurrent uploads: -10", |
| }, |
| { |
| name: "negative max-download-attempts", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| MaxDownloadAttempts: -10, |
| }, |
| }, |
| expectedErr: "invalid max download attempts: -10", |
| }, |
| // TODO(thaJeztah) temporarily excluding this test as it assumes defaults are set before validating and applying updated configs |
| /* |
| { |
| name: "zero max-download-attempts", |
| field: "MaxDownloadAttempts", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| MaxDownloadAttempts: 0, |
| }, |
| }, |
| expectedErr: "invalid max download attempts: 0", |
| }, |
| */ |
| { |
| name: "generic resource without =", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| NodeGenericResources: []string{"foo"}, |
| }, |
| }, |
| expectedErr: "could not parse GenericResource: incorrect term foo, missing '=' or malformed expression", |
| }, |
| { |
| name: "generic resource mixed named and discrete", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| NodeGenericResources: []string{"foo=bar", "foo=1"}, |
| }, |
| }, |
| expectedErr: "could not parse GenericResource: mixed discrete and named resources in expression 'foo=[bar 1]'", |
| }, |
| { |
| name: "with invalid hosts", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| Hosts: []string{"127.0.0.1:2375/path"}, |
| }, |
| }, |
| expectedErr: "invalid bind address (127.0.0.1:2375/path): should not contain a path element", |
| }, |
| { |
| name: "with invalid log-level", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| LogLevel: "foobar", |
| }, |
| }, |
| expectedErr: "invalid logging level: foobar", |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| cfg, err := New() |
| assert.NilError(t, err) |
| if tc.field != "" { |
| assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride, withForceOverwrite(tc.field))) |
| } else { |
| assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride)) |
| } |
| err = Validate(cfg) |
| assert.Error(t, err, tc.expectedErr) |
| }) |
| } |
| } |
| |
| func withForceOverwrite(fieldName string) func(config *mergo.Config) { |
| return mergo.WithTransformers(overwriteTransformer{fieldName: fieldName}) |
| } |
| |
| type overwriteTransformer struct { |
| fieldName string |
| } |
| |
| func (tf overwriteTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error { |
| if typ == reflect.TypeOf(CommonConfig{}) { |
| return func(dst, src reflect.Value) error { |
| dst.FieldByName(tf.fieldName).Set(src.FieldByName(tf.fieldName)) |
| return nil |
| } |
| } |
| return nil |
| } |
| |
| func TestValidateConfiguration(t *testing.T) { |
| testCases := []struct { |
| name string |
| field string |
| config *Config |
| }{ |
| { |
| name: "with label", |
| field: "Labels", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| Labels: []string{"one=two"}, |
| }, |
| }, |
| }, |
| { |
| name: "with dns", |
| field: "DNSConfig", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| DNSConfig: DNSConfig{ |
| DNS: []string{"1.1.1.1"}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "with dns-search", |
| field: "DNSConfig", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| DNSConfig: DNSConfig{ |
| DNSSearch: []string{"a.b.c"}, |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "with mtu", |
| field: "Mtu", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| Mtu: 1234, |
| }, |
| }, |
| }, |
| { |
| name: "with max-concurrent-downloads", |
| field: "MaxConcurrentDownloads", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| MaxConcurrentDownloads: 4, |
| }, |
| }, |
| }, |
| { |
| name: "with max-concurrent-uploads", |
| field: "MaxConcurrentUploads", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| MaxConcurrentUploads: 4, |
| }, |
| }, |
| }, |
| { |
| name: "with max-download-attempts", |
| field: "MaxDownloadAttempts", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| MaxDownloadAttempts: 4, |
| }, |
| }, |
| }, |
| { |
| name: "with multiple node generic resources", |
| field: "NodeGenericResources", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| NodeGenericResources: []string{"foo=bar", "foo=baz"}, |
| }, |
| }, |
| }, |
| { |
| name: "with node generic resources", |
| field: "NodeGenericResources", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| NodeGenericResources: []string{"foo=1"}, |
| }, |
| }, |
| }, |
| { |
| name: "with hosts", |
| field: "Hosts", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| Hosts: []string{"tcp://127.0.0.1:2375"}, |
| }, |
| }, |
| }, |
| { |
| name: "with log-level warn", |
| field: "LogLevel", |
| config: &Config{ |
| CommonConfig: CommonConfig{ |
| LogLevel: "warn", |
| }, |
| }, |
| }, |
| } |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| // Start with a config with all defaults set, so that we only |
| cfg, err := New() |
| assert.NilError(t, err) |
| assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride)) |
| |
| // Check that the override happened :) |
| assert.Check(t, is.DeepEqual(cfg, tc.config, field(tc.field))) |
| err = Validate(cfg) |
| assert.NilError(t, err) |
| }) |
| } |
| } |
| |
| func field(field string) cmp.Option { |
| tmp := reflect.TypeOf(Config{}) |
| ignoreFields := make([]string, 0, tmp.NumField()) |
| for i := 0; i < tmp.NumField(); i++ { |
| if tmp.Field(i).Name != field { |
| ignoreFields = append(ignoreFields, tmp.Field(i).Name) |
| } |
| } |
| return cmpopts.IgnoreFields(Config{}, ignoreFields...) |
| } |
| |
| // TestReloadSetConfigFileNotExist tests that when `--config-file` is set, and it doesn't exist the `Reload` function |
| // returns an error. |
| func TestReloadSetConfigFileNotExist(t *testing.T) { |
| configFile := "/tmp/blabla/not/exists/config.json" |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.String("config-file", "", "") |
| assert.Check(t, flags.Set("config-file", configFile)) |
| |
| err := Reload(configFile, flags, func(c *Config) {}) |
| assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file")) |
| } |
| |
| // TestReloadDefaultConfigNotExist tests that if the default configuration file doesn't exist the daemon still will |
| // still be reloaded. |
| func TestReloadDefaultConfigNotExist(t *testing.T) { |
| skip.If(t, os.Getuid() != 0, "skipping test that requires root") |
| defaultConfigFile := "/tmp/blabla/not/exists/daemon.json" |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.String("config-file", defaultConfigFile, "") |
| reloaded := false |
| err := Reload(defaultConfigFile, flags, func(c *Config) { |
| reloaded = true |
| }) |
| assert.Check(t, err) |
| assert.Check(t, reloaded) |
| } |
| |
| // TestReloadBadDefaultConfig tests that when `--config-file` is not set and the default configuration file exists and |
| // is bad, an error is returned. |
| func TestReloadBadDefaultConfig(t *testing.T) { |
| configFile := makeConfigFile(t, `{wrong: "configuration"}`) |
| |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.String("config-file", configFile, "") |
| reloaded := false |
| err := Reload(configFile, flags, func(c *Config) { |
| reloaded = true |
| }) |
| assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file")) |
| assert.Check(t, reloaded == false) |
| } |
| |
| func TestReloadWithConflictingLabels(t *testing.T) { |
| configFile := makeConfigFile(t, `{"labels": ["foo=bar", "foo=baz"]}`) |
| |
| var lbls []string |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.String("config-file", configFile, "") |
| flags.StringSlice("labels", lbls, "") |
| reloaded := false |
| err := Reload(configFile, flags, func(c *Config) { |
| reloaded = true |
| }) |
| assert.Check(t, is.ErrorContains(err, "conflict labels for foo=baz and foo=bar")) |
| assert.Check(t, reloaded == false) |
| } |
| |
| func TestReloadWithDuplicateLabels(t *testing.T) { |
| configFile := makeConfigFile(t, `{"labels": ["foo=the-same", "foo=the-same"]}`) |
| |
| var lbls []string |
| flags := pflag.NewFlagSet("test", pflag.ContinueOnError) |
| flags.String("config-file", configFile, "") |
| flags.StringSlice("labels", lbls, "") |
| reloaded := false |
| err := Reload(configFile, flags, func(c *Config) { |
| reloaded = true |
| assert.Check(t, is.DeepEqual(c.Labels, []string{"foo=the-same"})) |
| }) |
| assert.Check(t, err) |
| assert.Check(t, reloaded) |
| } |
| |
| func TestMaskURLCredentials(t *testing.T) { |
| tests := []struct { |
| rawURL string |
| maskedURL string |
| }{ |
| { |
| rawURL: "", |
| maskedURL: "", |
| }, { |
| rawURL: "invalidURL", |
| maskedURL: "invalidURL", |
| }, { |
| rawURL: "http://proxy.example.com:80/", |
| maskedURL: "http://proxy.example.com:80/", |
| }, { |
| rawURL: "http://USER:PASSWORD@proxy.example.com:80/", |
| maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", |
| }, { |
| rawURL: "http://PASSWORD:PASSWORD@proxy.example.com:80/", |
| maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", |
| }, { |
| rawURL: "http://USER:@proxy.example.com:80/", |
| maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", |
| }, { |
| rawURL: "http://:PASSWORD@proxy.example.com:80/", |
| maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", |
| }, { |
| rawURL: "http://USER@docker:password@proxy.example.com:80/", |
| maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", |
| }, { |
| rawURL: "http://USER%40docker:password@proxy.example.com:80/", |
| maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", |
| }, { |
| rawURL: "http://USER%40docker:pa%3Fsword@proxy.example.com:80/", |
| maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/", |
| }, { |
| rawURL: "http://USER%40docker:pa%3Fsword@proxy.example.com:80/hello%20world", |
| maskedURL: "http://xxxxx:xxxxx@proxy.example.com:80/hello%20world", |
| }, |
| } |
| for _, test := range tests { |
| maskedURL := MaskCredentials(test.rawURL) |
| assert.Equal(t, maskedURL, test.maskedURL) |
| } |
| } |