Added queryparams support for custom mode

This commit is contained in:
Marcin Grzejszczak
2020-09-15 14:21:22 +02:00
committed by Marcin Grzejszczak
parent 5e5782f736
commit bd4c01a5d8
6 changed files with 161 additions and 88 deletions

View File

@@ -41,6 +41,7 @@ class CustomModeGiven implements Given, BodyMethodVisitor, CustomModeAcceptor {
new CustomModeRequestGiven(blockBuilder, generatedClassMetaData)));
this.bodyGivens.addAll(
Arrays.asList(new CustomModeMethodWithUrlGiven(blockBuilder, bodyParser),
new CustomModeQueryParamsGiven(blockBuilder, bodyParser),
new CustomModeSchemeProtocolGiven(blockBuilder),
new CustomModeHeadersGiven(blockBuilder),
new CustomModeCookiesGiven(blockBuilder),

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.builder;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.cloud.contract.spec.internal.MatchingStrategy;
import org.springframework.cloud.contract.spec.internal.QueryParameter;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.spec.internal.Url;
import org.springframework.cloud.contract.verifier.file.SingleContractMetadata;
import org.springframework.cloud.contract.verifier.util.MapConverter;
class CustomModeQueryParamsGiven
implements Given, CustomModeAcceptor, QueryParamsResolver {
private final BlockBuilder blockBuilder;
private final BodyParser bodyParser;
CustomModeQueryParamsGiven(BlockBuilder blockBuilder, BodyParser bodyParser) {
this.blockBuilder = blockBuilder;
this.bodyParser = bodyParser;
}
@Override
public MethodVisitor<Given> apply(SingleContractMetadata metadata) {
Request request = metadata.getContract().getRequest();
Url url = getUrl(request);
addQueryParameters(url);
return this;
}
private Url getUrl(Request request) {
if (request.getUrl() != null) {
return request.getUrl();
}
if (request.getUrlPath() != null) {
return request.getUrlPath();
}
throw new IllegalStateException("URL is not set!");
}
private void addQueryParameters(Url buildUrl) {
List<QueryParameter> queryParameters = buildUrl.getQueryParameters()
.getParameters().stream().filter(this::allowedQueryParameter)
.collect(Collectors.toList());
Iterator<QueryParameter> iterator = queryParameters.iterator();
while (iterator.hasNext()) {
QueryParameter parameter = iterator.next();
String text = addQueryParameter(parameter);
if (iterator.hasNext()) {
this.blockBuilder.addLine(text);
}
else {
this.blockBuilder.addIndented(text);
}
}
}
private boolean allowedQueryParameter(Object o) {
if (o instanceof QueryParameter) {
return allowedQueryParameter(((QueryParameter) o).getServerValue());
}
else if (o instanceof MatchingStrategy) {
return !MatchingStrategy.Type.ABSENT.equals(((MatchingStrategy) o).getType());
}
return true;
}
private String addQueryParameter(QueryParameter queryParam) {
return "." + "queryParam(" + this.bodyParser.quotedLongText(queryParam.getName())
+ "," + this.bodyParser.quotedLongText(resolveParamValue(
MapConverter.getTestSideValuesForNonBody(queryParam)))
+ ")";
}
@Override
public boolean accept(SingleContractMetadata metadata) {
return acceptType(metadata) && metadata.getContract().getRequest() != null
&& getUrl(metadata.getContract().getRequest())
.getQueryParameters() != null;
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.contract.verifier.builder;
import org.springframework.cloud.contract.spec.internal.Request;
import org.springframework.cloud.contract.spec.internal.Url;
import org.springframework.cloud.contract.verifier.file.SingleContractMetadata;
class CustomQueryParamsGiven implements Given, CustomModeAcceptor, QueryParamsResolver {
@Override
public MethodVisitor<Given> apply(SingleContractMetadata metadata) {
throw new UnsupportedOperationException("Query params are not supported");
}
private Url getUrl(Request request) {
if (request.getUrl() != null) {
return request.getUrl();
}
if (request.getUrlPath() != null) {
return request.getUrlPath();
}
throw new IllegalStateException("URL is not set!");
}
@Override
public boolean accept(SingleContractMetadata metadata) {
Request request = metadata.getContract().getRequest();
Url url = getUrl(request);
return url.getQueryParameters() != null;
}
}

View File

@@ -17,11 +17,11 @@
package org.springframework.cloud.contract.verifier.http;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -57,9 +57,7 @@ public class OkHttpHttpVerifier implements HttpVerifier {
public Response exchange(Request request) {
String requestContentType = request.contentType();
OkHttpClient client = new OkHttpClient.Builder()
.protocols(Collections
.singletonList(toProtocol(request.protocol().toString())))
.build();
.protocols(toProtocol(request.protocol().toString())).build();
Map<String, String> headers = stringTyped(request.headers());
if (!request.cookies().isEmpty()) {
headers.put("Set-Cookie",
@@ -67,10 +65,7 @@ public class OkHttpHttpVerifier implements HttpVerifier {
.map(e -> e.getKey() + "=" + e.getValue().toString())
.collect(Collectors.joining(";")));
}
okhttp3.Request req = new okhttp3.Request.Builder()
.url(request.scheme().name().toLowerCase() + ":" + this.hostAndPort
+ (request.path().startsWith("/") ? request.path()
: "/" + request.path()))
okhttp3.Request req = new okhttp3.Request.Builder().url(url(request))
.method(request.method().name(), requestBody(request, requestContentType))
.headers(Headers.of(headers)).build();
try (okhttp3.Response res = client.newCall(req).execute()) {
@@ -81,9 +76,33 @@ public class OkHttpHttpVerifier implements HttpVerifier {
}
}
private Protocol toProtocol(String string) {
private String url(Request request) {
String url = request.scheme().name().toLowerCase() + ":" + this.hostAndPort
+ (request.path().startsWith("/") ? request.path()
: "/" + request.path());
if (!request.queryParams().isEmpty()) {
return url + "?"
+ request.queryParams().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&"));
}
return url;
}
private List<Protocol> toProtocol(String string) {
try {
return Protocol.get(string);
Protocol protocol = Protocol.get(string);
switch (protocol) {
case HTTP_1_0:
case HTTP_2:
case QUIC:
return Arrays.asList(protocol, Protocol.HTTP_1_1);
case HTTP_1_1:
return Collections.singletonList(Protocol.HTTP_1_1);
case H2_PRIOR_KNOWLEDGE:
return Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE);
}
return Collections.emptyList();
}
catch (IOException e) {
throw new IllegalStateException(e);
@@ -114,34 +133,6 @@ public class OkHttpHttpVerifier implements HttpVerifier {
return RequestBody.create(MediaType.parse(requestContentType), bodyArray);
}
private boolean isGrpc(String contentType) {
return contentType.startsWith("application/grpc");
}
// the encoded body should already have proper byte values
// TODO: This should be removed?
private byte[] grpcRequestBody(Request request) {
byte[] bodyArray; // TODO: Add compression support
byte compressedFlag = 0;
byte[] message = request.body().asByteArray();
byte[] messageLength = ByteBuffer.allocate(4).putInt(message.length).array();
bodyArray = ByteBuffer.allocate(1 + messageLength.length + message.length)
.put(compressedFlag).put(messageLength).put(message).array();
return bodyArray;
}
// TODO: This should be removed?
private byte[] grpcResponseBody(byte[] responseBody) {
// 5 value = 4th index
// 1 for compression, 4 for message size
int actualPayloadSize = responseBody.length - 5;
byte[] destination = new byte[actualPayloadSize];
System.arraycopy(responseBody, 5, destination, 0, actualPayloadSize);
responseBody = destination;
return responseBody;
}
@Nullable
private byte[] responseBody(okhttp3.Response res) throws IOException {
return res.body() != null ? res.body().bytes() : null;
}

View File

@@ -18,6 +18,8 @@ package org.springframework.cloud.contract.verifier.http;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.cloud.contract.spec.internal.HttpMethods;
@@ -40,6 +42,8 @@ public class Request {
private final String path;
private final List<AbstractMap.SimpleEntry<String, String>> queryParameters;
private final Body body;
private final Map<String, Object> headers;
@@ -48,12 +52,14 @@ public class Request {
Request(ContractVerifierHttpMetadata.Protocol protocol,
ContractVerifierHttpMetadata.Scheme scheme, HttpMethods.HttpMethod method,
String path, Body body, Map<String, Object> headers,
Map<String, Object> cookies) {
String path, List<AbstractMap.SimpleEntry<String, String>> queryParameters,
Body body, Map<String, Object> headers, Map<String, Object> cookies) {
this.protocol = protocol;
this.scheme = scheme;
this.method = method;
this.path = path;
this.queryParameters = queryParameters == null ? new LinkedList<>()
: queryParameters;
this.body = body;
this.headers = headers == null ? new HashMap<>() : headers;
this.cookies = cookies == null ? new HashMap<>() : cookies;
@@ -121,6 +127,13 @@ public class Request {
return this.cookies;
}
/**
* @return query parameters
*/
public List<AbstractMap.SimpleEntry<String, String>> queryParams() {
return this.queryParameters;
}
/**
* Factory method to pick the HTTP method.
* @return method builder
@@ -217,6 +230,8 @@ public class Request {
final String path;
List<AbstractMap.SimpleEntry<String, String>> queryParameters = new LinkedList<>();
ContractVerifierHttpMetadata.Protocol protocol = ContractVerifierHttpMetadata.Protocol.HTTP_1_1;
ContractVerifierHttpMetadata.Scheme scheme = ContractVerifierHttpMetadata.Scheme.HTTP;
@@ -259,6 +274,16 @@ public class Request {
return this;
}
/**
* @param name - query parameter name
* @param value - query parameter value
* @return builder
*/
public Request.Builder queryParam(String name, String value) {
this.queryParameters.add(new AbstractMap.SimpleEntry<>(name, value));
return this;
}
/**
* @param headers HTTP headers
* @return builder
@@ -292,7 +317,7 @@ public class Request {
*/
public Request build() {
return new Request(this.protocol, this.scheme, this.method, this.path,
this.body, this.headers, this.cookies);
this.queryParameters, this.body, this.headers, this.cookies);
}
}

View File

@@ -553,6 +553,7 @@ response:
"spock" | { properties.testFramework = TestFramework.SPOCK }
"mockmvc" | { properties.testMode = TestMode.MOCKMVC }
"webclient" | { properties.testMode = TestMode.WEBTESTCLIENT }
"custom" | { properties.testMode = TestMode.CUSTOM }
}
@Issue('#169')
@@ -627,6 +628,7 @@ response:
"spock" | { properties.testFramework = TestFramework.SPOCK }
"mockmvc" | { properties.testMode = TestMode.MOCKMVC }
"webclient" | { properties.testMode = TestMode.WEBTESTCLIENT }
"custom" | { properties.testMode = TestMode.CUSTOM }
}
def 'should generate test for empty body with #methodBuilderName'() {