github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/queryrangebase/query_range_test.go (about)

     1  package queryrangebase
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"strconv"
     9  	"testing"
    10  
    11  	jsoniter "github.com/json-iterator/go"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/weaveworks/common/httpgrpc"
    15  	"github.com/weaveworks/common/user"
    16  
    17  	"github.com/grafana/loki/pkg/logproto"
    18  )
    19  
    20  func TestRequest(t *testing.T) {
    21  	// Create a Copy parsedRequest to assign the expected headers to the request without affecting other tests using the global.
    22  	// The test below adds a Test-Header header to the request and expects it back once the encode/decode of request is done via PrometheusCodec
    23  	parsedRequestWithHeaders := *parsedRequest
    24  	parsedRequestWithHeaders.Headers = reqHeaders
    25  	for i, tc := range []struct {
    26  		url         string
    27  		expected    Request
    28  		expectedErr error
    29  	}{
    30  		{
    31  			url:      query,
    32  			expected: &parsedRequestWithHeaders,
    33  		},
    34  		{
    35  			url:         "api/v1/query_range?start=foo",
    36  			expectedErr: httpgrpc.Errorf(http.StatusBadRequest, "invalid parameter \"start\"; cannot parse \"foo\" to a valid timestamp"),
    37  		},
    38  		{
    39  			url:         "api/v1/query_range?start=123&end=bar",
    40  			expectedErr: httpgrpc.Errorf(http.StatusBadRequest, "invalid parameter \"end\"; cannot parse \"bar\" to a valid timestamp"),
    41  		},
    42  		{
    43  			url:         "api/v1/query_range?start=123&end=0",
    44  			expectedErr: errEndBeforeStart,
    45  		},
    46  		{
    47  			url:         "api/v1/query_range?start=123&end=456&step=baz",
    48  			expectedErr: httpgrpc.Errorf(http.StatusBadRequest, "invalid parameter \"step\"; cannot parse \"baz\" to a valid duration"),
    49  		},
    50  		{
    51  			url:         "api/v1/query_range?start=123&end=456&step=-1",
    52  			expectedErr: errNegativeStep,
    53  		},
    54  		{
    55  			url:         "api/v1/query_range?start=0&end=11001&step=1",
    56  			expectedErr: errStepTooSmall,
    57  		},
    58  	} {
    59  		t.Run(strconv.Itoa(i), func(t *testing.T) {
    60  			r, err := http.NewRequest("GET", tc.url, nil)
    61  			require.NoError(t, err)
    62  			r.Header.Add("Test-Header", "test")
    63  
    64  			ctx := user.InjectOrgID(context.Background(), "1")
    65  
    66  			// Get a deep copy of the request with Context changed to ctx
    67  			r = r.Clone(ctx)
    68  
    69  			req, err := PrometheusCodec.DecodeRequest(ctx, r, []string{"Test-Header"})
    70  			if err != nil {
    71  				require.EqualValues(t, tc.expectedErr, err)
    72  				return
    73  			}
    74  			require.EqualValues(t, tc.expected, req)
    75  
    76  			rdash, err := PrometheusCodec.EncodeRequest(context.Background(), req)
    77  			require.NoError(t, err)
    78  			require.EqualValues(t, tc.url, rdash.RequestURI)
    79  		})
    80  	}
    81  }
    82  
    83  func TestResponse(t *testing.T) {
    84  	r := *parsedResponse
    85  	r.Headers = respHeaders
    86  	for i, tc := range []struct {
    87  		body     string
    88  		expected *PrometheusResponse
    89  	}{
    90  		{
    91  			body:     responseBody,
    92  			expected: &r,
    93  		},
    94  	} {
    95  		t.Run(strconv.Itoa(i), func(t *testing.T) {
    96  			response := &http.Response{
    97  				StatusCode: 200,
    98  				Header:     http.Header{"Content-Type": []string{"application/json"}},
    99  				Body:       ioutil.NopCloser(bytes.NewBuffer([]byte(tc.body))),
   100  			}
   101  			resp, err := PrometheusCodec.DecodeResponse(context.Background(), response, nil)
   102  			require.NoError(t, err)
   103  			assert.Equal(t, tc.expected, resp)
   104  
   105  			// Reset response, as the above call will have consumed the body reader.
   106  			response = &http.Response{
   107  				StatusCode:    200,
   108  				Header:        http.Header{"Content-Type": []string{"application/json"}},
   109  				Body:          ioutil.NopCloser(bytes.NewBuffer([]byte(tc.body))),
   110  				ContentLength: int64(len(tc.body)),
   111  			}
   112  			resp2, err := PrometheusCodec.EncodeResponse(context.Background(), resp)
   113  			require.NoError(t, err)
   114  			assert.Equal(t, response, resp2)
   115  		})
   116  	}
   117  }
   118  
   119  func TestMergeAPIResponses(t *testing.T) {
   120  	for _, tc := range []struct {
   121  		name     string
   122  		input    []Response
   123  		expected Response
   124  	}{
   125  		{
   126  			name:  "No responses shouldn't panic and return a non-null result and result type.",
   127  			input: []Response{},
   128  			expected: &PrometheusResponse{
   129  				Status: StatusSuccess,
   130  				Data: PrometheusData{
   131  					ResultType: matrix,
   132  					Result:     []SampleStream{},
   133  				},
   134  			},
   135  		},
   136  
   137  		{
   138  			name: "A single empty response shouldn't panic.",
   139  			input: []Response{
   140  				&PrometheusResponse{
   141  					Data: PrometheusData{
   142  						ResultType: matrix,
   143  						Result:     []SampleStream{},
   144  					},
   145  				},
   146  			},
   147  			expected: &PrometheusResponse{
   148  				Status: StatusSuccess,
   149  				Data: PrometheusData{
   150  					ResultType: matrix,
   151  					Result:     []SampleStream{},
   152  				},
   153  			},
   154  		},
   155  
   156  		{
   157  			name: "Multiple empty responses shouldn't panic.",
   158  			input: []Response{
   159  				&PrometheusResponse{
   160  					Data: PrometheusData{
   161  						ResultType: matrix,
   162  						Result:     []SampleStream{},
   163  					},
   164  				},
   165  				&PrometheusResponse{
   166  					Data: PrometheusData{
   167  						ResultType: matrix,
   168  						Result:     []SampleStream{},
   169  					},
   170  				},
   171  			},
   172  			expected: &PrometheusResponse{
   173  				Status: StatusSuccess,
   174  				Data: PrometheusData{
   175  					ResultType: matrix,
   176  					Result:     []SampleStream{},
   177  				},
   178  			},
   179  		},
   180  
   181  		{
   182  			name: "Basic merging of two responses.",
   183  			input: []Response{
   184  				&PrometheusResponse{
   185  					Data: PrometheusData{
   186  						ResultType: matrix,
   187  						Result: []SampleStream{
   188  							{
   189  								Labels: []logproto.LabelAdapter{},
   190  								Samples: []logproto.LegacySample{
   191  									{Value: 0, TimestampMs: 0},
   192  									{Value: 1, TimestampMs: 1},
   193  								},
   194  							},
   195  						},
   196  					},
   197  				},
   198  				&PrometheusResponse{
   199  					Data: PrometheusData{
   200  						ResultType: matrix,
   201  						Result: []SampleStream{
   202  							{
   203  								Labels: []logproto.LabelAdapter{},
   204  								Samples: []logproto.LegacySample{
   205  									{Value: 2, TimestampMs: 2},
   206  									{Value: 3, TimestampMs: 3},
   207  								},
   208  							},
   209  						},
   210  					},
   211  				},
   212  			},
   213  			expected: &PrometheusResponse{
   214  				Status: StatusSuccess,
   215  				Data: PrometheusData{
   216  					ResultType: matrix,
   217  					Result: []SampleStream{
   218  						{
   219  							Labels: []logproto.LabelAdapter{},
   220  							Samples: []logproto.LegacySample{
   221  								{Value: 0, TimestampMs: 0},
   222  								{Value: 1, TimestampMs: 1},
   223  								{Value: 2, TimestampMs: 2},
   224  								{Value: 3, TimestampMs: 3},
   225  							},
   226  						},
   227  					},
   228  				},
   229  			},
   230  		},
   231  
   232  		{
   233  			name: "Merging of responses when labels are in different order.",
   234  			input: []Response{
   235  				mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[0,"0"],[1,"1"]]}]}}`),
   236  				mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"]]}]}}`),
   237  			},
   238  			expected: &PrometheusResponse{
   239  				Status: StatusSuccess,
   240  				Data: PrometheusData{
   241  					ResultType: matrix,
   242  					Result: []SampleStream{
   243  						{
   244  							Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}},
   245  							Samples: []logproto.LegacySample{
   246  								{Value: 0, TimestampMs: 0},
   247  								{Value: 1, TimestampMs: 1000},
   248  								{Value: 2, TimestampMs: 2000},
   249  								{Value: 3, TimestampMs: 3000},
   250  							},
   251  						},
   252  					},
   253  				},
   254  			},
   255  		},
   256  
   257  		{
   258  			name: "Merging of samples where there is single overlap.",
   259  			input: []Response{
   260  				mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[1,"1"],[2,"2"]]}]}}`),
   261  				mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"]]}]}}`),
   262  			},
   263  			expected: &PrometheusResponse{
   264  				Status: StatusSuccess,
   265  				Data: PrometheusData{
   266  					ResultType: matrix,
   267  					Result: []SampleStream{
   268  						{
   269  							Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}},
   270  							Samples: []logproto.LegacySample{
   271  								{Value: 1, TimestampMs: 1000},
   272  								{Value: 2, TimestampMs: 2000},
   273  								{Value: 3, TimestampMs: 3000},
   274  							},
   275  						},
   276  					},
   277  				},
   278  			},
   279  		},
   280  		{
   281  			name: "Merging of samples where there is multiple partial overlaps.",
   282  			input: []Response{
   283  				mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[1,"1"],[2,"2"],[3,"3"]]}]}}`),
   284  				mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"],[4,"4"],[5,"5"]]}]}}`),
   285  			},
   286  			expected: &PrometheusResponse{
   287  				Status: StatusSuccess,
   288  				Data: PrometheusData{
   289  					ResultType: matrix,
   290  					Result: []SampleStream{
   291  						{
   292  							Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}},
   293  							Samples: []logproto.LegacySample{
   294  								{Value: 1, TimestampMs: 1000},
   295  								{Value: 2, TimestampMs: 2000},
   296  								{Value: 3, TimestampMs: 3000},
   297  								{Value: 4, TimestampMs: 4000},
   298  								{Value: 5, TimestampMs: 5000},
   299  							},
   300  						},
   301  					},
   302  				},
   303  			},
   304  		},
   305  		{
   306  			name: "Merging of samples where there is complete overlap.",
   307  			input: []Response{
   308  				mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[2,"2"],[3,"3"]]}]}}`),
   309  				mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"],[4,"4"],[5,"5"]]}]}}`),
   310  			},
   311  			expected: &PrometheusResponse{
   312  				Status: StatusSuccess,
   313  				Data: PrometheusData{
   314  					ResultType: matrix,
   315  					Result: []SampleStream{
   316  						{
   317  							Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}},
   318  							Samples: []logproto.LegacySample{
   319  								{Value: 2, TimestampMs: 2000},
   320  								{Value: 3, TimestampMs: 3000},
   321  								{Value: 4, TimestampMs: 4000},
   322  								{Value: 5, TimestampMs: 5000},
   323  							},
   324  						},
   325  					},
   326  				},
   327  			},
   328  		}} {
   329  		t.Run(tc.name, func(t *testing.T) {
   330  			output, err := PrometheusCodec.MergeResponse(tc.input...)
   331  			require.NoError(t, err)
   332  			require.Equal(t, tc.expected, output)
   333  		})
   334  	}
   335  }
   336  
   337  func mustParse(t *testing.T, response string) Response {
   338  	var resp PrometheusResponse
   339  	// Needed as goimports automatically add a json import otherwise.
   340  	json := jsoniter.ConfigCompatibleWithStandardLibrary
   341  	require.NoError(t, json.Unmarshal([]byte(response), &resp))
   342  	return &resp
   343  }