Mappings to the Same Path with Multiple JmesPathMatchers Matches Incorrectly #548

Closed
opened 2025-12-29 08:29:55 +01:00 by adam · 4 comments
Owner

Originally created by @joshua-woolf on GitHub (Oct 13, 2023).

Originally assigned to: @StefH on GitHub.

Describe the bug

Given two mappings to the same path with multiple different JmesPathMatcher matchers on the body, the incorrect match is returned depending on the order of the mapping in the mapping file.

Expected behavior:

The response for the request where all matchers match should be returned.

Test to reproduce

There are 4 example tests included here:

  • Example A (Fail): Two body matchers where one value differs in each mapping.
  • Example B (Pass): Same mappings as Example A just listed in the opposite order in the static mappings.
  • Example C (Fail): Different number of body matchers per mapping.
  • Example D (Pass): Same mappings as Example C, just listed in the opposite order in the static mappings.

Setup static mappings as follows:

[
  {
    "Title": "Example_A_Pass",
    "Request": {
      "Path": "/examplea",
      "Method": "POST",
      "Body": {
        "MatchOperator": "and",
        "Matchers": [
          {
            "Name": "JmesPathMatcher",
            "Pattern": "requestId == '1'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "value == 'A'"
          }
        ]
      }
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": "application/json"
      },
      "BodyAsJson": {
        "value": "Pass"
      }
    }
  },
  {
    "Title": "Example_A_Fail",
    "Request": {
      "Path": "/examplea",
      "Method": "POST",
      "Body": {
        "MatchOperator": "and",
        "Matchers": [
          {
            "Name": "JmesPathMatcher",
            "Pattern": "requestId == '2'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "value == 'A'"
          }
        ]
      }
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": "application/json"
      },
      "BodyAsJson": {
        "value": "Fail"
      }
    }
  },
  {
    "Title": "Example_B_Fail",
    "Request": {
      "Path": "/exampleb",
      "Method": "POST",
      "Body": {
        "MatchOperator": "and",
        "Matchers": [
          {
            "Name": "JmesPathMatcher",
            "Pattern": "requestId == '2'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "value == 'A'"
          }
        ]
      }
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": "application/json"
      },
      "BodyAsJson": {
        "value": "Fail"
      }
    }
  },
  {
    "Title": "Example_B_Pass",
    "Request": {
      "Path": "/exampleb",
      "Method": "POST",
      "Body": {
        "MatchOperator": "and",
        "Matchers": [
          {
            "Name": "JmesPathMatcher",
            "Pattern": "requestId == '1'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "value == 'A'"
          }
        ]
      }
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": "application/json"
      },
      "BodyAsJson": {
        "value": "Pass"
      }
    }
  },
  {
    "Title": "Example_C_Pass",
    "Request": {
      "Path": "/examplec",
      "Method": "POST",
      "Body": {
        "MatchOperator": "and",
        "Matchers": [
          {
            "Name": "JmesPathMatcher",
            "Pattern": "extra == 'X'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "requestId == '1'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "value == 'A'"
          }
        ]
      }
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": "application/json"
      },
      "BodyAsJson": {
        "value": "Pass"
      }
    }
  },
  {
    "Title": "Example_C_Fail",
    "Request": {
      "Path": "/examplec",
      "Method": "POST",
      "Body": {
        "MatchOperator": "and",
        "Matchers": [
          {
            "Name": "JmesPathMatcher",
            "Pattern": "requestId == '1'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "value == 'A'"
          }
        ]
      }
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": "application/json"
      },
      "BodyAsJson": {
        "value": "Fail"
      }
    }
  },
  {
    "Title": "Example_D_Fail",
    "Request": {
      "Path": "/exampled",
      "Method": "POST",
      "Body": {
        "MatchOperator": "and",
        "Matchers": [
          {
            "Name": "JmesPathMatcher",
            "Pattern": "requestId == '1'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "value == 'A'"
          }
        ]
      }
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": "application/json"
      },
      "BodyAsJson": {
        "value": "Fail"
      }
    }
  },
  {
    "Title": "Example_D_Pass",
    "Request": {
      "Path": "/exampled",
      "Method": "POST",
      "Body": {
        "MatchOperator": "and",
        "Matchers": [
          {
            "Name": "JmesPathMatcher",
            "Pattern": "extra == 'X'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "requestId == '1'"
          },
          {
            "Name": "JmesPathMatcher",
            "Pattern": "value == 'A'"
          }
        ]
      }
    },
    "Response": {
      "StatusCode": 200,
      "Headers": {
        "Content-Type": "application/json"
      },
      "BodyAsJson": {
        "value": "Pass"
      }
    }
  }
]

Run the dotnet global tool as follows (version 1.5.39):

dotnet-wiremock --Urls "http://*:8080" --ReadStaticMappings "true" --WireMockLogger "WireMockConsoleLogger"

Run the following PowerShell code to see results:

$resultA = Invoke-RestMethod `
  -Body '{ "requestId": "1", "value": "A" }}' `
  -ContentType "application/json" `
  -Method "Post" `
  -Uri "http://localhost:8080/examplea"

Write-Host "[Example A]: $($resultA.value)"

$resultB = Invoke-RestMethod `
  -Body '{ "requestId": "1", "value": "A" }}' `
  -ContentType "application/json" `
  -Method "Post" `
  -Uri "http://localhost:8080/exampleb"

Write-Host "[Example B]: $($resultB.value)"

$resultC = Invoke-RestMethod `
  -Body '{ "extra": "X", "requestId": "1", "value": "A" }}' `
  -ContentType "application/json" `
  -Method "Post" `
  -Uri "http://localhost:8080/examplec"

Write-Host "[Example C]: $($resultC.value)"

$resultD = Invoke-RestMethod `
  -Body '{ "extra": "X", "requestId": "1", "value": "A" }}' `
  -ContentType "application/json" `
  -Method "Post" `
  -Uri "http://localhost:8080/exampled"

Write-Host "[Example D]: $($resultD.value)"

Results show:

[Example A]: Fail
[Example B]: Pass
[Example C]: Fail
[Example D]: Pass
Originally created by @joshua-woolf on GitHub (Oct 13, 2023). Originally assigned to: @StefH on GitHub. ### Describe the bug Given two mappings to the same path with multiple different `JmesPathMatcher` matchers on the body, the incorrect match is returned depending on the order of the mapping in the mapping file. ### Expected behavior: The response for the request where all matchers match should be returned. ### Test to reproduce There are 4 example tests included here: - Example A (Fail): Two body matchers where one value differs in each mapping. - Example B (Pass): Same mappings as Example A just listed in the opposite order in the static mappings. - Example C (Fail): Different number of body matchers per mapping. - Example D (Pass): Same mappings as Example C, just listed in the opposite order in the static mappings. Setup static mappings as follows: ```json [ { "Title": "Example_A_Pass", "Request": { "Path": "/examplea", "Method": "POST", "Body": { "MatchOperator": "and", "Matchers": [ { "Name": "JmesPathMatcher", "Pattern": "requestId == '1'" }, { "Name": "JmesPathMatcher", "Pattern": "value == 'A'" } ] } }, "Response": { "StatusCode": 200, "Headers": { "Content-Type": "application/json" }, "BodyAsJson": { "value": "Pass" } } }, { "Title": "Example_A_Fail", "Request": { "Path": "/examplea", "Method": "POST", "Body": { "MatchOperator": "and", "Matchers": [ { "Name": "JmesPathMatcher", "Pattern": "requestId == '2'" }, { "Name": "JmesPathMatcher", "Pattern": "value == 'A'" } ] } }, "Response": { "StatusCode": 200, "Headers": { "Content-Type": "application/json" }, "BodyAsJson": { "value": "Fail" } } }, { "Title": "Example_B_Fail", "Request": { "Path": "/exampleb", "Method": "POST", "Body": { "MatchOperator": "and", "Matchers": [ { "Name": "JmesPathMatcher", "Pattern": "requestId == '2'" }, { "Name": "JmesPathMatcher", "Pattern": "value == 'A'" } ] } }, "Response": { "StatusCode": 200, "Headers": { "Content-Type": "application/json" }, "BodyAsJson": { "value": "Fail" } } }, { "Title": "Example_B_Pass", "Request": { "Path": "/exampleb", "Method": "POST", "Body": { "MatchOperator": "and", "Matchers": [ { "Name": "JmesPathMatcher", "Pattern": "requestId == '1'" }, { "Name": "JmesPathMatcher", "Pattern": "value == 'A'" } ] } }, "Response": { "StatusCode": 200, "Headers": { "Content-Type": "application/json" }, "BodyAsJson": { "value": "Pass" } } }, { "Title": "Example_C_Pass", "Request": { "Path": "/examplec", "Method": "POST", "Body": { "MatchOperator": "and", "Matchers": [ { "Name": "JmesPathMatcher", "Pattern": "extra == 'X'" }, { "Name": "JmesPathMatcher", "Pattern": "requestId == '1'" }, { "Name": "JmesPathMatcher", "Pattern": "value == 'A'" } ] } }, "Response": { "StatusCode": 200, "Headers": { "Content-Type": "application/json" }, "BodyAsJson": { "value": "Pass" } } }, { "Title": "Example_C_Fail", "Request": { "Path": "/examplec", "Method": "POST", "Body": { "MatchOperator": "and", "Matchers": [ { "Name": "JmesPathMatcher", "Pattern": "requestId == '1'" }, { "Name": "JmesPathMatcher", "Pattern": "value == 'A'" } ] } }, "Response": { "StatusCode": 200, "Headers": { "Content-Type": "application/json" }, "BodyAsJson": { "value": "Fail" } } }, { "Title": "Example_D_Fail", "Request": { "Path": "/exampled", "Method": "POST", "Body": { "MatchOperator": "and", "Matchers": [ { "Name": "JmesPathMatcher", "Pattern": "requestId == '1'" }, { "Name": "JmesPathMatcher", "Pattern": "value == 'A'" } ] } }, "Response": { "StatusCode": 200, "Headers": { "Content-Type": "application/json" }, "BodyAsJson": { "value": "Fail" } } }, { "Title": "Example_D_Pass", "Request": { "Path": "/exampled", "Method": "POST", "Body": { "MatchOperator": "and", "Matchers": [ { "Name": "JmesPathMatcher", "Pattern": "extra == 'X'" }, { "Name": "JmesPathMatcher", "Pattern": "requestId == '1'" }, { "Name": "JmesPathMatcher", "Pattern": "value == 'A'" } ] } }, "Response": { "StatusCode": 200, "Headers": { "Content-Type": "application/json" }, "BodyAsJson": { "value": "Pass" } } } ] ``` Run the dotnet global tool as follows (version 1.5.39): ```powershell dotnet-wiremock --Urls "http://*:8080" --ReadStaticMappings "true" --WireMockLogger "WireMockConsoleLogger" ``` Run the following PowerShell code to see results: ```powershell $resultA = Invoke-RestMethod ` -Body '{ "requestId": "1", "value": "A" }}' ` -ContentType "application/json" ` -Method "Post" ` -Uri "http://localhost:8080/examplea" Write-Host "[Example A]: $($resultA.value)" $resultB = Invoke-RestMethod ` -Body '{ "requestId": "1", "value": "A" }}' ` -ContentType "application/json" ` -Method "Post" ` -Uri "http://localhost:8080/exampleb" Write-Host "[Example B]: $($resultB.value)" $resultC = Invoke-RestMethod ` -Body '{ "extra": "X", "requestId": "1", "value": "A" }}' ` -ContentType "application/json" ` -Method "Post" ` -Uri "http://localhost:8080/examplec" Write-Host "[Example C]: $($resultC.value)" $resultD = Invoke-RestMethod ` -Body '{ "extra": "X", "requestId": "1", "value": "A" }}' ` -ContentType "application/json" ` -Method "Post" ` -Uri "http://localhost:8080/exampled" Write-Host "[Example D]: $($resultD.value)" ``` Results show: ```text [Example A]: Fail [Example B]: Pass [Example C]: Fail [Example D]: Pass ```
adam added the question label 2025-12-29 08:29:55 +01:00
adam closed this issue 2025-12-29 08:29:55 +01:00
Author
Owner

@StefH commented on GitHub (Oct 13, 2023):

@joshua-woolf
I think the internal logic defaults to OR because the MatchOperator should be case-sensitive.

Can you try using the value And instead of and ?

@StefH commented on GitHub (Oct 13, 2023): @joshua-woolf I think the internal logic defaults to `OR` because the **MatchOperator** should be case-sensitive. Can you try using the value `And` instead of `and` ?
Author
Owner

@StefH commented on GitHub (Oct 13, 2023):

https://github.com/WireMock-Net/WireMock.Net/pull/1009

@StefH commented on GitHub (Oct 13, 2023): https://github.com/WireMock-Net/WireMock.Net/pull/1009
Author
Owner

@joshua-woolf commented on GitHub (Oct 16, 2023):

@StefH

I can confirm that changing the casing of and to And does cause Example A to pass, however Example C still fails.

@joshua-woolf commented on GitHub (Oct 16, 2023): @StefH I can confirm that changing the casing of `and` to `And` does cause Example A to pass, however Example C still fails.
Author
Owner

@StefH commented on GitHub (Oct 16, 2023):

@joshua-woolf
The current implementation has no logic yet to determine that in your case the Example_C_Pass match is the best of the two "C"-tests.

For now just use .AtPriority(...) --> give the Example_C_Pass a value of 1, and the other a value of 2 or higher.

See https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing#stub-priority

@StefH commented on GitHub (Oct 16, 2023): @joshua-woolf The current implementation has no logic yet to determine that in your case the `Example_C_Pass` match is the best of the two "C"-tests. For now just use `.AtPriority(...)` --> give the `Example_C_Pass` a value of 1, and the other a value of 2 or higher. See https://github.com/WireMock-Net/WireMock.Net/wiki/Stubbing#stub-priority
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/WireMock.Net#548