Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow composing multiple matches to one response #463

Open
demus-nine opened this issue Oct 7, 2019 · 6 comments
Open

Allow composing multiple matches to one response #463

demus-nine opened this issue Oct 7, 2019 · 6 comments
Projects

Comments

@demus-nine
Copy link

demus-nine commented Oct 7, 2019

Assume you have set up predicates that each matches one of a list of numbers (fx, PINs). Assume the expected response contains a block of xml or json for each matched result, but only for the matched values.
As I understand it, the first matching predicate wins, so the only way I see to do this is to create mocks with a predicates in the right order that matches all the expected combinations or with javascript.

I suggest to add a factility that allows all predicates to be evaluated and their respective outputs to be concatenated with a header and footer block.

@bbyars
Copy link
Owner

bbyars commented Oct 13, 2019 via email

@demus-nine
Copy link
Author

demus-nine commented Jan 26, 2020

Let's say your request body is something like

req
  - searchid: 1111
  - searchid: 3333
  - searchid: 4444
/req

I am not using json or xml to avoid being refered to the xpath and jsonpath predicates.
The predicate is

"predicates": [
        {
          "or": [
            { "contains": { "- searchid: 1111" } },
            { "contains": { "- searchid: 2222" } }
            { "contains": { "- searchid: 3333" } }
          ]
        }
]

Let's say the return value is supposed to be xml:

<response>
   <somemetadata>1245</somemetadata>
   <results>
      <result>
        <name>Dummy<name/>
        <id>1111</id>
      </result>
      <result>
        <name>Dummy<name/>
        <id>3333</id>
      </result>
   <results>
</response>

Notice that the result element needs to be repeated for every match substituting part of the matched text.
So there is a header, some variable number of repeating blocks and a footer. I couldn't find any easy way to implement this other than implementing it in javascript, and I think this isn't an unusual pattern.
I would suggest something like:

"responses": {
   "header": "<response>\n   <somemetadata>1245</somemetadata>\n   <results>",
   "footer": "   <results>\n</response>",
   "eachMatch": {
     "body": "      <result>\n        <name>Dummy<name/>\n        <id>${ID}</id>\n      </result>",
     "copy": {
       "from": "match",
       "into": "${ID}",
       "method": "regex",
       "selector": "<id>(\d+)</id>"
     }
}

The "from" could also just default to the text matched by a given predicate. You could also implement something like this for xpath or jsonpath predicates that match multiple nodes.

@bbyars bbyars added this to v2.7.0 in Roadmap Mar 3, 2020
@bbyars bbyars moved this from v2.7.0 to v2.3.0 in Roadmap Mar 3, 2020
@bbyars bbyars moved this from v2.3.0 to Wishlist in Roadmap Mar 3, 2020
@demus-nine
Copy link
Author

I can send you the javascript implementation that produces the required result, if that would help.

@bbyars
Copy link
Owner

bbyars commented Mar 18, 2020 via email

@demus-nine
Copy link
Author

Sorry, I was distracted. Anyway here is an example of what I ended up doing in cucumber/groovy to generate a chunk of json, that could be sent to mountebank, so it could have a chunk of javascript, that could generate a chunk of XML with the correct aggregate response, when the input was a set of search values that contained some matches and some non-matches:

void fakeConditionalCSRPResponse(Map<String, SomeMockData> validValues) {
    List<String> responseBlocks = []
    validValues.eachWithIndex { String validValue, SomeMockData someMockData, Integer index ->
        responseBlocks << "\"${validValue}\": ${JsonOutput.toJson(Responses.blockOfValidResponse(replacementValue1, replacementValue2, replacementValue3))}".toString()
    }

    String injection = """function (request, state, logger) {
    const info = (logger || {}).info || (() => {});
    const xpath = require('xpath');
    const DOMParser = require('xmldom').DOMParser;
    const parser = new DOMParser({
        errorHandler: (level, message) => {
        const warn = (logger || {}).warn || (() => {});
        warn("%s (source: %s)", message, JSON.stringify(request.body));
        }
    });

    const validInputValues = new Set(${validValues.keySet().inspect()});
    const valuesToFind = xpath.select("//*[local-name(.)='ElementToMatchValueOf']", parser.parseFromString(request.body)).map(node => node.textContent);
    
    const bodyMap = {
        ${responseBlocks.join(',\n    ')}
    };
    let outputForNotFound = "";
    let outputForFound = "";
    for (let foundValue of valuesToFind) {
        if (validInputValues.has(cpr)) {
        outputForFound += bodyMap[cpr];
        } else {
        outputForNotFound += '${
                StringEscapeUtils.escapeJavaScript('''
                        <ns1:Elem1>
                            <ns1:Elem2>
                                <ns1:Elem3>11</ns1:Elem3>
                                <ns1:Text>Fejl i PNR</ns1:Text>
                            </ns1:Elem2>
                        </ns1:Elem1>''')}'
        }
    }

    const output = "${StringEscapeUtils.escapeJavaScript(
    """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
        <ns1:WebServiceReponse xmlns="http://fake.io">
        <ns1:Ugh>
            <ns1:Yech>
            <ns1:TransactionId>4251934a-08f4-4d43-8527-2e9578400340.1</ns1:TransactionId>
            <ns1:ServiceId>Service1</ns1:ServiceId>
            <ns1:TransaktionsTime>2015-09-03T14:04:18</ns1:TransaktionsTime>""")}" + outputForNotFound + "${StringEscapeUtils.escapeJavaScript("""
            </ns1:Yech>
        </ns1:Ugh>
        <ns1:ResultList>""")}" + outputForFound + "${StringEscapeUtils.escapeJavaScript("""
        </ns1:ResultList>
        </ns1:WebServiceReponse>
    </soapenv:Body>
    </soapenv:Envelope>""")}";

    info('Result ' + output);
    return {
        headers: { Connection: "close", "Content-Type": "text/xml; charset=UTF-8" },
        statusCode: 200,
        body: output
    };

    }
    """

        String imposterJson =
"""{
    "protocol": "http",
    "port": $MOUNTEBANK_PORT,
    "name": "Example",
    "stubs":
    [
        {
            "responses":
            [
                {
                    "inject": ${JsonOutput.toJson(injection)}
                }
            ]
        }
    ]
}
"""
        return createImposterFromJson(imposterJson)
    }

I hope you read groovy.

@konflic
Copy link

konflic commented May 6, 2021

Hi, sorry, is it working? Or going to be merged someday?

@bbyars bbyars moved this from Wishlist to Unevaluated in Roadmap Sep 19, 2021
@bbyars bbyars moved this from Unevaluated to Wishlist in Roadmap Mar 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Roadmap
Wishlist
Development

No branches or pull requests

3 participants