diff --git a/apps/checker/checker/http.go b/apps/checker/checker/http.go index 0f9fdd41..f85f0a0f 100644 --- a/apps/checker/checker/http.go +++ b/apps/checker/checker/http.go @@ -122,20 +122,25 @@ func Http(ctx context.Context, client *http.Client, inputData request.HttpChecke latency := time.Since(start).Milliseconds() if err != nil { + errorMsg := err.Error() var urlErr *url.Error if errors.As(err, &urlErr) && urlErr.Timeout() { - return Response{ - Latency: latency, - Timing: timing, - Timestamp: start.UTC().UnixMilli(), - Error: fmt.Sprintf("Timeout after %d ms", latency), - }, nil + errorMsg = fmt.Sprintf("Timeout after %d ms", latency) } logger.Error().Err(err).Msg("error while pinging") - return Response{}, err + // Return Response with error field instead of returning a Go error + // This ensures all failures (timeouts, connection refused, DNS failures, etc.) + // are properly ingested and displayed in the dashboard + return Response{ + Latency: latency, + Timing: timing, + Timestamp: start.UTC().UnixMilli(), + Error: errorMsg, + Status: 0, + }, nil } defer response.Body.Close() diff --git a/apps/checker/checker/http_test.go b/apps/checker/checker/http_test.go index 13fe3a6b..070717a9 100644 --- a/apps/checker/checker/http_test.go +++ b/apps/checker/checker/http_test.go @@ -73,7 +73,7 @@ func Test_ping(t *testing.T) { want: checker.Response{Status: 500, Body: "OK"}, wantErr: false}, {name: "Wrong url should return an error", args: args{client: &http.Client{}, inputData: request.HttpCheckerRequest{URL: "https://somethingthatwillfail.ed", CronTimestamp: 1}}, - want: checker.Response{Status: 0}, wantErr: true}, + want: checker.Response{Status: 0}, wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -91,6 +91,11 @@ func Test_ping(t *testing.T) { if got.Body != tt.want.Body { t.Errorf("Ping() = %v, want %v", got, tt.want) } + + // For the error test case, verify Response.Error is populated + if tt.name == "Wrong url should return an error" && got.Error == "" { + t.Errorf("Expected Response.Error to be populated for transport failure") + } }) } } diff --git a/apps/checker/handlers/checker.go b/apps/checker/handlers/checker.go index 01725283..5a22ecca 100644 --- a/apps/checker/handlers/checker.go +++ b/apps/checker/handlers/checker.go @@ -167,6 +167,7 @@ func (h Handler) HTTPCheckerHandler(c *gin.Context) { Body: string(res.Body), Trigger: trigger, RequestStatus: requestStatus, + Message: res.Error, } var isSuccessfull bool = true @@ -332,6 +333,12 @@ func (h Handler) HTTPCheckerHandler(c *gin.Context) { } func EvaluateHTTPAssertions(raw []json.RawMessage, data PingData, res checker.Response) (bool, error) { + // If there's a transport error, always fail regardless of assertions + // This prevents false positives where empty responses might satisfy assertions + if res.Error != "" { + return false, nil + } + statusCode := statusCode(res.Status) if len(raw) == 0 { return statusCode.IsSuccessful(), nil diff --git a/apps/checker/pkg/job/http_job_test.go b/apps/checker/pkg/job/http_job_test.go index 36d91a3d..58fc1155 100644 --- a/apps/checker/pkg/job/http_job_test.go +++ b/apps/checker/pkg/job/http_job_test.go @@ -47,11 +47,14 @@ func TestHTTPJob_Failure(t *testing.T) { } data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") - if err == nil { - t.Fatalf("expected error, got nil") + if err != nil { + t.Fatalf("expected no Go error, got %v", err) + } + if data == nil { + t.Fatalf("expected data to be populated, got nil") } - if data != nil { - t.Errorf("expected data to be nil on error, got %+v", data) + if data.Message == "" { + t.Errorf("expected error message to be populated for transport failure") } } diff --git a/apps/checker/pkg/job/otel_wiring_test.go b/apps/checker/pkg/job/otel_wiring_test.go index 1c2bf743..b7b04dd0 100644 --- a/apps/checker/pkg/job/otel_wiring_test.go +++ b/apps/checker/pkg/job/otel_wiring_test.go @@ -115,8 +115,9 @@ func TestHTTPJob_RecordsOTelOnFailure(t *testing.T) { OtelConfig: &v1.OtelConfig{Endpoint: otlp.server.URL}, } - _, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") - require.Error(t, err) + data, err := job.NewJobRunner().HTTPJob(context.Background(), monitor, "test-region") + require.NoError(t, err) + require.NotEmpty(t, data.Message, "Expected error message to be populated for transport failure") otlp.requireMetric(t, "openstatus.error") }