mock-server / mockserver

MockServer enables easy mocking of any system you integrate with via HTTP or HTTPS with clients written in Java, JavaScript and Ruby. MockServer also includes a proxy that introspects all proxied traffic including encrypted SSL traffic and supports Port Forwarding, Web Proxying (i.e. HTTP proxy), HTTPS Tunneling Proxying (using HTTP CONNECT) and SOCKS Proxying (i.e. dynamic port forwarding).
http://mock-server.com
Apache License 2.0
4.54k stars 1.06k forks source link

Order of registering unrelated expectations influences verification. #1312

Closed andre-aktivconsultancy closed 1 year ago

andre-aktivconsultancy commented 2 years ago

Describe the issue The order in which expectations with path parameters for different paths are registered seems to influence whether a verification passes or not.

What you are trying to do I have multiple expectations configured for different paths, containing path parameters. I try to execute a verification based on the path and path parameters.

MockServer version 5.13.2

To Reproduce

I created the tests below to illustrate the problem. The tests are similar except for the order in which the expectations are configured.

package com.example

import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
import static org.mockserver.model.JsonBody.json;
import static org.mockserver.model.Parameter.param;

import java.net.MalformedURLException;
import java.net.URL;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockserver.client.MockServerClient;
import org.mockserver.junit.jupiter.MockServerExtension;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.client.WebClient;

@ExtendWith(MockServerExtension.class)
public class MockServerPathParamsReproductionTests {
  private WebClient client;
  private MockServerClient mock;

  @BeforeEach
  public void beforeEach(MockServerClient m) throws MalformedURLException {
    mock = m;
    m.reset();

    client =
        WebClient.builder()
            .baseUrl(new URL("http", "127.0.0.1", m.getPort(), "").toString())
            .build();
  }

  private String get(final String url) {
    return client
        .method(HttpMethod.GET)
        .uri(url)
        .exchangeToMono(response -> response.bodyToMono(String.class))
        .block();
  }

  private void expectFoo() {
    mock.when(request().withPath("/foo/{fooId}").withPathParameter(param("fooId", "[A-Z0-9\\-]+")))
        .respond(
            httpRequest ->
                response()
                    .withBody(
                        json(
                            "{\"foo\": \"id=%s\"}"
                                .formatted(httpRequest.getFirstPathParameter("fooId")))));
  }

  private void expectBar() {
    mock.when(request().withPath("/bar/{barId}").withPathParameter(param("barId", "[A-Z0-9\\-]+")))
        .respond(
            httpRequest ->
                response()
                    .withBody(
                        json(
                            "{\"bar\": \"id=%s\"}"
                                .formatted(httpRequest.getFirstPathParameter("barId")))));
  }

  @Test
  public void requestBar_withFooAndBarExpectations() {
    expectFoo();
    expectBar();

    get("bar/1");

    // TEST FAILS
    mock.verify(request().withPath("/bar/{barId}").withPathParameter("barId", "1"));
  }

  @Test
  public void requestBar_withBarAndFooExpectations() {
    expectBar();
    expectFoo();

    get("bar/2");

    // TEST PASSES
    mock.verify(request().withPath("/bar/{barId}").withPathParameter("barId", "2"));
  }
}

Expected behaviour I expect both tests to pass. The order in which expectations for unrelated requests are registered should not influence verification.

The difference seems to be in the recorded path parameters for the request

"pathParameters" : {
      "barId" : [ "2" ]
    }
"pathParameters" : {
      "fooId" : [ "1" ],
      "barId" : [ "1" ]
    }

I don't understand why the fooId path is recorded for failing case.

MockServer Log

2022-05-06 17:25:41,262  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 started on port: 34529 
2022-05-06 17:25:41,834  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 resetting all expectations and request logs 
2022-05-06 17:25:43,061  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 creating expectation:

  {
    "httpRequest" : {
      "path" : "/bar/{barId}",
      "pathParameters" : {
        "barId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "3a6f064b-a01e-4821-a06e-c938eac535fc"
    },
    "id" : "e21da9b7-aa32-4e78-aa11-19eabdd5c3e9",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

 with id:

  e21da9b7-aa32-4e78-aa11-19eabdd5c3e9

2022-05-06 17:25:43,095  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 creating expectation:

  {
    "httpRequest" : {
      "path" : "/foo/{fooId}",
      "pathParameters" : {
        "fooId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "e87f7583-f795-4f07-ad2f-8c1322f7e11f"
    },
    "id" : "938502fe-89e2-4a46-ae95-da7275e22fce",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

 with id:

  938502fe-89e2-4a46-ae95-da7275e22fce

2022-05-06 17:25:43,696  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 received request:

  {
    "method" : "GET",
    "path" : "/bar/2",
    "headers" : {
      "user-agent" : [ "ReactorNetty/1.0.17" ],
      "host" : [ "127.0.0.1:34529" ],
      "content-length" : [ "0" ],
      "accept-encoding" : [ "gzip" ],
      "accept" : [ "*/*" ]
    },
    "keepAlive" : true,
    "secure" : false,
    "remoteAddress" : "127.0.0.1"
  }

2022-05-06 17:25:43,701  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 request:

  {
    "method" : "GET",
    "path" : "/bar/2",
    "pathParameters" : {
      "barId" : [ "2" ]
    },
    "headers" : {
      "user-agent" : [ "ReactorNetty/1.0.17" ],
      "host" : [ "127.0.0.1:34529" ],
      "content-length" : [ "0" ],
      "accept-encoding" : [ "gzip" ],
      "accept" : [ "*/*" ]
    },
    "keepAlive" : true,
    "secure" : false,
    "remoteAddress" : "127.0.0.1"
  }

 matched expectation:

  {
    "httpRequest" : {
      "path" : "/bar/{barId}",
      "pathParameters" : {
        "barId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "3a6f064b-a01e-4821-a06e-c938eac535fc"
    },
    "id" : "e21da9b7-aa32-4e78-aa11-19eabdd5c3e9",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

2022-05-06 17:25:43,706  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 returning response:

  {
    "body" : {
      "bar" : "id=2"
    }
  }

 for request:

  {
    "method" : "GET",
    "path" : "/bar/2",
    "pathParameters" : {
      "barId" : [ "2" ]
    },
    "headers" : {
      "user-agent" : [ "ReactorNetty/1.0.17" ],
      "host" : [ "127.0.0.1:34529" ],
      "content-length" : [ "0" ],
      "accept-encoding" : [ "gzip" ],
      "accept" : [ "*/*" ]
    },
    "keepAlive" : true,
    "secure" : false,
    "remoteAddress" : "127.0.0.1"
  }

 for action:

  {
    "clientId" : "3a6f064b-a01e-4821-a06e-c938eac535fc"
  }

 from expectation:

  e21da9b7-aa32-4e78-aa11-19eabdd5c3e9

2022-05-06 17:25:43,784  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 verifying sequence that match:

  {
    "httpRequests" : [ {
      "path" : "/bar/{barId}",
      "pathParameters" : {
        "barId" : [ "2" ]
      }
    } ]
  }

2022-05-06 17:25:43,794  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | request sequence found:

  [{
    "path" : "/bar/{barId}",
    "pathParameters" : {
      "barId" : [ "2" ]
    }
  }]

2022-05-06 17:25:43,915  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 removed expectation:

  {
    "httpRequest" : {
      "path" : "/bar/{barId}",
      "pathParameters" : {
        "barId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "3a6f064b-a01e-4821-a06e-c938eac535fc"
    },
    "id" : "e21da9b7-aa32-4e78-aa11-19eabdd5c3e9",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

 with id:

  e21da9b7-aa32-4e78-aa11-19eabdd5c3e9

2022-05-06 17:25:43,917  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 removed expectation:

  {
    "httpRequest" : {
      "path" : "/foo/{fooId}",
      "pathParameters" : {
        "fooId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "e87f7583-f795-4f07-ad2f-8c1322f7e11f"
    },
    "id" : "938502fe-89e2-4a46-ae95-da7275e22fce",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

 with id:

  938502fe-89e2-4a46-ae95-da7275e22fce

2022-05-06 17:25:43,918  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 resetting all expectations and request logs 
2022-05-06 17:25:43,936  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 creating expectation:

  {
    "httpRequest" : {
      "path" : "/foo/{fooId}",
      "pathParameters" : {
        "fooId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "91c9da1e-9d12-4b46-9d45-829c786d5287"
    },
    "id" : "423c308c-7524-4d49-8696-a873906f74d8",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

 with id:

  423c308c-7524-4d49-8696-a873906f74d8

2022-05-06 17:25:43,960  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 creating expectation:

  {
    "httpRequest" : {
      "path" : "/bar/{barId}",
      "pathParameters" : {
        "barId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "013fab9c-9b99-4fac-8394-ae428693170a"
    },
    "id" : "9dfde505-c355-4979-96a3-2339e3405545",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

 with id:

  9dfde505-c355-4979-96a3-2339e3405545

2022-05-06 17:25:43,969  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 received request:

  {
    "method" : "GET",
    "path" : "/bar/1",
    "headers" : {
      "user-agent" : [ "ReactorNetty/1.0.17" ],
      "host" : [ "127.0.0.1:34529" ],
      "content-length" : [ "0" ],
      "accept-encoding" : [ "gzip" ],
      "accept" : [ "*/*" ]
    },
    "keepAlive" : true,
    "secure" : false,
    "remoteAddress" : "127.0.0.1"
  }

2022-05-06 17:25:43,970  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 request:

  {
    "method" : "GET",
    "path" : "/bar/1",
    "pathParameters" : {
      "fooId" : [ "1" ],
      "barId" : [ "1" ]
    },
    "headers" : {
      "user-agent" : [ "ReactorNetty/1.0.17" ],
      "host" : [ "127.0.0.1:34529" ],
      "content-length" : [ "0" ],
      "accept-encoding" : [ "gzip" ],
      "accept" : [ "*/*" ]
    },
    "keepAlive" : true,
    "secure" : false,
    "remoteAddress" : "127.0.0.1"
  }

 didn't match expectation:

  {
    "httpRequest" : {
      "path" : "/foo/{fooId}",
      "pathParameters" : {
        "fooId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "91c9da1e-9d12-4b46-9d45-829c786d5287"
    },
    "id" : "423c308c-7524-4d49-8696-a873906f74d8",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

 because:

  method matched
  path didn't match

2022-05-06 17:25:43,972  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 request:

  {
    "method" : "GET",
    "path" : "/bar/1",
    "pathParameters" : {
      "fooId" : [ "1" ],
      "barId" : [ "1" ]
    },
    "headers" : {
      "user-agent" : [ "ReactorNetty/1.0.17" ],
      "host" : [ "127.0.0.1:34529" ],
      "content-length" : [ "0" ],
      "accept-encoding" : [ "gzip" ],
      "accept" : [ "*/*" ]
    },
    "keepAlive" : true,
    "secure" : false,
    "remoteAddress" : "127.0.0.1"
  }

 matched expectation:

  {
    "httpRequest" : {
      "path" : "/bar/{barId}",
      "pathParameters" : {
        "barId" : [ "[A-Z0-9\\-]+" ]
      }
    },
    "httpResponseObjectCallback" : {
      "clientId" : "013fab9c-9b99-4fac-8394-ae428693170a"
    },
    "id" : "9dfde505-c355-4979-96a3-2339e3405545",
    "priority" : 0,
    "timeToLive" : {
      "unlimited" : true
    },
    "times" : {
      "unlimited" : true
    }
  }

2022-05-06 17:25:43,975  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 returning response:

  {
    "body" : {
      "bar" : "id=1"
    }
  }

 for request:

  {
    "method" : "GET",
    "path" : "/bar/1",
    "pathParameters" : {
      "fooId" : [ "1" ],
      "barId" : [ "1" ]
    },
    "headers" : {
      "user-agent" : [ "ReactorNetty/1.0.17" ],
      "host" : [ "127.0.0.1:34529" ],
      "content-length" : [ "0" ],
      "accept-encoding" : [ "gzip" ],
      "accept" : [ "*/*" ]
    },
    "keepAlive" : true,
    "secure" : false,
    "remoteAddress" : "127.0.0.1"
  }

 for action:

  {
    "clientId" : "013fab9c-9b99-4fac-8394-ae428693170a"
  }

 from expectation:

  9dfde505-c355-4979-96a3-2339e3405545

2022-05-06 17:25:43,985  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 verifying sequence that match:

  {
    "httpRequests" : [ {
      "path" : "/bar/{barId}",
      "pathParameters" : {
        "barId" : [ "1" ]
      }
    } ]
  }

2022-05-06 17:25:43,999  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | request sequence not found, expected:

  [{
    "path" : "/bar/{barId}",
    "pathParameters" : {
      "barId" : [ "1" ]
    }
  }]

 but was:

  {
    "method" : "GET",
    "path" : "/bar/1",
    "pathParameters" : {
      "fooId" : [ "1" ],
      "barId" : [ "1" ]
    },
    "headers" : {
      "user-agent" : [ "ReactorNetty/1.0.17" ],
      "host" : [ "127.0.0.1:34529" ],
      "content-length" : [ "0" ],
      "accept-encoding" : [ "gzip" ],
      "accept" : [ "*/*" ]
    },
    "keepAlive" : true,
    "secure" : false,
    "remoteAddress" : "127.0.0.1"
  }

java.lang.AssertionError: Request sequence not found, expected:<[ {
  "path" : "/bar/{barId}"
} ]> but was:<{
  "headers" : {
    "content-length" : [ "0" ],
    "host" : [ "127.0.0.1:34529" ],
    "accept-encoding" : [ "gzip" ],
    "user-agent" : [ "ReactorNetty/1.0.17" ],
    "accept" : [ "*/*" ]
  },
  "keepAlive" : true,
  "method" : "GET",
  "path" : "/bar/1",
  "secure" : false
}>
<Click to see difference>

    at org.mockserver.client.MockServerClient.verify(MockServerClient.java:864)
    at org.mockserver.client.MockServerClient.verify(MockServerClient.java:822)
    at com.oceaneering.oasys.athena.supervisor.unit.ant.MockServerPathParamsReproductionTests.requestBar_withFooAndBarExpectations(MockServerPathParamsReproductionTests.java:80)
[...]
2022-05-06 17:25:44,024  INFO | MockServer-EventLog0 | org.mockserver.log.MockServerEventLog                   | 34529 stopped for port: 34529 

Process finished with exit code 255
jamesdbloom commented 1 year ago

This has now been fixed as part of #1376