prometheus/web/api/v1/json_codec_test.go

179 lines
5.3 KiB
Go
Raw Normal View History

Add extension point for returning different content types from API endpoints (#412) * Add initial sketch of Codec interface. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Introduce JSON codec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Expose Response type so that consuming applications (eg. Mimir) can implement their own Codecs. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add sketch of what supporting different codecs could look like. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename fallbackCodec to defaultCodec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Remove defaultCodec as a field on API. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename AddCodec() and clarify expected behaviour. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Modify TestRespond to test JsonCodec directly. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Refactor existing respond() test in preparation for content negotiation test cases. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add tests for content negotiation. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add missing documentation comments. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add another test case. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename JsonCodec to JSONCodec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Fix linting issue. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Fallback to JSON codec if no acceptable codec can be found for the Accept header. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Move custom jsoniter code into json_codec.go. Signed-off-by: Charles Korn <charles.korn@grafana.com> --------- Signed-off-by: Charles Korn <charles.korn@grafana.com>
2023-02-01 00:33:50 -08: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.
package v1
import (
"math"
"testing"
"github.com/prometheus/prometheus/model/exemplar"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser"
)
func TestJsonCodec_Encode(t *testing.T) {
cases := []struct {
response interface{}
expected string
}{
{
response: &QueryData{
Add extension point for returning different content types from API endpoints (#412) * Add initial sketch of Codec interface. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Introduce JSON codec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Expose Response type so that consuming applications (eg. Mimir) can implement their own Codecs. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add sketch of what supporting different codecs could look like. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename fallbackCodec to defaultCodec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Remove defaultCodec as a field on API. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename AddCodec() and clarify expected behaviour. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Modify TestRespond to test JsonCodec directly. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Refactor existing respond() test in preparation for content negotiation test cases. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add tests for content negotiation. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add missing documentation comments. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add another test case. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename JsonCodec to JSONCodec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Fix linting issue. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Fallback to JSON codec if no acceptable codec can be found for the Accept header. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Move custom jsoniter code into json_codec.go. Signed-off-by: Charles Korn <charles.korn@grafana.com> --------- Signed-off-by: Charles Korn <charles.korn@grafana.com>
2023-02-01 00:33:50 -08:00
ResultType: parser.ValueTypeMatrix,
Result: promql.Matrix{
promql.Series{
Points: []promql.Point{{V: 1, T: 1000}},
Metric: labels.FromStrings("__name__", "foo"),
},
},
},
expected: `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"__name__":"foo"},"values":[[1,"1"]]}]}}`,
},
{
response: &QueryData{
Add extension point for returning different content types from API endpoints (#412) * Add initial sketch of Codec interface. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Introduce JSON codec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Expose Response type so that consuming applications (eg. Mimir) can implement their own Codecs. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add sketch of what supporting different codecs could look like. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename fallbackCodec to defaultCodec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Remove defaultCodec as a field on API. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename AddCodec() and clarify expected behaviour. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Modify TestRespond to test JsonCodec directly. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Refactor existing respond() test in preparation for content negotiation test cases. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add tests for content negotiation. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add missing documentation comments. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Add another test case. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Rename JsonCodec to JSONCodec. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Fix linting issue. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Fallback to JSON codec if no acceptable codec can be found for the Accept header. Signed-off-by: Charles Korn <charles.korn@grafana.com> * Move custom jsoniter code into json_codec.go. Signed-off-by: Charles Korn <charles.korn@grafana.com> --------- Signed-off-by: Charles Korn <charles.korn@grafana.com>
2023-02-01 00:33:50 -08:00
ResultType: parser.ValueTypeMatrix,
Result: promql.Matrix{
promql.Series{
Points: []promql.Point{{H: &histogram.FloatHistogram{
Schema: 2,
ZeroThreshold: 0.001,
ZeroCount: 12,
Count: 10,
Sum: 20,
PositiveSpans: []histogram.Span{
{Offset: 3, Length: 2},
{Offset: 1, Length: 3},
},
NegativeSpans: []histogram.Span{
{Offset: 2, Length: 2},
},
PositiveBuckets: []float64{1, 2, 2, 1, 1},
NegativeBuckets: []float64{2, 1},
}, T: 1000}},
Metric: labels.FromStrings("__name__", "foo"),
},
},
},
expected: `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"__name__":"foo"},"histograms":[[1,{"count":"10","sum":"20","buckets":[[1,"-1.6817928305074288","-1.414213562373095","1"],[1,"-1.414213562373095","-1.189207115002721","2"],[3,"-0.001","0.001","12"],[0,"1.414213562373095","1.6817928305074288","1"],[0,"1.6817928305074288","2","2"],[0,"2.378414230005442","2.82842712474619","2"],[0,"2.82842712474619","3.3635856610148576","1"],[0,"3.3635856610148576","4","1"]]}]]}]}}`,
},
{
response: promql.Point{V: 0, T: 0},
expected: `{"status":"success","data":[0,"0"]}`,
},
{
response: promql.Point{V: 20, T: 1},
expected: `{"status":"success","data":[0.001,"20"]}`,
},
{
response: promql.Point{V: 20, T: 10},
expected: `{"status":"success","data":[0.010,"20"]}`,
},
{
response: promql.Point{V: 20, T: 100},
expected: `{"status":"success","data":[0.100,"20"]}`,
},
{
response: promql.Point{V: 20, T: 1001},
expected: `{"status":"success","data":[1.001,"20"]}`,
},
{
response: promql.Point{V: 20, T: 1010},
expected: `{"status":"success","data":[1.010,"20"]}`,
},
{
response: promql.Point{V: 20, T: 1100},
expected: `{"status":"success","data":[1.100,"20"]}`,
},
{
response: promql.Point{V: 20, T: 12345678123456555},
expected: `{"status":"success","data":[12345678123456.555,"20"]}`,
},
{
response: promql.Point{V: 20, T: -1},
expected: `{"status":"success","data":[-0.001,"20"]}`,
},
{
response: promql.Point{V: math.NaN(), T: 0},
expected: `{"status":"success","data":[0,"NaN"]}`,
},
{
response: promql.Point{V: math.Inf(1), T: 0},
expected: `{"status":"success","data":[0,"+Inf"]}`,
},
{
response: promql.Point{V: math.Inf(-1), T: 0},
expected: `{"status":"success","data":[0,"-Inf"]}`,
},
{
response: promql.Point{V: 1.2345678e6, T: 0},
expected: `{"status":"success","data":[0,"1234567.8"]}`,
},
{
response: promql.Point{V: 1.2345678e-6, T: 0},
expected: `{"status":"success","data":[0,"0.0000012345678"]}`,
},
{
response: promql.Point{V: 1.2345678e-67, T: 0},
expected: `{"status":"success","data":[0,"1.2345678e-67"]}`,
},
{
response: []exemplar.QueryResult{
{
SeriesLabels: labels.FromStrings("foo", "bar"),
Exemplars: []exemplar.Exemplar{
{
Labels: labels.FromStrings("traceID", "abc"),
Value: 100.123,
Ts: 1234,
},
},
},
},
expected: `{"status":"success","data":[{"seriesLabels":{"foo":"bar"},"exemplars":[{"labels":{"traceID":"abc"},"value":"100.123","timestamp":1.234}]}]}`,
},
{
response: []exemplar.QueryResult{
{
SeriesLabels: labels.FromStrings("foo", "bar"),
Exemplars: []exemplar.Exemplar{
{
Labels: labels.FromStrings("traceID", "abc"),
Value: math.Inf(1),
Ts: 1234,
},
},
},
},
expected: `{"status":"success","data":[{"seriesLabels":{"foo":"bar"},"exemplars":[{"labels":{"traceID":"abc"},"value":"+Inf","timestamp":1.234}]}]}`,
},
}
codec := JSONCodec{}
for _, c := range cases {
body, err := codec.Encode(&Response{
Status: statusSuccess,
Data: c.response,
})
if err != nil {
t.Fatalf("Error encoding response body: %s", err)
}
if string(body) != c.expected {
t.Fatalf("Expected response \n%v\n but got \n%v\n", c.expected, string(body))
}
}
}