github.com/rohankumardubey/aresdb@v0.0.2-0.20190517170215-e54e3ca06b9c/api/query_handler_test.go (about)

     1  //  Copyright (c) 2017-2018 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package api
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net/http"
    22  	"net/http/httptest"
    23  
    24  	"github.com/uber/aresdb/memstore"
    25  	memMocks "github.com/uber/aresdb/memstore/mocks"
    26  	metaCom "github.com/uber/aresdb/metastore/common"
    27  
    28  	"encoding/json"
    29  	"github.com/gorilla/mux"
    30  	"github.com/onsi/ginkgo"
    31  	. "github.com/onsi/gomega"
    32  	"github.com/pkg/errors"
    33  	"github.com/uber/aresdb/common"
    34  	"github.com/uber/aresdb/query"
    35  )
    36  
    37  var _ = ginkgo.Describe("QueryHandler", func() {
    38  	var testServer *httptest.Server
    39  	var testSchema = memstore.NewTableSchema(&metaCom.Table{
    40  		Name:        "trips",
    41  		IsFactTable: false,
    42  		Columns: []metaCom.Column{
    43  			{
    44  				Name: "request_at",
    45  				Type: "Uint32",
    46  			},
    47  			{
    48  				Name: "fare_total",
    49  				Type: "Flaot",
    50  			},
    51  			{
    52  				Name: "city_id",
    53  				Type: "Uint16",
    54  			},
    55  			{
    56  				Name: "status",
    57  				Type: "SmallEnum",
    58  			},
    59  		},
    60  		Config: metaCom.TableConfig{
    61  			BatchSize: 10,
    62  		},
    63  	})
    64  
    65  	var memStore *memMocks.MemStore
    66  	ginkgo.BeforeEach(func() {
    67  		memStore = CreateMemStore(testSchema, 0, nil, CreateMockDiskStore())
    68  		queryHandler := NewQueryHandler(memStore, common.QueryConfig{
    69  			DeviceMemoryUtilization: 1.0,
    70  		})
    71  		testRouter := mux.NewRouter()
    72  		testRouter.HandleFunc("/aql", queryHandler.HandleAQL).Methods(http.MethodGet, http.MethodPost)
    73  		testServer = httptest.NewUnstartedServer(WithPanicHandling(testRouter))
    74  		testServer.Start()
    75  	})
    76  
    77  	ginkgo.AfterEach(func() {
    78  		testServer.Close()
    79  	})
    80  
    81  	ginkgo.It("HandleAQL should succeed on valid requests", func() {
    82  		hostPort := testServer.Listener.Addr().String()
    83  		// Invalid timeBucketizer days.
    84  		query := `
    85  			{
    86  			  "queries": [
    87  				{
    88  				  "measures": [
    89  					{
    90  					  "sqlExpression": "count(*)"
    91  					}
    92  				  ],
    93  				  "rowFilters": [
    94  					"trips.status!='ACTIVE'"
    95  				  ],
    96  				  "table": "trips",
    97  				  "timeFilter": {
    98  					"column": "trips.request_at",
    99  					"from": "-6d"
   100  				  },
   101  				  "dimensions": [
   102  					{
   103  					  "sqlExpression": "trips.request_at",
   104  					  "timeBucketizer": "day",
   105  					  "timeUnit": "second"
   106  					}
   107  				  ]
   108  				}
   109  			  ]
   110  			}
   111  		`
   112  		resp, err := http.Post(fmt.Sprintf("http://%s/aql", hostPort), "application/json", bytes.NewBuffer([]byte(query)))
   113  		Ω(err).Should(BeNil())
   114  		bs, err := ioutil.ReadAll(resp.Body)
   115  		Ω(err).Should(BeNil())
   116  		Ω(string(bs)).Should(MatchJSON(`{
   117  				"results": [
   118  				  {}
   119  				]
   120  			  }`))
   121  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   122  	})
   123  
   124  	ginkgo.It("HandleAQL should fail on request that cannot be unmarshaled", func() {
   125  		hostPort := testServer.Listener.Addr().String()
   126  		resp, err := http.Post(fmt.Sprintf("http://%s/aql", hostPort), "application/json", bytes.NewBuffer([]byte{}))
   127  		Ω(err).Should(BeNil())
   128  		bs, err := ioutil.ReadAll(resp.Body)
   129  		Ω(err).Should(BeNil())
   130  		Ω(string(bs)).Should(MatchJSON(`
   131  			{"message":"Bad request: failed to unmarshal request body","cause":{"Offset":0}}
   132  		`))
   133  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   134  	})
   135  
   136  	ginkgo.It("HandleAQL should succeed on empty query requests", func() {
   137  		hostPort := testServer.Listener.Addr().String()
   138  		query := `
   139  			{
   140  			  "queries": [
   141  			  ]
   142  			}
   143  		`
   144  		resp, err := http.Post(fmt.Sprintf("http://%s/aql", hostPort), "application/json", bytes.NewBuffer([]byte(query)))
   145  		Ω(err).Should(BeNil())
   146  		bs, err := ioutil.ReadAll(resp.Body)
   147  		Ω(err).Should(BeNil())
   148  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   149  		Ω(string(bs)).Should(MatchJSON(`
   150  			{
   151  				"results": []
   152              }
   153  		`))
   154  	})
   155  
   156  	ginkgo.It("HandleAQL should fail requests without queries", func() {
   157  		hostPort := testServer.Listener.Addr().String()
   158  		query := `
   159  			{"q":{}}
   160  		`
   161  		resp, err := http.Post(fmt.Sprintf("http://%s/aql", hostPort), "application/json", bytes.NewBuffer([]byte(query)))
   162  		Ω(err).Should(BeNil())
   163  		bs, err := ioutil.ReadAll(resp.Body)
   164  		Ω(err).Should(BeNil())
   165  		Ω(resp.StatusCode).Should(Equal(http.StatusBadRequest))
   166  		Ω(string(bs)).Should(ContainSubstring("Bad request: missing/invalid parameter"))
   167  	})
   168  
   169  	ginkgo.It("HandleAQL should work for queries overwritten by parameter q", func() {
   170  		hostPort := testServer.Listener.Addr().String()
   171  		query := "{}"
   172  		resp, err := http.Post(fmt.Sprintf("http://%s/aql?q={\"queries\":[]}", hostPort), "application/json", bytes.NewBuffer([]byte(query)))
   173  		Ω(err).Should(BeNil())
   174  		_, err = ioutil.ReadAll(resp.Body)
   175  		Ω(err).Should(BeNil())
   176  		Ω(resp.StatusCode).Should(Equal(http.StatusOK))
   177  	})
   178  
   179  	ginkgo.It("ReportError should work", func() {
   180  		rw := NewHLLQueryResponseWriter()
   181  		Ω(rw.GetStatusCode()).Should(Equal(http.StatusOK))
   182  		rw.ReportError(0, "test", errors.New("test err"), http.StatusBadRequest)
   183  		Ω(rw.GetStatusCode()).Should(Equal(http.StatusBadRequest))
   184  
   185  		hllRW := rw.(*HLLQueryResponseWriter)
   186  		Ω(hllRW.response.GetBytes()).Should(Equal([]byte{2, 1, 237, 172, 0, 0, 0, 0, 8, 0, 0, 0,
   187  			1, 0, 0, 0, 116, 101, 115, 116, 32, 101, 114, 114, 0, 0, 0, 0, 0, 0, 0, 0}))
   188  	})
   189  
   190  	ginkgo.It("ReportQueryContext should work", func() {
   191  		rw := NewHLLQueryResponseWriter()
   192  		Ω(func() { rw.ReportQueryContext(nil) }).ShouldNot(Panic())
   193  	})
   194  
   195  	ginkgo.It("ReportResult should work", func() {
   196  		rw := NewHLLQueryResponseWriter()
   197  		rw.ReportResult(0, &query.AQLQueryContext{HLLQueryResult: []byte{0, 0, 0, 0, 0, 0, 0, 0}})
   198  
   199  		hllRW := rw.(*HLLQueryResponseWriter)
   200  		Ω(hllRW.response.GetBytes()).Should(Equal([]byte{2, 1, 237, 172, 0, 0, 0, 0, 8, 0, 0,
   201  			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
   202  	})
   203  
   204  	ginkgo.It("Report hll result in JsonResponseWriter should work", func() {
   205  		q := &query.AQLQuery{
   206  			Table: "trips",
   207  		}
   208  
   209  		oopk := query.OOPKContext{
   210  			// C.AGGR_HLL
   211  			AggregateType: 10,
   212  		}
   213  
   214  		data, err := ioutil.ReadFile("../testing/data/query/hll")
   215  		Ω(err).Should(BeNil())
   216  		rw := NewJSONQueryResponseWriter(2)
   217  		rw.ReportResult(0, &query.AQLQueryContext{
   218  			Query:          q,
   219  			OOPK:           oopk,
   220  			HLLQueryResult: []byte{0, 0, 0, 0, 0, 0, 0, 0},
   221  		})
   222  		rw.ReportResult(1, &query.AQLQueryContext{
   223  			Query:          q,
   224  			OOPK:           oopk,
   225  			HLLQueryResult: data,
   226  		})
   227  
   228  		Ω(rw.(*JSONQueryResponseWriter).response.Results).Should(HaveLen(2))
   229  		resultJson, err := json.Marshal(rw.(*JSONQueryResponseWriter).response.Results)
   230  		Ω(resultJson).Should(MatchJSON(`
   231  		[
   232  			null,
   233  			{
   234  				"1": {
   235  					"c": {
   236  						"2": 2
   237  					}
   238  				},
   239  				"4294967295": {
   240  					"d": {
   241  						"514": 4
   242  					}
   243  				},
   244  				"NULL": {
   245  					"NULL": {
   246  						"NULL": 3
   247  					}
   248  				}
   249  			}
   250  		]
   251  		`))
   252  		Ω(rw.(*JSONQueryResponseWriter).response.Errors).Should(HaveLen(2))
   253  		Ω(rw.(*JSONQueryResponseWriter).response.Errors[0]).ShouldNot(BeNil())
   254  		Ω(rw.(*JSONQueryResponseWriter).response.Errors[1]).Should(BeNil())
   255  	})
   256  
   257  	ginkgo.It("Verbose should work", func() {
   258  		hostPort := testServer.Listener.Addr().String()
   259  		query := `
   260  			{
   261  			  "queries": [
   262  				{
   263  				  "measures": [
   264  					{
   265  					  "sqlExpression": "count(*)"
   266  					}
   267  				  ],
   268  				  "rowFilters": [
   269  					"trips.status!='ACTIVE'"
   270  				  ],
   271  				  "table": "trips",
   272  				  "timeFilter": {
   273  					"column": "trips.request_at",
   274  					"from": "-6d"
   275  				  },
   276  				  "dimensions": [
   277  					{
   278  					  "sqlExpression": "trips.request_at",
   279  					  "timeBucketizer": "day",
   280  					  "timeUnit": "second"
   281  					}
   282  				  ]
   283  				}
   284  			  ]
   285  			}
   286  		`
   287  		resp, err := http.Post(fmt.Sprintf("http://%s/aql?verbose=1", hostPort), "application/json", bytes.NewBuffer([]byte(query)))
   288  		Ω(err).Should(BeNil())
   289  		bs, err := ioutil.ReadAll(resp.Body)
   290  		Ω(err).Should(BeNil())
   291  		Ω(string(bs)).Should(ContainSubstring("FLOOR"))
   292  		Ω(string(bs)).Should(ContainSubstring("Unsigned"))
   293  		Ω(string(bs)).Should(ContainSubstring("allBatches"))
   294  	})
   295  })