...

Source file src/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go

Documentation: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig

     1  // Code created by gotmpl. DO NOT MODIFY.
     2  // source: internal/shared/otlp/envconfig/envconfig_test.go.tmpl
     3  
     4  // Copyright The OpenTelemetry Authors
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package envconfig
    19  
    20  import (
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"errors"
    24  	"net/url"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/stretchr/testify/assert"
    29  )
    30  
    31  const WeakKey = `
    32  -----BEGIN EC PRIVATE KEY-----
    33  MHcCAQEEIEbrSPmnlSOXvVzxCyv+VR3a0HDeUTvOcqrdssZ2k4gFoAoGCCqGSM49
    34  AwEHoUQDQgAEDMTfv75J315C3K9faptS9iythKOMEeV/Eep73nWX531YAkmmwBSB
    35  2dXRD/brsgLnfG57WEpxZuY7dPRbxu33BA==
    36  -----END EC PRIVATE KEY-----
    37  `
    38  
    39  const WeakCertificate = `
    40  -----BEGIN CERTIFICATE-----
    41  MIIBjjCCATWgAwIBAgIUKQSMC66MUw+kPp954ZYOcyKAQDswCgYIKoZIzj0EAwIw
    42  EjEQMA4GA1UECgwHb3RlbC1nbzAeFw0yMjEwMTkwMDA5MTlaFw0yMzEwMTkwMDA5
    43  MTlaMBIxEDAOBgNVBAoMB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
    44  AAQMxN+/vknfXkLcr19qm1L2LK2Eo4wR5X8R6nvedZfnfVgCSabAFIHZ1dEP9uuy
    45  Aud8bntYSnFm5jt09FvG7fcEo2kwZzAdBgNVHQ4EFgQUicGuhnTTkYLZwofXMNLK
    46  SHFeCWgwHwYDVR0jBBgwFoAUicGuhnTTkYLZwofXMNLKSHFeCWgwDwYDVR0TAQH/
    47  BAUwAwEB/zAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDRwAwRAIg
    48  Lfma8FnnxeSOi6223AsFfYwsNZ2RderNsQrS0PjEHb0CIBkrWacqARUAu7uT4cGu
    49  jVcIxYQqhId5L8p/mAv2PWZS
    50  -----END CERTIFICATE-----
    51  `
    52  
    53  type testOption struct {
    54  	TestString   string
    55  	TestBool     bool
    56  	TestDuration time.Duration
    57  	TestHeaders  map[string]string
    58  	TestURL      *url.URL
    59  	TestTLS      *tls.Config
    60  }
    61  
    62  func TestEnvConfig(t *testing.T) {
    63  	parsedURL, err := url.Parse("https://example.com")
    64  	assert.NoError(t, err)
    65  
    66  	options := []testOption{}
    67  	for _, testcase := range []struct {
    68  		name            string
    69  		reader          EnvOptionsReader
    70  		configs         []ConfigFn
    71  		expectedOptions []testOption
    72  	}{
    73  		{
    74  			name: "with no namespace and a matching key",
    75  			reader: EnvOptionsReader{
    76  				GetEnv: func(n string) string {
    77  					if n == "HELLO" {
    78  						return "world"
    79  					}
    80  					return ""
    81  				},
    82  			},
    83  			configs: []ConfigFn{
    84  				WithString("HELLO", func(v string) {
    85  					options = append(options, testOption{TestString: v})
    86  				}),
    87  			},
    88  			expectedOptions: []testOption{
    89  				{
    90  					TestString: "world",
    91  				},
    92  			},
    93  		},
    94  		{
    95  			name: "with no namespace and a non-matching key",
    96  			reader: EnvOptionsReader{
    97  				GetEnv: func(n string) string {
    98  					if n == "HELLO" {
    99  						return "world"
   100  					}
   101  					return ""
   102  				},
   103  			},
   104  			configs: []ConfigFn{
   105  				WithString("HOLA", func(v string) {
   106  					options = append(options, testOption{TestString: v})
   107  				}),
   108  			},
   109  			expectedOptions: []testOption{},
   110  		},
   111  		{
   112  			name: "with a namespace and a matching key",
   113  			reader: EnvOptionsReader{
   114  				Namespace: "MY_NAMESPACE",
   115  				GetEnv: func(n string) string {
   116  					if n == "MY_NAMESPACE_HELLO" {
   117  						return "world"
   118  					}
   119  					return ""
   120  				},
   121  			},
   122  			configs: []ConfigFn{
   123  				WithString("HELLO", func(v string) {
   124  					options = append(options, testOption{TestString: v})
   125  				}),
   126  			},
   127  			expectedOptions: []testOption{
   128  				{
   129  					TestString: "world",
   130  				},
   131  			},
   132  		},
   133  		{
   134  			name: "with no namespace and a non-matching key",
   135  			reader: EnvOptionsReader{
   136  				Namespace: "MY_NAMESPACE",
   137  				GetEnv: func(n string) string {
   138  					if n == "HELLO" {
   139  						return "world"
   140  					}
   141  					return ""
   142  				},
   143  			},
   144  			configs: []ConfigFn{
   145  				WithString("HELLO", func(v string) {
   146  					options = append(options, testOption{TestString: v})
   147  				}),
   148  			},
   149  			expectedOptions: []testOption{},
   150  		},
   151  		{
   152  			name: "with a bool config",
   153  			reader: EnvOptionsReader{
   154  				GetEnv: func(n string) string {
   155  					if n == "HELLO" {
   156  						return "true"
   157  					} else if n == "WORLD" {
   158  						return "false"
   159  					}
   160  					return ""
   161  				},
   162  			},
   163  			configs: []ConfigFn{
   164  				WithBool("HELLO", func(b bool) {
   165  					options = append(options, testOption{TestBool: b})
   166  				}),
   167  				WithBool("WORLD", func(b bool) {
   168  					options = append(options, testOption{TestBool: b})
   169  				}),
   170  			},
   171  			expectedOptions: []testOption{
   172  				{
   173  					TestBool: true,
   174  				},
   175  				{
   176  					TestBool: false,
   177  				},
   178  			},
   179  		},
   180  		{
   181  			name: "with an invalid bool config",
   182  			reader: EnvOptionsReader{
   183  				GetEnv: func(n string) string {
   184  					if n == "HELLO" {
   185  						return "world"
   186  					}
   187  					return ""
   188  				},
   189  			},
   190  			configs: []ConfigFn{
   191  				WithBool("HELLO", func(b bool) {
   192  					options = append(options, testOption{TestBool: b})
   193  				}),
   194  			},
   195  			expectedOptions: []testOption{
   196  				{
   197  					TestBool: false,
   198  				},
   199  			},
   200  		},
   201  		{
   202  			name: "with a duration config",
   203  			reader: EnvOptionsReader{
   204  				GetEnv: func(n string) string {
   205  					if n == "HELLO" {
   206  						return "60"
   207  					}
   208  					return ""
   209  				},
   210  			},
   211  			configs: []ConfigFn{
   212  				WithDuration("HELLO", func(v time.Duration) {
   213  					options = append(options, testOption{TestDuration: v})
   214  				}),
   215  			},
   216  			expectedOptions: []testOption{
   217  				{
   218  					TestDuration: 60_000_000, // 60 milliseconds
   219  				},
   220  			},
   221  		},
   222  		{
   223  			name: "with an invalid duration config",
   224  			reader: EnvOptionsReader{
   225  				GetEnv: func(n string) string {
   226  					if n == "HELLO" {
   227  						return "world"
   228  					}
   229  					return ""
   230  				},
   231  			},
   232  			configs: []ConfigFn{
   233  				WithDuration("HELLO", func(v time.Duration) {
   234  					options = append(options, testOption{TestDuration: v})
   235  				}),
   236  			},
   237  			expectedOptions: []testOption{},
   238  		},
   239  		{
   240  			name: "with headers",
   241  			reader: EnvOptionsReader{
   242  				GetEnv: func(n string) string {
   243  					if n == "HELLO" {
   244  						return "userId=42,userName=alice"
   245  					}
   246  					return ""
   247  				},
   248  			},
   249  			configs: []ConfigFn{
   250  				WithHeaders("HELLO", func(v map[string]string) {
   251  					options = append(options, testOption{TestHeaders: v})
   252  				}),
   253  			},
   254  			expectedOptions: []testOption{
   255  				{
   256  					TestHeaders: map[string]string{
   257  						"userId":   "42",
   258  						"userName": "alice",
   259  					},
   260  				},
   261  			},
   262  		},
   263  		{
   264  			name: "with invalid headers",
   265  			reader: EnvOptionsReader{
   266  				GetEnv: func(n string) string {
   267  					if n == "HELLO" {
   268  						return "world"
   269  					}
   270  					return ""
   271  				},
   272  			},
   273  			configs: []ConfigFn{
   274  				WithHeaders("HELLO", func(v map[string]string) {
   275  					options = append(options, testOption{TestHeaders: v})
   276  				}),
   277  			},
   278  			expectedOptions: []testOption{
   279  				{
   280  					TestHeaders: map[string]string{},
   281  				},
   282  			},
   283  		},
   284  		{
   285  			name: "with URL",
   286  			reader: EnvOptionsReader{
   287  				GetEnv: func(n string) string {
   288  					if n == "HELLO" {
   289  						return "https://example.com"
   290  					}
   291  					return ""
   292  				},
   293  			},
   294  			configs: []ConfigFn{
   295  				WithURL("HELLO", func(v *url.URL) {
   296  					options = append(options, testOption{TestURL: v})
   297  				}),
   298  			},
   299  			expectedOptions: []testOption{
   300  				{
   301  					TestURL: parsedURL,
   302  				},
   303  			},
   304  		},
   305  		{
   306  			name: "with invalid URL",
   307  			reader: EnvOptionsReader{
   308  				GetEnv: func(n string) string {
   309  					if n == "HELLO" {
   310  						return "i nvalid://url"
   311  					}
   312  					return ""
   313  				},
   314  			},
   315  			configs: []ConfigFn{
   316  				WithURL("HELLO", func(v *url.URL) {
   317  					options = append(options, testOption{TestURL: v})
   318  				}),
   319  			},
   320  			expectedOptions: []testOption{},
   321  		},
   322  	} {
   323  		t.Run(testcase.name, func(t *testing.T) {
   324  			testcase.reader.Apply(testcase.configs...)
   325  			assert.Equal(t, testcase.expectedOptions, options)
   326  			options = []testOption{}
   327  		})
   328  	}
   329  }
   330  
   331  func TestWithTLSConfig(t *testing.T) {
   332  	pool, err := createCertPool([]byte(WeakCertificate))
   333  	assert.NoError(t, err)
   334  
   335  	reader := EnvOptionsReader{
   336  		GetEnv: func(n string) string {
   337  			if n == "CERTIFICATE" {
   338  				return "/path/cert.pem"
   339  			}
   340  			return ""
   341  		},
   342  		ReadFile: func(p string) ([]byte, error) {
   343  			if p == "/path/cert.pem" {
   344  				return []byte(WeakCertificate), nil
   345  			}
   346  			return []byte{}, nil
   347  		},
   348  	}
   349  
   350  	var option testOption
   351  	reader.Apply(
   352  		WithCertPool("CERTIFICATE", func(cp *x509.CertPool) {
   353  			option = testOption{TestTLS: &tls.Config{RootCAs: cp}}
   354  		}),
   355  	)
   356  
   357  	// nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool.
   358  	assert.Equal(t, pool.Subjects(), option.TestTLS.RootCAs.Subjects())
   359  }
   360  
   361  func TestWithClientCert(t *testing.T) {
   362  	cert, err := tls.X509KeyPair([]byte(WeakCertificate), []byte(WeakKey))
   363  	assert.NoError(t, err)
   364  
   365  	reader := EnvOptionsReader{
   366  		GetEnv: func(n string) string {
   367  			switch n {
   368  			case "CLIENT_CERTIFICATE":
   369  				return "/path/tls.crt"
   370  			case "CLIENT_KEY":
   371  				return "/path/tls.key"
   372  			}
   373  			return ""
   374  		},
   375  		ReadFile: func(n string) ([]byte, error) {
   376  			switch n {
   377  			case "/path/tls.crt":
   378  				return []byte(WeakCertificate), nil
   379  			case "/path/tls.key":
   380  				return []byte(WeakKey), nil
   381  			}
   382  			return []byte{}, nil
   383  		},
   384  	}
   385  
   386  	var option testOption
   387  	reader.Apply(
   388  		WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) {
   389  			option = testOption{TestTLS: &tls.Config{Certificates: []tls.Certificate{c}}}
   390  		}),
   391  	)
   392  	assert.Equal(t, cert, option.TestTLS.Certificates[0])
   393  
   394  	reader.ReadFile = func(s string) ([]byte, error) { return nil, errors.New("oops") }
   395  	option.TestTLS = nil
   396  	reader.Apply(
   397  		WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) {
   398  			option = testOption{TestTLS: &tls.Config{Certificates: []tls.Certificate{c}}}
   399  		}),
   400  	)
   401  	assert.Nil(t, option.TestTLS)
   402  
   403  	reader.GetEnv = func(s string) string { return "" }
   404  	option.TestTLS = nil
   405  	reader.Apply(
   406  		WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) {
   407  			option = testOption{TestTLS: &tls.Config{Certificates: []tls.Certificate{c}}}
   408  		}),
   409  	)
   410  	assert.Nil(t, option.TestTLS)
   411  }
   412  
   413  func TestStringToHeader(t *testing.T) {
   414  	tests := []struct {
   415  		name  string
   416  		value string
   417  		want  map[string]string
   418  	}{
   419  		{
   420  			name:  "simple test",
   421  			value: "userId=alice",
   422  			want:  map[string]string{"userId": "alice"},
   423  		},
   424  		{
   425  			name:  "simple test with spaces",
   426  			value: " userId = alice  ",
   427  			want:  map[string]string{"userId": "alice"},
   428  		},
   429  		{
   430  			name:  "simple header conforms to RFC 3986 spec",
   431  			value: " userId = alice+test ",
   432  			want:  map[string]string{"userId": "alice+test"},
   433  		},
   434  		{
   435  			name:  "multiple headers encoded",
   436  			value: "userId=alice,serverNode=DF%3A28,isProduction=false",
   437  			want: map[string]string{
   438  				"userId":       "alice",
   439  				"serverNode":   "DF:28",
   440  				"isProduction": "false",
   441  			},
   442  		},
   443  		{
   444  			name:  "multiple headers encoded per RFC 3986 spec",
   445  			value: "userId=alice+test,serverNode=DF%3A28,isProduction=false,namespace=localhost/test",
   446  			want: map[string]string{
   447  				"userId":       "alice+test",
   448  				"serverNode":   "DF:28",
   449  				"isProduction": "false",
   450  				"namespace":    "localhost/test",
   451  			},
   452  		},
   453  		{
   454  			name:  "invalid headers format",
   455  			value: "userId:alice",
   456  			want:  map[string]string{},
   457  		},
   458  		{
   459  			name:  "invalid key",
   460  			value: "%XX=missing,userId=alice",
   461  			want: map[string]string{
   462  				"userId": "alice",
   463  			},
   464  		},
   465  		{
   466  			name:  "invalid value",
   467  			value: "missing=%XX,userId=alice",
   468  			want: map[string]string{
   469  				"userId": "alice",
   470  			},
   471  		},
   472  	}
   473  
   474  	for _, tt := range tests {
   475  		t.Run(tt.name, func(t *testing.T) {
   476  			assert.Equal(t, tt.want, stringToHeader(tt.value))
   477  		})
   478  	}
   479  }
   480  

View as plain text