package org.zalando.riptide;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import lombok.Value;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.test.web.client.MockRestServiceServer;
import java.net.URI;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static java.util.EnumSet.allOf;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.zalando.riptide.Route.call;
import static org.zalando.riptide.Route.pass;
import static org.zalando.riptide.UrlResolution.APPEND;
import static org.zalando.riptide.UrlResolution.RFC;
@RunWith(Parameterized.class)
public class RequestUriTest {
private final String baseUrl;
private final UrlResolution resolution;
private final HttpMethod method;
private final String uri;
private final Result result;
public RequestUriTest(final TestCase test, final HttpMethod method) {
this.baseUrl = test.getBaseUrl();
this.resolution = test.getResolution();
this.method = method;
this.uri = test.getUri();
this.result = test.getResult();
}
@Parameters(name = "{1} {0}")
@SuppressWarnings("unchecked")
public static Collection<Object[]> data() {
return Sets.cartesianProduct(getCases(), allOf(HttpMethod.class)).stream()
.map(Collection::toArray)
.collect(toList());
}
private static ImmutableSet<TestCase> getCases() {
return ImmutableSet.of(
new TestCase("https://example.com", RFC, null, uri("https://example.com")),
new TestCase("https://example.com/", RFC, null, uri("https://example.com/")),
new TestCase("https://example.com", RFC, "", uri("https://example.com")),
new TestCase("https://example.com/", RFC, "", uri("https://example.com/")),
new TestCase("https://example.com", RFC, "/", uri("https://example.com/")),
new TestCase("https://example.com/", RFC, "/", uri("https://example.com/")),
new TestCase("https://example.com", RFC, "https://example.org/foo", uri("https://example.org/foo")),
new TestCase("https://example.com", RFC, "/foo/bar", uri("https://example.com/foo/bar")),
new TestCase("https://example.com", RFC, "foo/bar", uri("https://example.com/foo/bar")),
new TestCase("https://example.com/api", RFC, "/foo/bar", uri("https://example.com/foo/bar")),
new TestCase("https://example.com/api", RFC, "foo/bar", uri("https://example.com/foo/bar")),
new TestCase("https://example.com/api/", RFC, "/foo/bar", uri("https://example.com/foo/bar")),
new TestCase("https://example.com/api/", RFC, "foo/bar", uri("https://example.com/api/foo/bar")),
new TestCase(null, RFC, "https://example.com/foo", uri("https://example.com/foo")),
new TestCase("/foo", RFC, "/", error("Base URL is not absolute")),
new TestCase(null, RFC, null, error("Either Base URL or absolute Request URI is required")),
new TestCase(null, RFC, "/foo", error("Request URI is not absolute")),
new TestCase(null, RFC, "foo", error("Request URI is not absolute")),
new TestCase("https://example.com", APPEND, null, uri("https://example.com")),
new TestCase("https://example.com/", APPEND, null, uri("https://example.com/")),
new TestCase("https://example.com", APPEND, "", uri("https://example.com")),
new TestCase("https://example.com/", APPEND, "", uri("https://example.com/")),
new TestCase("https://example.com", APPEND, "/", uri("https://example.com/")),
new TestCase("https://example.com/", APPEND, "/", uri("https://example.com/")),
new TestCase("https://example.com", APPEND, "https://example.org/foo", uri("https://example.org/foo")),
new TestCase("https://example.com", APPEND, "/foo/bar", uri("https://example.com/foo/bar")),
new TestCase("https://example.com", APPEND, "foo/bar", uri("https://example.com/foo/bar")),
new TestCase("https://example.com/api", APPEND, "/foo/bar", uri("https://example.com/api/foo/bar")),
new TestCase("https://example.com/api", APPEND, "foo/bar", uri("https://example.com/api/foo/bar")),
new TestCase("https://example.com/api/", APPEND, "/foo/bar", uri("https://example.com/api/foo/bar")),
new TestCase("https://example.com/api/", APPEND, "foo/bar", uri("https://example.com/api/foo/bar")),
new TestCase(null, APPEND, "https://example.com/foo", uri("https://example.com/foo")),
new TestCase("/foo", APPEND, "/", error("Base URL is not absolute")),
new TestCase(null, APPEND, null, error("Either Base URL or absolute Request URI is required")),
new TestCase(null, APPEND, "/foo", error("Request URI is not absolute")),
new TestCase(null, APPEND, "foo", error("Request URI is not absolute")));
}
@Value
private static final class TestCase {
String baseUrl;
UrlResolution resolution;
String uri;
Result result;
@Override
public String toString() {
return MoreObjects.toStringHelper("")
.addValue(baseUrl)
.addValue(resolution)
.addValue(uri)
.addValue(result)
.toString();
}
}
private interface Result {
void execute(final String baseUrl, final UrlResolution resolution, final String uri, final HttpMethod method, final Consumer<Rest> tester);
}
@Value
private static final class RequestUri implements Result {
String requestUri;
@Override
public void execute(final String baseUrl, final UrlResolution resolution, final String uri,
final HttpMethod method, final Consumer<Rest> tester) {
final MockSetup setup = new MockSetup(baseUrl);
final Rest unit = setup.getRestBuilder().urlResolution(resolution).build();
final MockRestServiceServer server = setup.getServer();
server.expect(requestTo(requestUri))
.andExpect(method(method))
.andRespond(withSuccess());
tester.accept(unit);
server.verify();
}
@Override
public String toString() {
return requestUri;
}
}
private static Result uri(final String uri) {
return new RequestUri(uri);
}
@Value
private static final class Failure implements Result {
String message;
@Override
public void execute(final String baseUrl, final UrlResolution resolution, final String uri,
final HttpMethod method, final Consumer<Rest> tester) {
try {
final Rest unit = Rest.builder()
.baseUrl(baseUrl)
.urlResolution(resolution)
.requestFactory(new SimpleClientHttpRequestFactory())
.build();
tester.accept(unit);
fail("Expected exception");
} catch (final Exception e) {
assertThat(e, is(instanceOf(IllegalArgumentException.class)));
assertThat(e.getMessage(), is(message));
}
}
@Override
public String toString() {
return "Exception";
}
}
private static Result error(final String message) {
return new Failure(message);
}
@Test
public void shouldIgnoreEmptyUri() {
assumeThat(uri, is(nullValue()));
result.execute(baseUrl, resolution, uri, method, rest ->
rest.execute(method).call(call(pass())));
}
@Test
public void shouldResolveUri() {
assumeThat(uri, is(notNullValue()));
result.execute(baseUrl, resolution, uri, method, rest ->
rest.execute(method, URI.create(uri)).call(call(pass())));
}
@Test
public void shouldResolveUriTemplate() {
assumeThat(uri, is(notNullValue()));
result.execute(baseUrl, resolution, uri, method, rest ->
rest.execute(method, uri).call(call(pass())));
}
/**
* Used to re-generate the URI example table in the {@code README.md}.
*
* @param args ignored
*/
public static void main(final String[] args) {
getCases().stream()
.map(test -> {
final Stream<String> row = Stream.of(
test.getBaseUrl(),
test.getResolution().name(),
test.getUri(),
test.getResult().toString());
return row
.map(Objects::toString)
.map(cell -> {
switch (cell) {
case "":
return "(empty)";
case "Exception":
return cell;
default:
return "`" + cell + "`";
}
}).collect(joining("|", "|", "|"));
})
.forEach(System.out::println);
}
}