Hello, Thank you for your product. Im using it fu...
# help
l
Hello, Thank you for your product. Im using it fully, but I am currently facing an issue when using a custom matcher. I created an extension to check the request hash, and the problem occurs when I send a parameter set that should be used in the extension for filtering hash fields in the request. However, when I make the request, it always sends the parameters set specified in the last request mapping. For example, when I send a request to
/api/check
, the parameters sent specified under
/api/payment
but I expected parameters set specified under
/api/check
Copy code
"parameters": {
  "HashFields": ["Inputs", "Amount", "TransactID"],
  "Token": "Secret-Token",
  "Checksum": "Checksum"
}
Wiremock version: 3.3.1(I tried also latest) Mapping file:
Copy code
{
  "mappings": [
    {
      "request": {
        "urlPath": "/api/check",
        "method": "POST",
        "customMatcher": {
          "name": "MD5Hash-matcher",
          "parameters": {
            "HashFields": [
              "Inputs"
            ],
            "Token": "Secret-Token",
            "Checksum": "Checksum"
          }
        },
        "bodyPatterns": [
          {
            "matchesJsonPath": "$.[?($.Lang)]"
          },
          {
            "matchesJsonPath": "$.[?($.Currency)]"
          },
          {
            "matchesJsonPath": "$.[?($.Checksum)]"
          },
          {
            "matchesJsonPath": "$[?(@.Inputs)]"
          }
        ]
      },
      "response": {
        "status": 200,
        "bodyFileName": "goodwin/api/checkResponse_{{jsonPath request.body '$.Inputs[0]'}}.json"
      },
      "metadata": {
        "serviceName": "GoodWin",
        "serviceId": "97",
        "action": "check",
        "description": ""
      }
    },
    {
      "request": {
        "urlPath": "/api/payment",
        "method": "POST",
        "customMatcher": {
          "name": "MD5Hash-matcher",
          "parameters": {
            "HashFields": [
              "Inputs",
              "Amount",
              "TransactID"
            ],
            "Token": "Secret-Token",
            "Checksum": "Checksum"
          }
        },
        "bodyPatterns": [
          {
            "matchesJsonPath": "$.[?($.Lang)]"
          },
          {
            "matchesJsonPath": "$.[?($.Currency)]"
          },
          {
            "matchesJsonPath": "$[?(@.Checksum && @.Checksum =~ /^[a-f0-9]{32}$/)]"
          },
          {
            "matchesJsonPath": "$[?(@.Inputs)]"
          },
          {
            "matchesJsonPath": "$[?($.DtTime)]"
          },
          {
            "matchesJsonPath": "$.[?($.Amount)]"
          },
          {
            "matchesJsonPath": "$.[?($.TransactID)]"
          }
        ]
      },
      "response": {
        "status": 200,
        "bodyFileName": "goodwin/api/paymentResponse_{{jsonPath request.body '$.Inputs[0]'}}.json"
      },
      "metadata": {
        "serviceName": "GoodWin",
        "serviceId": "97",
        "action": "pay",
        "description": ""
      }
    }
  ]
}
Extension Matcher class:
Copy code
package org.wiremock.extensions.matchers;

import com.github.tomakehurst.wiremock.core.ConfigurationException;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.matching.MatchResult;
import com.github.tomakehurst.wiremock.matching.RequestMatcherExtension;

import static com.github.tomakehurst.wiremock.common.LocalNotifier.notifier;
import static org.wiremock.helpers.HashFieldsHelper.getConcatenatedValues;
import static org.wiremock.helpers.MD5HashGenerator.calculateMD5Hash;
import static org.wiremock.helpers.RequestHelper.extractJsonPathValue;

/**
 * Custom WireMock request matcher that validates a request based on an MD5 hash.
 * The computed hash is generated using specific JSON fields then
 * compared against the provided checksum in the request body.
 *
 * <p><b>Required Parameters:</b></p>
 * <ul>
 *      <li><b>HashFields (String Array)</b>: The JSON fields whose values will be concatenated and hashed.
 *  *         These fields must exist in the request body and be specified in the WireMock mapping file.</li>
 *  *     <li><b>Token (String)</b>: A token provided in the WireMock mapping, which is prepended to the concatenated hash values before computing the checksum.</li>
 *  *     <li><b>Checksum (String)</b>: The JSON path (provided in the WireMock mapping) to locate the expected MD5 hash in the request body.</li>
 * </ul>
 *
 * <p><b>Example WireMock Mapping:</b></p>
 * <pre>
 * {
 *   "request": {
 *     "method": "POST",
 *     "url": "/api/payment",
 *     "customMatcher": {
 *           "name": "MD5Hash-matcher",
 *           "parameters": {
 *             "HashFields": ["Inputs","Amount","TransactID"],
 *             "Token": "Secret-Token",
 *             "Checksum": "Checksum"
 *           }
 *   }
 * }
 * </pre>
 */
public class GoodwinMD5HashMatcher extends RequestMatcherExtension {
    private static final String hashFields = "HashFields";
    private static final String token = "Token";
    private static final String checksum = "Checksum";

    @Override
    public String getName() {
        return "MD5Hash-matcher";
    }

    @Override
    public MatchResult match(Request request, Parameters parameters) {
        try {
            String requestPath = request.getUrl();
            notifier().info("Request Path: " + requestPath + " | Parameters: " + parameters.values());
            String requestBody = request.getBodyAsString();
            String computedChecksum = calculateMD5Hash(
                String.format("%s%s", parameters.getString(token), getConcatenatedValues(hashFields, requestBody, parameters))
            ).toLowerCase();
            String providedChecksum = extractJsonPathValue(requestBody, parameters.getString(checksum));

            if (providedChecksum.equals(computedChecksum)) {
                return MatchResult.exactMatch();
            } else {
                String errorMessage = "Checksum mismatch: expected '" + computedChecksum + "' but got '" + providedChecksum + "'";
                notifier().error(errorMessage);
                throw new ConfigurationException(errorMessage);
            }
        } catch (Exception e) {
            String errorMessage = "Error processing request: " + e.getClass().getSimpleName() + " - " + e.getMessage();
            notifier().error(errorMessage);
            throw new ConfigurationException(errorMessage);
        }
    }
}
Thank you in advance for your help.
l
That certainly is strange. Just out of interest, have you tried using separate mapping files or if you only ever have one stub mapping, are the correct parameters passed then ?
l
@Lee Turner Yes, I tried using separate mapping files, each containing a single request. However, it always sends the parameters specified in the last mapping file. For example, if I have
1_mapping
,
2_mapping
, and
3_mapping
, it always sends the parameters from
3_mapping
.
l
Let me see if I can recreate it. If there is anything you can provide to help recreate the issue that would be great.
I don't seem to be able to recreate this. I have written a little test in the WireMock codebase to create a new custom matcher that just logs the parameters it is passed. I then register 2 stubs with the new custom matcher and different parameters for each stub. When I run the test the correct parameters are logged for each stub. This was created through code and not the wiremock json like you are doing but the correct parameters are passed all the same
I have just had a play with the WireMock state extension which has a custom matcher built in. I then configured wiremock using a mapping json file with multiple mappings in it where two of the 4 mappings are using the state custom matcher both with different parameters. This worked as expected where the endpoints only matched based on the values in the parameters passed in. As a result I am pretty confident that each instance of the custom matcher are getting the correct parameters. This leaves me a little stumped as to what your issue is
l
Hello @Lee Turner , thank you for your contribution. As you can see in the following video, I created two stubs and made a request to the first stub, but the parameters are those specified in the second stub.
Please could you provide your test example?
l
I will push my example to my test repo so you can take a look. Could you post your
createStubMatchesJsonPathPOST
method so I can take a look at what that is doing ?
l
@Lee Turner
Copy code
package org.wiremock.helpers;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.Json;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.http.HttpHeaders;

import java.util.Map;

import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching;
import static org.wiremock.AbstractTestBase.wm;

public class WiremockStubHelper {
    public static void createStubMatchesJsonPathPOST(String urlPath,
                            String matcherName,
                            Map<String, Object> matcherParams,
                            Map<String, String> requestFields,
                            Map<String, String> responseBody,
                            HttpHeaders headers) {
        wm.stubFor(
            <http://WireMock.post|WireMock.post>(urlPathMatching(urlPath))
                .andMatching(matcherName, Parameters.from(matcherParams))
                .withRequestBody(
                    requestFields.entrySet().stream()
                        .map(entry -> WireMock.matchingJsonPath(entry.getKey()))
                        .reduce((a, b) -> a.and(b))
                        .orElseThrow()
                )
                .willReturn(
                    WireMock.ok()
                        .withHeaders(headers)
                        .withJsonBody(Json.node(Json.write(responseBody)))
                )
        );
    }
}
l
You can see the state extension mappings here - https://github.com/leeturner/wiremock-standalone-docker-example/blob/main/wiremock/mappings/state.json Example usage here - https://github.com/leeturner/wiremock-standalone-docker-example/blob/main/tests/state.http The get requests should only match when the state is present
🙌 1
Can't see anything in the code that stands out as an issue. I take it this extension isn't public ?
l
@Lee Turner can we have a short call?
l
Unfortunately I am in meetings for the afternoon so won't be able to today. Is there any way you can provide a cut down example of the code that demonstrates the problem so I can take a look later this evening ? It doesn't have to be your production code, just a cut down example of what you are doing with a matcher that just logs the parameters passed in. If you can push that to a repo somewhere I can try and debug later
l
@Lee Turner ok will push, thanks a lot!
🙌 1
@Lee Turner please you can check it here: https://github.com/Lyudvig003/MD5HashMatcher/tree/main
🙌 1
l
Haven't had time to look at the code yet but I spotted you are using quite an old version of wiremock in the dependencies. We are now on
3.12.1
Could you try updating to the latest version of wiremock and giving that a try first ?
l
@Lee Turner I updated the version to latest and logic works as expected, thanks a lot!!
l
Great news. Maybe this was a bug in a previous version of WireMock that was later fixed