From d6fa2c955b1be2f030aa76c76a6c7d4b15dacea3 Mon Sep 17 00:00:00 2001 From: TwiN Date: Sun, 21 Sep 2025 13:15:59 -0400 Subject: [PATCH] fix(suites): Handle invalid paths in store and update needsToReadBody to check store (#1282) * fix(suites): Invalid path in store parameter should return an error * Refactor * fix(suites): Update needsToReadBody to check store mappings for body placeholders --- config/endpoint/endpoint.go | 10 ++++++- config/endpoint/endpoint_test.go | 34 ++++++++++++++++++++++++ config/suite/suite.go | 12 +++++++++ config/suite/suite_test.go | 45 ++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 1 deletion(-) diff --git a/config/endpoint/endpoint.go b/config/endpoint/endpoint.go index 13b6acea..d0e2c151 100644 --- a/config/endpoint/endpoint.go +++ b/config/endpoint/endpoint.go @@ -565,13 +565,21 @@ func (e *Endpoint) buildHTTPRequest() *http.Request { return request } -// needsToReadBody checks if there's any condition that requires the response Body to be read +// needsToReadBody checks if there's any condition or store mapping that requires the response Body to be read func (e *Endpoint) needsToReadBody() bool { for _, condition := range e.Conditions { if condition.hasBodyPlaceholder() { return true } } + // Check store values for body placeholders + if e.Store != nil { + for _, value := range e.Store { + if strings.Contains(value, BodyPlaceholder) { + return true + } + } + } return false } diff --git a/config/endpoint/endpoint_test.go b/config/endpoint/endpoint_test.go index 60d7a6a9..080b7313 100644 --- a/config/endpoint/endpoint_test.go +++ b/config/endpoint/endpoint_test.go @@ -914,6 +914,40 @@ func TestEndpoint_needsToReadBody(t *testing.T) { if !(&Endpoint{Conditions: []Condition{bodyConditionWithLength, statusCondition}}).needsToReadBody() { t.Error("expected true, got false") } + // Test store configuration with body placeholder + storeWithBodyPlaceholder := map[string]string{ + "token": "[BODY].accessToken", + } + if !(&Endpoint{ + Conditions: []Condition{statusCondition}, + Store: storeWithBodyPlaceholder, + }).needsToReadBody() { + t.Error("expected true when store has body placeholder, got false") + } + // Test store configuration without body placeholder + storeWithoutBodyPlaceholder := map[string]string{ + "status": "[STATUS]", + } + if (&Endpoint{ + Conditions: []Condition{statusCondition}, + Store: storeWithoutBodyPlaceholder, + }).needsToReadBody() { + t.Error("expected false when store has no body placeholder, got true") + } + // Test empty store + if (&Endpoint{ + Conditions: []Condition{statusCondition}, + Store: map[string]string{}, + }).needsToReadBody() { + t.Error("expected false when store is empty, got true") + } + // Test nil store + if (&Endpoint{ + Conditions: []Condition{statusCondition}, + Store: nil, + }).needsToReadBody() { + t.Error("expected false when store is nil, got true") + } } func TestEndpoint_needsToRetrieveDomainExpiration(t *testing.T) { diff --git a/config/suite/suite.go b/config/suite/suite.go index fdbbeee4..186fd9a9 100644 --- a/config/suite/suite.go +++ b/config/suite/suite.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strconv" + "strings" "time" "github.com/TwiN/gatus/v5/config/endpoint" @@ -175,10 +176,12 @@ func StoreResultValues(ctx *gontext.Gontext, mappings map[string]string, result return nil, nil } storedValues := make(map[string]interface{}) + var extractionErrors []string for contextKey, placeholder := range mappings { value, err := extractValueForStorage(placeholder, result) if err != nil { // Continue storing other values even if one fails + extractionErrors = append(extractionErrors, fmt.Sprintf("%s: %v", contextKey, err)) storedValues[contextKey] = fmt.Sprintf("ERROR: %v", err) continue } @@ -187,6 +190,10 @@ func StoreResultValues(ctx *gontext.Gontext, mappings map[string]string, result } storedValues[contextKey] = value } + // Return an error if any values failed to extract + if len(extractionErrors) > 0 { + return storedValues, fmt.Errorf("failed to extract values: %s", strings.Join(extractionErrors, "; ")) + } return storedValues, nil } @@ -197,6 +204,11 @@ func extractValueForStorage(placeholder string, result *endpoint.Result) (interf if err != nil { return nil, err } + // Check if the resolution resulted in an INVALID placeholder + // This happens when a path doesn't exist (e.g., [BODY].nonexistent) + if strings.HasSuffix(resolved, " "+endpoint.InvalidConditionElementSuffix) { + return nil, fmt.Errorf("invalid path: %s", strings.TrimSuffix(resolved, " "+endpoint.InvalidConditionElementSuffix)) + } // Try to parse as number or boolean to store as proper types // Try int first for whole numbers if num, err := strconv.ParseInt(resolved, 10, 64); err == nil { diff --git a/config/suite/suite_test.go b/config/suite/suite_test.go index 3a7a48be..55b93a51 100644 --- a/config/suite/suite_test.go +++ b/config/suite/suite_test.go @@ -1,6 +1,7 @@ package suite import ( + "strings" "testing" "time" @@ -240,6 +241,50 @@ func TestStoreResultValues(t *testing.T) { } } +func TestStoreResultValuesWithInvalidPath(t *testing.T) { + ctx := gontext.New(map[string]interface{}{}) + result := &endpoint.Result{ + HTTPStatus: 200, + Body: []byte(`{"data": {"name": "john"}}`), + } + // Define store mappings with invalid paths + mappings := map[string]string{ + "valid_status": "[STATUS]", + "invalid_token": "[BODY].accessToken", // This path doesn't exist + "invalid_nested": "[BODY].user.id.invalid", // This nested path doesn't exist + } + // Store values - should return error for invalid paths + stored, err := StoreResultValues(ctx, mappings, result) + if err == nil { + t.Fatal("Expected error when storing invalid paths, got nil") + } + // Check that the error message contains information about the invalid paths + if !strings.Contains(err.Error(), "invalid_token") { + t.Errorf("Error should mention invalid_token, got: %v", err) + } + if !strings.Contains(err.Error(), "invalid path") { + t.Errorf("Error should mention 'invalid path', got: %v", err) + } + // Verify that valid values were still stored + if stored["valid_status"] != int64(200) { + t.Errorf("Expected valid_status=200, got %v", stored["valid_status"]) + } + // Verify that invalid values show error messages in stored map + if !strings.Contains(stored["invalid_token"].(string), "ERROR") { + t.Errorf("Expected invalid_token to contain ERROR, got %v", stored["invalid_token"]) + } + // Verify that invalid values are NOT in context + _, err = ctx.Get("invalid_token") + if err == nil { + t.Error("Invalid token should not be stored in context") + } + // Verify that valid value IS in context + val, err := ctx.Get("valid_status") + if err != nil || val != int64(200) { + t.Errorf("Expected valid_status=200 in context, got %v, err=%v", val, err) + } +} + func TestSuite_ExecuteWithAlwaysRunEndpoints(t *testing.T) { suite := &Suite{ Name: "test-suite",