diff --git a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java index bbb5f36d9a..d3e93e3814 100644 --- a/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java +++ b/spring-cloud-contract-wiremock/src/main/java/org/springframework/cloud/contract/wiremock/WireMockRestServiceServer.java @@ -35,6 +35,8 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import com.github.tomakehurst.wiremock.common.Json; @@ -43,7 +45,6 @@ import com.github.tomakehurst.wiremock.http.MultiValue; import com.github.tomakehurst.wiremock.http.ResponseDefinition; import com.github.tomakehurst.wiremock.matching.MultiValuePattern; import com.github.tomakehurst.wiremock.matching.RequestPattern; -import com.github.tomakehurst.wiremock.stubbing.SortedConcurrentMappingSet; import com.github.tomakehurst.wiremock.stubbing.StubMapping; import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; @@ -151,7 +152,7 @@ public class WireMockRestServiceServer { */ public MockRestServiceServer build() { MockRestServiceServer server = this.builder.build(); - SortedConcurrentMappingSet mappings = new SortedConcurrentMappingSet(); + List mappings = new ArrayList<>(); for (String location : this.locations) { try { for (Resource resource : this.resolver.getResources(pattern(location))) { @@ -164,6 +165,7 @@ public class WireMockRestServiceServer { e); } } + Collections.sort(mappings, new StubMappingComparator()); for (StubMapping mapping : mappings) { ResponseActions expect = server .expect(requestTo(request(mapping.getRequest()))); @@ -247,4 +249,42 @@ public class WireMockRestServiceServer { return value == null ? MediaType.TEXT_PLAIN : MediaType.valueOf(value); } + private static class StubMappingComparator implements Comparator { + + @Override + public int compare(StubMapping one, StubMapping two) { + if (one==two) { + return 0; + } + int value = request(one.getRequest()).compareTo(request(two.getRequest())); + if (value == 0) { + + // Every mapping has a url pattern, and zero or more header patterns + int twos = 0; + if (two.getRequest().getHeaders() != null) { + twos = two.getRequest().getHeaders().size(); + } + int ones = 0; + if (one.getRequest().getHeaders() != null) { + ones = one.getRequest().getHeaders().size(); + } + value = twos - ones; + if (value == 0) { + // Same number of header matchers + if (two.getPriority() != null) { + return one.getPriority() != null + ? one.getPriority() - two.getPriority() : 1; + } + value = (int) (one.getInsertionIndex() - two.getInsertionIndex()); + } + } + return value; + } + + private String request(RequestPattern request) { + return (request.getUrlPath() == null ? (request.getUrl() == null ? "/" : request.getUrl()) : request.getUrlPath()); + } + + } + } diff --git a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockMockServerApplicationTests.java b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockMockServerApplicationTests.java index 59f0d0e686..070badb92a 100644 --- a/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockMockServerApplicationTests.java +++ b/spring-cloud-contract-wiremock/src/test/java/org/springframework/cloud/contract/wiremock/WiremockMockServerApplicationTests.java @@ -68,33 +68,56 @@ public class WiremockMockServerApplicationTests { public void postWithHeader() throws Exception { WireMockRestServiceServer.with(this.restTemplate) // .baseUrl("http://example.org") // order matters... - .stubs("classpath:/mappings/poster.json", "classpath:/mappings/accept.json").ignoreExpectOrder(true).build(); - assertThat(this.restTemplate - .exchange(RequestEntity.post(new URI("http://example.org/poster")) - .accept(MediaType.TEXT_PLAIN).build(), String.class) - .getBody()).isEqualTo("Accepted World"); + .stubs("classpath:/mappings/poster.json", + "classpath:/mappings/accept.json") + .ignoreExpectOrder(true).build(); + assertThat( + this.restTemplate + .exchange( + RequestEntity.post(new URI("http://example.org/poster")) + .accept(MediaType.TEXT_PLAIN).build(), + String.class) + .getBody()).isEqualTo("Accepted World"); } @Test public void postWithHeaderContains() throws Exception { WireMockRestServiceServer.with(this.restTemplate) // .baseUrl("http://example.org") // order matters... - .stubs("classpath:/mappings/poster.json", "classpath:/mappings/header-contains.json").ignoreExpectOrder(true).build(); - assertThat(this.restTemplate - .exchange(RequestEntity.post(new URI("http://example.org/poster")) - .accept(MediaType.valueOf("application/v.foo")).build(), String.class) - .getBody()).isEqualTo("Foo World"); + .stubs("classpath:/mappings/poster.json", + "classpath:/mappings/header-contains.json") + .ignoreExpectOrder(true).build(); + assertThat(this.restTemplate.exchange( + RequestEntity.post(new URI("http://example.org/poster")) + .accept(MediaType.valueOf("application/v.foo")).build(), + String.class).getBody()).isEqualTo("Foo World"); } @Test public void postWithHeaderMatches() throws Exception { WireMockRestServiceServer.with(this.restTemplate) // .baseUrl("http://example.org") // order matters... - .stubs("classpath:/mappings/poster.json", "classpath:/mappings/header-matches.json").ignoreExpectOrder(true).build(); + .stubs("classpath:/mappings/poster.json", + "classpath:/mappings/header-matches.json") + .ignoreExpectOrder(true).build(); + assertThat(this.restTemplate.exchange( + RequestEntity.post(new URI("http://example.org/poster")) + .accept(MediaType.valueOf("application/v.bar")).build(), + String.class).getBody()).isEqualTo("Bar World"); + } + + @Test + public void postWithMoreExactHeaderMatch() throws Exception { + WireMockRestServiceServer.with(this.restTemplate) // + .baseUrl("http://example.org") // order matters... + .stubs("classpath:/mappings/header-matches.json", + "classpath:/mappings/header-matches-precise.json") + .ignoreExpectOrder(true).build(); assertThat(this.restTemplate .exchange(RequestEntity.post(new URI("http://example.org/poster")) - .accept(MediaType.valueOf("application/v.bar")).build(), String.class) - .getBody()).isEqualTo("Bar World"); + .accept(MediaType.valueOf("application/v.bar")) + .header("X-Precise", "true").build(), String.class) + .getBody()).isEqualTo("Precise World"); } @Test diff --git a/spring-cloud-contract-wiremock/src/test/resources/mappings/header-matches-precise.json b/spring-cloud-contract-wiremock/src/test/resources/mappings/header-matches-precise.json new file mode 100644 index 0000000000..3ffd54ed24 --- /dev/null +++ b/spring-cloud-contract-wiremock/src/test/resources/mappings/header-matches-precise.json @@ -0,0 +1,11 @@ +{ + "request" : { + "urlPath" : "/poster", + "method" : "POST", + "headers" : { "Accept" : { "matches" : ".*/v.bar" }, "X-Precise" : { "equalTo" : "true" }} + }, + "response" : { + "status" : 200, + "body" : "Precise World" + } +} \ No newline at end of file