2016-04-13 07:08:22 -07:00
|
|
|
// Copyright 2016 The Prometheus Authors
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2015-06-04 09:07:57 -07:00
|
|
|
package v1
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2015-07-02 01:37:19 -07:00
|
|
|
"io/ioutil"
|
2015-06-04 09:07:57 -07:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"net/url"
|
|
|
|
"reflect"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2015-08-20 08:18:46 -07:00
|
|
|
"github.com/prometheus/common/model"
|
2015-09-24 08:07:11 -07:00
|
|
|
"github.com/prometheus/common/route"
|
2015-08-22 00:42:45 -07:00
|
|
|
"golang.org/x/net/context"
|
2015-06-04 09:07:57 -07:00
|
|
|
|
|
|
|
"github.com/prometheus/prometheus/promql"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestEndpoints(t *testing.T) {
|
|
|
|
suite, err := promql.NewTest(t, `
|
|
|
|
load 1m
|
|
|
|
test_metric1{foo="bar"} 0+100x100
|
|
|
|
test_metric1{foo="boo"} 1+0x100
|
|
|
|
test_metric2{foo="boo"} 1+0x100
|
|
|
|
`)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer suite.Close()
|
|
|
|
|
|
|
|
if err := suite.Run(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2015-11-11 11:46:57 -08:00
|
|
|
now := model.Now()
|
2015-06-04 09:07:57 -07:00
|
|
|
api := &API{
|
|
|
|
Storage: suite.Storage(),
|
|
|
|
QueryEngine: suite.QueryEngine(),
|
promql: Allow per-query contexts.
For Weaveworks' Frankenstein, we need to support multitenancy. In
Frankenstein, we initially solved this without modifying the promql
package at all: we constructed a new promql.Engine for every
query and injected a storage implementation into that engine which would
be primed to only collect data for a given user.
This is problematic to upstream, however. Prometheus assumes that there
is only one engine: the query concurrency gate is part of the engine,
and the engine contains one central cancellable context to shut down all
queries. Also, creating a new engine for every query seems like overkill.
Thus, we want to be able to pass per-query contexts into a single engine.
This change gets rid of the promql.Engine's built-in base context and
allows passing in a per-query context instead. Central cancellation of
all queries is still possible by deriving all passed-in contexts from
one central one, but this is now the responsibility of the caller. The
central query context is now created in main() and passed into the
relevant components (web handler / API, rule manager).
In a next step, the per-query context would have to be passed to the
storage implementation, so that the storage can implement multi-tenancy
or other features based on the contextual information.
2016-09-15 04:52:50 -07:00
|
|
|
QueryCtx: suite.Context(),
|
2015-11-11 11:46:57 -08:00
|
|
|
now: func() model.Time { return now },
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
|
|
|
|
2015-08-20 08:18:46 -07:00
|
|
|
start := model.Time(0)
|
2015-06-04 09:07:57 -07:00
|
|
|
var tests = []struct {
|
|
|
|
endpoint apiFunc
|
2015-06-08 12:19:52 -07:00
|
|
|
params map[string]string
|
2015-06-04 09:07:57 -07:00
|
|
|
query url.Values
|
|
|
|
response interface{}
|
|
|
|
errType errorType
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
endpoint: api.query,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"2"},
|
|
|
|
"time": []string{"123.3"},
|
|
|
|
},
|
|
|
|
response: &queryData{
|
2015-08-24 09:04:41 -07:00
|
|
|
ResultType: model.ValScalar,
|
|
|
|
Result: &model.Scalar{
|
2015-06-04 09:07:57 -07:00
|
|
|
Value: 2,
|
|
|
|
Timestamp: start.Add(123*time.Second + 300*time.Millisecond),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.query,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"0.333"},
|
|
|
|
"time": []string{"1970-01-01T00:02:03Z"},
|
|
|
|
},
|
|
|
|
response: &queryData{
|
2015-08-24 09:04:41 -07:00
|
|
|
ResultType: model.ValScalar,
|
|
|
|
Result: &model.Scalar{
|
2015-06-04 09:07:57 -07:00
|
|
|
Value: 0.333,
|
|
|
|
Timestamp: start.Add(123 * time.Second),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.query,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"0.333"},
|
|
|
|
"time": []string{"1970-01-01T01:02:03+01:00"},
|
|
|
|
},
|
|
|
|
response: &queryData{
|
2015-08-24 09:04:41 -07:00
|
|
|
ResultType: model.ValScalar,
|
|
|
|
Result: &model.Scalar{
|
2015-06-04 09:07:57 -07:00
|
|
|
Value: 0.333,
|
|
|
|
Timestamp: start.Add(123 * time.Second),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2015-11-11 11:46:57 -08:00
|
|
|
{
|
|
|
|
endpoint: api.query,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"0.333"},
|
|
|
|
},
|
|
|
|
response: &queryData{
|
|
|
|
ResultType: model.ValScalar,
|
|
|
|
Result: &model.Scalar{
|
|
|
|
Value: 0.333,
|
|
|
|
Timestamp: now,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2015-06-09 04:44:49 -07:00
|
|
|
{
|
|
|
|
endpoint: api.queryRange,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"time()"},
|
|
|
|
"start": []string{"0"},
|
|
|
|
"end": []string{"2"},
|
|
|
|
"step": []string{"1"},
|
|
|
|
},
|
|
|
|
response: &queryData{
|
2015-08-24 09:04:41 -07:00
|
|
|
ResultType: model.ValMatrix,
|
|
|
|
Result: model.Matrix{
|
|
|
|
&model.SampleStream{
|
2015-08-22 05:52:35 -07:00
|
|
|
Values: []model.SamplePair{
|
2015-06-09 04:44:49 -07:00
|
|
|
{Value: 0, Timestamp: start},
|
|
|
|
{Value: 1, Timestamp: start.Add(1 * time.Second)},
|
|
|
|
{Value: 2, Timestamp: start.Add(2 * time.Second)},
|
|
|
|
},
|
2015-08-24 09:04:41 -07:00
|
|
|
Metric: model.Metric{},
|
2015-06-09 04:44:49 -07:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Missing query params in range queries.
|
|
|
|
{
|
|
|
|
endpoint: api.queryRange,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"time()"},
|
|
|
|
"end": []string{"2"},
|
|
|
|
"step": []string{"1"},
|
|
|
|
},
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.queryRange,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"time()"},
|
|
|
|
"start": []string{"0"},
|
|
|
|
"step": []string{"1"},
|
|
|
|
},
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.queryRange,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"time()"},
|
|
|
|
"start": []string{"0"},
|
|
|
|
"end": []string{"2"},
|
|
|
|
},
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
|
|
|
// Bad query expression.
|
|
|
|
{
|
|
|
|
endpoint: api.query,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"invalid][query"},
|
|
|
|
"time": []string{"1970-01-01T01:02:03+01:00"},
|
|
|
|
},
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.queryRange,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"invalid][query"},
|
|
|
|
"start": []string{"0"},
|
|
|
|
"end": []string{"100"},
|
|
|
|
"step": []string{"1"},
|
|
|
|
},
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
2016-08-16 06:10:02 -07:00
|
|
|
// Invalid step
|
|
|
|
{
|
|
|
|
endpoint: api.queryRange,
|
|
|
|
query: url.Values{
|
|
|
|
"query": []string{"time()"},
|
|
|
|
"start": []string{"1"},
|
|
|
|
"end": []string{"2"},
|
|
|
|
"step": []string{"0"},
|
|
|
|
},
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
2015-06-04 09:07:57 -07:00
|
|
|
{
|
2015-06-08 12:19:52 -07:00
|
|
|
endpoint: api.labelValues,
|
|
|
|
params: map[string]string{
|
|
|
|
"name": "__name__",
|
|
|
|
},
|
2015-08-20 08:18:46 -07:00
|
|
|
response: model.LabelValues{
|
2015-06-04 09:07:57 -07:00
|
|
|
"test_metric1",
|
|
|
|
"test_metric2",
|
|
|
|
},
|
|
|
|
},
|
2015-06-08 12:19:52 -07:00
|
|
|
{
|
|
|
|
endpoint: api.labelValues,
|
|
|
|
params: map[string]string{
|
|
|
|
"name": "foo",
|
|
|
|
},
|
2015-08-20 08:18:46 -07:00
|
|
|
response: model.LabelValues{
|
2015-06-08 12:19:52 -07:00
|
|
|
"bar",
|
|
|
|
"boo",
|
|
|
|
},
|
|
|
|
},
|
2015-06-09 04:44:49 -07:00
|
|
|
// Bad name parameter.
|
|
|
|
{
|
|
|
|
endpoint: api.labelValues,
|
|
|
|
params: map[string]string{
|
|
|
|
"name": "not!!!allowed",
|
|
|
|
},
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
2015-06-09 07:09:31 -07:00
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
|
|
|
"match[]": []string{`test_metric2`},
|
|
|
|
},
|
2015-08-20 08:18:46 -07:00
|
|
|
response: []model.Metric{
|
2015-06-09 07:09:31 -07:00
|
|
|
{
|
|
|
|
"__name__": "test_metric2",
|
|
|
|
"foo": "boo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
2015-11-05 02:23:43 -08:00
|
|
|
"match[]": []string{`test_metric1{foo=~".+o"}`},
|
2015-06-09 07:09:31 -07:00
|
|
|
},
|
2015-08-20 08:18:46 -07:00
|
|
|
response: []model.Metric{
|
2015-06-09 07:09:31 -07:00
|
|
|
{
|
|
|
|
"__name__": "test_metric1",
|
|
|
|
"foo": "boo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
2015-11-05 02:23:43 -08:00
|
|
|
"match[]": []string{`test_metric1{foo=~"o$"}`, `test_metric1{foo=~".+o"}`},
|
2015-06-09 07:09:31 -07:00
|
|
|
},
|
2015-08-20 08:18:46 -07:00
|
|
|
response: []model.Metric{
|
2015-06-09 07:09:31 -07:00
|
|
|
{
|
|
|
|
"__name__": "test_metric1",
|
|
|
|
"foo": "boo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
2015-11-05 02:23:43 -08:00
|
|
|
"match[]": []string{`test_metric1{foo=~".+o"}`, `none`},
|
2015-06-09 07:09:31 -07:00
|
|
|
},
|
2015-08-20 08:18:46 -07:00
|
|
|
response: []model.Metric{
|
2015-06-09 07:09:31 -07:00
|
|
|
{
|
|
|
|
"__name__": "test_metric1",
|
|
|
|
"foo": "boo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2016-05-11 14:59:52 -07:00
|
|
|
// Start and end before series starts.
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
|
|
|
"match[]": []string{`test_metric2`},
|
|
|
|
"start": []string{"-2"},
|
|
|
|
"end": []string{"-1"},
|
|
|
|
},
|
|
|
|
response: []model.Metric{},
|
|
|
|
},
|
|
|
|
// Start and end after series ends.
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
|
|
|
"match[]": []string{`test_metric2`},
|
|
|
|
"start": []string{"100000"},
|
|
|
|
"end": []string{"100001"},
|
|
|
|
},
|
|
|
|
response: []model.Metric{},
|
|
|
|
},
|
|
|
|
// Start before series starts, end after series ends.
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
|
|
|
"match[]": []string{`test_metric2`},
|
|
|
|
"start": []string{"-1"},
|
|
|
|
"end": []string{"100000"},
|
|
|
|
},
|
|
|
|
response: []model.Metric{
|
|
|
|
{
|
|
|
|
"__name__": "test_metric2",
|
|
|
|
"foo": "boo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Start and end within series.
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
|
|
|
"match[]": []string{`test_metric2`},
|
|
|
|
"start": []string{"1"},
|
|
|
|
"end": []string{"100"},
|
|
|
|
},
|
|
|
|
response: []model.Metric{
|
|
|
|
{
|
|
|
|
"__name__": "test_metric2",
|
|
|
|
"foo": "boo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Start within series, end after.
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
|
|
|
"match[]": []string{`test_metric2`},
|
|
|
|
"start": []string{"1"},
|
|
|
|
"end": []string{"100000"},
|
|
|
|
},
|
|
|
|
response: []model.Metric{
|
|
|
|
{
|
|
|
|
"__name__": "test_metric2",
|
|
|
|
"foo": "boo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
// Start before series, end within series.
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
|
|
|
"match[]": []string{`test_metric2`},
|
|
|
|
"start": []string{"-1"},
|
|
|
|
"end": []string{"1"},
|
|
|
|
},
|
|
|
|
response: []model.Metric{
|
|
|
|
{
|
|
|
|
"__name__": "test_metric2",
|
|
|
|
"foo": "boo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2015-06-09 07:09:31 -07:00
|
|
|
// Missing match[] query params in series requests.
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.dropSeries,
|
|
|
|
errType: errorBadData,
|
|
|
|
},
|
|
|
|
// The following tests delete time series from the test storage. They
|
|
|
|
// must remain at the end and are fixed in their order.
|
|
|
|
{
|
|
|
|
endpoint: api.dropSeries,
|
|
|
|
query: url.Values{
|
2015-11-05 02:23:43 -08:00
|
|
|
"match[]": []string{`test_metric1{foo=~".+o"}`},
|
2015-06-09 07:09:31 -07:00
|
|
|
},
|
|
|
|
response: struct {
|
|
|
|
NumDeleted int `json:"numDeleted"`
|
|
|
|
}{1},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
endpoint: api.series,
|
|
|
|
query: url.Values{
|
|
|
|
"match[]": []string{`test_metric1`},
|
|
|
|
},
|
2015-08-20 08:18:46 -07:00
|
|
|
response: []model.Metric{
|
2015-06-09 07:09:31 -07:00
|
|
|
{
|
|
|
|
"__name__": "test_metric1",
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
endpoint: api.dropSeries,
|
|
|
|
query: url.Values{
|
2015-06-15 09:34:41 -07:00
|
|
|
"match[]": []string{`{__name__=~".+"}`},
|
2015-06-09 07:09:31 -07:00
|
|
|
},
|
|
|
|
response: struct {
|
|
|
|
NumDeleted int `json:"numDeleted"`
|
|
|
|
}{2},
|
|
|
|
},
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
2015-06-08 12:19:52 -07:00
|
|
|
// Build a context with the correct request params.
|
|
|
|
ctx := context.Background()
|
|
|
|
for p, v := range test.params {
|
|
|
|
ctx = route.WithParam(ctx, p, v)
|
|
|
|
}
|
|
|
|
api.context = func(r *http.Request) context.Context {
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
2015-06-04 09:07:57 -07:00
|
|
|
req, err := http.NewRequest("ANY", fmt.Sprintf("http://example.com?%s", test.query.Encode()), nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-06-08 12:19:52 -07:00
|
|
|
resp, apiErr := test.endpoint(req)
|
|
|
|
if apiErr != nil {
|
|
|
|
if test.errType == errorNone {
|
|
|
|
t.Fatalf("Unexpected error: %s", apiErr)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
2015-06-08 12:19:52 -07:00
|
|
|
if test.errType != apiErr.typ {
|
|
|
|
t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.typ)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
2015-06-08 12:19:52 -07:00
|
|
|
if apiErr == nil && test.errType != errorNone {
|
2015-06-04 09:07:57 -07:00
|
|
|
t.Fatalf("Expected error of type %q but got none", test.errType)
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(resp, test.response) {
|
2015-08-24 09:04:41 -07:00
|
|
|
t.Fatalf("Response does not match, expected:\n%+v\ngot:\n%+v", test.response, resp)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
2015-06-09 07:09:31 -07:00
|
|
|
// Ensure that removed metrics are unindexed before the next request.
|
|
|
|
suite.Storage().WaitForIndexing()
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRespondSuccess(t *testing.T) {
|
2015-07-02 01:37:19 -07:00
|
|
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
respond(w, "test")
|
|
|
|
}))
|
|
|
|
defer s.Close()
|
2015-06-04 09:07:57 -07:00
|
|
|
|
2015-07-02 01:37:19 -07:00
|
|
|
resp, err := http.Get(s.URL)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error on test request: %s", err)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
2015-07-02 01:37:19 -07:00
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
defer resp.Body.Close()
|
2015-06-04 09:07:57 -07:00
|
|
|
if err != nil {
|
2015-07-02 01:37:19 -07:00
|
|
|
t.Fatalf("Error reading response body: %s", err)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
|
|
|
|
2015-07-02 01:37:19 -07:00
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
t.Fatalf("Return code %d expected in success response but got %d", 200, resp.StatusCode)
|
|
|
|
}
|
|
|
|
if h := resp.Header.Get("Content-Type"); h != "application/json" {
|
|
|
|
t.Fatalf("Expected Content-Type %q but got %q", "application/json", h)
|
|
|
|
}
|
|
|
|
|
|
|
|
var res response
|
|
|
|
if err = json.Unmarshal([]byte(body), &res); err != nil {
|
|
|
|
t.Fatalf("Error unmarshaling JSON body: %s", err)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
exp := &response{
|
|
|
|
Status: statusSuccess,
|
|
|
|
Data: "test",
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(&res, exp) {
|
|
|
|
t.Fatalf("Expected response \n%v\n but got \n%v\n", res, exp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRespondError(t *testing.T) {
|
2015-07-02 01:37:19 -07:00
|
|
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
respondError(w, &apiError{errorTimeout, errors.New("message")}, "test")
|
|
|
|
}))
|
|
|
|
defer s.Close()
|
2015-06-04 09:07:57 -07:00
|
|
|
|
2015-07-02 01:37:19 -07:00
|
|
|
resp, err := http.Get(s.URL)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error on test request: %s", err)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
2015-07-02 01:37:19 -07:00
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
defer resp.Body.Close()
|
2015-06-04 09:07:57 -07:00
|
|
|
if err != nil {
|
2015-07-02 01:37:19 -07:00
|
|
|
t.Fatalf("Error reading response body: %s", err)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
|
|
|
|
2015-11-11 14:00:54 -08:00
|
|
|
if want, have := http.StatusServiceUnavailable, resp.StatusCode; want != have {
|
|
|
|
t.Fatalf("Return code %d expected in error response but got %d", want, have)
|
2015-07-02 01:37:19 -07:00
|
|
|
}
|
|
|
|
if h := resp.Header.Get("Content-Type"); h != "application/json" {
|
|
|
|
t.Fatalf("Expected Content-Type %q but got %q", "application/json", h)
|
|
|
|
}
|
|
|
|
|
|
|
|
var res response
|
|
|
|
if err = json.Unmarshal([]byte(body), &res); err != nil {
|
|
|
|
t.Fatalf("Error unmarshaling JSON body: %s", err)
|
2015-06-04 09:07:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
exp := &response{
|
|
|
|
Status: statusError,
|
|
|
|
Data: "test",
|
|
|
|
ErrorType: errorTimeout,
|
|
|
|
Error: "message",
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(&res, exp) {
|
|
|
|
t.Fatalf("Expected response \n%v\n but got \n%v\n", res, exp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseTime(t *testing.T) {
|
|
|
|
ts, err := time.Parse(time.RFC3339Nano, "2015-06-03T13:21:58.555Z")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var tests = []struct {
|
|
|
|
input string
|
|
|
|
fail bool
|
|
|
|
result time.Time
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
input: "",
|
|
|
|
fail: true,
|
|
|
|
}, {
|
|
|
|
input: "abc",
|
|
|
|
fail: true,
|
|
|
|
}, {
|
|
|
|
input: "30s",
|
|
|
|
fail: true,
|
|
|
|
}, {
|
|
|
|
input: "123",
|
|
|
|
result: time.Unix(123, 0),
|
|
|
|
}, {
|
|
|
|
input: "123.123",
|
|
|
|
result: time.Unix(123, 123000000),
|
|
|
|
}, {
|
|
|
|
input: "123.123",
|
|
|
|
result: time.Unix(123, 123000000),
|
|
|
|
}, {
|
|
|
|
input: "2015-06-03T13:21:58.555Z",
|
|
|
|
result: ts,
|
|
|
|
}, {
|
|
|
|
input: "2015-06-03T14:21:58.555+01:00",
|
|
|
|
result: ts,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
ts, err := parseTime(test.input)
|
|
|
|
if err != nil && !test.fail {
|
|
|
|
t.Errorf("Unexpected error for %q: %s", test.input, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err == nil && test.fail {
|
|
|
|
t.Errorf("Expected error for %q but got none", test.input)
|
|
|
|
continue
|
|
|
|
}
|
2015-08-20 08:18:46 -07:00
|
|
|
res := model.TimeFromUnixNano(test.result.UnixNano())
|
2015-06-04 09:07:57 -07:00
|
|
|
if !test.fail && ts != res {
|
|
|
|
t.Errorf("Expected time %v for input %q but got %v", res, test.input, ts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseDuration(t *testing.T) {
|
|
|
|
var tests = []struct {
|
|
|
|
input string
|
|
|
|
fail bool
|
|
|
|
result time.Duration
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
input: "",
|
|
|
|
fail: true,
|
|
|
|
}, {
|
|
|
|
input: "abc",
|
|
|
|
fail: true,
|
|
|
|
}, {
|
|
|
|
input: "2015-06-03T13:21:58.555Z",
|
|
|
|
fail: true,
|
|
|
|
}, {
|
|
|
|
input: "123",
|
|
|
|
result: 123 * time.Second,
|
|
|
|
}, {
|
|
|
|
input: "123.333",
|
|
|
|
result: 123*time.Second + 333*time.Millisecond,
|
|
|
|
}, {
|
|
|
|
input: "15s",
|
|
|
|
result: 15 * time.Second,
|
|
|
|
}, {
|
|
|
|
input: "5m",
|
|
|
|
result: 5 * time.Minute,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
d, err := parseDuration(test.input)
|
|
|
|
if err != nil && !test.fail {
|
|
|
|
t.Errorf("Unexpected error for %q: %s", test.input, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err == nil && test.fail {
|
|
|
|
t.Errorf("Expected error for %q but got none", test.input)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if !test.fail && d != test.result {
|
|
|
|
t.Errorf("Expected duration %v for input %q but got %v", test.result, test.input, d)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-01-25 16:32:46 -08:00
|
|
|
|
|
|
|
func TestOptionsMethod(t *testing.T) {
|
|
|
|
r := route.New()
|
|
|
|
api := &API{}
|
|
|
|
api.Register(r)
|
|
|
|
|
|
|
|
s := httptest.NewServer(r)
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
req, err := http.NewRequest("OPTIONS", s.URL+"/any_path", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error creating OPTIONS request: %s", err)
|
|
|
|
}
|
|
|
|
client := &http.Client{}
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error executing OPTIONS request: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusNoContent {
|
|
|
|
t.Fatalf("Expected status %d, got %d", http.StatusNoContent, resp.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
for h, v := range corsHeaders {
|
|
|
|
if resp.Header.Get(h) != v {
|
|
|
|
t.Fatalf("Expected %q for header %q, got %q", v, h, resp.Header.Get(h))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|