github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/integration/legacy/query_api_test.go (about)

     1  // +build cluster_integration
     2  //
     3  // Copyright (c) 2020  Uber Technologies, Inc.
     4  //
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  //
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  //
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  package legacy
    24  
    25  import (
    26  	"encoding/json"
    27  	"fmt"
    28  	"strings"
    29  	"testing"
    30  
    31  	"github.com/m3db/m3/src/integration/resources"
    32  
    33  	"github.com/stretchr/testify/assert"
    34  )
    35  
    36  type urlTest struct {
    37  	name string
    38  	url  string
    39  }
    40  
    41  func TestInvalidInstantQueryReturns400(t *testing.T) {
    42  	urlPrefixes := []string{"", "prometheus/", "m3query/"}
    43  
    44  	urlTests := addPrefixes(urlPrefixes, []urlTest{
    45  		{"missing query", queryURL("query", "")},
    46  		{"invalid query", queryURL("query", "@!")},
    47  		{"invalid time", queryURL("time", "INVALID")},
    48  		{"invalid timeout", queryURL("timeout", "INVALID")},
    49  	})
    50  
    51  	testInvalidQueryReturns400(t, urlTests)
    52  }
    53  
    54  func TestInvalidRangeQueryReturns400(t *testing.T) {
    55  	urlPrefixes := []string{"", "prometheus/", "m3query/"}
    56  
    57  	urlTests := addPrefixes(urlPrefixes, []urlTest{
    58  		{"missing query", queryRangeURL("query", "")},
    59  		{"invalid query", queryRangeURL("query", "@!")},
    60  		{"missing start", queryRangeURL("start", "")},
    61  		{"invalid start", queryRangeURL("start", "INVALID")},
    62  		{"missing end", queryRangeURL("end", "")},
    63  		{"invalid end", queryRangeURL("end", "INVALID")},
    64  		{"missing step", queryRangeURL("step", "")},
    65  		{"invalid step", queryRangeURL("step", "INVALID")},
    66  		{"invalid timeout", queryRangeURL("timeout", "INVALID")},
    67  	})
    68  
    69  	testInvalidQueryReturns400(t, urlTests)
    70  }
    71  
    72  func testInvalidQueryReturns400(t *testing.T, tests []urlTest) {
    73  	coord := m3.Coordinator()
    74  
    75  	for _, tt := range tests {
    76  		tt := tt
    77  		t.Run(tt.name, func(t *testing.T) {
    78  			t.Parallel()
    79  			err := coord.RunQuery(verifyResponse(400), tt.url, nil)
    80  			assert.NoError(t, err, "for query '%v'", tt.url)
    81  		})
    82  	}
    83  }
    84  
    85  func addPrefixes(prefixes []string, tests []urlTest) []urlTest {
    86  	res := make([]urlTest, 0)
    87  	for _, prefix := range prefixes {
    88  		for _, t := range tests {
    89  			res = append(res, urlTest{
    90  				fmt.Sprintf("%v %v", prefix, t.name),
    91  				fmt.Sprintf("%v%v", prefix, t.url),
    92  			})
    93  		}
    94  	}
    95  	return res
    96  }
    97  
    98  func queryURL(key, value string) string {
    99  	params := map[string]string{
   100  		"query": "foo",
   101  	}
   102  
   103  	if value == "" {
   104  		delete(params, key)
   105  	} else {
   106  		params[key] = value
   107  	}
   108  
   109  	return "api/v1/query?" + queryString(params)
   110  }
   111  
   112  func queryRangeURL(key, value string) string {
   113  	params := map[string]string{
   114  		"query": "foo",
   115  		"start": "now",
   116  		"end":   "now",
   117  		"step":  "1000",
   118  	}
   119  
   120  	if value == "" {
   121  		delete(params, key)
   122  	} else {
   123  		params[key] = value
   124  	}
   125  
   126  	return "api/v1/query_range?" + queryString(params)
   127  }
   128  
   129  func queryString(params map[string]string) string {
   130  	p := make([]string, 0)
   131  	for k, v := range params {
   132  		p = append(p, fmt.Sprintf("%v=%v", k, v))
   133  	}
   134  
   135  	return strings.Join(p, "&")
   136  }
   137  
   138  func verifyResponse(expectedStatus int) resources.ResponseVerifier {
   139  	return func(status int, headers map[string][]string, resp string, err error) error {
   140  		if err != nil {
   141  			return err
   142  		}
   143  
   144  		if status != expectedStatus {
   145  			return fmt.Errorf("expeceted %v status code, got %v", expectedStatus, status)
   146  		}
   147  
   148  		if contentType, ok := headers["Content-Type"]; !ok {
   149  			return fmt.Errorf("missing Content-Type header")
   150  		} else if len(contentType) != 1 || contentType[0] != "application/json" {
   151  			return fmt.Errorf("expected json content type, got %v", contentType)
   152  		}
   153  
   154  		errorResponse := struct {
   155  			Status string `json:"status,omitempty"`
   156  			Error  string `json:"error,omitempty"`
   157  		}{}
   158  
   159  		err = json.Unmarshal([]byte(resp), &errorResponse)
   160  		if err != nil {
   161  			return fmt.Errorf("failed unmarshalling response: %w", err)
   162  		}
   163  
   164  		if errorResponse.Status != "error" {
   165  			return fmt.Errorf("expected body to contain status 'error', got %v", errorResponse.Status)
   166  		}
   167  
   168  		if errorResponse.Error == "" {
   169  			return fmt.Errorf("expected body to contain error message")
   170  		}
   171  
   172  		return nil
   173  	}
   174  }