1482 lines
41 KiB
Go
1482 lines
41 KiB
Go
|
package http_response
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"net/http/httptest"
|
||
|
"net/url"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/stretchr/testify/require"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/config"
|
||
|
"github.com/influxdata/telegraf/internal"
|
||
|
"github.com/influxdata/telegraf/plugins/common/tls"
|
||
|
"github.com/influxdata/telegraf/testutil"
|
||
|
)
|
||
|
|
||
|
// Receives a list with fields that are expected to be absent
|
||
|
func checkAbsentFields(t *testing.T, fields []string, acc *testutil.Accumulator) {
|
||
|
for _, field := range fields {
|
||
|
ok := acc.HasField("http_response", field)
|
||
|
require.False(t, ok)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Receives a list with tags that are expected to be absent
|
||
|
func checkAbsentTags(t *testing.T, tags []string, acc *testutil.Accumulator) {
|
||
|
for _, tag := range tags {
|
||
|
ok := acc.HasTag("http_response", tag)
|
||
|
require.False(t, ok)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Receives a dictionary and with expected fields and their values. If a value is nil, it will only check
|
||
|
// that the field exists, but not its contents
|
||
|
func checkFields(t *testing.T, fields map[string]interface{}, acc *testutil.Accumulator) {
|
||
|
t.Helper()
|
||
|
for key, field := range fields {
|
||
|
switch v := field.(type) {
|
||
|
case int:
|
||
|
value, ok := acc.IntField("http_response", key)
|
||
|
require.True(t, ok)
|
||
|
require.Equal(t, field, value)
|
||
|
case float64:
|
||
|
value, ok := acc.FloatField("http_response", key)
|
||
|
require.True(t, ok)
|
||
|
require.InDelta(t, field, value, testutil.DefaultDelta)
|
||
|
case string:
|
||
|
value, ok := acc.StringField("http_response", key)
|
||
|
require.True(t, ok)
|
||
|
require.Equal(t, field, value)
|
||
|
case nil:
|
||
|
ok := acc.HasField("http_response", key)
|
||
|
require.True(t, ok)
|
||
|
default:
|
||
|
t.Log("Unsupported type for field: ", v)
|
||
|
t.Fail()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Receives a dictionary and with expected tags and their values. If a value is nil, it will only check
|
||
|
// that the tag exists, but not its contents
|
||
|
func checkTags(t *testing.T, tags map[string]interface{}, acc *testutil.Accumulator) {
|
||
|
for key, tag := range tags {
|
||
|
switch v := tag.(type) {
|
||
|
case string:
|
||
|
ok := acc.HasTag("http_response", key)
|
||
|
require.True(t, ok)
|
||
|
require.Equal(t, tag, acc.TagValue("http_response", key))
|
||
|
case nil:
|
||
|
ok := acc.HasTag("http_response", key)
|
||
|
require.True(t, ok)
|
||
|
default:
|
||
|
t.Log("Unsupported type for tag: ", v)
|
||
|
t.Fail()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func setUpTestMux() http.Handler {
|
||
|
mux := http.NewServeMux()
|
||
|
// Ignore all returned errors below as the tests will fail anyway
|
||
|
mux.HandleFunc("/redirect", func(w http.ResponseWriter, req *http.Request) {
|
||
|
http.Redirect(w, req, "/good", http.StatusMovedPermanently)
|
||
|
})
|
||
|
mux.HandleFunc("/good", func(w http.ResponseWriter, _ *http.Request) {
|
||
|
w.Header().Set("Server", "MyTestServer")
|
||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
fmt.Fprintf(w, "hit the good page!")
|
||
|
})
|
||
|
mux.HandleFunc("/form", func(w http.ResponseWriter, req *http.Request) {
|
||
|
body, err := io.ReadAll(req.Body)
|
||
|
defer req.Body.Close()
|
||
|
if err != nil {
|
||
|
http.Error(w, "couldn't read request body", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
if string(body) != "list=foobar&list=fizbuzz&test=42" {
|
||
|
fmt.Println(string(body))
|
||
|
w.WriteHeader(http.StatusBadRequest)
|
||
|
} else {
|
||
|
w.WriteHeader(http.StatusOK)
|
||
|
}
|
||
|
})
|
||
|
mux.HandleFunc("/invalidUTF8", func(w http.ResponseWriter, _ *http.Request) {
|
||
|
w.Write([]byte{0xff, 0xfe, 0xfd}) //nolint:errcheck // ignore the returned error as the test will fail anyway
|
||
|
})
|
||
|
mux.HandleFunc("/noheader", func(w http.ResponseWriter, _ *http.Request) {
|
||
|
fmt.Fprintf(w, "hit the good page!")
|
||
|
})
|
||
|
mux.HandleFunc("/jsonresponse", func(w http.ResponseWriter, _ *http.Request) {
|
||
|
fmt.Fprintf(w, "\"service_status\": \"up\", \"healthy\" : \"true\"")
|
||
|
})
|
||
|
mux.HandleFunc("/badredirect", func(w http.ResponseWriter, req *http.Request) {
|
||
|
http.Redirect(w, req, "/badredirect", http.StatusMovedPermanently)
|
||
|
})
|
||
|
mux.HandleFunc("/mustbepostmethod", func(w http.ResponseWriter, req *http.Request) {
|
||
|
if req.Method != "POST" {
|
||
|
http.Error(w, "method wasn't post", http.StatusMethodNotAllowed)
|
||
|
return
|
||
|
}
|
||
|
fmt.Fprintf(w, "used post correctly!")
|
||
|
})
|
||
|
mux.HandleFunc("/musthaveabody", func(w http.ResponseWriter, req *http.Request) {
|
||
|
body, err := io.ReadAll(req.Body)
|
||
|
defer req.Body.Close()
|
||
|
if err != nil {
|
||
|
http.Error(w, "couldn't read request body", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
if len(body) == 0 {
|
||
|
http.Error(w, "body was empty", http.StatusBadRequest)
|
||
|
return
|
||
|
}
|
||
|
fmt.Fprintf(w, "sent a body!")
|
||
|
})
|
||
|
mux.HandleFunc("/twosecondnap", func(http.ResponseWriter, *http.Request) {
|
||
|
time.Sleep(time.Second * 2)
|
||
|
})
|
||
|
mux.HandleFunc("/nocontent", func(w http.ResponseWriter, _ *http.Request) {
|
||
|
w.WriteHeader(http.StatusNoContent)
|
||
|
})
|
||
|
return mux
|
||
|
}
|
||
|
|
||
|
func checkOutput(t *testing.T, acc *testutil.Accumulator, presentFields, presentTags map[string]interface{}, absentFields, absentTags []string) {
|
||
|
t.Helper()
|
||
|
if presentFields != nil {
|
||
|
checkFields(t, presentFields, acc)
|
||
|
}
|
||
|
|
||
|
if presentTags != nil {
|
||
|
checkTags(t, presentTags, acc)
|
||
|
}
|
||
|
|
||
|
if absentFields != nil {
|
||
|
checkAbsentFields(t, absentFields, acc)
|
||
|
}
|
||
|
|
||
|
if absentTags != nil {
|
||
|
checkAbsentTags(t, absentTags, acc)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestHeaders(t *testing.T) {
|
||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
if r.Host != "Hello" {
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
t.Errorf("Not equal, expected: %q, actual: %q", "Hello", r.Host)
|
||
|
return
|
||
|
}
|
||
|
if cHeader := r.Header.Get("Content-Type"); cHeader != "application/json" {
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
t.Errorf("Not equal, expected: %q, actual: %q", "application/json", cHeader)
|
||
|
return
|
||
|
}
|
||
|
if uaHeader := r.Header.Get("User-Agent"); uaHeader != internal.ProductToken() {
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
t.Errorf("Not equal, expected: %q, actual: %q", internal.ProductToken(), uaHeader)
|
||
|
return
|
||
|
}
|
||
|
w.WriteHeader(http.StatusOK)
|
||
|
}))
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL},
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 2),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
"Host": "Hello",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func TestFields(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func TestResponseBodyField(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
ResponseBodyField: "my_body_field",
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
"my_body_field": "hit the good page!",
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
|
||
|
// Invalid UTF-8 String
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/invalidUTF8"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
ResponseBodyField: "my_body_field",
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields = map[string]interface{}{
|
||
|
"result_type": "body_read_error",
|
||
|
"result_code": 2,
|
||
|
}
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"result": "body_read_error",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestResponseBodyFormField(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/form"},
|
||
|
BodyForm: map[string][]string{
|
||
|
"test": {"42"},
|
||
|
"list": {"foobar", "fizbuzz"},
|
||
|
},
|
||
|
Method: "POST",
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||
|
},
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
ResponseBodyField: "my_body_field",
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
"my_body_field": "",
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "POST",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestResponseBodyMaxSize(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
ResponseBodyMaxSize: config.Size(5),
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"result_type": "body_read_error",
|
||
|
"result_code": 2,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"result": "body_read_error",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestHTTPHeaderTags(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
HTTPHeaderTags: map[string]string{"Server": "my_server", "Content-Type": "content_type"},
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
"my_server": "MyTestServer",
|
||
|
"content_type": "application/json; charset=utf-8",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/noheader"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
HTTPHeaderTags: map[string]string{"Server": "my_server", "Content-Type": "content_type"},
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
|
||
|
// Connection failed
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{"https:/nonexistent.nonexistent"}, // Any non-routable IP works here
|
||
|
Body: "",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 5),
|
||
|
HTTPHeaderTags: map[string]string{"Server": "my_server", "Content-Type": "content_type"},
|
||
|
FollowRedirects: false,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields = map[string]interface{}{
|
||
|
"result_type": "connection_failed",
|
||
|
"result_code": 3,
|
||
|
}
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"result": "connection_failed",
|
||
|
}
|
||
|
absentFields = []string{"http_response_code", "response_time", "content_length", "response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func findInterface() (net.Interface, error) {
|
||
|
potential, err := net.Interfaces()
|
||
|
if err != nil {
|
||
|
return net.Interface{}, err
|
||
|
}
|
||
|
|
||
|
for _, i := range potential {
|
||
|
// we are only interest in loopback interfaces which are up
|
||
|
if (i.Flags&net.FlagUp == 0) || (i.Flags&net.FlagLoopback == 0) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if addrs, err := i.Addrs(); err == nil && len(addrs) > 0 {
|
||
|
return i, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return net.Interface{}, errors.New("cannot find suitable loopback interface")
|
||
|
}
|
||
|
|
||
|
func TestInterface(t *testing.T) {
|
||
|
var (
|
||
|
mux = setUpTestMux()
|
||
|
ts = httptest.NewServer(mux)
|
||
|
)
|
||
|
|
||
|
defer ts.Close()
|
||
|
|
||
|
intf, err := findInterface()
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
Interface: intf.Name,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func TestRedirects(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/redirect"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/badredirect"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields = map[string]interface{}{
|
||
|
"result_type": "connection_failed",
|
||
|
"result_code": 3,
|
||
|
}
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"result": "connection_failed",
|
||
|
}
|
||
|
absentFields = []string{"http_response_code", "response_time", "response_string_match"}
|
||
|
absentTags := []string{"status_code"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
|
||
|
expectedFields = map[string]interface{}{"result_type": "connection_failed"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, absentTags)
|
||
|
}
|
||
|
|
||
|
func TestMethod(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/mustbepostmethod"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "POST",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "POST",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/mustbepostmethod"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields = map[string]interface{}{
|
||
|
"http_response_code": http.StatusMethodNotAllowed,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "405",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields = []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
|
||
|
// check that lowercase methods work correctly
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/mustbepostmethod"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "head",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields = map[string]interface{}{
|
||
|
"http_response_code": http.StatusMethodNotAllowed,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "head",
|
||
|
"status_code": "405",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields = []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func TestBody(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/musthaveabody"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/musthaveabody"},
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields = map[string]interface{}{
|
||
|
"http_response_code": http.StatusBadRequest,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
}
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "400",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields = []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func TestStringMatch(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseStringMatch: "hit the good page",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"response_string_match": 1,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestStringMatchJson(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/jsonresponse"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseStringMatch: "\"service_status\": \"up\"",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"response_string_match": 1,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestStringMatchFail(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseStringMatch: "hit the bad page",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"response_string_match": 0,
|
||
|
"result_type": "response_string_mismatch",
|
||
|
"result_code": 1,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "response_string_mismatch",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestTimeout(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("Skipping test with sleep in short mode.")
|
||
|
}
|
||
|
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/twosecondnap"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"result_type": "timeout",
|
||
|
"result_code": 4,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"result": "timeout",
|
||
|
}
|
||
|
absentFields := []string{"http_response_code", "response_time", "content_length", "response_string_match"}
|
||
|
absentTags := []string{"status_code"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, absentTags)
|
||
|
}
|
||
|
|
||
|
func TestBadRegex(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseStringMatch: "bad regex:[[",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
require.ErrorContains(t, h.Init(), "failed to compile regular expression")
|
||
|
}
|
||
|
|
||
|
type fakeClient struct {
|
||
|
statusCode int
|
||
|
err error
|
||
|
}
|
||
|
|
||
|
func (f *fakeClient) Do(_ *http.Request) (*http.Response, error) {
|
||
|
return &http.Response{StatusCode: f.statusCode}, f.err
|
||
|
}
|
||
|
|
||
|
func TestNetworkErrors(t *testing.T) {
|
||
|
cl := client{
|
||
|
httpClient: &fakeClient{err: &url.Error{Err: &net.OpError{Err: &net.DNSError{Err: "DNS error"}}}},
|
||
|
address: "",
|
||
|
}
|
||
|
// DNS error
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{"https://nonexistent.nonexistent"}, // Any non-resolvable URL works here
|
||
|
Body: "",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
FollowRedirects: false,
|
||
|
clients: []client{cl},
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"result_type": "dns_error",
|
||
|
"result_code": 5,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"result": "dns_error",
|
||
|
}
|
||
|
absentFields := []string{"http_response_code", "response_time", "content_length", "response_string_match"}
|
||
|
absentTags := []string{"status_code"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, absentTags)
|
||
|
|
||
|
// Connection failed
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{"https:/nonexistent.nonexistent"}, // Any non-routable IP works here
|
||
|
Body: "",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 5),
|
||
|
FollowRedirects: false,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields = map[string]interface{}{
|
||
|
"result_type": "connection_failed",
|
||
|
"result_code": 3,
|
||
|
}
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"result": "connection_failed",
|
||
|
}
|
||
|
absentFields = []string{"http_response_code", "response_time", "content_length", "response_string_match"}
|
||
|
absentTags = []string{"status_code"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, absentTags)
|
||
|
}
|
||
|
|
||
|
func TestContentLength(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": len([]byte("hit the good page!")),
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
|
||
|
h = &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/musthaveabody"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
FollowRedirects: true,
|
||
|
}
|
||
|
|
||
|
acc = testutil.Accumulator{}
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields = map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": len([]byte("sent a body!")),
|
||
|
}
|
||
|
expectedTags = map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields = []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func TestRedirect(t *testing.T) {
|
||
|
ts := httptest.NewServer(http.NotFoundHandler())
|
||
|
defer ts.Close()
|
||
|
|
||
|
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||
|
w.Header().Add("Location", "http://example.org")
|
||
|
w.WriteHeader(http.StatusMovedPermanently)
|
||
|
if _, err := w.Write([]byte("test")); err != nil {
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
t.Error(err)
|
||
|
return
|
||
|
}
|
||
|
})
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
URLs: []string{ts.URL},
|
||
|
ResponseStringMatch: "test",
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expected := []telegraf.Metric{
|
||
|
testutil.MustMetric(
|
||
|
"http_response",
|
||
|
map[string]string{
|
||
|
"server": ts.URL,
|
||
|
"method": "GET",
|
||
|
"result": "success",
|
||
|
"status_code": "301",
|
||
|
},
|
||
|
map[string]interface{}{
|
||
|
"result_code": 0,
|
||
|
"result_type": "success",
|
||
|
"http_response_code": 301,
|
||
|
"response_string_match": 1,
|
||
|
"content_length": 4,
|
||
|
},
|
||
|
time.Unix(0, 0),
|
||
|
),
|
||
|
}
|
||
|
|
||
|
actual := acc.GetTelegrafMetrics()
|
||
|
for _, m := range actual {
|
||
|
m.RemoveField("response_time")
|
||
|
}
|
||
|
|
||
|
testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime())
|
||
|
}
|
||
|
|
||
|
func TestBasicAuth(t *testing.T) {
|
||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
if aHeader := r.Header.Get("Authorization"); aHeader != "Basic bWU6bXlwYXNzd29yZA==" {
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
t.Errorf("Not equal, expected: %q, actual: %q", "Basic bWU6bXlwYXNzd29yZA==", aHeader)
|
||
|
return
|
||
|
}
|
||
|
w.WriteHeader(http.StatusOK)
|
||
|
}))
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Body: "{ 'test': 'data'}",
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
Username: config.NewSecret([]byte("me")),
|
||
|
Password: config.NewSecret([]byte("mypassword")),
|
||
|
Headers: map[string]string{
|
||
|
"Content-Type": "application/json",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func TestStatusCodeMatchFail(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/nocontent"},
|
||
|
ResponseStatusCode: http.StatusOK,
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusNoContent,
|
||
|
"response_status_code_match": 0,
|
||
|
"result_type": "response_status_code_mismatch",
|
||
|
"result_code": 6,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": http.MethodGet,
|
||
|
"status_code": "204",
|
||
|
"result": "response_status_code_mismatch",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestStatusCodeMatch(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/nocontent"},
|
||
|
ResponseStatusCode: http.StatusNoContent,
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusNoContent,
|
||
|
"response_status_code_match": 1,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": http.MethodGet,
|
||
|
"status_code": "204",
|
||
|
"result": "success",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestStatusCodeAndStringMatch(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
ResponseStatusCode: http.StatusOK,
|
||
|
ResponseStringMatch: "hit the good page",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"response_status_code_match": 1,
|
||
|
"response_string_match": 1,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": http.MethodGet,
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestStatusCodeAndStringMatchFail(t *testing.T) {
|
||
|
mux := setUpTestMux()
|
||
|
ts := httptest.NewServer(mux)
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/nocontent"},
|
||
|
ResponseStatusCode: http.StatusOK,
|
||
|
ResponseStringMatch: "hit the good page",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusNoContent,
|
||
|
"response_status_code_match": 0,
|
||
|
"response_string_match": 0,
|
||
|
"result_type": "response_status_code_mismatch",
|
||
|
"result_code": 6,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": http.MethodGet,
|
||
|
"status_code": "204",
|
||
|
"result": "response_status_code_mismatch",
|
||
|
}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, nil, nil)
|
||
|
}
|
||
|
|
||
|
func TestSNI(t *testing.T) {
|
||
|
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
if r.TLS.ServerName != "super-special-hostname.example.com" {
|
||
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
t.Errorf("Not equal, expected: %q, actual: %q", "super-special-hostname.example.com", r.TLS.ServerName)
|
||
|
return
|
||
|
}
|
||
|
w.WriteHeader(http.StatusOK)
|
||
|
}))
|
||
|
defer ts.Close()
|
||
|
|
||
|
h := &HTTPResponse{
|
||
|
Log: testutil.Logger{},
|
||
|
URLs: []string{ts.URL + "/good"},
|
||
|
Method: "GET",
|
||
|
ResponseTimeout: config.Duration(time.Second * 20),
|
||
|
ClientConfig: tls.ClientConfig{
|
||
|
InsecureSkipVerify: true,
|
||
|
ServerName: "super-special-hostname.example.com",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
require.NoError(t, h.Init())
|
||
|
require.NoError(t, h.Gather(&acc))
|
||
|
|
||
|
expectedFields := map[string]interface{}{
|
||
|
"http_response_code": http.StatusOK,
|
||
|
"result_type": "success",
|
||
|
"result_code": 0,
|
||
|
"response_time": nil,
|
||
|
"content_length": nil,
|
||
|
}
|
||
|
expectedTags := map[string]interface{}{
|
||
|
"server": nil,
|
||
|
"method": "GET",
|
||
|
"status_code": "200",
|
||
|
"result": "success",
|
||
|
}
|
||
|
absentFields := []string{"response_string_match"}
|
||
|
checkOutput(t, &acc, expectedFields, expectedTags, absentFields, nil)
|
||
|
}
|
||
|
|
||
|
func Test_isURLInIPv6(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
address url.URL
|
||
|
want bool
|
||
|
}{
|
||
|
{
|
||
|
address: parseURL(t, "http://[2001:db8:a0b:12f0::1]/index.html"),
|
||
|
want: true,
|
||
|
}, {
|
||
|
address: parseURL(t, "http://[2001:db8:a0b:12f0::1]:80/index.html"),
|
||
|
want: true,
|
||
|
}, {
|
||
|
address: parseURL(t, "https://[2001:db8:a0b:12f0::1%25eth0]:15000/"), // `%25` escapes `%`
|
||
|
want: true,
|
||
|
}, {
|
||
|
address: parseURL(t, "https://2001:0db8:0001:0000:0000:0ab9:C0A8:0102"),
|
||
|
want: true,
|
||
|
}, {
|
||
|
address: parseURL(t, "http://[2607:f8b0:4005:802::1007]/"),
|
||
|
want: true,
|
||
|
}, {
|
||
|
address: parseURL(t, "https://127.0.0.1"),
|
||
|
want: false,
|
||
|
}, {
|
||
|
address: parseURL(t, "https://google.com"),
|
||
|
want: false,
|
||
|
}, {
|
||
|
address: parseURL(t, "https://thispagemayexist.ornot/index.html"),
|
||
|
want: false,
|
||
|
},
|
||
|
}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.address.String(), func(t *testing.T) {
|
||
|
if got, _ := isURLInIPv6(tt.address); got != tt.want {
|
||
|
t.Errorf("isURLInIPv6() = %v, want %v", got, tt.want)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func Test_isIPNetInIPv6(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
address *net.IPNet
|
||
|
want bool
|
||
|
}{
|
||
|
{
|
||
|
address: &net.IPNet{
|
||
|
IP: net.IPv4(127, 0, 0, 1),
|
||
|
Mask: net.CIDRMask(8, 32),
|
||
|
},
|
||
|
want: false,
|
||
|
}, {
|
||
|
address: &net.IPNet{
|
||
|
IP: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||
|
Mask: net.CIDRMask(128, 128),
|
||
|
},
|
||
|
want: true,
|
||
|
}, {
|
||
|
address: &net.IPNet{
|
||
|
IP: net.IPv4(192, 168, 0, 1),
|
||
|
Mask: net.CIDRMask(24, 32),
|
||
|
},
|
||
|
want: false,
|
||
|
}, {
|
||
|
address: &net.IPNet{
|
||
|
IP: net.ParseIP("fe80::43ac:7835:471a:faba"),
|
||
|
Mask: net.CIDRMask(64, 128),
|
||
|
},
|
||
|
want: true,
|
||
|
},
|
||
|
}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.address.String(), func(t *testing.T) {
|
||
|
if got := isIPNetInIPv6(tt.address); got != tt.want {
|
||
|
t.Errorf("isIPNetInIPv6() = %v, want %v", got, tt.want)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func parseURL(t *testing.T, address string) url.URL {
|
||
|
u, err := url.Parse(address)
|
||
|
require.NoError(t, err)
|
||
|
require.NotNil(t, u)
|
||
|
return *u
|
||
|
}
|