Add unit tests

Signed-off-by: rustyclock <rustyclock@protonmail.com>
This commit is contained in:
rustyclock 2020-08-20 11:48:09 +09:00
parent a6b9654e6a
commit 014e2df99b
No known key found for this signature in database
GPG Key ID: FB2B3735971D7E3F
8 changed files with 311 additions and 13 deletions

View File

@ -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)

220
cmd/main_test.go Normal file
View 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)
}
}

View File

@ -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
}

View File

@ -31,6 +31,3 @@ headers:
# username: myuser
# #password: veryverysecret
# password_file: /tmp/mysecret.txt
#
# global_config:
# timeout_seconds: 30 // defaults to 10

21
test/config/good.yml Normal file
View 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
View 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
View 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"
}

View 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"
}