Skip to content

Commit

Permalink
Added support for multi-valued cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
tomakehurst committed Oct 18, 2017
1 parent 336fc23 commit 827e7cc
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 37 deletions.
4 changes: 3 additions & 1 deletion docs-v2/_docs/response-templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ The model of the request is supplied to the header and body templates. The follo

`request.headers.<key>.[<n>]`- nth value of a header (zero indexed) e.g. `request.headers.ManyThings.[1]`

`request.cookies.<key>` - Value of a request cookie e.g. `request.cookies.JSESSIONID`
`request.cookies.<key>` - First value of a request cookie e.g. `request.cookies.JSESSIONID`

`request.cookies.<key>.[<n>]` - nth value of a request cookie e.g. `request.cookies.JSESSIONID.[2]`

`request.body` - Request body text (avoid for non-text bodies)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.tomakehurst.wiremock.extension.responsetemplating;
package com.github.tomakehurst.wiremock.common;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static java.util.Arrays.asList;

@JsonSerialize(using = ListOrSingleSerialiser.class)
@JsonDeserialize(using = ListOrStringDeserialiser.class)
public class ListOrSingle<T> extends ArrayList<T> {

public ListOrSingle(Collection<? extends T> c) {
Expand All @@ -43,4 +48,12 @@ public static <T> ListOrSingle<T> of(T... items) {
public static <T> ListOrSingle<T> of(List<T> items) {
return new ListOrSingle<>(items);
}

public T first() {
return get(0);
}

public boolean isSingle() {
return size() == 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.tomakehurst.wiremock.common;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.TypeFactory;

import java.io.IOException;
import java.util.List;

public class ListOrSingleSerialiser extends JsonSerializer<ListOrSingle<Object>> {

@Override
public void serialize(ListOrSingle<Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
if (value.isEmpty()) {
gen.writeStartArray();
gen.writeEndArray();
return;
}

Object firstValue = value.first();
if (value.isSingle()) {
JsonSerializer<Object> serializer = serializers.findValueSerializer(firstValue.getClass());
serializer.serialize(firstValue, gen, serializers);
} else {
CollectionType type = TypeFactory.defaultInstance().constructCollectionType(List.class, firstValue.getClass());
JsonSerializer<Object> serializer = serializers.findValueSerializer(type);
serializer.serialize(value, gen, serializers);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.github.tomakehurst.wiremock.common;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import static com.google.common.collect.Lists.newArrayList;

public class ListOrStringDeserialiser<T> extends JsonDeserializer<ListOrSingle<T>> {

@Override
@SuppressWarnings("unchecked")
public ListOrSingle<T> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode rootNode = parser.readValueAsTree();
if (rootNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) rootNode;
List<T> items = newArrayList();
for (Iterator<JsonNode> i = arrayNode.elements(); i.hasNext();) {
JsonNode node = i.next();
Object value = getValue(node);
items.add((T) value);
}

return new ListOrSingle<>(items);
}

return new ListOrSingle<>((T) getValue(rootNode));
}

private static Object getValue(JsonNode node) {
return node.isTextual() ? node.textValue() :
node.isNumber() ? node.numberValue() :
node.isBoolean() ? node.booleanValue() : node.textValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.github.tomakehurst.wiremock.extension.responsetemplating;

import com.github.tomakehurst.wiremock.common.ListOrSingle;
import com.github.tomakehurst.wiremock.common.Urls;
import com.github.tomakehurst.wiremock.http.Cookie;
import com.github.tomakehurst.wiremock.http.MultiValue;
Expand Down Expand Up @@ -57,8 +58,8 @@ public ListOrSingle<String> apply(String input) {
});
Map<String, ListOrSingle<String>> adaptedCookies = Maps.transformValues(request.getCookies(), new Function<Cookie, ListOrSingle<String>>() {
@Override
public ListOrSingle<String> apply(Cookie input) {
return ListOrSingle.of(input.getValue());
public ListOrSingle<String> apply(Cookie cookie) {
return ListOrSingle.of(cookie.getValues());
}
});

Expand Down
44 changes: 33 additions & 11 deletions src/main/java/com/github/tomakehurst/wiremock/http/Cookie.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,63 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonValue;
import com.github.tomakehurst.wiremock.common.ListOrSingle;
import com.google.common.base.Joiner;

public class Cookie {
import java.util.Collections;
import java.util.List;

private String value;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;

public class Cookie extends MultiValue {

@JsonCreator
public static Cookie cookie(ListOrSingle<String> values) {
return new Cookie(null, values);
}

public static Cookie cookie(String value) {
return new Cookie(value);
return new Cookie(null, value);
}

public static Cookie absent() {
return new Cookie(null);
return new Cookie(null, Collections.<String>emptyList());
}

public Cookie(String value) {
this.value = value;
super(null, singletonList(value));
}

@JsonIgnore
public boolean isPresent() {
return value != null;
public Cookie(List<String> values) {
this(null, values);
}

public Cookie(String name, String... value) {
super(name, asList(value));
}

public Cookie(String name, List<String> values) {
super(name, values);
}

@JsonIgnore
public boolean isAbsent() {
return value == null;
return !isPresent();
}

@JsonValue
public ListOrSingle<String> getValues() {
return new ListOrSingle<>(isPresent() ? values() : Collections.<String>emptyList());
}

@JsonIgnore
public String getValue() {
return value;
return firstValue();
}

@Override
public String toString() {
return isAbsent() ? "(absent)" : value;
return isAbsent() ? "(absent)" : Joiner.on("; ").join(getValues());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
import com.github.tomakehurst.wiremock.http.RequestMethod;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;

import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
import static com.github.tomakehurst.wiremock.matching.RequestMatcherExtension.NEVER;
Expand Down Expand Up @@ -161,11 +161,25 @@ private MatchResult allCookiesMatch(final Request request) {
return MatchResult.aggregate(
from(cookies.entrySet())
.transform(new Function<Map.Entry<String, StringValuePattern>, MatchResult>() {
public MatchResult apply(Map.Entry<String, StringValuePattern> cookiePattern) {
Cookie cookie =
firstNonNull(request.getCookies().get(cookiePattern.getKey()), Cookie.absent());

return cookiePattern.getValue().match(cookie.getValue());
public MatchResult apply(final Map.Entry<String, StringValuePattern> cookiePattern) {
Cookie cookie = request.getCookies().get(cookiePattern.getKey());
if (cookie == null) {
return cookiePattern.getValue().nullSafeIsAbsent() ?
MatchResult.exactMatch() :
MatchResult.noMatch();
}

return from(cookie.getValues()).transform(new Function<String, MatchResult>() {
@Override
public MatchResult apply(String cookieValue) {
return cookiePattern.getValue().match(cookieValue);
}
}).toSortedList(new Comparator<MatchResult>() {
@Override
public int compare(MatchResult o1, MatchResult o2) {
return o2.compareTo(o1);
}
}).get(0);
}
}).toList()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@

import com.github.tomakehurst.wiremock.common.Gzip;
import com.github.tomakehurst.wiremock.http.*;
import com.github.tomakehurst.wiremock.http.Cookie;
import com.github.tomakehurst.wiremock.jetty9.JettyUtils;
import com.google.common.base.*;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.*;

Expand Down Expand Up @@ -197,18 +198,19 @@ public Set<String> getAllHeaderKeys() {

@Override
public Map<String, Cookie> getCookies() {
ImmutableMap.Builder<String, Cookie> builder = ImmutableMap.builder();
ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();

for (javax.servlet.http.Cookie cookie :
firstNonNull(request.getCookies(), new javax.servlet.http.Cookie[0])) {
builder.put(cookie.getName(), convertCookie(cookie));
javax.servlet.http.Cookie[] cookies = firstNonNull(request.getCookies(), new javax.servlet.http.Cookie[0]);
for (javax.servlet.http.Cookie cookie: cookies) {
builder.put(cookie.getName(), cookie.getValue());
}

return builder.build();
}

private static Cookie convertCookie(javax.servlet.http.Cookie servletCookie) {
return new Cookie(servletCookie.getValue());
return Maps.transformValues(builder.build().asMap(), new Function<Collection<String>, Cookie>() {
@Override
public Cookie apply(Collection<String> input) {
return new Cookie(null, ImmutableList.copyOf(input));
}
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,19 @@ public void revealsCookiesInLoggedRequests() {
assertThat(requests.size(), is(1));
assertThat(requests.get(0).getCookies().keySet(), hasItem("my_other_cookie"));
}

@Test
public void matchesWhenRequiredCookieSentAsDuplicate() {
stubFor(get(urlEqualTo("/duplicate/cookie"))
.withCookie("my_cookie", containing("mycookievalue"))
.withCookie("my_other_cookie", equalTo("value-2"))
.willReturn(aResponse().withStatus(200)));

WireMockResponse response =
testClient.get("/duplicate/cookie",
withHeader(COOKIE, "my_cookie=xxx-mycookievalue-xxx; my_other_cookie=value-1; my_other_cookie=value-2"));

assertThat(response.statusCode(), is(200));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ public void cookies() {
));
}

@Test
public void multiValueCookies() {
ResponseDefinition transformedResponseDef = transform(mockRequest()
.url("/things")
.cookie("multi", "one", "two"),
aResponse().withBody(
"{{request.cookies.multi}}, {{request.cookies.multi.[0]}}, {{request.cookies.multi.[1]}}"
)
);

assertThat(transformedResponseDef.getBody(), is(
"one, one, two"
));
}

@Test
public void urlPath() {
ResponseDefinition transformedResponseDef = transform(mockRequest()
Expand Down

0 comments on commit 827e7cc

Please sign in to comment.