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
This commit is contained in:
@@ -565,13 +565,21 @@ func (e *Endpoint) buildHTTPRequest() *http.Request {
|
|||||||
return 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 {
|
func (e *Endpoint) needsToReadBody() bool {
|
||||||
for _, condition := range e.Conditions {
|
for _, condition := range e.Conditions {
|
||||||
if condition.hasBodyPlaceholder() {
|
if condition.hasBodyPlaceholder() {
|
||||||
return true
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -914,6 +914,40 @@ func TestEndpoint_needsToReadBody(t *testing.T) {
|
|||||||
if !(&Endpoint{Conditions: []Condition{bodyConditionWithLength, statusCondition}}).needsToReadBody() {
|
if !(&Endpoint{Conditions: []Condition{bodyConditionWithLength, statusCondition}}).needsToReadBody() {
|
||||||
t.Error("expected true, got false")
|
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) {
|
func TestEndpoint_needsToRetrieveDomainExpiration(t *testing.T) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/TwiN/gatus/v5/config/endpoint"
|
"github.com/TwiN/gatus/v5/config/endpoint"
|
||||||
@@ -175,10 +176,12 @@ func StoreResultValues(ctx *gontext.Gontext, mappings map[string]string, result
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
storedValues := make(map[string]interface{})
|
storedValues := make(map[string]interface{})
|
||||||
|
var extractionErrors []string
|
||||||
for contextKey, placeholder := range mappings {
|
for contextKey, placeholder := range mappings {
|
||||||
value, err := extractValueForStorage(placeholder, result)
|
value, err := extractValueForStorage(placeholder, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Continue storing other values even if one fails
|
// Continue storing other values even if one fails
|
||||||
|
extractionErrors = append(extractionErrors, fmt.Sprintf("%s: %v", contextKey, err))
|
||||||
storedValues[contextKey] = fmt.Sprintf("ERROR: %v", err)
|
storedValues[contextKey] = fmt.Sprintf("ERROR: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -187,6 +190,10 @@ func StoreResultValues(ctx *gontext.Gontext, mappings map[string]string, result
|
|||||||
}
|
}
|
||||||
storedValues[contextKey] = value
|
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
|
return storedValues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,6 +204,11 @@ func extractValueForStorage(placeholder string, result *endpoint.Result) (interf
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 to parse as number or boolean to store as proper types
|
||||||
// Try int first for whole numbers
|
// Try int first for whole numbers
|
||||||
if num, err := strconv.ParseInt(resolved, 10, 64); err == nil {
|
if num, err := strconv.ParseInt(resolved, 10, 64); err == nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package suite
|
package suite
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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) {
|
func TestSuite_ExecuteWithAlwaysRunEndpoints(t *testing.T) {
|
||||||
suite := &Suite{
|
suite := &Suite{
|
||||||
Name: "test-suite",
|
Name: "test-suite",
|
||||||
|
|||||||
Reference in New Issue
Block a user