consul/agent/hcp/client/metrics_client_test.go

156 lines
3.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package client
import (
"context"
"math/rand"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
colpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
"google.golang.org/protobuf/proto"
"github.com/hashicorp/go-retryablehttp"
)
type mockClientProvider struct {
client *retryablehttp.Client
header *http.Header
}
func (m *mockClientProvider) GetHTTPClient() *retryablehttp.Client { return m.client }
func (m *mockClientProvider) GetHeader() http.Header { return m.header.Clone() }
func newMockClientProvider() *mockClientProvider {
header := make(http.Header)
header.Set("content-type", "application/x-protobuf")
client := retryablehttp.NewClient()
return &mockClientProvider{
header: &header,
client: client,
}
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZäöüÄÖÜ世界")
func randStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
func TestExportMetrics(t *testing.T) {
for name, test := range map[string]struct {
wantErr string
status int
largeBodyError bool
mutateProvider func(*mockClientProvider)
}{
"success": {
status: http.StatusOK,
},
"failsWithNonRetryableError": {
status: http.StatusBadRequest,
wantErr: "failed to export metrics: code 400",
},
"failsWithNonRetryableErrorWithLongError": {
status: http.StatusBadRequest,
wantErr: "failed to export metrics: code 400",
largeBodyError: true,
},
"failsWithClientNotConfigured": {
mutateProvider: func(m *mockClientProvider) {
m.client = nil
},
wantErr: "http client not configured",
},
} {
t.Run(name, func(t *testing.T) {
randomBody := randStringRunes(1000)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, r.Header.Get("content-type"), "application/x-protobuf")
body := colpb.ExportMetricsServiceResponse{}
bytes, err := proto.Marshal(&body)
require.NoError(t, err)
w.Header().Set("Content-Type", "application/x-protobuf")
w.WriteHeader(test.status)
if test.largeBodyError {
w.Write([]byte(randomBody))
} else {
w.Write(bytes)
}
}))
defer srv.Close()
provider := newMockClientProvider()
if test.mutateProvider != nil {
test.mutateProvider(provider)
}
client := NewMetricsClient(context.Background(), provider)
ctx := context.Background()
metrics := &metricpb.ResourceMetrics{}
err := client.ExportMetrics(ctx, metrics, srv.URL)
if test.wantErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), test.wantErr)
if test.largeBodyError {
truncatedBody := truncate(randomBody, defaultErrRespBodyLength)
require.Contains(t, err.Error(), truncatedBody)
}
return
}
require.NoError(t, err)
})
}
}
func TestTruncate(t *testing.T) {
for name, tc := range map[string]struct {
body string
expectedSize int
}{
"ZeroSize": {
body: "",
expectedSize: 0,
},
"LessThanSize": {
body: "foobar",
expectedSize: 6,
},
"defaultSize": {
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel tincidunt nunc, sed tristique risu",
expectedSize: 100,
},
"greaterThanSize": {
body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel tincidunt nunc, sed tristique risus",
expectedSize: 103,
},
"greaterThanSizeWithUnicode": {
body: randStringRunes(1000),
expectedSize: 103,
},
} {
t.Run(name, func(t *testing.T) {
truncatedBody := truncate(tc.body, defaultErrRespBodyLength)
truncatedRunes := []rune(truncatedBody)
require.Equal(t, len(truncatedRunes), tc.expectedSize)
})
}
}