2018-09-14 04:01:07 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2018-11-20 18:56:58 +00:00
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
2018-09-14 04:01:07 +00:00
|
|
|
"github.com/influxdata/flux"
|
|
|
|
"github.com/influxdata/flux/ast"
|
2018-10-10 19:19:20 +00:00
|
|
|
"github.com/influxdata/flux/csv"
|
|
|
|
"github.com/influxdata/flux/lang"
|
2018-09-14 04:01:07 +00:00
|
|
|
"github.com/influxdata/platform"
|
2018-10-10 19:19:20 +00:00
|
|
|
"github.com/influxdata/platform/mock"
|
2018-09-14 04:01:07 +00:00
|
|
|
"github.com/influxdata/platform/query"
|
2018-10-23 17:51:13 +00:00
|
|
|
_ "github.com/influxdata/platform/query/builtin"
|
2018-09-14 04:01:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestQueryRequest_WithDefaults(t *testing.T) {
|
|
|
|
type fields struct {
|
|
|
|
Spec *flux.Spec
|
2019-01-04 18:08:07 +00:00
|
|
|
AST *ast.Package
|
2018-09-14 04:01:07 +00:00
|
|
|
Query string
|
|
|
|
Type string
|
|
|
|
Dialect QueryDialect
|
|
|
|
org *platform.Organization
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
fields fields
|
|
|
|
want QueryRequest
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "empty query has defaults set",
|
|
|
|
want: QueryRequest{
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: ",",
|
|
|
|
DateTimeFormat: "RFC3339",
|
|
|
|
Header: func(x bool) *bool { return &x }(true),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
r := QueryRequest{
|
|
|
|
Spec: tt.fields.Spec,
|
|
|
|
AST: tt.fields.AST,
|
|
|
|
Query: tt.fields.Query,
|
|
|
|
Type: tt.fields.Type,
|
|
|
|
Dialect: tt.fields.Dialect,
|
2018-11-07 16:31:42 +00:00
|
|
|
Org: tt.fields.org,
|
2018-09-14 04:01:07 +00:00
|
|
|
}
|
|
|
|
if got := r.WithDefaults(); !reflect.DeepEqual(got, tt.want) {
|
|
|
|
t.Errorf("QueryRequest.WithDefaults() = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestQueryRequest_Validate(t *testing.T) {
|
|
|
|
type fields struct {
|
|
|
|
Spec *flux.Spec
|
2019-01-04 18:08:07 +00:00
|
|
|
AST *ast.Package
|
2018-09-14 04:01:07 +00:00
|
|
|
Query string
|
|
|
|
Type string
|
|
|
|
Dialect QueryDialect
|
|
|
|
org *platform.Organization
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
fields fields
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "requires query, spec, or ast",
|
|
|
|
fields: fields{
|
|
|
|
Type: "flux",
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "requires flux type",
|
|
|
|
fields: fields{
|
|
|
|
Query: "howdy",
|
|
|
|
Type: "doody",
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "comment must be a single character",
|
|
|
|
fields: fields{
|
|
|
|
Query: "from()",
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
CommentPrefix: "error!",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "delimiter must be a single character",
|
|
|
|
fields: fields{
|
|
|
|
Query: "from()",
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: "",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "characters must be unicode runes",
|
|
|
|
fields: fields{
|
|
|
|
Query: "from()",
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: string([]byte{0x80}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unknown annotations",
|
|
|
|
fields: fields{
|
|
|
|
Query: "from()",
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: ",",
|
|
|
|
Annotations: []string{"error"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "unknown date time format",
|
|
|
|
fields: fields{
|
|
|
|
Query: "from()",
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: ",",
|
|
|
|
DateTimeFormat: "error",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "valid query",
|
|
|
|
fields: fields{
|
|
|
|
Query: "from()",
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: ",",
|
|
|
|
DateTimeFormat: "RFC3339",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
r := QueryRequest{
|
|
|
|
Spec: tt.fields.Spec,
|
|
|
|
AST: tt.fields.AST,
|
|
|
|
Query: tt.fields.Query,
|
|
|
|
Type: tt.fields.Type,
|
|
|
|
Dialect: tt.fields.Dialect,
|
2018-11-07 16:31:42 +00:00
|
|
|
Org: tt.fields.org,
|
2018-09-14 04:01:07 +00:00
|
|
|
}
|
|
|
|
if err := r.Validate(); (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("QueryRequest.Validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_toSpec(t *testing.T) {
|
|
|
|
type args struct {
|
2019-01-04 18:08:07 +00:00
|
|
|
p *ast.Package
|
2018-09-14 04:01:07 +00:00
|
|
|
now func() time.Time
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
want *flux.Spec
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "ast converts to spec",
|
|
|
|
args: args{
|
2019-01-04 18:08:07 +00:00
|
|
|
p: &ast.Package{},
|
2018-09-14 04:01:07 +00:00
|
|
|
now: func() time.Time { return time.Unix(0, 0) },
|
|
|
|
},
|
|
|
|
want: &flux.Spec{
|
|
|
|
Now: time.Unix(0, 0).UTC(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "bad semantics error",
|
|
|
|
args: args{
|
2019-01-04 18:08:07 +00:00
|
|
|
p: &ast.Package{
|
|
|
|
Files: []*ast.File{{
|
|
|
|
Body: []ast.Statement{
|
|
|
|
&ast.ReturnStatement{},
|
|
|
|
},
|
|
|
|
}},
|
2018-09-14 04:01:07 +00:00
|
|
|
},
|
|
|
|
now: func() time.Time { return time.Unix(0, 0) },
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
|
|
|
got, err := toSpec(tt.args.p, tt.args.now)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("toSpec() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
|
|
t.Errorf("toSpec() = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestQueryRequest_proxyRequest(t *testing.T) {
|
|
|
|
type fields struct {
|
|
|
|
Spec *flux.Spec
|
2019-01-04 18:08:07 +00:00
|
|
|
AST *ast.Package
|
2018-09-14 04:01:07 +00:00
|
|
|
Query string
|
|
|
|
Type string
|
|
|
|
Dialect QueryDialect
|
|
|
|
org *platform.Organization
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
fields fields
|
|
|
|
now func() time.Time
|
|
|
|
want *query.ProxyRequest
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "requires query, spec, or ast",
|
|
|
|
fields: fields{
|
|
|
|
Type: "flux",
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "valid query",
|
|
|
|
fields: fields{
|
|
|
|
Query: "howdy",
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: ",",
|
|
|
|
DateTimeFormat: "RFC3339",
|
|
|
|
},
|
|
|
|
org: &platform.Organization{},
|
|
|
|
},
|
|
|
|
want: &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
|
|
|
Compiler: lang.FluxCompiler{
|
|
|
|
Query: "howdy",
|
|
|
|
},
|
|
|
|
},
|
2018-11-07 16:31:42 +00:00
|
|
|
Dialect: &csv.Dialect{
|
2018-09-14 04:01:07 +00:00
|
|
|
ResultEncoderConfig: csv.ResultEncoderConfig{
|
|
|
|
NoHeader: false,
|
|
|
|
Delimiter: ',',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "valid AST",
|
|
|
|
fields: fields{
|
2019-01-04 18:08:07 +00:00
|
|
|
AST: &ast.Package{},
|
2018-09-14 04:01:07 +00:00
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: ",",
|
|
|
|
DateTimeFormat: "RFC3339",
|
|
|
|
},
|
|
|
|
org: &platform.Organization{},
|
|
|
|
},
|
|
|
|
now: func() time.Time { return time.Unix(0, 0).UTC() },
|
|
|
|
want: &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
|
|
|
Compiler: lang.SpecCompiler{
|
|
|
|
Spec: &flux.Spec{
|
|
|
|
Now: time.Unix(0, 0).UTC(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-11-07 16:31:42 +00:00
|
|
|
Dialect: &csv.Dialect{
|
2018-09-14 04:01:07 +00:00
|
|
|
ResultEncoderConfig: csv.ResultEncoderConfig{
|
|
|
|
NoHeader: false,
|
|
|
|
Delimiter: ',',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "valid spec",
|
|
|
|
fields: fields{
|
|
|
|
Type: "flux",
|
|
|
|
Spec: &flux.Spec{
|
|
|
|
Now: time.Unix(0, 0).UTC(),
|
|
|
|
},
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: ",",
|
|
|
|
DateTimeFormat: "RFC3339",
|
|
|
|
},
|
|
|
|
org: &platform.Organization{},
|
|
|
|
},
|
|
|
|
want: &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
|
|
|
Compiler: lang.SpecCompiler{
|
|
|
|
Spec: &flux.Spec{
|
|
|
|
Now: time.Unix(0, 0).UTC(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-11-07 16:31:42 +00:00
|
|
|
Dialect: &csv.Dialect{
|
2018-09-14 04:01:07 +00:00
|
|
|
ResultEncoderConfig: csv.ResultEncoderConfig{
|
|
|
|
NoHeader: false,
|
|
|
|
Delimiter: ',',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
r := QueryRequest{
|
|
|
|
Spec: tt.fields.Spec,
|
|
|
|
AST: tt.fields.AST,
|
|
|
|
Query: tt.fields.Query,
|
|
|
|
Type: tt.fields.Type,
|
|
|
|
Dialect: tt.fields.Dialect,
|
2018-11-07 16:31:42 +00:00
|
|
|
Org: tt.fields.org,
|
2018-09-14 04:01:07 +00:00
|
|
|
}
|
|
|
|
got, err := r.proxyRequest(tt.now)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("QueryRequest.ProxyRequest() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
2018-11-07 16:31:42 +00:00
|
|
|
t.Errorf("QueryRequest.ProxyRequest() = %#v, want %#v", got, tt.want)
|
2018-09-14 04:01:07 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_decodeQueryRequest(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
ctx context.Context
|
|
|
|
r *http.Request
|
|
|
|
svc platform.OrganizationService
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
want *QueryRequest
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "valid query request",
|
|
|
|
args: args{
|
|
|
|
r: httptest.NewRequest("POST", "/", bytes.NewBufferString(`{"query": "from()"}`)),
|
|
|
|
svc: &mock.OrganizationService{
|
|
|
|
FindOrganizationF: func(ctx context.Context, filter platform.OrganizationFilter) (*platform.Organization, error) {
|
|
|
|
return &platform.Organization{
|
2018-09-25 16:55:34 +00:00
|
|
|
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeefdeadbeef"); return *s }(),
|
2018-09-14 04:01:07 +00:00
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: &QueryRequest{
|
|
|
|
Query: "from()",
|
|
|
|
Type: "flux",
|
|
|
|
Dialect: QueryDialect{
|
|
|
|
Delimiter: ",",
|
|
|
|
DateTimeFormat: "RFC3339",
|
|
|
|
Header: func(x bool) *bool { return &x }(true),
|
|
|
|
},
|
2018-11-07 16:31:42 +00:00
|
|
|
Org: &platform.Organization{
|
2018-09-25 16:55:34 +00:00
|
|
|
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeefdeadbeef"); return *s }(),
|
2018-09-14 04:01:07 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error decoding json",
|
|
|
|
args: args{
|
|
|
|
r: httptest.NewRequest("POST", "/", bytes.NewBufferString(`error`)),
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error validating query",
|
|
|
|
args: args{
|
|
|
|
r: httptest.NewRequest("POST", "/", bytes.NewBufferString(`{}`)),
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
got, err := decodeQueryRequest(tt.args.ctx, tt.args.r, tt.args.svc)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("decodeQueryRequest() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
|
|
t.Errorf("decodeQueryRequest() = %v, want %v", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test_decodeProxyQueryRequest(t *testing.T) {
|
|
|
|
type args struct {
|
|
|
|
ctx context.Context
|
|
|
|
r *http.Request
|
|
|
|
auth *platform.Authorization
|
|
|
|
svc platform.OrganizationService
|
|
|
|
}
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
args args
|
|
|
|
want *query.ProxyRequest
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
2019-01-02 19:36:16 +00:00
|
|
|
name: "valid post query request",
|
2018-09-14 04:01:07 +00:00
|
|
|
args: args{
|
|
|
|
r: httptest.NewRequest("POST", "/", bytes.NewBufferString(`{"query": "from()"}`)),
|
|
|
|
svc: &mock.OrganizationService{
|
|
|
|
FindOrganizationF: func(ctx context.Context, filter platform.OrganizationFilter) (*platform.Organization, error) {
|
|
|
|
return &platform.Organization{
|
2018-09-25 16:55:34 +00:00
|
|
|
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeefdeadbeef"); return *s }(),
|
2018-09-14 04:01:07 +00:00
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
2018-09-25 16:55:34 +00:00
|
|
|
OrganizationID: func() platform.ID { s, _ := platform.IDFromString("deadbeefdeadbeef"); return *s }(),
|
2018-09-14 04:01:07 +00:00
|
|
|
Compiler: lang.FluxCompiler{
|
|
|
|
Query: "from()",
|
|
|
|
},
|
|
|
|
},
|
2018-11-07 16:31:42 +00:00
|
|
|
Dialect: &csv.Dialect{
|
2018-09-14 04:01:07 +00:00
|
|
|
ResultEncoderConfig: csv.ResultEncoderConfig{
|
|
|
|
NoHeader: false,
|
|
|
|
Delimiter: ',',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-01-02 19:36:16 +00:00
|
|
|
{
|
|
|
|
name: "valid get query request",
|
|
|
|
args: args{
|
|
|
|
r: httptest.NewRequest("GET", "/api/v2/query?query=from(bucket%3A%20%22mybucket%22)&org=myorg", nil),
|
|
|
|
svc: &mock.OrganizationService{
|
|
|
|
FindOrganizationF: func(ctx context.Context, filter platform.OrganizationFilter) (*platform.Organization, error) {
|
|
|
|
return &platform.Organization{
|
|
|
|
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeefdeadbeef"); return *s }(),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
|
|
|
OrganizationID: func() platform.ID { s, _ := platform.IDFromString("deadbeefdeadbeef"); return *s }(),
|
|
|
|
Compiler: lang.FluxCompiler{
|
|
|
|
Query: "from(bucket: \"mybucket\")",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Dialect: &csv.Dialect{
|
|
|
|
ResultEncoderConfig: csv.ResultEncoderConfig{
|
|
|
|
NoHeader: false,
|
|
|
|
Delimiter: ',',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2018-09-14 04:01:07 +00:00
|
|
|
}
|
2018-11-20 18:56:58 +00:00
|
|
|
var cmpOptions = cmp.Options{
|
|
|
|
cmpopts.IgnoreUnexported(query.ProxyRequest{}),
|
|
|
|
cmpopts.IgnoreUnexported(query.Request{}),
|
|
|
|
cmpopts.EquateEmpty(),
|
|
|
|
}
|
2018-09-14 04:01:07 +00:00
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
got, err := decodeProxyQueryRequest(tt.args.ctx, tt.args.r, tt.args.auth, tt.args.svc)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("decodeProxyQueryRequest() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
2018-11-20 18:56:58 +00:00
|
|
|
if diff := cmp.Diff(got, tt.want, cmpOptions...); diff != "" {
|
|
|
|
t.Errorf("decodeProxyQueryRequest() = got/want %v", diff)
|
2018-09-14 04:01:07 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|