/***
* Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
* All rights reserved.
*
* 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
*
* http://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 br.com.caelum.vraptor.http.route;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.junit.Before;
import org.junit.Test;
import br.com.caelum.vraptor.Controller;
import br.com.caelum.vraptor.Get;
import br.com.caelum.vraptor.Path;
import br.com.caelum.vraptor.cache.CacheStore;
import br.com.caelum.vraptor.cache.DefaultCacheStore;
import br.com.caelum.vraptor.controller.BeanClass;
import br.com.caelum.vraptor.controller.ControllerMethod;
import br.com.caelum.vraptor.controller.DefaultBeanClass;
import br.com.caelum.vraptor.controller.DefaultControllerMethod;
import br.com.caelum.vraptor.controller.HttpMethod;
import br.com.caelum.vraptor.core.Converters;
import br.com.caelum.vraptor.http.EncodingHandler;
import br.com.caelum.vraptor.http.ParameterNameProvider;
import br.com.caelum.vraptor.http.ParanamerNameProvider;
import br.com.caelum.vraptor.http.VRaptorRequest;
import br.com.caelum.vraptor.interceptor.VRaptorMatchers;
import br.com.caelum.vraptor.proxy.JavassistProxifier;
import br.com.caelum.vraptor.proxy.Proxifier;
/**
* Those are more likely to be acceptance than unit tests.
* @author guilherme silveira
*/
public class DefaultRouterTest {
private Proxifier proxifier;
private DefaultRouter router;
private VRaptorRequest request;
private ControllerMethod method;
private Converters converters;
private ParameterNameProvider nameProvider;
private EncodingHandler encodingHandler;
private CacheStore<Invocation,Route> cache;
@Before
public void setup() {
this.request = new VRaptorRequest(mock(HttpServletRequest.class));
this.proxifier = new JavassistProxifier();
this.method = mock(ControllerMethod.class);
this.converters = mock(Converters.class);
this.encodingHandler = mock(EncodingHandler.class);
this.nameProvider = new ParanamerNameProvider();
this.cache = new DefaultCacheStore<>();
router = new DefaultRouter(proxifier, new NoTypeFinder(), converters, nameProvider, new JavaEvaluator(), encodingHandler,cache);
}
@Test
public void shouldThrowControllerNotFoundExceptionWhenNoRoutesMatchTheURI() throws Exception {
Route route = mock(Route.class);
when(route.canHandle(anyString())).thenReturn(false);
when(route.getControllerMethod()).thenReturn(anyControllerMethod());
router.add(route);
try {
router.parse("any uri", HttpMethod.DELETE, request);
fail("ControllerNotFoundException is expected");
} catch (ControllerNotFoundException e) {
}
}
@Test
public void shouldThrowMethodNotAllowedExceptionWhenNoRoutesMatchTheURIWithGivenHttpMethod() throws Exception {
Route route = mock(Route.class);
when(route.canHandle(anyString())).thenReturn(true);
when(route.getControllerMethod()).thenReturn(anyControllerMethod());
when(route.allowedMethods()).thenReturn(EnumSet.of(HttpMethod.GET));
router.add(route);
try {
router.parse("any uri", HttpMethod.DELETE, request);
fail("MethodNotAllowedException is expected");
} catch (MethodNotAllowedException e) {
assertThat(e.getAllowedMethods(), is((Set<HttpMethod>)EnumSet.of(HttpMethod.GET)));
}
}
private DefaultControllerMethod anyControllerMethod() throws NoSuchMethodException {
return new DefaultControllerMethod(new DefaultBeanClass(MyController.class), MyController.class.getMethod("customizedPath"));
}
@Test
public void shouldObeyPriorityOfRoutes() throws Exception {
Route first = mock(Route.class);
when(first.getControllerMethod()).thenReturn(anyControllerMethod());
Route second = mock(Route.class);
when(second.getControllerMethod()).thenReturn(anyControllerMethod());
ControllerMethod method2 = second.controllerMethod(request, "second");
router.add(second);
router.add(first);
when(first.getPriority()).thenReturn(Path.HIGH);
when(second.getPriority()).thenReturn(Path.LOW);
EnumSet<HttpMethod> get = EnumSet.of(HttpMethod.GET);
when(first.allowedMethods()).thenReturn(get);
when(second.allowedMethods()).thenReturn(get);
when(first.canHandle(anyString())).thenReturn(false);
when(second.canHandle(anyString())).thenReturn(true);
ControllerMethod found = router.parse("anything", HttpMethod.GET, request);
assertThat(found, is(method2));
}
@Test
public void acceptsASingleMappingRule() throws SecurityException, NoSuchMethodException {
Route route = mock(Route.class);
when(route.getControllerMethod()).thenReturn(anyControllerMethod());
when(route.canHandle("/clients/add")).thenReturn(true);
when(route.allowedMethods()).thenReturn(EnumSet.of(HttpMethod.POST));
when(route.controllerMethod(request, "/clients/add")).thenReturn(method);
router.add(route);
ControllerMethod found = router.parse("/clients/add", HttpMethod.POST, request);
assertThat(found, is(equalTo(method)));
verify(route, atLeastOnce()).getPriority();
}
@Test
public void passesTheWebMethod() throws SecurityException, NoSuchMethodException {
HttpMethod delete = HttpMethod.DELETE;
Route route = mock(Route.class);
when(route.getControllerMethod()).thenReturn(anyControllerMethod());
when(route.canHandle("/clients/add")).thenReturn(true);
when(route.allowedMethods()).thenReturn(EnumSet.of(delete));
when(route.controllerMethod(request, "/clients/add")).thenReturn(method);
router.add(route);
ControllerMethod found = router.parse("/clients/add", delete, request);
assertThat(found, is(equalTo(method)));
verify(route, atLeastOnce()).getPriority();
}
@Test
public void usesTheFirstRegisteredRuleMatchingThePattern() throws SecurityException, NoSuchMethodException {
Route route = mock(Route.class);
Route second = mock(Route.class, "second");
when(route.getControllerMethod()).thenReturn(anyControllerMethod());
when(second.getControllerMethod()).thenReturn(anyControllerMethod());
when(route.canHandle("/clients/add")).thenReturn(true);
when(second.canHandle("/clients/add")).thenReturn(true);
EnumSet<HttpMethod> all = EnumSet.allOf(HttpMethod.class);
when(route.allowedMethods()).thenReturn(all);
when(second.allowedMethods()).thenReturn(all);
when(route.controllerMethod(request, "/clients/add")).thenReturn(method);
when(route.getPriority()).thenReturn(Path.HIGHEST);
when(second.getPriority()).thenReturn(Path.LOWEST);
router.add(route);
router.add(second);
ControllerMethod found = router.parse("/clients/add", HttpMethod.POST, request);
assertThat(found, is(equalTo(method)));
}
@Test
public void throwsExceptionIfMoreThanOneUriMatchesWithSamePriority() throws NoSuchMethodException {
Route route = mock(Route.class);
Route second = mock(Route.class, "second");
when(route.getControllerMethod()).thenReturn(anyControllerMethod());
when(second.getControllerMethod()).thenReturn(anyControllerMethod());
when(route.canHandle("/clients/add")).thenReturn(true);
when(second.canHandle("/clients/add")).thenReturn(true);
EnumSet<HttpMethod> all = EnumSet.allOf(HttpMethod.class);
when(route.allowedMethods()).thenReturn(all);
when(second.allowedMethods()).thenReturn(all);
when(route.getPriority()).thenReturn(Path.DEFAULT);
when(second.getPriority()).thenReturn(Path.DEFAULT);
router.add(route);
router.add(second);
try {
router.parse("/clients/add", HttpMethod.POST, request);
fail("IllegalStateException expected");
} catch (IllegalStateException e) {
}
}
@Test
public void acceptsAnHttpMethodLimitedMappingRule() throws NoSuchMethodException {
new Rules(router) {
@Override
public void routes() {
routeFor("/clients/add").with(HttpMethod.POST).is(SomeController.class).add(null);
}
};
assertThat(router.parse("/clients/add", HttpMethod.POST, request), is(VRaptorMatchers.controllerMethod(method(
"add", Dog.class))));
}
private Method method(String methodName, Class<?>... params) throws SecurityException, NoSuchMethodException {
return SomeController.class.getDeclaredMethod(methodName, params);
}
@Test
public void acceptsAnHttpMethodLimitedMappingRuleWithBothMethods() throws NoSuchMethodException {
new Rules(router) {
@Override
public void routes() {
routeFor("/clients/add").with(HttpMethod.POST).with(HttpMethod.GET).is(SomeController.class).add(null);
}
};
assertThat(router.parse("/clients/add", HttpMethod.POST, request), is(VRaptorMatchers.controllerMethod(method(
"add", Dog.class))));
assertThat(router.parse("/clients/add", HttpMethod.GET, request), is(VRaptorMatchers.controllerMethod(method(
"add", Dog.class))));
}
@Test
public void shouldReturnTheFirstRouteFound() throws Exception {
Method method = MyController.class.getDeclaredMethod("listDogs", Integer.class);
registerRulesFor(MyController.class);
assertEquals("/dogs/1", router.urlFor(MyController.class, method, new Object[] { "1" }));
}
class Dog {
private Long id;
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
}
@Controller
public static class SomeController {
public void add(Dog object) {
}
public void unknownMethod() {
}
public void list() {
}
public void show(Dog dog) {
}
}
@Controller
public static class MyController {
public void notAnnotated() {
}
@Path("/myPath")
public void customizedPath() {
}
@Path("/*/customPath")
public void starPath() {
}
@Get({ "/dogs/{page}", "/dogs" })
public void listDogs(Integer page) {
}
}
@Controller
class InheritanceExample extends MyController {
}
@Test
public void usesAsteriskBothWays() throws NoSuchMethodException {
registerRulesFor(MyController.class);
final Method method = MyController.class.getMethod("starPath");
String url = router.urlFor(MyController.class, method, new Object[] {});
assertThat(router.parse(url, HttpMethod.POST, null).getMethod(), is(equalTo(method)));
}
@Test
public void shouldCacheInvocationsAfterFirstCall() throws NoSuchMethodException {
registerRulesFor(MyController.class);
final Method method = MyController.class.getMethod("starPath");
router.urlFor(MyController.class, method, new Object[] {});
String url = router.urlFor(MyController.class, method, new Object[] {});
assertThat(router.parse(url, HttpMethod.POST, null).getMethod(), is(equalTo(method)));
}
private void registerRulesFor(Class<?> type) {
RoutesParser parser = new PathAnnotationRoutesParser(router);
BeanClass controllerClass = new DefaultBeanClass(type);
List<Route> rules = parser.rulesFor(controllerClass);
for (Route route : rules) {
router.add(route);
}
}
@Test
public void canTranslateAInheritedControllerBothWays() throws NoSuchMethodException {
registerRulesFor(MyController.class);
registerRulesFor(InheritanceExample.class);
final Method method = MyController.class.getMethod("notAnnotated");
String url = router.urlFor(InheritanceExample.class, method, new Object[] {});
assertThat(router.parse(url, HttpMethod.POST, null).getMethod(), is(equalTo(method)));
}
@Test
public void canTranslateAnnotatedMethodBothWays() throws NoSuchMethodException {
registerRulesFor(MyController.class);
final Method method = MyController.class.getMethod("customizedPath");
String url = router.urlFor(MyController.class, method, new Object[] {});
assertThat(router.parse(url, HttpMethod.POST, null).getMethod(), is(equalTo(method)));
}
@Test
public void canFindUrlForProxyClasses() throws Exception {
registerRulesFor(MyController.class);
MyController proxy = proxifier.proxify(MyController.class, null);
Class<? extends MyController> type = proxy.getClass();
Method method = type.getMethod("notAnnotated");
assertEquals("/my/notAnnotated", router.urlFor(type, method));
}
}
class MyCustomController {
public void notAnnotated() {
}
}