Add unit tests
Signed-off-by: rustyclock <rustyclock@protonmail.com>
This commit is contained in:
parent
a6b9654e6a
commit
014e2df99b
|
@ -18,7 +18,6 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
|
@ -78,7 +77,7 @@ func Run() {
|
|||
|
||||
func probeHandler(w http.ResponseWriter, r *http.Request, logger log.Logger, config config.Config) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(config.Global.TimeoutSeconds*float64(time.Second)))
|
||||
ctx, cancel := context.WithCancel(r.Context())
|
||||
defer cancel()
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
// Copyright 2020 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 cmd
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/prometheus-community/json_exporter/config"
|
||||
pconfig "github.com/prometheus/common/config"
|
||||
)
|
||||
|
||||
func TestFailIfSelfSignedCA(t *testing.T) {
|
||||
target := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{})
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusServiceUnavailable {
|
||||
t.Fatalf("Fail if (not strict) selfsigned CA test fails unexpectedly, got %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSucceedIfSelfSignedCA(t *testing.T) {
|
||||
c := config.Config{}
|
||||
c.HTTPClientConfig.TLSConfig.InsecureSkipVerify = true
|
||||
target := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Succeed if (not strict) selfsigned CA test fails unexpectedly, got %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailIfTargetMissing(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{})
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("Fail if 'target' query parameter missing test fails unexpectedly, got %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAcceptHeader(t *testing.T) {
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
expected := "application/json"
|
||||
if got := r.Header.Get("Accept"); got != expected {
|
||||
t.Errorf("Default 'Accept' header mismatch, got %s, expected: %s", got, expected)
|
||||
w.WriteHeader(http.StatusNotAcceptable)
|
||||
}
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), config.Config{})
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Default 'Accept: application/json' header test fails unexpectedly, got %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorrectResponse(t *testing.T) {
|
||||
tests := []struct {
|
||||
ConfigFile string
|
||||
ServeFile string
|
||||
ResponseFile string
|
||||
ShouldSucceed bool
|
||||
}{
|
||||
{"../test/config/good.yml", "/serve/good.json", "../test/response/good.txt", true},
|
||||
{"../test/config/good.yml", "/serve/repeat-metric.json", "../test/response/good.txt", false},
|
||||
}
|
||||
|
||||
target := httptest.NewServer(http.FileServer(http.Dir("../test")))
|
||||
defer target.Close()
|
||||
|
||||
for i, test := range tests {
|
||||
c, err := config.LoadConfig(test.ConfigFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load config file %s", test.ConfigFile)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL+test.ServeFile, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
expected, _ := ioutil.ReadFile(test.ResponseFile)
|
||||
|
||||
if test.ShouldSucceed && string(body) != string(expected) {
|
||||
t.Fatalf("Correct response validation test %d fails unexpectedly.\nGOT:\n%s\nEXPECTED:\n%s", i, body, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuth(t *testing.T) {
|
||||
username := "myUser"
|
||||
password := "mySecretPassword"
|
||||
expected := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got := r.Header.Get("Authorization"); got != expected {
|
||||
t.Errorf("BasicAuth mismatch, got: %s, expected: %s", got, expected)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
c := config.Config{}
|
||||
auth := &pconfig.BasicAuth{
|
||||
Username: username,
|
||||
Password: pconfig.Secret(password),
|
||||
}
|
||||
|
||||
c.HTTPClientConfig.BasicAuth = auth
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("BasicAuth test fails unexpectedly. Got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBearerToken(t *testing.T) {
|
||||
token := "mySecretToken"
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
expected := "Bearer " + token
|
||||
if got := r.Header.Get("Authorization"); got != expected {
|
||||
t.Errorf("BearerToken mismatch, got: %s, expected: %s", got, expected)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
c := config.Config{}
|
||||
|
||||
c.HTTPClientConfig.BearerToken = pconfig.Secret(token)
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("BearerToken test fails unexpectedly. Got: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPHeaders(t *testing.T) {
|
||||
headers := map[string]string{
|
||||
"X-Dummy": "test",
|
||||
"User-Agent": "unsuspicious user",
|
||||
"Accept-Language": "en-US",
|
||||
}
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for key, value := range headers {
|
||||
if got := r.Header.Get(key); got != value {
|
||||
t.Errorf("Unexpected value of header %q: expected %q, got %q", key, value, got)
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
req := httptest.NewRequest("GET", "http://example.com/foo"+"?target="+target.URL, nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
c := config.Config{}
|
||||
c.Headers = headers
|
||||
|
||||
probeHandler(recorder, req, log.NewNopLogger(), c)
|
||||
|
||||
resp := recorder.Result()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("Setting custom headers failed unexpectedly. Got: %s", body)
|
||||
}
|
||||
}
|
|
@ -41,14 +41,9 @@ const (
|
|||
type Config struct {
|
||||
Headers map[string]string `yaml:"headers,omitempty"`
|
||||
Metrics []Metric `yaml:"metrics"`
|
||||
Global GlobalConfig `yaml:"global_config,omitempty"`
|
||||
HTTPClientConfig pconfig.HTTPClientConfig `yaml:"http_client_config,omitempty"`
|
||||
}
|
||||
|
||||
type GlobalConfig struct {
|
||||
TimeoutSeconds float64 `yaml:"timeout_seconds,omitempty"`
|
||||
}
|
||||
|
||||
func LoadConfig(configPath string) (Config, error) {
|
||||
var config Config
|
||||
data, err := ioutil.ReadFile(configPath)
|
||||
|
@ -70,9 +65,6 @@ func LoadConfig(configPath string) (Config, error) {
|
|||
config.Metrics[i].Help = config.Metrics[i].Name
|
||||
}
|
||||
}
|
||||
if config.Global.TimeoutSeconds == 0 {
|
||||
config.Global.TimeoutSeconds = 10
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
|
@ -31,6 +31,3 @@ headers:
|
|||
# username: myuser
|
||||
# #password: veryverysecret
|
||||
# password_file: /tmp/mysecret.txt
|
||||
#
|
||||
# global_config:
|
||||
# timeout_seconds: 30 // defaults to 10
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
metrics:
|
||||
- name: example_global_value
|
||||
path: $.counter
|
||||
help: Example of a top-level global value scrape in the json
|
||||
labels:
|
||||
environment: beta # static label
|
||||
location: $.location # dynamic label
|
||||
|
||||
- name: example_value
|
||||
type: object
|
||||
help: Example of sub-level value scrapes from a json
|
||||
path: $.values[*]?(@.state == "ACTIVE")
|
||||
labels:
|
||||
environment: beta # static label
|
||||
id: $.id # dynamic label
|
||||
values:
|
||||
active: 1 # static value
|
||||
count: $.count # dynamic value
|
||||
boolean: $.some_boolean
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# HELP example_global_value Example of a top-level global value scrape in the json
|
||||
# TYPE example_global_value untyped
|
||||
example_global_value{environment="beta",location="mars"} 1234
|
||||
# HELP example_value_active Example of sub-level value scrapes from a json
|
||||
# TYPE example_value_active untyped
|
||||
example_value_active{environment="beta",id="id-A"} 1
|
||||
example_value_active{environment="beta",id="id-C"} 1
|
||||
# HELP example_value_boolean Example of sub-level value scrapes from a json
|
||||
# TYPE example_value_boolean untyped
|
||||
example_value_boolean{environment="beta",id="id-A"} 1
|
||||
example_value_boolean{environment="beta",id="id-C"} 0
|
||||
# HELP example_value_count Example of sub-level value scrapes from a json
|
||||
# TYPE example_value_count untyped
|
||||
example_value_count{environment="beta",id="id-A"} 1
|
||||
example_value_count{environment="beta",id="id-C"} 3
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"counter": 1234,
|
||||
"values": [
|
||||
{
|
||||
"id": "id-A",
|
||||
"count": 1,
|
||||
"some_boolean": true,
|
||||
"state": "ACTIVE"
|
||||
},
|
||||
{
|
||||
"id": "id-B",
|
||||
"count": 2,
|
||||
"some_boolean": true,
|
||||
"state": "INACTIVE"
|
||||
},
|
||||
{
|
||||
"id": "id-C",
|
||||
"count": 3,
|
||||
"some_boolean": false,
|
||||
"state": "ACTIVE"
|
||||
}
|
||||
],
|
||||
"location": "mars"
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"counter": 1234,
|
||||
"values": [
|
||||
{
|
||||
"id": "id-A",
|
||||
"count": 1,
|
||||
"some_boolean": true,
|
||||
"state": "ACTIVE"
|
||||
},
|
||||
{
|
||||
"id": "id-B",
|
||||
"count": 2,
|
||||
"some_boolean": true,
|
||||
"state": "INACTIVE"
|
||||
},
|
||||
{
|
||||
"id": "id-C",
|
||||
"count": 3,
|
||||
"some_boolean": true,
|
||||
"state": "ACTIVE"
|
||||
},
|
||||
{
|
||||
"id": "id-C",
|
||||
"count": 4,
|
||||
"some_boolean": false,
|
||||
"state": "ACTIVE"
|
||||
}
|
||||
],
|
||||
"location": "mars"
|
||||
}
|
Loading…
Reference in New Issue