mirror of
https://github.com/nikdoof/vsphere-influxdb-go.git
synced 2025-12-19 13:39:21 +00:00
add vendoring with go dep
This commit is contained in:
706
vendor/github.com/influxdata/influxdb/services/httpd/handler_test.go
generated
vendored
Normal file
706
vendor/github.com/influxdata/influxdb/services/httpd/handler_test.go
generated
vendored
Normal file
@@ -0,0 +1,706 @@
|
||||
package httpd_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxdb/internal"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/influxdata/influxdb/influxql"
|
||||
"github.com/influxdata/influxdb/models"
|
||||
"github.com/influxdata/influxdb/services/httpd"
|
||||
"github.com/influxdata/influxdb/services/meta"
|
||||
)
|
||||
|
||||
// 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.AdminUserExistsFn = func() bool { return true }
|
||||
|
||||
h.MetaClient.UserFn = func(username string) (meta.User, 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.User, 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.User, 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 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 the handler with valid user and password using basic auth.
|
||||
w = httptest.NewRecorder()
|
||||
r := MustNewJSONRequest("GET", "/query?db=foo&q=SELECT+*+FROM+bar", nil)
|
||||
r.SetBasicAuth("user1", "abcd")
|
||||
h.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String())
|
||||
} 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 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 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 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 body := strings.TrimSpace(w.Body.String()); body != `{"error":"signature is invalid"}` {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
|
||||
// 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 body := strings.TrimSpace(w.Body.String()); body != `{"error":"user not found"}` {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
|
||||
// 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.(jwt.MapClaims), "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 body := strings.TrimSpace(w.Body.String()); body != `{"error":"token expiration required"}` {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
|
||||
// Test the handler with valid user and password in the url and invalid in
|
||||
// basic auth (prioritize url).
|
||||
w = httptest.NewRecorder()
|
||||
r = MustNewJSONRequest("GET", "/query?u=user1&p=abcd&db=foo&q=SELECT+*+FROM+bar", nil)
|
||||
r.SetBasicAuth("user1", "efgh")
|
||||
h.ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("unexpected status: %d: %s", w.Code, w.Body.String())
|
||||
} 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 (including nil results).
|
||||
func TestHandler_QueryRegex(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error {
|
||||
if stmt.String() != `SELECT * FROM test WHERE url =~ /http\:\/\/www.akamai\.com/` {
|
||||
t.Fatalf("unexpected query: %s", stmt.String())
|
||||
} else if ctx.Database != `test` {
|
||||
t.Fatalf("unexpected db: %s", ctx.Database)
|
||||
}
|
||||
ctx.Results <- nil
|
||||
return nil
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, MustNewRequest("GET", "/query?db=test&q=SELECT%20%2A%20FROM%20test%20WHERE%20url%20%3D~%20%2Fhttp%5C%3A%5C%2F%5C%2Fwww.akamai%5C.com%2F", nil))
|
||||
}
|
||||
|
||||
// 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 body := strings.TrimSpace(w.Body.String()); body != `{"results":[{"statement_id":1,"series":[{"name":"series0"},{"name":"series1"}]}]}` {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the handler merges results from the same statement.
|
||||
func TestHandler_Query_MergeEmptyResults(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{}}
|
||||
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 body := strings.TrimSpace(w.Body.String()); body != `{"results":[{"statement_id":1,"series":[{"name":"series1"}]}]}` {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the handler can parse chunked and chunk size query parameters.
|
||||
func TestHandler_Query_Chunked(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error {
|
||||
if ctx.ChunkSize != 2 {
|
||||
t.Fatalf("unexpected chunk size: %d", ctx.ChunkSize)
|
||||
}
|
||||
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&chunked=true&chunk_size=2", nil))
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("unexpected status: %d", w.Code)
|
||||
} else if w.Body.String() != `{"results":[{"statement_id":1,"series":[{"name":"series0"}]}]}
|
||||
{"results":[{"statement_id":1,"series":[{"name":"series1"}]}]}
|
||||
` {
|
||||
t.Fatalf("unexpected body: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// 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 returns a status 400 if the query is not passed in.
|
||||
func TestHandler_Query_ErrQueryRequired(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, MustNewJSONRequest("GET", "/query", nil))
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("unexpected status: %d", w.Code)
|
||||
} else if body := strings.TrimSpace(w.Body.String()); body != `{"error":"missing required parameter \"q\""}` {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the handler returns a status 400 if the query cannot be parsed.
|
||||
func TestHandler_Query_ErrInvalidQuery(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?q=SELECT", nil))
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("unexpected status: %d", w.Code)
|
||||
} else if body := strings.TrimSpace(w.Body.String()); body != `{"error":"error parsing query: found EOF, expected identifier, string, number, bool at line 1, char 8"}` {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the handler returns an appropriate 401 or 403 status when authentication or authorization fails.
|
||||
func TestHandler_Query_ErrAuthorize(t *testing.T) {
|
||||
h := NewHandler(true)
|
||||
h.QueryAuthorizer.AuthorizeQueryFn = func(u meta.User, q *influxql.Query, db string) error {
|
||||
return errors.New("marker")
|
||||
}
|
||||
h.MetaClient.AdminUserExistsFn = func() bool { return true }
|
||||
h.MetaClient.AuthenticateFn = func(u, p string) (meta.User, error) {
|
||||
|
||||
users := []meta.UserInfo{
|
||||
{
|
||||
Name: "admin",
|
||||
Hash: "admin",
|
||||
Admin: true,
|
||||
},
|
||||
{
|
||||
Name: "user1",
|
||||
Hash: "abcd",
|
||||
Privileges: map[string]influxql.Privilege{
|
||||
"db0": influxql.ReadPrivilege,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if u == user.Name {
|
||||
if p == user.Hash {
|
||||
return &user, nil
|
||||
}
|
||||
return nil, meta.ErrAuthenticate
|
||||
}
|
||||
}
|
||||
return nil, meta.ErrUserNotFound
|
||||
}
|
||||
|
||||
for i, tt := range []struct {
|
||||
user string
|
||||
password string
|
||||
query string
|
||||
code int
|
||||
}{
|
||||
{
|
||||
query: "/query?q=SHOW+DATABASES",
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
user: "user1",
|
||||
password: "abcd",
|
||||
query: "/query?q=SHOW+DATABASES",
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
user: "user2",
|
||||
password: "abcd",
|
||||
query: "/query?q=SHOW+DATABASES",
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
} {
|
||||
w := httptest.NewRecorder()
|
||||
r := MustNewJSONRequest("GET", tt.query, nil)
|
||||
params := r.URL.Query()
|
||||
if tt.user != "" {
|
||||
params.Set("u", tt.user)
|
||||
}
|
||||
if tt.password != "" {
|
||||
params.Set("p", tt.password)
|
||||
}
|
||||
r.URL.RawQuery = params.Encode()
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
if w.Code != tt.code {
|
||||
t.Errorf("%d. unexpected status: got=%d exp=%d\noutput: %s", i, w.Code, tt.code, w.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the handler returns a status 200 if an error is returned in the result.
|
||||
func TestHandler_Query_ErrResult(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error {
|
||||
return errors.New("measurement not found")
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, MustNewJSONRequest("GET", "/query?db=foo&q=SHOW+SERIES+from+bin", 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":0,"error":"measurement not found"}]}` {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that closing the HTTP connection causes the query to be interrupted.
|
||||
func TestHandler_Query_CloseNotify(t *testing.T) {
|
||||
// Avoid leaking a goroutine when this fails.
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
interrupted := make(chan struct{})
|
||||
h := NewHandler(false)
|
||||
h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error {
|
||||
select {
|
||||
case <-ctx.InterruptCh:
|
||||
case <-done:
|
||||
}
|
||||
close(interrupted)
|
||||
return nil
|
||||
}
|
||||
|
||||
s := httptest.NewServer(h)
|
||||
defer s.Close()
|
||||
|
||||
// Parse the URL and generate a query request.
|
||||
u, err := url.Parse(s.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
u.Path = "/query"
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("q", "SELECT * FROM cpu")
|
||||
values.Set("db", "db0")
|
||||
values.Set("rp", "rp0")
|
||||
values.Set("chunked", "true")
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
req, err := http.NewRequest("GET", u.String(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Perform the request and retrieve the response.
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Validate that the interrupted channel has NOT been closed yet.
|
||||
timer := time.NewTimer(100 * time.Millisecond)
|
||||
select {
|
||||
case <-interrupted:
|
||||
timer.Stop()
|
||||
t.Fatal("query interrupted unexpectedly")
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
// Close the response body which should abort the query in the handler.
|
||||
resp.Body.Close()
|
||||
|
||||
// The query should abort within 100 milliseconds.
|
||||
timer.Reset(100 * time.Millisecond)
|
||||
select {
|
||||
case <-interrupted:
|
||||
timer.Stop()
|
||||
case <-timer.C:
|
||||
t.Fatal("timeout while waiting for query to abort")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the handler handles ping requests correctly.
|
||||
// TODO: This should be expanded to verify the MetaClient check in servePing is working correctly
|
||||
func TestHandler_Ping(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, MustNewRequest("GET", "/ping", nil))
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("unexpected status: %d", w.Code)
|
||||
}
|
||||
h.ServeHTTP(w, MustNewRequest("HEAD", "/ping", nil))
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("unexpected status: %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the handler returns the version correctly from the different endpoints.
|
||||
func TestHandler_Version(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
h.StatementExecutor.ExecuteStatementFn = func(stmt influxql.Statement, ctx influxql.ExecutionContext) error {
|
||||
return nil
|
||||
}
|
||||
tests := []struct {
|
||||
method string
|
||||
endpoint string
|
||||
body io.Reader
|
||||
}{
|
||||
{
|
||||
method: "GET",
|
||||
endpoint: "/ping",
|
||||
body: nil,
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
endpoint: "/query?db=foo&q=SELECT+*+FROM+bar",
|
||||
body: nil,
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
endpoint: "/write",
|
||||
body: bytes.NewReader(make([]byte, 10)),
|
||||
},
|
||||
{
|
||||
method: "GET",
|
||||
endpoint: "/notfound",
|
||||
body: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, MustNewRequest(test.method, test.endpoint, test.body))
|
||||
if v, ok := w.HeaderMap["X-Influxdb-Version"]; ok {
|
||||
if v[0] != "0.0.0" {
|
||||
t.Fatalf("unexpected version: %s", v)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("Header entry 'X-Influxdb-Version' not present")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the handler handles status requests correctly.
|
||||
func TestHandler_Status(t *testing.T) {
|
||||
h := NewHandler(false)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, MustNewRequest("GET", "/status", nil))
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("unexpected status: %d", w.Code)
|
||||
}
|
||||
h.ServeHTTP(w, MustNewRequest("HEAD", "/status", nil))
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Fatalf("unexpected status: %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure write endpoint can handle bad requests
|
||||
func TestHandler_HandleBadRequestBody(t *testing.T) {
|
||||
b := bytes.NewReader(make([]byte, 10))
|
||||
h := NewHandler(false)
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, MustNewRequest("POST", "/write", b))
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Fatalf("unexpected status: %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure X-Forwarded-For header writes the correct log message.
|
||||
func TestHandler_XForwardedFor(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
h := NewHandler(false)
|
||||
h.CLFLogger = log.New(&buf, "", 0)
|
||||
|
||||
req := MustNewRequest("GET", "/query", nil)
|
||||
req.Header.Set("X-Forwarded-For", "192.168.0.1")
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
h.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
parts := strings.Split(buf.String(), " ")
|
||||
if parts[0] != "192.168.0.1,127.0.0.1" {
|
||||
t.Errorf("unexpected host ip address: %s", parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
// NewHandler represents a test wrapper for httpd.Handler.
|
||||
type Handler struct {
|
||||
*httpd.Handler
|
||||
MetaClient *internal.MetaClientMock
|
||||
StatementExecutor HandlerStatementExecutor
|
||||
QueryAuthorizer HandlerQueryAuthorizer
|
||||
}
|
||||
|
||||
// NewHandler returns a new instance of Handler.
|
||||
func NewHandler(requireAuthentication bool) *Handler {
|
||||
config := httpd.NewConfig()
|
||||
config.AuthEnabled = requireAuthentication
|
||||
config.SharedSecret = "super secret key"
|
||||
|
||||
h := &Handler{
|
||||
Handler: httpd.NewHandler(config),
|
||||
}
|
||||
|
||||
h.MetaClient = &internal.MetaClientMock{}
|
||||
|
||||
h.Handler.MetaClient = h.MetaClient
|
||||
h.Handler.QueryExecutor = influxql.NewQueryExecutor()
|
||||
h.Handler.QueryExecutor.StatementExecutor = &h.StatementExecutor
|
||||
h.Handler.QueryAuthorizer = &h.QueryAuthorizer
|
||||
h.Handler.Version = "0.0.0"
|
||||
return h
|
||||
}
|
||||
|
||||
// HandlerStatementExecutor is a mock implementation of Handler.StatementExecutor.
|
||||
type HandlerStatementExecutor struct {
|
||||
ExecuteStatementFn func(stmt influxql.Statement, ctx influxql.ExecutionContext) error
|
||||
}
|
||||
|
||||
func (e *HandlerStatementExecutor) ExecuteStatement(stmt influxql.Statement, ctx influxql.ExecutionContext) error {
|
||||
return e.ExecuteStatementFn(stmt, ctx)
|
||||
}
|
||||
|
||||
// HandlerQueryAuthorizer is a mock implementation of Handler.QueryAuthorizer.
|
||||
type HandlerQueryAuthorizer struct {
|
||||
AuthorizeQueryFn func(u meta.User, query *influxql.Query, database string) error
|
||||
}
|
||||
|
||||
func (a *HandlerQueryAuthorizer) AuthorizeQuery(u meta.User, query *influxql.Query, database string) error {
|
||||
return a.AuthorizeQueryFn(u, query, database)
|
||||
}
|
||||
|
||||
// MustNewRequest returns a new HTTP request. Panic on error.
|
||||
func MustNewRequest(method, urlStr string, body io.Reader) *http.Request {
|
||||
r, err := http.NewRequest(method, urlStr, body)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// MustNewRequest returns a new HTTP request with the content type set. Panic on error.
|
||||
func MustNewJSONRequest(method, urlStr string, body io.Reader) *http.Request {
|
||||
r := MustNewRequest(method, urlStr, body)
|
||||
r.Header.Set("Accept", "application/json")
|
||||
return r
|
||||
}
|
||||
|
||||
// MustJWTToken returns a new JWT token and signed string or panics trying.
|
||||
func MustJWTToken(username, secret string, expired bool) (*jwt.Token, string) {
|
||||
token := jwt.New(jwt.GetSigningMethod("HS512"))
|
||||
token.Claims.(jwt.MapClaims)["username"] = username
|
||||
if expired {
|
||||
token.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(-time.Second).Unix()
|
||||
} else {
|
||||
token.Claims.(jwt.MapClaims)["exp"] = time.Now().Add(time.Minute * 10).Unix()
|
||||
}
|
||||
signed, err := token.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return token, signed
|
||||
}
|
||||
Reference in New Issue
Block a user