Skip to content

Commit 5a746bb

Browse files
committed
test: Improve pkg/monitors/network test coverage from 76.8% to 80.2%
Add comprehensive tests for network monitoring functions: - TestClassifyHTTPError_NetworkErrors: DNS, dial, and generic network errors - TestValidateURL: URL validation edge cases - TestIsValidHTTPMethod: HTTP method validation - TestGatewayMonitor_getGatewayIP: Gateway IP resolution - TestParseEndpointConfig_EdgeCases: Endpoint config type validation - TestNewConnectivityMonitor_EdgeCases: Monitor creation limits - TestValidateDNSConfig_EdgeCases: DNS config validation - TestCheckCustomQueries: Custom DNS query handling - TestParseConnectivityConfig_EdgeCases: Connectivity config parsing Coverage improvements: - classifyHTTPError: 57.9% → 89.5% - validateURL: 60% → 90% - isValidHTTPMethod: 75% → 100% - getGatewayIP: 40% → 80% - checkCustomQueries: 73.3% → 100% - parseConnectivityConfig: 82.6% → 100%
1 parent 4ff0f59 commit 5a746bb

File tree

4 files changed

+692
-0
lines changed

4 files changed

+692
-0
lines changed

pkg/monitors/network/connectivity_test.go

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package network
33
import (
44
"context"
55
"fmt"
6+
"strings"
67
"testing"
78
"time"
89

@@ -693,3 +694,356 @@ type mockHTTPClientFunc struct {
693694
func (m *mockHTTPClientFunc) CheckEndpoint(ctx context.Context, endpoint EndpointConfig) (*EndpointResult, error) {
694695
return m.checkFunc(ctx, endpoint)
695696
}
697+
698+
// TestParseEndpointConfig_EdgeCases tests edge cases in endpoint configuration parsing.
699+
func TestParseEndpointConfig_EdgeCases(t *testing.T) {
700+
tests := []struct {
701+
name string
702+
config map[string]interface{}
703+
wantErr bool
704+
errMsg string
705+
}{
706+
{
707+
name: "url is not a string",
708+
config: map[string]interface{}{
709+
"url": 12345,
710+
},
711+
wantErr: true,
712+
errMsg: "url must be a string",
713+
},
714+
{
715+
name: "name is not a string",
716+
config: map[string]interface{}{
717+
"url": "https://example.com",
718+
"name": 12345,
719+
},
720+
wantErr: true,
721+
errMsg: "name must be a string",
722+
},
723+
{
724+
name: "method is not a string",
725+
config: map[string]interface{}{
726+
"url": "https://example.com",
727+
"method": 12345,
728+
},
729+
wantErr: true,
730+
errMsg: "method must be a string",
731+
},
732+
{
733+
name: "invalid method",
734+
config: map[string]interface{}{
735+
"url": "https://example.com",
736+
"method": "POST",
737+
},
738+
wantErr: true,
739+
errMsg: "invalid HTTP method",
740+
},
741+
{
742+
name: "expectedStatusCode is not a number",
743+
config: map[string]interface{}{
744+
"url": "https://example.com",
745+
"expectedStatusCode": "200",
746+
},
747+
wantErr: true,
748+
errMsg: "expectedStatusCode must be an integer",
749+
},
750+
{
751+
name: "expectedStatusCode as float64",
752+
config: map[string]interface{}{
753+
"url": "https://example.com",
754+
"expectedStatusCode": float64(201),
755+
},
756+
wantErr: false,
757+
},
758+
{
759+
name: "followRedirects is not a bool",
760+
config: map[string]interface{}{
761+
"url": "https://example.com",
762+
"followRedirects": "true",
763+
},
764+
wantErr: true,
765+
errMsg: "followRedirects must be a boolean",
766+
},
767+
{
768+
name: "valid minimal config",
769+
config: map[string]interface{}{
770+
"url": "https://example.com",
771+
},
772+
wantErr: false,
773+
},
774+
}
775+
776+
for _, tt := range tests {
777+
t.Run(tt.name, func(t *testing.T) {
778+
_, err := parseEndpointConfig(tt.config)
779+
780+
if tt.wantErr {
781+
if err == nil {
782+
t.Error("expected error, got nil")
783+
return
784+
}
785+
if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
786+
t.Errorf("error = %v, want error containing %q", err, tt.errMsg)
787+
}
788+
return
789+
}
790+
791+
if err != nil {
792+
t.Errorf("unexpected error: %v", err)
793+
}
794+
})
795+
}
796+
}
797+
798+
// TestNewConnectivityMonitor_EdgeCases tests edge cases in monitor creation.
799+
func TestNewConnectivityMonitor_EdgeCases(t *testing.T) {
800+
tests := []struct {
801+
name string
802+
config types.MonitorConfig
803+
wantErr bool
804+
errMsg string
805+
}{
806+
{
807+
name: "too many endpoints",
808+
config: types.MonitorConfig{
809+
Name: "test-monitor",
810+
Type: "network-connectivity-check",
811+
Interval: 30 * time.Second,
812+
Timeout: 10 * time.Second,
813+
Enabled: true,
814+
Config: map[string]interface{}{
815+
"endpoints": func() []interface{} {
816+
endpoints := make([]interface{}, 51) // exceeds maxEndpoints (50)
817+
for i := 0; i < 51; i++ {
818+
endpoints[i] = map[string]interface{}{
819+
"url": fmt.Sprintf("https://example%d.com", i),
820+
}
821+
}
822+
return endpoints
823+
}(),
824+
},
825+
},
826+
wantErr: true,
827+
errMsg: "too many endpoints",
828+
},
829+
}
830+
831+
for _, tt := range tests {
832+
t.Run(tt.name, func(t *testing.T) {
833+
ctx := context.Background()
834+
_, err := NewConnectivityMonitor(ctx, tt.config)
835+
836+
if tt.wantErr {
837+
if err == nil {
838+
t.Error("expected error, got nil")
839+
return
840+
}
841+
if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
842+
t.Errorf("error = %v, want error containing %q", err, tt.errMsg)
843+
}
844+
return
845+
}
846+
847+
if err != nil {
848+
t.Errorf("unexpected error: %v", err)
849+
}
850+
})
851+
}
852+
}
853+
854+
// TestIsValidHTTPMethod tests HTTP method validation.
855+
func TestIsValidHTTPMethod(t *testing.T) {
856+
tests := []struct {
857+
method string
858+
valid bool
859+
}{
860+
{"GET", true},
861+
{"HEAD", true},
862+
{"OPTIONS", true},
863+
{"get", true}, // lowercase
864+
{"head", true}, // lowercase
865+
{"options", true}, // lowercase
866+
{"Get", true}, // mixed case
867+
{"POST", false},
868+
{"PUT", false},
869+
{"DELETE", false},
870+
{"PATCH", false},
871+
{"", false},
872+
{"INVALID", false},
873+
}
874+
875+
for _, tt := range tests {
876+
t.Run(tt.method, func(t *testing.T) {
877+
got := isValidHTTPMethod(tt.method)
878+
if got != tt.valid {
879+
t.Errorf("isValidHTTPMethod(%q) = %v, want %v", tt.method, got, tt.valid)
880+
}
881+
})
882+
}
883+
}
884+
885+
// TestValidateURL tests URL validation for connectivity endpoints.
886+
func TestValidateURL(t *testing.T) {
887+
tests := []struct {
888+
name string
889+
url string
890+
wantErr bool
891+
errMsg string
892+
}{
893+
{
894+
name: "valid https URL",
895+
url: "https://example.com",
896+
wantErr: false,
897+
},
898+
{
899+
name: "valid http URL",
900+
url: "http://example.com",
901+
wantErr: false,
902+
},
903+
{
904+
name: "valid URL with path",
905+
url: "https://example.com/api/v1/health",
906+
wantErr: false,
907+
},
908+
{
909+
name: "valid URL with port",
910+
url: "https://example.com:8443",
911+
wantErr: false,
912+
},
913+
{
914+
name: "missing scheme",
915+
url: "example.com",
916+
wantErr: true,
917+
errMsg: "scheme",
918+
},
919+
{
920+
name: "unsupported scheme - ftp",
921+
url: "ftp://example.com",
922+
wantErr: true,
923+
errMsg: "unsupported URL scheme",
924+
},
925+
{
926+
name: "unsupported scheme - file",
927+
url: "file:///etc/passwd",
928+
wantErr: true,
929+
errMsg: "unsupported URL scheme",
930+
},
931+
{
932+
name: "missing host",
933+
url: "https://",
934+
wantErr: true,
935+
errMsg: "host",
936+
},
937+
{
938+
name: "empty string",
939+
url: "",
940+
wantErr: true,
941+
errMsg: "scheme",
942+
},
943+
}
944+
945+
for _, tt := range tests {
946+
t.Run(tt.name, func(t *testing.T) {
947+
err := validateURL(tt.url)
948+
949+
if tt.wantErr {
950+
if err == nil {
951+
t.Error("expected error, got nil")
952+
return
953+
}
954+
if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
955+
t.Errorf("error = %v, want error containing %q", err, tt.errMsg)
956+
}
957+
return
958+
}
959+
960+
if err != nil {
961+
t.Errorf("unexpected error: %v", err)
962+
}
963+
})
964+
}
965+
}
966+
967+
// TestParseConnectivityConfig_EdgeCases tests edge cases in connectivity config parsing.
968+
func TestParseConnectivityConfig_EdgeCases(t *testing.T) {
969+
tests := []struct {
970+
name string
971+
config map[string]interface{}
972+
wantErr bool
973+
errMsg string
974+
}{
975+
{
976+
name: "nil config",
977+
config: nil,
978+
wantErr: true,
979+
errMsg: "connectivity monitor requires configuration",
980+
},
981+
{
982+
name: "failureThreshold invalid type",
983+
config: map[string]interface{}{
984+
"failureThreshold": "not-a-number",
985+
"endpoints": []interface{}{
986+
map[string]interface{}{"url": "https://example.com"},
987+
},
988+
},
989+
wantErr: true,
990+
errMsg: "failureThreshold must be an integer",
991+
},
992+
{
993+
name: "failureThreshold as float64",
994+
config: map[string]interface{}{
995+
"failureThreshold": float64(3),
996+
"endpoints": []interface{}{
997+
map[string]interface{}{"url": "https://example.com"},
998+
},
999+
},
1000+
wantErr: false,
1001+
},
1002+
{
1003+
name: "failureThreshold too low",
1004+
config: map[string]interface{}{
1005+
"failureThreshold": 0,
1006+
"endpoints": []interface{}{
1007+
map[string]interface{}{"url": "https://example.com"},
1008+
},
1009+
},
1010+
wantErr: true,
1011+
errMsg: "failureThreshold must be at least 1",
1012+
},
1013+
{
1014+
name: "endpoints not a list",
1015+
config: map[string]interface{}{
1016+
"endpoints": "not-a-list",
1017+
},
1018+
wantErr: true,
1019+
errMsg: "endpoints must be a list",
1020+
},
1021+
{
1022+
name: "endpoint not a map",
1023+
config: map[string]interface{}{
1024+
"endpoints": []interface{}{"not-a-map"},
1025+
},
1026+
wantErr: true,
1027+
errMsg: "endpoint 0 must be a map",
1028+
},
1029+
}
1030+
1031+
for _, tt := range tests {
1032+
t.Run(tt.name, func(t *testing.T) {
1033+
_, err := parseConnectivityConfig(tt.config)
1034+
1035+
if tt.wantErr {
1036+
if err == nil {
1037+
t.Error("expected error but got none")
1038+
} else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
1039+
t.Errorf("error = %v, want error containing %q", err, tt.errMsg)
1040+
}
1041+
return
1042+
}
1043+
1044+
if err != nil {
1045+
t.Errorf("unexpected error: %v", err)
1046+
}
1047+
})
1048+
}
1049+
}

0 commit comments

Comments
 (0)