477 lines
10 KiB
Go
477 lines
10 KiB
Go
|
package http
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"github.com/influxdata/flux/csv"
|
||
|
"github.com/influxdata/flux/lang"
|
||
|
"github.com/influxdata/platform/mock"
|
||
|
"net/http"
|
||
|
"net/http/httptest"
|
||
|
"reflect"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/influxdata/flux"
|
||
|
"github.com/influxdata/flux/ast"
|
||
|
"github.com/influxdata/platform"
|
||
|
"github.com/influxdata/platform/query"
|
||
|
)
|
||
|
|
||
|
func TestQueryRequest_WithDefaults(t *testing.T) {
|
||
|
type fields struct {
|
||
|
Spec *flux.Spec
|
||
|
AST *ast.Program
|
||
|
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,
|
||
|
org: tt.fields.org,
|
||
|
}
|
||
|
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
|
||
|
AST *ast.Program
|
||
|
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,
|
||
|
org: tt.fields.org,
|
||
|
}
|
||
|
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) {
|
||
|
flux.FinalizeBuiltIns()
|
||
|
type args struct {
|
||
|
p *ast.Program
|
||
|
now func() time.Time
|
||
|
}
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
args args
|
||
|
want *flux.Spec
|
||
|
wantErr bool
|
||
|
}{
|
||
|
{
|
||
|
name: "ast converts to spec",
|
||
|
args: args{
|
||
|
p: &ast.Program{},
|
||
|
now: func() time.Time { return time.Unix(0, 0) },
|
||
|
},
|
||
|
want: &flux.Spec{
|
||
|
Now: time.Unix(0, 0).UTC(),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "bad semantics error",
|
||
|
args: args{
|
||
|
p: &ast.Program{
|
||
|
Body: []ast.Statement{
|
||
|
&ast.ReturnStatement{},
|
||
|
},
|
||
|
},
|
||
|
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
|
||
|
AST *ast.Program
|
||
|
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",
|
||
|
},
|
||
|
},
|
||
|
Dialect: csv.Dialect{
|
||
|
ResultEncoderConfig: csv.ResultEncoderConfig{
|
||
|
NoHeader: false,
|
||
|
Delimiter: ',',
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "valid AST",
|
||
|
fields: fields{
|
||
|
AST: &ast.Program{},
|
||
|
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(),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
Dialect: csv.Dialect{
|
||
|
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(),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
Dialect: csv.Dialect{
|
||
|
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,
|
||
|
org: tt.fields.org,
|
||
|
}
|
||
|
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) {
|
||
|
t.Errorf("QueryRequest.ProxyRequest() = %v, want %v", got, tt.want)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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{
|
||
|
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeef"); return *s }(),
|
||
|
}, nil
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
want: &QueryRequest{
|
||
|
Query: "from()",
|
||
|
Type: "flux",
|
||
|
Dialect: QueryDialect{
|
||
|
Delimiter: ",",
|
||
|
DateTimeFormat: "RFC3339",
|
||
|
Header: func(x bool) *bool { return &x }(true),
|
||
|
},
|
||
|
org: &platform.Organization{
|
||
|
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeef"); return *s }(),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
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
|
||
|
}{
|
||
|
{
|
||
|
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{
|
||
|
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeef"); return *s }(),
|
||
|
}, nil
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
want: &query.ProxyRequest{
|
||
|
Request: query.Request{
|
||
|
OrganizationID: func() platform.ID { s, _ := platform.IDFromString("deadbeef"); return *s }(),
|
||
|
Compiler: lang.FluxCompiler{
|
||
|
Query: "from()",
|
||
|
},
|
||
|
},
|
||
|
Dialect: csv.Dialect{
|
||
|
ResultEncoderConfig: csv.ResultEncoderConfig{
|
||
|
NoHeader: false,
|
||
|
Delimiter: ',',
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
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
|
||
|
}
|
||
|
if !reflect.DeepEqual(got, tt.want) {
|
||
|
t.Errorf("decodeProxyQueryRequest() = %v, want %v", got, tt.want)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|