package httpd_test import ( "bytes" "encoding/json" "fmt" "math" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" "github.com/influxdata/influxdb/models" "github.com/influxdata/influxdb/query" "github.com/influxdata/influxdb/services/httpd" "github.com/tinylib/msgp/msgp" ) func TestResponseWriter_CSV(t *testing.T) { tableTest := []struct { header string }{ {header: "*/csv"}, {header: "text/*"}, {header: "text/csv"}, {header: "text/csv,application/json"}, {header: "text/csv;q=1,application/json"}, {header: "text/csv;q=0.9,application/json;q=0.8"}, {header: "application/json;q=0.8,text/csv;q=0.9"}, } for _, testCase := range tableTest { testCase := testCase t.Run(testCase.header, func(t *testing.T) { t.Parallel() header := make(http.Header) header.Set("Accept", testCase.header) r := &http.Request{ Header: header, URL: &url.URL{}, } w := httptest.NewRecorder() writer := httpd.NewResponseWriter(w, r) n, err := writer.WriteResponse(httpd.Response{ Results: []*query.Result{ { StatementID: 0, Series: []*models.Row{ { Name: "cpu", Tags: map[string]string{ "host": "server01", "region": "uswest", }, Columns: []string{"time", "value"}, Values: [][]interface{}{ {time.Unix(0, 10), float64(2.5)}, {time.Unix(0, 20), int64(5)}, {time.Unix(0, 30), nil}, {time.Unix(0, 40), "foobar"}, {time.Unix(0, 50), true}, {time.Unix(0, 60), false}, {time.Unix(0, 70), uint64(math.MaxInt64 + 1)}, }, }, { Name: "cpu", Tags: map[string]string{ "host": "", "region": "", }, Columns: []string{"time", "value"}, Values: [][]interface{}{ {time.Unix(0, 10), float64(2.5)}, {time.Unix(0, 20), int64(5)}, {time.Unix(0, 30), nil}, {time.Unix(0, 40), "foobar"}, {time.Unix(0, 50), true}, {time.Unix(0, 60), false}, {time.Unix(0, 70), uint64(math.MaxInt64 + 1)}, }, }, }, }, }, }) if err != nil { t.Fatalf("unexpected error: %s", err) } if got, want := w.Body.String(), `name,tags,time,value cpu,"host=server01,region=uswest",10,2.5 cpu,"host=server01,region=uswest",20,5 cpu,"host=server01,region=uswest",30, cpu,"host=server01,region=uswest",40,foobar cpu,"host=server01,region=uswest",50,true cpu,"host=server01,region=uswest",60,false cpu,"host=server01,region=uswest",70,9223372036854775808 cpu,,10,2.5 cpu,,20,5 cpu,,30, cpu,,40,foobar cpu,,50,true cpu,,60,false cpu,,70,9223372036854775808 `; got != want { t.Errorf("unexpected output:\n\ngot=%v\nwant=%s", got, want) } else if got, want := n, len(want); got != want { t.Errorf("unexpected output length: got=%d want=%d", got, want) } }) } } func TestResponseWriter_MessagePack(t *testing.T) { tableTest := []struct { header string }{ {header: "*/x-msgpack"}, {header: "application/x-msgpack"}, {header: "application/x-msgpack,application/json"}, {header: "application/x-msgpack;q=1,application/json"}, {header: "application/x-msgpack;q=0.9,application/json;q=0.8"}, {header: "application/json;q=0.8,application/x-msgpack;q=0.9"}, } for _, testCase := range tableTest { testCase := testCase t.Run(testCase.header, func(t *testing.T) { t.Parallel() header := make(http.Header) header.Set("Accept", testCase.header) r := &http.Request{ Header: header, URL: &url.URL{}, } w := httptest.NewRecorder() writer := httpd.NewResponseWriter(w, r) _, err := writer.WriteResponse(httpd.Response{ Results: []*query.Result{ { StatementID: 0, Series: []*models.Row{ { Name: "cpu", Tags: map[string]string{ "host": "server01", }, Columns: []string{"time", "value"}, Values: [][]interface{}{ {time.Unix(0, 10), float64(2.5)}, {time.Unix(0, 20), int64(5)}, {time.Unix(0, 30), nil}, {time.Unix(0, 40), "foobar"}, {time.Unix(0, 50), true}, {time.Unix(0, 60), false}, {time.Unix(0, 70), uint64(math.MaxInt64 + 1)}, }, }, }, }, }, }) if err != nil { t.Fatalf("unexpected error: %s", err) } // The reader always reads times as time.Local so encode the expected response // as JSON and insert it into the expected values. values, err := json.Marshal([][]interface{}{ {time.Unix(0, 10).Local(), float64(2.5)}, {time.Unix(0, 20).Local(), int64(5)}, {time.Unix(0, 30).Local(), nil}, {time.Unix(0, 40).Local(), "foobar"}, {time.Unix(0, 50).Local(), true}, {time.Unix(0, 60).Local(), false}, {time.Unix(0, 70).Local(), uint64(math.MaxInt64 + 1)}, }) if err != nil { t.Fatalf("unexpected error: %s", err) } reader := msgp.NewReader(w.Body) var buf bytes.Buffer if _, err := reader.WriteToJSON(&buf); err != nil { t.Fatalf("unexpected error: %s", err) } want := fmt.Sprintf(`{"results":[{"statement_id":0,"series":[{"name":"cpu","tags":{"host":"server01"},"columns":["time","value"],"values":%s}]}]}`, string(values)) if got := strings.TrimSpace(buf.String()); got != want { t.Fatalf("unexpected output:\n\ngot=%v\nwant=%v", got, want) } }) } } func TestResponseWriter_MessagePack_Error(t *testing.T) { tableTest := []struct { header string }{ {header: "application/x-msgpack"}, {header: "application/x-msgpack,application/json"}, {header: "application/x-msgpack;q=1,application/json"}, {header: "application/x-msgpack;q=0.9,application/json;q=0.8"}, {header: "application/json;q=0.8,application/x-msgpack;q=0.9"}, } for _, testCase := range tableTest { testCase := testCase t.Run(testCase.header, func(t *testing.T) { t.Parallel() header := make(http.Header) header.Set("Accept", testCase.header) r := &http.Request{ Header: header, URL: &url.URL{}, } w := httptest.NewRecorder() writer := httpd.NewResponseWriter(w, r) writer.WriteResponse(httpd.Response{ Err: fmt.Errorf("test error"), }) reader := msgp.NewReader(w.Body) var buf bytes.Buffer if _, err := reader.WriteToJSON(&buf); err != nil { t.Fatalf("unexpected error: %s", err) } want := `{"error":"test error"}` if have := strings.TrimSpace(buf.String()); have != want { t.Fatalf("unexpected output: %s != %s", have, want) } }) } } func TestResponseWriter_CSV_DifferentColumns(t *testing.T) { header := make(http.Header) header.Set("Accept", "text/csv") r := &http.Request{ Header: header, URL: &url.URL{}, } w := httptest.NewRecorder() writer := httpd.NewResponseWriter(w, r) writer.WriteResponse(httpd.Response{ Results: []*query.Result{ { StatementID: 0, Series: []*models.Row{ { Name: "network", Columns: []string{"hostname"}, Values: [][]interface{}{ {"localhost"}, }, }, { Name: "runtime", Columns: []string{"GOARCH", "GOMAXPROCS", "GOOS", "version"}, Values: [][]interface{}{ {"amd64", int64(8), "darwin", "go1.12"}, }, }, }, }, }, }) if got, want := w.Body.String(), `name,tags,hostname network,,localhost name,tags,GOARCH,GOMAXPROCS,GOOS,version runtime,,amd64,8,darwin,go1.12 `; got != want { t.Errorf("unexpected output:\n\ngot=%v\nwant=%s", got, want) } }