mirror of
https://github.com/prometheus-community/json_exporter
synced 2025-05-10 03:38:36 +00:00
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"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-kit/kit/log"
|
"github.com/go-kit/kit/log"
|
||||||
"github.com/go-kit/kit/log/level"
|
"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) {
|
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()
|
defer cancel()
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
220
cmd/main_test.go
Normal file
220
cmd/main_test.go
Normal file
@ -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 {
|
type Config struct {
|
||||||
Headers map[string]string `yaml:"headers,omitempty"`
|
Headers map[string]string `yaml:"headers,omitempty"`
|
||||||
Metrics []Metric `yaml:"metrics"`
|
Metrics []Metric `yaml:"metrics"`
|
||||||
Global GlobalConfig `yaml:"global_config,omitempty"`
|
|
||||||
HTTPClientConfig pconfig.HTTPClientConfig `yaml:"http_client_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) {
|
func LoadConfig(configPath string) (Config, error) {
|
||||||
var config Config
|
var config Config
|
||||||
data, err := ioutil.ReadFile(configPath)
|
data, err := ioutil.ReadFile(configPath)
|
||||||
@ -70,9 +65,6 @@ func LoadConfig(configPath string) (Config, error) {
|
|||||||
config.Metrics[i].Help = config.Metrics[i].Name
|
config.Metrics[i].Help = config.Metrics[i].Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.Global.TimeoutSeconds == 0 {
|
|
||||||
config.Global.TimeoutSeconds = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,3 @@ headers:
|
|||||||
# username: myuser
|
# username: myuser
|
||||||
# #password: veryverysecret
|
# #password: veryverysecret
|
||||||
# password_file: /tmp/mysecret.txt
|
# password_file: /tmp/mysecret.txt
|
||||||
#
|
|
||||||
# global_config:
|
|
||||||
# timeout_seconds: 30 // defaults to 10
|
|
||||||
|
21
test/config/good.yml
Normal file
21
test/config/good.yml
Normal file
@ -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
|
||||||
|
|
15
test/response/good.txt
Normal file
15
test/response/good.txt
Normal file
@ -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
|
24
test/serve/good.json
Normal file
24
test/serve/good.json
Normal file
@ -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"
|
||||||
|
}
|
30
test/serve/repeat-metric.json
Normal file
30
test/serve/repeat-metric.json
Normal file
@ -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
Block a user