/*** * 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.CoreMatchers.containsString; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.when; import java.util.EnumSet; import java.util.List; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import br.com.caelum.vraptor.Controller; import br.com.caelum.vraptor.Delete; import br.com.caelum.vraptor.Get; import br.com.caelum.vraptor.Head; import br.com.caelum.vraptor.Options; import br.com.caelum.vraptor.Patch; import br.com.caelum.vraptor.Path; import br.com.caelum.vraptor.Post; import br.com.caelum.vraptor.controller.DefaultBeanClass; import br.com.caelum.vraptor.controller.HttpMethod; import br.com.caelum.vraptor.core.Converters; import br.com.caelum.vraptor.core.DefaultReflectionProvider; import br.com.caelum.vraptor.core.ReflectionProvider; 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.proxy.JavassistProxifier; import br.com.caelum.vraptor.proxy.Proxifier; public class PathAnnotationRoutesParserTest { @Rule public ExpectedException exception = ExpectedException.none(); private Proxifier proxifier; private @Mock Converters converters; private NoTypeFinder typeFinder; private @Mock Router router; private ParameterNameProvider nameProvider; private @Mock EncodingHandler encodingHandler; private PathAnnotationRoutesParser parser; private ReflectionProvider reflectionProvider; @Before public void setup() { MockitoAnnotations.initMocks(this); this.proxifier = new JavassistProxifier(); this.typeFinder = new NoTypeFinder(); this.nameProvider = new ParanamerNameProvider(); this.reflectionProvider = new DefaultReflectionProvider(); when(router.builderFor(anyString())).thenAnswer(new Answer<DefaultRouteBuilder>() { @Override public DefaultRouteBuilder answer(InvocationOnMock invocation) throws Throwable { return new DefaultRouteBuilder(proxifier, typeFinder, converters, nameProvider, new JavaEvaluator(reflectionProvider), (String) invocation.getArguments()[0],encodingHandler); } }); parser = new PathAnnotationRoutesParser(router, reflectionProvider); } @Controller @Path("/prefix") public static class PathAnnotatedController { public void withoutPath() { } @Path("/absolutePath") public void withAbsolutePath() { } @Path("relativePath") public void withRelativePath() { } @Path("") public void withEmptyPath() { } } @Controller @Path("/prefix") public static class GetAnnotatedController { public void withoutPath() { } @Get("/absolutePath") public void withAbsolutePath() { } @Get("relativePath") public void withRelativePath() { } @Get("") public void withEmptyPath() { } } @Controller @Path("/endSlash/") public static class EndSlashAnnotatedController { public void withoutPath() { } @Path("/absolutePath") public void withAbsolutePath() { } @Path("relativePath") public void withRelativePath() { } @Path("") public void withEmptyPath() { } } @Controller @Path("/endSlash/") public static class EndSlashAnnotatedGetController { public void withoutPath() { } @Get("/absolutePath") public void withAbsolutePath() { } @Get("relativePath") public void withRelativePath() { } @Get("") public void withEmptyPath() { } } @Controller @Path("prefix") public static class WrongPathAnnotatedController { public void noSlashPath() { } } @Test public void addsAPrefixToMethodsWhenTheControllerHasMoreThanOneAnnotatedPath() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("You must specify exactly one path on @Path at class " + MoreThanOnePathAnnotatedController.class.getName()); parser.rulesFor(new DefaultBeanClass(MoreThanOnePathAnnotatedController.class)); } @Test public void addsAPrefixToMethodsWhenTheControllerAndTheMethodAreAnnotatedWithRelativePath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(PathAnnotatedController.class)); Route route = getRouteMatching(routes, "/prefix/relativePath"); assertThat(route, canHandle(PathAnnotatedController.class, "withRelativePath")); } @Test public void addsAPrefixToMethodsWhenTheControllerEndsWithSlashAndTheMethodAreAnnotatedWithRelativePath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(EndSlashAnnotatedController.class)); Route route = getRouteMatching(routes, "/endSlash/relativePath"); assertThat(route, canHandle(EndSlashAnnotatedController.class, "withRelativePath")); } @Test public void addsAPrefixToMethodsWhenTheControllerEndsWithSlashAndTheMethodAreAnnotatedWithAbsolutePath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(EndSlashAnnotatedController.class)); Route route = getRouteMatching(routes, "/endSlash/absolutePath"); assertThat(route, canHandle(EndSlashAnnotatedController.class, "withAbsolutePath")); } @Test public void addsAPrefixToMethodsWhenTheControllerEndsWithSlashAndTheMethodAreAnnotatedWithEmptyPath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(EndSlashAnnotatedController.class)); Route route = getRouteMatching(routes, "/endSlash/"); assertThat(route, canHandle(EndSlashAnnotatedController.class, "withEmptyPath")); } public void addsAPrefixToMethodsWhenTheControllerEndsWithSlashAndTheMethodAreNotAnnotated() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(EndSlashAnnotatedController.class)); Route route = getRouteMatching(routes, "/endSlash/withoutPath"); assertThat(route, canHandle(EndSlashAnnotatedController.class, "withoutPath")); } @Test public void addsAPrefixToMethodsWhenTheControllerAndTheMethodAreAnnotatedWithAbsolutePath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(PathAnnotatedController.class)); Route route = getRouteMatching(routes, "/prefix/absolutePath"); assertThat(route, canHandle(PathAnnotatedController.class, "withAbsolutePath")); } @Test public void addsAPrefixToMethodsWhenTheControllerAndTheMethodAreAnnotatedWithEmptyPath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(PathAnnotatedController.class)); Route route = getRouteMatching(routes, "/prefix"); assertThat(route, canHandle(PathAnnotatedController.class, "withEmptyPath")); } @Test public void addsAPrefixToMethodsWhenTheControllerIsAnnotatedWithPath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(PathAnnotatedController.class)); Route route = getRouteMatching(routes, "/prefix/withoutPath"); assertThat(route, canHandle(PathAnnotatedController.class, "withoutPath")); } @Test public void findsTheCorrectAnnotatedMethodIfThereIsNoWebMethodAnnotationPresent() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/clients"); assertThat(route, canHandle(ClientsController.class, "list")); } @Test public void suportsTheDefaultNameForANonAnnotatedMethod() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/clients/add"); assertThat(route, canHandle(ClientsController.class, "add")); } @Test public void ignoresTheControllerSuffixForANonAnnotatedMethod() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/clients/add"); assertThat(route, canHandle(ClientsController.class, "add")); } @Test public void addsASlashWhenUserForgotIt() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/noSlash"); assertThat(route, canHandle(ClientsController.class, "noSlash")); } @Test public void matchesWhenUsingAWildcard() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/move/second/child"); assertThat(route, canHandle(ClientsController.class, "move")); } @Test public void dontRegisterRouteIfMethodIsNotPublic() { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/protectMe"); assertNull(route); } @Test public void dontRegisterRouteIfMethodIsStatic() { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/staticMe"); assertNull(route); } @Controller public static class ClientsController { @Path("/move/*/child") public void move() { } @Path("noSlash") public void noSlash() { } @Path("/clients") public void list() { } @Path("/clients/remove") @Delete public void remove() { } @Path("/clients/head") @Head public void head() { } @Options("/clients/options") public void options() { } @Patch("/clients/update") public void update() { } public void add() { } @Path("/protectMe") protected void protectMe() { } @Path({"/path1", "/path2"}) public void manyPaths() { } @Path("/staticMe") public static void staticMe() { } public void toInherit() { } } @Test public void shouldThrowExceptionIfPathAnnotationHasEmptyArray() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage(containsString("You must specify at least one path on @Path at")); parser.rulesFor(new DefaultBeanClass(NoPath.class)); } @Test public void shouldFindNonAnnotatedNonStaticPublicMethodWithComponentNameInVariableCamelCaseConventionAsURI() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/clients/add"); assertThat(route, canHandle(ClientsController.class, "add")); } @Test public void shouldFindSeveralPathsForMethodWithManyValue() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/path1"); assertThat(route, canHandle(ClientsController.class, "manyPaths")); Route route2 = getRouteMatching(routes, "/path2"); assertThat(route2, canHandle(ClientsController.class, "manyPaths")); } @Test public void shouldNotMatchIfAControllerHasTheWrongWebMethod() throws SecurityException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/clients/remove"); assertThat(route.allowedMethods(), not(contains(HttpMethod.POST))); } @Test public void shouldAcceptAResultWithASpecificWebMethod() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/clients/head"); assertThat(route.allowedMethods(), is(EnumSet.of(HttpMethod.HEAD))); } @Test public void shouldAcceptAResultWithOptionsWebMethod() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/clients/options"); assertThat(route.allowedMethods(), is(EnumSet.of(HttpMethod.OPTIONS))); } @Test public void shouldAcceptAResultWithPatchWebMethod() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(ClientsController.class)); Route route = getRouteMatching(routes, "/clients/update"); assertThat(route.allowedMethods(), is(EnumSet.of(HttpMethod.PATCH))); } static class NiceClients extends ClientsController { @Override public void add() { super.add(); } } @Test public void findsInheritedMethodsWithDefaultNames() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(NiceClients.class)); Route route = getRouteMatching(routes, "/niceClients/toInherit"); assertTrue(route.canHandle(NiceClients.class, ClientsController.class.getDeclaredMethod("toInherit"))); } @Test public void supportMethodOverriding() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(NiceClients.class)); Route route = getRouteMatching(routes, "/niceClients/add"); assertThat(route, canHandle(NiceClients.class, "add")); } static class UPPERController { public void method() {} } public void shouldlowerFirstWord() { List<Route> routes = parser.rulesFor(new DefaultBeanClass(UPPERController.class)); Route route = getRouteMatching(routes, "/upper/method"); assertThat(route, canHandle(UPPERController.class, "method")); } @Post static class AnnotatedController { public void test() {} @Get public void overridden() {} } @Test public void supportTypeHttpMethodAnnotation() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(AnnotatedController.class)); Route route = getRouteMatching(routes, "/annotated/test"); assertThat(route.allowedMethods(), is(EnumSet.of(HttpMethod.POST))); } @Test public void supportOverrideTypeHttpMethodAnnotation() throws SecurityException, NoSuchMethodException { List<Route> routes = parser.rulesFor(new DefaultBeanClass(AnnotatedController.class)); Route route = getRouteMatching(routes, "/annotated/overridden"); assertThat(route.allowedMethods(), is(EnumSet.of(HttpMethod.GET))); } private Route getRouteMatching(List<Route> routes, String uri) { for (Route route : routes) { if (route.canHandle(uri)) { return route; } } return null; } private Matcher<Route> canHandle(final Class<?> type, final String method) { return new TypeSafeMatcher<Route>() { @Override protected void describeMismatchSafely(Route item, Description mismatchDescription) { } @Override protected boolean matchesSafely(Route item) { try { return item.canHandle(type, type.getDeclaredMethod(method)); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void describeTo(Description description) { description.appendText("a route which can handle ").appendValue(type).appendText(".").appendValue(method); } }; } @Test public void addsAPrefixToMethodsWhenTheGetControllerAndTheMethodAreAnnotatedWithRelativePath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(GetAnnotatedController.class)); Route route = getRouteMatching(routes, "/prefix/relativePath"); assertThat(route, canHandle(GetAnnotatedController.class, "withRelativePath")); } @Test public void priorityForGetAnnotationShouldBeDefault() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(GetAnnotatedController.class)); Route route = getRouteMatching(routes, "/prefix/relativePath"); assertThat(route.getPriority(), is(Path.DEFAULT)); } @Test public void addsAPrefixToMethodsWhenTheGetControllerEndsWithSlashAndTheMethodAreAnnotatedWithRelativePath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(EndSlashAnnotatedGetController.class)); Route route = getRouteMatching(routes, "/endSlash/relativePath"); assertThat(route, canHandle(EndSlashAnnotatedGetController.class, "withRelativePath")); } @Test public void addsAPrefixToMethodsWhenTheGetControllerEndsWithSlashAndTheMethodAreAnnotatedWithAbsolutePath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(EndSlashAnnotatedGetController.class)); Route route = getRouteMatching(routes, "/endSlash/absolutePath"); assertThat(route, canHandle(EndSlashAnnotatedGetController.class, "withAbsolutePath")); } @Test public void addsAPrefixToMethodsWhenTheGetControllerAndTheMethodAreAnnotatedWithAbsolutePath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(GetAnnotatedController.class)); Route route = getRouteMatching(routes, "/prefix/absolutePath"); assertThat(route, canHandle(GetAnnotatedController.class, "withAbsolutePath")); } @Test public void addsAPrefixToMethodsWhenTheGetControllerIsAnnotatedWithPath() throws Exception { List<Route> routes = parser.rulesFor(new DefaultBeanClass(GetAnnotatedController.class)); Route route = getRouteMatching(routes, "/prefix/withoutPath"); assertThat(route, canHandle(GetAnnotatedController.class, "withoutPath")); } @Test public void throwsExceptionWhenTheGetControllerHasAmbiguousDeclaration() throws Exception { exception.expect(IllegalArgumentException.class); exception.expectMessage("You should specify paths either in @Path(\"/path\") or @Get(\"/path\") (or @Post, @Put, @Delete), not both at"); parser.rulesFor(new DefaultBeanClass(WrongGetAnnotatedController.class)); } }