diff --git a/README.md b/README.md index b6919a5d..73e95332 100644 --- a/README.md +++ b/README.md @@ -309,6 +309,7 @@ You can then configure alerts to be triggered when an endpoint is unhealthy once | `endpoints[].ui.hide-hostname` | Whether to hide the hostname from the results. | `false` | | `endpoints[].ui.hide-port` | Whether to hide the port from the results. | `false` | | `endpoints[].ui.hide-url` | Whether to hide the URL from the results. Useful if the URL contains a token. | `false` | +| `endpoints[].ui.hide-errors` | Whether to hide errors from the results. | `false` | | `endpoints[].ui.dont-resolve-failed-conditions` | Whether to resolve failed conditions for the UI. | `false` | | `endpoints[].ui.badge.response-time` | List of response time thresholds. Each time a threshold is reached, the badge has a different color. | `[50, 200, 300, 500, 750]` | | `endpoints[].extra-labels` | Extra labels to add to the metrics. Useful for grouping endpoints together. | `{}` | diff --git a/client/client_test.go b/client/client_test.go index 10bfec16..c381d410 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -131,8 +131,8 @@ func TestPing(t *testing.T) { func TestCanPerformStartTLS(t *testing.T) { type args struct { - address string - insecure bool + address string + insecure bool dnsresolver string } tests := []struct { @@ -168,7 +168,7 @@ func TestCanPerformStartTLS(t *testing.T) { { name: "dns resolver", args: args{ - address: "smtp.gmail.com:587", + address: "smtp.gmail.com:587", dnsresolver: "tcp://1.1.1.1:53", }, wantConnected: true, @@ -340,7 +340,7 @@ func TestQueryWebSocket(t *testing.T) { } func TestTlsRenegotiation(t *testing.T) { - tests := []struct { + scenarios := []struct { name string cfg TLSConfig expectedConfig tls.RenegotiationSupport @@ -371,12 +371,12 @@ func TestTlsRenegotiation(t *testing.T) { expectedConfig: tls.RenegotiateNever, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { + for _, scenario := range scenarios { + t.Run(scenario.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) + tlsConfig := configureTLS(tls, scenario.cfg) + if tlsConfig.Renegotiation != scenario.expectedConfig { + t.Errorf("expected tls renegotiation to be %v, but got %v", scenario.expectedConfig, tls.Renegotiation) } }) } @@ -513,14 +513,11 @@ func TestQueryDNS(t *testing.T) { func TestCheckSSHBanner(t *testing.T) { cfg := &Config{Timeout: 3} - t.Run("no-auth-ssh", func(t *testing.T) { connected, status, err := CheckSSHBanner("tty.sdf.org", cfg) - if err != nil { t.Errorf("Expected: error != nil, got: %v ", err) } - if connected == false { t.Errorf("Expected: connected == true, got: %v", connected) } @@ -528,14 +525,11 @@ func TestCheckSSHBanner(t *testing.T) { t.Errorf("Expected: 0, got: %v", status) } }) - t.Run("invalid-address", func(t *testing.T) { connected, status, err := CheckSSHBanner("idontplaytheodds.com", cfg) - if err == nil { t.Errorf("Expected: error, got: %v ", err) } - if connected != false { t.Errorf("Expected: connected == false, got: %v", connected) } @@ -543,5 +537,4 @@ func TestCheckSSHBanner(t *testing.T) { t.Errorf("Expected: 1, got: %v", status) } }) - } diff --git a/config/endpoint/endpoint.go b/config/endpoint/endpoint.go index 1ba1c248..68cf0474 100644 --- a/config/endpoint/endpoint.go +++ b/config/endpoint/endpoint.go @@ -353,6 +353,9 @@ func (e *Endpoint) EvaluateHealthWithContext(context *gontext.Gontext) *Result { } result.port = "" } + if processedEndpoint.UIConfig.HideErrors { + result.Errors = nil + } if processedEndpoint.UIConfig.HideConditions { result.ConditionResults = nil } diff --git a/config/endpoint/endpoint_test.go b/config/endpoint/endpoint_test.go index 080b7313..740a478a 100644 --- a/config/endpoint/endpoint_test.go +++ b/config/endpoint/endpoint_test.go @@ -1448,3 +1448,168 @@ func TestEndpoint_preprocessWithContext(t *testing.T) { }) } } + +func TestEndpoint_HideUIFeatures(t *testing.T) { + defer client.InjectHTTPClient(nil) + tests := []struct { + name string + endpoint Endpoint + mockResponse test.MockRoundTripper + checkHostname bool + expectHostname string + checkErrors bool + expectErrors bool + checkConditions bool + expectConditions bool + checkErrorContent string + }{ + { + name: "hide-conditions", + endpoint: Endpoint{ + Name: "test-endpoint", + URL: "https://example.com/health", + Conditions: []Condition{"[STATUS] == 200", "[BODY].status == UP"}, + UIConfig: &ui.Config{HideConditions: true}, + }, + mockResponse: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString(`{"status": "UP"}`))} + }), + checkConditions: true, + expectConditions: false, + }, + { + name: "hide-hostname", + endpoint: Endpoint{ + Name: "test-endpoint", + URL: "https://example.com/health", + Conditions: []Condition{"[STATUS] == 200"}, + UIConfig: &ui.Config{HideHostname: true}, + }, + mockResponse: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + checkHostname: true, + expectHostname: "", + }, + { + name: "hide-url-in-errors", + endpoint: Endpoint{ + Name: "test-endpoint", + URL: "https://example.com/health", + Conditions: []Condition{"[CONNECTED] == true"}, + UIConfig: &ui.Config{HideURL: true}, + ClientConfig: &client.Config{Timeout: time.Millisecond}, + }, + mockResponse: nil, + checkErrors: true, + expectErrors: true, + checkErrorContent: "", + }, + { + name: "hide-port-in-errors", + endpoint: Endpoint{ + Name: "test-endpoint", + URL: "https://example.com:9999/health", + Conditions: []Condition{"[CONNECTED] == true"}, + UIConfig: &ui.Config{HidePort: true}, + ClientConfig: &client.Config{Timeout: time.Millisecond}, + }, + mockResponse: nil, + checkErrors: true, + expectErrors: true, + checkErrorContent: "", + }, + { + name: "hide-errors", + endpoint: Endpoint{ + Name: "test-endpoint", + URL: "https://example.com/health", + Conditions: []Condition{"[CONNECTED] == true"}, + UIConfig: &ui.Config{HideErrors: true}, + ClientConfig: &client.Config{Timeout: time.Millisecond}, + }, + mockResponse: nil, + checkErrors: true, + expectErrors: false, + }, + { + name: "dont-resolve-failed-conditions", + endpoint: Endpoint{ + Name: "test-endpoint", + URL: "https://example.com/health", + Conditions: []Condition{"[STATUS] == 200"}, + UIConfig: &ui.Config{DontResolveFailedConditions: true}, + }, + mockResponse: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusBadGateway, Body: http.NoBody} + }), + checkConditions: true, + expectConditions: true, + }, + { + name: "multiple-hide-features", + endpoint: Endpoint{ + Name: "test-endpoint", + URL: "https://example.com/health", + Conditions: []Condition{"[STATUS] == 200"}, + UIConfig: &ui.Config{HideConditions: true, HideHostname: true, HideErrors: true}, + }, + mockResponse: test.MockRoundTripper(func(r *http.Request) *http.Response { + return &http.Response{StatusCode: http.StatusOK, Body: http.NoBody} + }), + checkConditions: true, + expectConditions: false, + checkHostname: true, + expectHostname: "", + checkErrors: true, + expectErrors: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.mockResponse != nil { + mockClient := &http.Client{Transport: tt.mockResponse} + if tt.endpoint.ClientConfig != nil && tt.endpoint.ClientConfig.Timeout > 0 { + mockClient.Timeout = tt.endpoint.ClientConfig.Timeout + } + client.InjectHTTPClient(mockClient) + } else { + client.InjectHTTPClient(nil) + } + err := tt.endpoint.ValidateAndSetDefaults() + if err != nil { + t.Fatalf("ValidateAndSetDefaults failed: %v", err) + } + result := tt.endpoint.EvaluateHealth() + if tt.checkHostname { + if result.Hostname != tt.expectHostname { + t.Errorf("Expected hostname '%s', got '%s'", tt.expectHostname, result.Hostname) + } + } + if tt.checkErrors { + hasErrors := len(result.Errors) > 0 + if hasErrors != tt.expectErrors { + t.Errorf("Expected errors=%v, got errors=%v (actual errors: %v)", tt.expectErrors, hasErrors, result.Errors) + } + if tt.checkErrorContent != "" && len(result.Errors) > 0 { + found := false + for _, err := range result.Errors { + if strings.Contains(err, tt.checkErrorContent) { + found = true + break + } + } + if !found { + t.Errorf("Expected error to contain '%s', but got: %v", tt.checkErrorContent, result.Errors) + } + } + } + if tt.checkConditions { + hasConditions := result.ConditionResults != nil && len(result.ConditionResults) > 0 + if hasConditions != tt.expectConditions { + t.Errorf("Expected conditions=%v, got conditions=%v (actual: %v)", tt.expectConditions, hasConditions, result.ConditionResults) + } + } + }) + } +} diff --git a/config/endpoint/ui/ui.go b/config/endpoint/ui/ui.go index 0f784cb5..7494f172 100644 --- a/config/endpoint/ui/ui.go +++ b/config/endpoint/ui/ui.go @@ -16,6 +16,9 @@ type Config struct { // HidePort whether to hide the port in the Result HidePort bool `yaml:"hide-port"` + // HideErrors whether to hide the errors in the Result + HideErrors bool `yaml:"hide-errors"` + // DontResolveFailedConditions whether to resolve failed conditions in the Result for display in the UI DontResolveFailedConditions bool `yaml:"dont-resolve-failed-conditions"` @@ -58,6 +61,7 @@ func GetDefaultConfig() *Config { HideHostname: false, HideURL: false, HidePort: false, + HideErrors: false, DontResolveFailedConditions: false, HideConditions: false, Badge: &Badge{