2020-09-26 20:23:43 +02:00
package client
2020-10-05 01:49:02 +02:00
import (
2022-03-10 02:53:51 +01:00
"bytes"
2024-04-10 00:41:37 +02:00
"crypto/tls"
2022-05-17 04:19:42 +02:00
"io"
2022-03-10 02:53:51 +01:00
"net/http"
2020-10-05 01:49:02 +02:00
"testing"
2021-01-13 03:26:28 +01:00
"time"
2022-03-10 02:53:51 +01:00
2024-05-10 04:56:16 +02:00
"github.com/TwiN/gatus/v5/config/endpoint/dns"
"github.com/TwiN/gatus/v5/pattern"
2022-12-06 07:41:09 +01:00
"github.com/TwiN/gatus/v5/test"
2020-10-05 01:49:02 +02:00
)
2020-09-26 20:23:43 +02:00
2020-12-28 23:19:41 +01:00
func TestGetHTTPClient ( t * testing . T ) {
2021-07-30 00:13:37 +02:00
cfg := & Config {
2021-07-29 03:41:26 +02:00
Insecure : false ,
IgnoreRedirect : false ,
Timeout : 0 ,
2022-06-13 00:45:08 +02:00
DNSResolver : "tcp://1.1.1.1:53" ,
2022-03-10 02:53:51 +01:00
OAuth2Config : & OAuth2Config {
ClientID : "00000000-0000-0000-0000-000000000000" ,
ClientSecret : "secretsauce" ,
TokenURL : "https://token-server.local/token" ,
Scopes : [ ] string { "https://application.local/.default" } ,
} ,
2021-07-30 00:13:37 +02:00
}
2022-06-13 00:45:08 +02:00
err := cfg . ValidateAndSetDefaults ( )
if err != nil {
t . Errorf ( "expected error to be nil, but got: `%s`" , err )
}
2021-07-30 00:13:37 +02:00
if GetHTTPClient ( cfg ) == nil {
t . Error ( "expected client to not be nil" )
}
if GetHTTPClient ( nil ) == nil {
t . Error ( "expected client to not be nil" )
}
2020-09-26 20:23:43 +02:00
}
2020-12-25 06:07:18 +01:00
2022-11-16 03:35:22 +01:00
func TestGetDomainExpiration ( t * testing . T ) {
2023-01-08 23:53:37 +01:00
t . Parallel ( )
2022-11-16 03:35:22 +01:00
if domainExpiration , err := GetDomainExpiration ( "example.com" ) ; err != nil {
t . Fatalf ( "expected error to be nil, but got: `%s`" , err )
} else if domainExpiration <= 0 {
t . Error ( "expected domain expiration to be higher than 0" )
}
if domainExpiration , err := GetDomainExpiration ( "example.com" ) ; err != nil {
t . Errorf ( "expected error to be nil, but got: `%s`" , err )
} else if domainExpiration <= 0 {
t . Error ( "expected domain expiration to be higher than 0" )
}
// Hack to pretend like the domain is expiring in 1 hour, which should trigger a refresh
whoisExpirationDateCache . SetWithTTL ( "example.com" , time . Now ( ) . Add ( time . Hour ) , 25 * time . Hour )
if domainExpiration , err := GetDomainExpiration ( "example.com" ) ; err != nil {
t . Errorf ( "expected error to be nil, but got: `%s`" , err )
} else if domainExpiration <= 0 {
t . Error ( "expected domain expiration to be higher than 0" )
}
// Make sure the refresh works when the ttl is <24 hours
whoisExpirationDateCache . SetWithTTL ( "example.com" , time . Now ( ) . Add ( 35 * time . Hour ) , 23 * time . Hour )
if domainExpiration , err := GetDomainExpiration ( "example.com" ) ; err != nil {
t . Errorf ( "expected error to be nil, but got: `%s`" , err )
} else if domainExpiration <= 0 {
t . Error ( "expected domain expiration to be higher than 0" )
}
}
2020-12-27 23:07:50 +01:00
func TestPing ( t * testing . T ) {
2023-01-08 23:53:37 +01:00
t . Parallel ( )
2021-07-29 03:41:26 +02:00
if success , rtt := Ping ( "127.0.0.1" , & Config { Timeout : 500 * time . Millisecond } ) ; ! success {
2020-12-25 06:07:18 +01:00
t . Error ( "expected true" )
2020-12-27 23:07:50 +01:00
if rtt == 0 {
t . Error ( "Round-trip time returned on success should've higher than 0" )
}
2020-12-25 06:07:18 +01:00
}
2021-07-29 03:41:26 +02:00
if success , rtt := Ping ( "256.256.256.256" , & Config { Timeout : 500 * time . Millisecond } ) ; success {
2021-01-13 03:26:28 +01:00
t . Error ( "expected false, because the IP is invalid" )
if rtt != 0 {
t . Error ( "Round-trip time returned on failure should've been 0" )
}
}
2021-07-29 03:41:26 +02:00
if success , rtt := Ping ( "192.168.152.153" , & Config { Timeout : 500 * time . Millisecond } ) ; success {
2021-01-13 03:26:28 +01:00
t . Error ( "expected false, because the IP is valid but the host should be unreachable" )
2020-12-27 23:07:50 +01:00
if rtt != 0 {
t . Error ( "Round-trip time returned on failure should've been 0" )
}
2020-12-25 06:07:18 +01:00
}
2024-02-07 03:15:51 +01:00
// Can't perform integration tests (e.g. pinging public targets by single-stacked hostname) here,
// because ICMP is blocked in the network of GitHub-hosted runners.
if success , rtt := Ping ( "127.0.0.1" , & Config { Timeout : 500 * time . Millisecond , Network : "ip" } ) ; ! success {
t . Error ( "expected true" )
if rtt == 0 {
t . Error ( "Round-trip time returned on failure should've been 0" )
}
}
if success , rtt := Ping ( "::1" , & Config { Timeout : 500 * time . Millisecond , Network : "ip" } ) ; ! success {
t . Error ( "expected true" )
if rtt == 0 {
t . Error ( "Round-trip time returned on failure should've been 0" )
}
}
if success , rtt := Ping ( "::1" , & Config { Timeout : 500 * time . Millisecond , Network : "ip4" } ) ; success {
t . Error ( "expected false, because the IP isn't an IPv4 address" )
if rtt != 0 {
t . Error ( "Round-trip time returned on failure should've been 0" )
}
}
if success , rtt := Ping ( "127.0.0.1" , & Config { Timeout : 500 * time . Millisecond , Network : "ip6" } ) ; success {
t . Error ( "expected false, because the IP isn't an IPv6 address" )
if rtt != 0 {
t . Error ( "Round-trip time returned on failure should've been 0" )
}
}
2020-12-25 06:07:18 +01:00
}
2021-06-05 21:47:11 +02:00
2021-06-05 22:35:52 +02:00
func TestCanPerformStartTLS ( t * testing . T ) {
2021-06-05 21:47:11 +02:00
type args struct {
address string
insecure bool
}
tests := [ ] struct {
2021-06-05 22:35:52 +02:00
name string
args args
wantConnected bool
wantErr bool
2021-06-05 21:47:11 +02:00
} {
{
name : "invalid address" ,
args : args {
address : "test" ,
} ,
2021-06-05 22:35:52 +02:00
wantConnected : false ,
wantErr : true ,
2021-06-05 21:47:11 +02:00
} ,
{
name : "error dial" ,
args : args {
address : "test:1234" ,
} ,
2021-06-05 22:35:52 +02:00
wantConnected : false ,
wantErr : true ,
2021-06-05 21:47:11 +02:00
} ,
{
name : "valid starttls" ,
args : args {
address : "smtp.gmail.com:587" ,
} ,
wantConnected : true ,
wantErr : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2023-01-08 23:53:37 +01:00
t . Parallel ( )
2021-07-29 03:41:26 +02:00
connected , _ , err := CanPerformStartTLS ( tt . args . address , & Config { Insecure : tt . args . insecure , Timeout : 5 * time . Second } )
2021-06-05 21:47:11 +02:00
if ( err != nil ) != tt . wantErr {
2021-06-05 22:35:52 +02:00
t . Errorf ( "CanPerformStartTLS() err=%v, wantErr=%v" , err , tt . wantErr )
2021-06-05 21:47:11 +02:00
return
}
2021-06-05 22:35:52 +02:00
if connected != tt . wantConnected {
t . Errorf ( "CanPerformStartTLS() connected=%v, wantConnected=%v" , connected , tt . wantConnected )
2021-06-05 21:47:11 +02:00
}
} )
}
}
2021-07-17 03:12:18 +02:00
2021-09-30 22:15:17 +02:00
func TestCanPerformTLS ( t * testing . T ) {
2021-10-01 02:45:47 +02:00
type args struct {
address string
insecure bool
}
tests := [ ] struct {
name string
args args
wantConnected bool
wantErr bool
} {
{
name : "invalid address" ,
args : args {
address : "test" ,
} ,
wantConnected : false ,
wantErr : true ,
} ,
{
name : "error dial" ,
args : args {
address : "test:1234" ,
} ,
wantConnected : false ,
wantErr : true ,
} ,
{
name : "valid tls" ,
args : args {
address : "smtp.gmail.com:465" ,
} ,
wantConnected : true ,
wantErr : false ,
} ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
2023-01-08 23:53:37 +01:00
t . Parallel ( )
2021-10-01 02:45:47 +02:00
connected , _ , err := CanPerformTLS ( tt . args . address , & Config { Insecure : tt . args . insecure , Timeout : 5 * time . Second } )
if ( err != nil ) != tt . wantErr {
t . Errorf ( "CanPerformTLS() err=%v, wantErr=%v" , err , tt . wantErr )
return
}
if connected != tt . wantConnected {
t . Errorf ( "CanPerformTLS() connected=%v, wantConnected=%v" , connected , tt . wantConnected )
}
} )
}
2021-09-30 22:15:17 +02:00
}
2021-07-17 03:12:18 +02:00
func TestCanCreateTCPConnection ( t * testing . T ) {
2021-07-29 03:41:26 +02:00
if CanCreateTCPConnection ( "127.0.0.1" , & Config { Timeout : 5 * time . Second } ) {
2021-07-17 03:12:18 +02:00
t . Error ( "should've failed, because there's no port in the address" )
}
2023-05-03 04:41:22 +02:00
if ! CanCreateTCPConnection ( "1.1.1.1:53" , & Config { Timeout : 5 * time . Second } ) {
t . Error ( "should've succeeded, because that IP should always™ be up" )
}
2021-07-17 03:12:18 +02:00
}
2022-03-10 02:53:51 +01:00
// This test checks if a HTTP client configured with `configureOAuth2()` automatically
// performs a Client Credentials OAuth2 flow and adds the obtained token as a `Authorization`
// header to all outgoing HTTP calls.
func TestHttpClientProvidesOAuth2BearerToken ( t * testing . T ) {
defer InjectHTTPClient ( nil )
oAuth2Config := & OAuth2Config {
ClientID : "00000000-0000-0000-0000-000000000000" ,
ClientSecret : "secretsauce" ,
TokenURL : "https://token-server.local/token" ,
Scopes : [ ] string { "https://application.local/.default" } ,
}
mockHttpClient := & http . Client {
Transport : test . MockRoundTripper ( func ( r * http . Request ) * http . Response {
// if the mock HTTP client tries to get a token from the `token-server`
// we provide the expected token response
if r . Host == "token-server.local" {
return & http . Response {
StatusCode : http . StatusOK ,
2022-05-17 04:19:42 +02:00
Body : io . NopCloser ( bytes . NewReader (
2022-03-10 02:53:51 +01:00
[ ] byte (
` { "token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"secret-token"} ` ,
) ,
) ) ,
}
}
// to verify the headers were sent as expected, we echo them back in the
// `X-Org-Authorization` header and check if the token value matches our
// mocked `token-server` response
return & http . Response {
StatusCode : http . StatusOK ,
Header : map [ string ] [ ] string {
"X-Org-Authorization" : { r . Header . Get ( "Authorization" ) } ,
} ,
Body : http . NoBody ,
}
} ) ,
}
mockHttpClientWithOAuth := configureOAuth2 ( mockHttpClient , * oAuth2Config )
InjectHTTPClient ( mockHttpClientWithOAuth )
request , err := http . NewRequest ( http . MethodPost , "http://127.0.0.1:8282" , http . NoBody )
if err != nil {
t . Error ( "expected no error, got" , err . Error ( ) )
}
response , err := mockHttpClientWithOAuth . Do ( request )
if err != nil {
t . Error ( "expected no error, got" , err . Error ( ) )
}
if response . Header == nil {
t . Error ( "expected response headers, but got nil" )
}
// the mock response echos the Authorization header used in the request back
// to us as `X-Org-Authorization` header, we check here if the value matches
// our expected token `secret-token`
if response . Header . Get ( "X-Org-Authorization" ) != "Bearer secret-token" {
2024-04-12 03:18:30 +02:00
t . Error ( "expected `secret-token` as Bearer token in the mocked response header `X-Org-Authorization`, but got" , response . Header . Get ( "X-Org-Authorization" ) )
2022-03-10 02:53:51 +01:00
}
}
2023-09-29 00:35:18 +02:00
func TestQueryWebSocket ( t * testing . T ) {
_ , _ , err := QueryWebSocket ( "" , "body" , & Config { Timeout : 2 * time . Second } )
if err == nil {
t . Error ( "expected an error due to the address being invalid" )
}
_ , _ , err = QueryWebSocket ( "ws://example.org" , "body" , & Config { Timeout : 2 * time . Second } )
if err == nil {
t . Error ( "expected an error due to the target not being websocket-friendly" )
}
}
2024-04-10 00:41:37 +02:00
func TestTlsRenegotiation ( t * testing . T ) {
tests := [ ] struct {
name string
cfg TLSConfig
expectedConfig tls . RenegotiationSupport
} {
{
name : "default" ,
cfg : TLSConfig { CertificateFile : "../testdata/cert.pem" , PrivateKeyFile : "../testdata/cert.key" } ,
expectedConfig : tls . RenegotiateNever ,
} ,
{
name : "never" ,
cfg : TLSConfig { RenegotiationSupport : "never" , CertificateFile : "../testdata/cert.pem" , PrivateKeyFile : "../testdata/cert.key" } ,
expectedConfig : tls . RenegotiateNever ,
} ,
{
name : "once" ,
cfg : TLSConfig { RenegotiationSupport : "once" , CertificateFile : "../testdata/cert.pem" , PrivateKeyFile : "../testdata/cert.key" } ,
expectedConfig : tls . RenegotiateOnceAsClient ,
} ,
{
name : "freely" ,
cfg : TLSConfig { RenegotiationSupport : "freely" , CertificateFile : "../testdata/cert.pem" , PrivateKeyFile : "../testdata/cert.key" } ,
expectedConfig : tls . RenegotiateFreelyAsClient ,
} ,
{
name : "not-valid-and-broken" ,
cfg : TLSConfig { RenegotiationSupport : "invalid" , CertificateFile : "../testdata/cert.pem" , PrivateKeyFile : "../testdata/cert.key" } ,
expectedConfig : tls . RenegotiateNever ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
tls := & tls . Config { }
tlsConfig := configureTLS ( tls , test . cfg )
if tlsConfig . Renegotiation != test . expectedConfig {
t . Errorf ( "expected tls renegotiation to be %v, but got %v" , test . expectedConfig , tls . Renegotiation )
}
} )
}
}
2024-05-10 04:56:16 +02:00
func TestQueryDNS ( t * testing . T ) {
tests := [ ] struct {
name string
inputDNS dns . Config
inputURL string
expectedDNSCode string
expectedBody string
isErrExpected bool
} {
{
name : "test Config with type A" ,
inputDNS : dns . Config {
QueryType : "A" ,
QueryName : "example.com." ,
} ,
inputURL : "8.8.8.8" ,
expectedDNSCode : "NOERROR" ,
expectedBody : "93.184.215.14" ,
} ,
{
name : "test Config with type AAAA" ,
inputDNS : dns . Config {
QueryType : "AAAA" ,
QueryName : "example.com." ,
} ,
inputURL : "8.8.8.8" ,
expectedDNSCode : "NOERROR" ,
expectedBody : "2606:2800:21f:cb07:6820:80da:af6b:8b2c" ,
} ,
{
name : "test Config with type CNAME" ,
inputDNS : dns . Config {
QueryType : "CNAME" ,
QueryName : "en.wikipedia.org." ,
} ,
inputURL : "8.8.8.8" ,
expectedDNSCode : "NOERROR" ,
expectedBody : "dyna.wikimedia.org." ,
} ,
{
name : "test Config with type MX" ,
inputDNS : dns . Config {
QueryType : "MX" ,
QueryName : "example.com." ,
} ,
inputURL : "8.8.8.8" ,
expectedDNSCode : "NOERROR" ,
expectedBody : "." ,
} ,
{
name : "test Config with type NS" ,
inputDNS : dns . Config {
QueryType : "NS" ,
QueryName : "example.com." ,
} ,
inputURL : "8.8.8.8" ,
expectedDNSCode : "NOERROR" ,
expectedBody : "*.iana-servers.net." ,
} ,
{
name : "test Config with fake type and retrieve error" ,
inputDNS : dns . Config {
QueryType : "B" ,
QueryName : "example" ,
} ,
inputURL : "8.8.8.8" ,
isErrExpected : true ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
_ , dnsRCode , body , err := QueryDNS ( test . inputDNS . QueryType , test . inputDNS . QueryName , test . inputURL )
if test . isErrExpected && err == nil {
t . Errorf ( "there should be an error" )
}
if dnsRCode != test . expectedDNSCode {
t . Errorf ( "expected DNSRCode to be %s, got %s" , test . expectedDNSCode , dnsRCode )
}
if test . inputDNS . QueryType == "NS" {
// Because there are often multiple nameservers backing a single domain, we'll only look at the suffix
if ! pattern . Match ( test . expectedBody , string ( body ) ) {
t . Errorf ( "got %s, expected result %s," , string ( body ) , test . expectedBody )
}
} else {
if string ( body ) != test . expectedBody {
t . Errorf ( "got %s, expected result %s," , string ( body ) , test . expectedBody )
}
}
} )
time . Sleep ( 5 * time . Millisecond )
}
}