// Ensure the handler can accept an async query. func TestHandler_Query_Async(t *testing.T) { done := make(chan struct{}) h := NewHandler(false) h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error { if stmt.String() != `SELECT * FROM bar` { t.Fatalf("unexpected query: %s", stmt.String()) } else if ctx.Database != `foo` { t.Fatalf("unexpected db: %s", ctx.Database) } ctx.Results <- &influxql.Result{StatementID: 1, Series: models.Rows([]*models.Row{{Name: "series0"}})} ctx.Results <- &influxql.Result{StatementID: 2, Series: models.Rows([]*models.Row{{Name: "series1"}})} close(done) return nil } w := httptest.NewRecorder() h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar&async=true", nil)) if w.Code != http.StatusNoContent { t.Fatalf("unexpected status: %d", w.Code) } else if body := strings.TrimSpace(w.Body.String()); body != `` { t.Fatalf("unexpected body: %s", body) } // Wait to make sure the async query runs and completes. timer := time.NewTimer(100 * time.Millisecond) defer timer.Stop() select { case <-timer.C: t.Fatal("timeout while waiting for async query to complete") case <-done: } }
// Ensure the handler merges results from the same statement. func TestHandler_Query_MergeResults(t *testing.T) { h := NewHandler(false) h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx *influxql.ExecutionContext) error { ctx.Results <- &influxql.Result{StatementID: 1, Series: models.Rows([]*models.Row{{Name: "series0"}})} ctx.Results <- &influxql.Result{StatementID: 1, Series: models.Rows([]*models.Row{{Name: "series1"}})} return nil } w := httptest.NewRecorder() h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar", nil)) if w.Code != http.StatusOK { t.Fatalf("unexpected status: %d", w.Code) } else if w.Body.String() != `{"results":[{"series":[{"name":"series0"},{"name":"series1"}]}]}` { t.Fatalf("unexpected body: %s", w.Body.String()) } }
// Ensure the handler merges results from the same statement. func TestHandler_Query_MergeResults(t *testing.T) { h := NewHandler(false) h.QueryExecutor.ExecuteQueryFn = func(q *influxql.Query, db string, chunkSize int, closing chan struct{}) <-chan *influxql.Result { return NewResultChan( &influxql.Result{StatementID: 1, Series: models.Rows([]*models.Row{{Name: "series0"}})}, &influxql.Result{StatementID: 1, Series: models.Rows([]*models.Row{{Name: "series1"}})}, ) } w := httptest.NewRecorder() h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar", nil)) if w.Code != http.StatusOK { t.Fatalf("unexpected status: %d", w.Code) } else if w.Body.String() != `{"results":[{"series":[{"name":"series0"},{"name":"series1"}]}]}` { t.Fatalf("unexpected body: %s", w.Body.String()) } }
// Ensure the handler returns results from a query (including nil results). func TestHandler_Query(t *testing.T) { h := NewHandler(false) h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error { if stmt.String() != `SELECT * FROM bar` { t.Fatalf("unexpected query: %s", stmt.String()) } else if ctx.Database != `foo` { t.Fatalf("unexpected db: %s", ctx.Database) } ctx.Results <- &influxql.Result{StatementID: 1, Series: models.Rows([]*models.Row{{Name: "series0"}})} ctx.Results <- &influxql.Result{StatementID: 2, Series: models.Rows([]*models.Row{{Name: "series1"}})} return nil } w := httptest.NewRecorder() h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar", nil)) if w.Code != http.StatusOK { t.Fatalf("unexpected status: %d", w.Code) } else if body := strings.TrimSpace(w.Body.String()); body != `{"results":[{"statement_id":1,"series":[{"name":"series0"}]},{"statement_id":2,"series":[{"name":"series1"}]}]}` { t.Fatalf("unexpected body: %s", body) } }
// Ensure the handler returns results from a query passed as a file. func TestHandler_Query_File(t *testing.T) { h := NewHandler(false) h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error { if stmt.String() != `SELECT * FROM bar` { t.Fatalf("unexpected query: %s", stmt.String()) } else if ctx.Database != `foo` { t.Fatalf("unexpected db: %s", ctx.Database) } ctx.Results <- &influxql.Result{StatementID: 1, Series: models.Rows([]*models.Row{{Name: "series0"}})} ctx.Results <- &influxql.Result{StatementID: 2, Series: models.Rows([]*models.Row{{Name: "series1"}})} return nil } var body bytes.Buffer writer := multipart.NewWriter(&body) part, err := writer.CreateFormFile("q", "") if err != nil { t.Fatal(err) } io.WriteString(part, "SELECT * FROM bar") if err := writer.Close(); err != nil { t.Fatal(err) } r := MustNewJSONRequest("POST", "/query?db=foo", &body) r.Header.Set("Content-Type", writer.FormDataContentType()) w := httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("unexpected status: %d", w.Code) } else if body := strings.TrimSpace(w.Body.String()); body != `{"results":[{"statement_id":1,"series":[{"name":"series0"}]},{"statement_id":2,"series":[{"name":"series1"}]}]}` { t.Fatalf("unexpected body: %s", body) } }
// Test query with user authentication. func TestHandler_Query_Auth(t *testing.T) { // Create the handler to be tested. h := NewHandler(true) // Set mock meta client functions for the handler to use. h.MetaClient.UsersFn = func() []meta.UserInfo { return []meta.UserInfo{ { Name: "user1", Hash: "abcd", Admin: true, Privileges: make(map[string]influxql.Privilege), }, } } h.MetaClient.UserFn = func(username string) (*meta.UserInfo, error) { if username != "user1" { return nil, meta.ErrUserNotFound } return &meta.UserInfo{ Name: "user1", Hash: "abcd", Admin: true, }, nil } h.MetaClient.AuthenticateFn = func(u, p string) (*meta.UserInfo, error) { if u != "user1" { return nil, fmt.Errorf("unexpected user: exp: user1, got: %s", u) } else if p != "abcd" { return nil, fmt.Errorf("unexpected password: exp: abcd, got: %s", p) } return h.MetaClient.User(u) } // Set mock query authorizer for handler to use. h.QueryAuthorizer.AuthorizeQueryFn = func(u *meta.UserInfo, query *influxql.Query, database string) error { return nil } // Set mock statement executor for handler to use. h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx *influxql.ExecutionContext) error { if stmt.String() != `SELECT * FROM bar` { t.Fatalf("unexpected query: %s", stmt.String()) } else if ctx.Database != `foo` { t.Fatalf("unexpected db: %s", ctx.Database) } ctx.Results <- &influxql.Result{StatementID: 1, Series: models.Rows([]*models.Row{{Name: "series0"}})} ctx.Results <- &influxql.Result{StatementID: 2, Series: models.Rows([]*models.Row{{Name: "series1"}})} return nil } // Test the handler with valid user and password in the URL parameters. w := httptest.NewRecorder() h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?u=user1&p=abcd&db=foo&q=SELECT+*+FROM+bar", nil)) if w.Code != http.StatusOK { t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String()) } else if w.Body.String() != `{"results":[{"series":[{"name":"series0"}]},{"series":[{"name":"series1"}]}]}` { t.Fatalf("unexpected body: %s", w.Body.String()) } // Test the handler with valid JWT bearer token. req := MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar", nil) // Create a signed JWT token string and add it to the request header. _, signedToken := MustJWTToken("user1", h.Config.SharedSecret, false) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken)) w = httptest.NewRecorder() h.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String()) } else if w.Body.String() != `{"results":[{"series":[{"name":"series0"}]},{"series":[{"name":"series1"}]}]}` { t.Fatalf("unexpected body: %s", w.Body.String()) } // Test the handler with JWT token signed with invalid key. req = MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar", nil) // Create a signed JWT token string and add it to the request header. _, signedToken = MustJWTToken("user1", "invalid key", false) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken)) w = httptest.NewRecorder() h.ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String()) } else if w.Body.String() != `{"error":"signature is invalid"}` { t.Fatalf("unexpected body: %s", w.Body.String()) } // Test handler with valid JWT token carrying non-existant user. _, signedToken = MustJWTToken("bad_user", h.Config.SharedSecret, false) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken)) w = httptest.NewRecorder() h.ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String()) } else if w.Body.String() != `{"error":"user not found"}` { t.Fatalf("unexpected body: %s", w.Body.String()) } // Test handler with expired JWT token. _, signedToken = MustJWTToken("user1", h.Config.SharedSecret, true) req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken)) w = httptest.NewRecorder() h.ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String()) } else if !strings.Contains(w.Body.String(), `{"error":"token is expired`) { t.Fatalf("unexpected body: %s", w.Body.String()) } // Test handler with JWT token that has no expiration set. token, _ := MustJWTToken("user1", h.Config.SharedSecret, false) delete(token.Claims, "exp") signedToken, err := token.SignedString([]byte(h.Config.SharedSecret)) if err != nil { t.Fatal(err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken)) w = httptest.NewRecorder() h.ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String()) } else if w.Body.String() != `{"error":"token expiration required"}` { t.Fatalf("unexpected body: %s", w.Body.String()) } }