github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/common_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package prometheus
    22  
    23  import (
    24  	"bytes"
    25  	"context"
    26  	"fmt"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/query/models"
    33  	"github.com/m3db/m3/src/query/parser/promql"
    34  	"github.com/m3db/m3/src/query/test"
    35  	xerrors "github.com/m3db/m3/src/x/errors"
    36  	xhttp "github.com/m3db/m3/src/x/net/http"
    37  
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  func TestPromCompressedReadSuccess(t *testing.T) {
    43  	req := httptest.NewRequest("POST", "/dummy", test.GeneratePromReadBody(t))
    44  	_, err := ParsePromCompressedRequest(req)
    45  	assert.NoError(t, err)
    46  }
    47  
    48  func TestPromCompressedReadNoBody(t *testing.T) {
    49  	req := httptest.NewRequest("POST", "/dummy", nil)
    50  	_, err := ParsePromCompressedRequest(req)
    51  	assert.Error(t, err)
    52  	assert.True(t, xerrors.IsInvalidParams(err))
    53  }
    54  
    55  func TestPromCompressedReadEmptyBody(t *testing.T) {
    56  	req := httptest.NewRequest("POST", "/dummy", bytes.NewReader([]byte{}))
    57  	_, err := ParsePromCompressedRequest(req)
    58  	assert.Error(t, err)
    59  	assert.True(t, xerrors.IsInvalidParams(err))
    60  }
    61  
    62  func TestPromCompressedReadInvalidEncoding(t *testing.T) {
    63  	req := httptest.NewRequest("POST", "/dummy", bytes.NewReader([]byte{'a'}))
    64  	_, err := ParsePromCompressedRequest(req)
    65  	assert.Error(t, err)
    66  	assert.True(t, xerrors.IsInvalidParams(err))
    67  }
    68  
    69  type writer struct {
    70  	value string
    71  }
    72  
    73  func (w *writer) Write(p []byte) (n int, err error) {
    74  	w.value = string(p)
    75  	return len(p), nil
    76  }
    77  
    78  type tag struct {
    79  	name, value string
    80  }
    81  
    82  func toTags(name string, tags ...tag) models.Metric {
    83  	tagOpts := models.NewTagOptions()
    84  	ts := models.NewTags(len(tags), tagOpts)
    85  	ts = ts.SetName([]byte(name))
    86  	for _, tag := range tags {
    87  		ts = ts.AddTag(models.Tag{Name: []byte(tag.name), Value: []byte(tag.value)})
    88  	}
    89  
    90  	return models.Metric{Tags: ts}
    91  }
    92  
    93  func TestParseStartAndEnd(t *testing.T) {
    94  	endTime := time.Now().Truncate(time.Hour)
    95  	opts := promql.NewParseOptions().SetNowFn(func() time.Time { return endTime })
    96  
    97  	tests := []struct {
    98  		querystring string
    99  		exStart     time.Time
   100  		exEnd       time.Time
   101  		exErr       bool
   102  	}{
   103  		{querystring: "", exStart: time.Unix(0, 0), exEnd: endTime},
   104  		{querystring: "start=100", exStart: time.Unix(100, 0), exEnd: endTime},
   105  		{querystring: "start=100&end=200", exStart: time.Unix(100, 0), exEnd: time.Unix(200, 0)},
   106  		{querystring: "start=200&end=100", exErr: true},
   107  		{querystring: "start=foo&end=100", exErr: true},
   108  		{querystring: "start=100&end=bar", exErr: true},
   109  	}
   110  
   111  	for _, tt := range tests {
   112  		t.Run(fmt.Sprintf("GET_%s", tt.querystring), func(t *testing.T) {
   113  			req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
   114  				fmt.Sprintf("/?%s", tt.querystring), nil)
   115  			require.NoError(t, err)
   116  
   117  			start, end, err := ParseStartAndEnd(req, opts)
   118  			if tt.exErr {
   119  				require.Error(t, err)
   120  			} else {
   121  				assert.Equal(t, tt.exStart, start)
   122  				assert.Equal(t, tt.exEnd, end)
   123  			}
   124  		})
   125  	}
   126  
   127  	for _, tt := range tests {
   128  		t.Run(fmt.Sprintf("POST_%s", tt.querystring), func(t *testing.T) {
   129  			b := bytes.NewBuffer([]byte(tt.querystring))
   130  			req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/", b)
   131  			require.NoError(t, err)
   132  			req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeFormURLEncoded)
   133  
   134  			start, end, err := ParseStartAndEnd(req, opts)
   135  			if tt.exErr {
   136  				require.Error(t, err)
   137  			} else {
   138  				assert.Equal(t, tt.exStart, start)
   139  				assert.Equal(t, tt.exEnd, end)
   140  			}
   141  		})
   142  	}
   143  }
   144  
   145  func TestParseRequireStartEnd(t *testing.T) {
   146  	opts := promql.NewParseOptions()
   147  
   148  	tests := []struct {
   149  		exStart         time.Time
   150  		querystring     string
   151  		requireStartEnd bool
   152  		exErr           bool
   153  	}{
   154  		{querystring: "start=100", requireStartEnd: true, exStart: time.Unix(100, 0)},
   155  		{querystring: "", requireStartEnd: true, exErr: true},
   156  		{querystring: "start=100", requireStartEnd: false, exStart: time.Unix(100, 0)},
   157  		{querystring: "", requireStartEnd: false, exStart: time.Unix(0, 0)},
   158  	}
   159  	for _, tt := range tests {
   160  		t.Run(fmt.Sprintf("GET_%s", tt.querystring), func(t *testing.T) {
   161  			req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
   162  				fmt.Sprintf("/?%s", tt.querystring), nil)
   163  			require.NoError(t, err)
   164  
   165  			start, _, err := ParseStartAndEnd(req, opts.SetRequireStartEndTime(tt.requireStartEnd))
   166  			if tt.exErr {
   167  				require.Error(t, err)
   168  			} else {
   169  				assert.Equal(t, tt.exStart, start)
   170  			}
   171  		})
   172  	}
   173  }
   174  
   175  // TestParseMatch tests the parsing / construction logic around ParseMatch().
   176  // matcher_test.go has more comprehensive testing on parsing details.
   177  func TestParseMatch(t *testing.T) {
   178  	parseOpts := promql.NewParseOptions()
   179  	tagOpts := models.NewTagOptions()
   180  
   181  	tests := []struct {
   182  		querystring string
   183  		exMatch     []ParsedMatch
   184  		exErr       bool
   185  		exEmpty     bool
   186  	}{
   187  		{exEmpty: true},
   188  		{
   189  			querystring: "match[]=eq_label",
   190  			exMatch: []ParsedMatch{
   191  				{
   192  					Match: "eq_label",
   193  					Matchers: models.Matchers{
   194  						{
   195  							Type:  models.MatchEqual,
   196  							Name:  []byte("__name__"),
   197  							Value: []byte("eq_label"),
   198  						},
   199  					},
   200  				},
   201  			},
   202  		},
   203  		{querystring: "match[]=illegal%match", exErr: true},
   204  	}
   205  
   206  	for _, tt := range tests {
   207  		t.Run(fmt.Sprintf("GET_%s", tt.querystring), func(t *testing.T) {
   208  			req, err := http.NewRequestWithContext(context.Background(), http.MethodGet,
   209  				fmt.Sprintf("/?%s", tt.querystring), nil)
   210  			require.NoError(t, err)
   211  
   212  			parsedMatches, ok, err := ParseMatch(req, parseOpts, tagOpts)
   213  
   214  			if tt.exErr {
   215  				require.Error(t, err)
   216  				require.False(t, ok)
   217  				require.Empty(t, parsedMatches)
   218  				return
   219  			}
   220  
   221  			require.NoError(t, err)
   222  			if tt.exEmpty {
   223  				require.False(t, ok)
   224  				require.Empty(t, parsedMatches)
   225  			} else {
   226  				require.True(t, ok)
   227  				require.Equal(t, tt.exMatch, parsedMatches)
   228  			}
   229  		})
   230  	}
   231  
   232  	for _, tt := range tests {
   233  		t.Run(fmt.Sprintf("POST_%s", tt.querystring), func(t *testing.T) {
   234  			b := bytes.NewBuffer([]byte(tt.querystring))
   235  			req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "/", b)
   236  			require.NoError(t, err)
   237  			req.Header.Add(xhttp.HeaderContentType, xhttp.ContentTypeFormURLEncoded)
   238  
   239  			parsedMatches, ok, err := ParseMatch(req, parseOpts, tagOpts)
   240  
   241  			if tt.exErr {
   242  				require.Error(t, err)
   243  				require.False(t, ok)
   244  				require.Empty(t, parsedMatches)
   245  				return
   246  			}
   247  
   248  			require.NoError(t, err)
   249  			if tt.exEmpty {
   250  				require.False(t, ok)
   251  				require.Empty(t, parsedMatches)
   252  			} else {
   253  				require.True(t, ok)
   254  				require.Equal(t, tt.exMatch, parsedMatches)
   255  			}
   256  		})
   257  	}
   258  }