/** * Copyright (C) 2012-2017 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 * * 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 ninja; import com.google.common.collect.Lists; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import com.google.inject.Injector; import com.google.inject.Provider; import java.util.List; import ninja.utils.MethodReference; import ninja.utils.NinjaBaseDirectoryResolver; import ninja.utils.NinjaConstant; import ninja.utils.NinjaProperties; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.startsWith; import org.hamcrest.Matchers; import static org.junit.Assert.assertThat; import org.junit.Before; import org.mockito.Mockito; import static org.mockito.Mockito.mock; import ninja.application.ApplicationFilters; @RunWith(MockitoJUnitRunner.class) public class RouteBuilderImplTest { @Mock Injector injector; @Mock NinjaProperties ninjaProperties; NinjaBaseDirectoryResolver ninjaBaseDirectoryResolver; RouteBuilderImpl routeBuilder; @Before public void before() { this.ninjaBaseDirectoryResolver = new NinjaBaseDirectoryResolver(ninjaProperties); this.routeBuilder = new RouteBuilderImpl(ninjaProperties, ninjaBaseDirectoryResolver); } @Test public void basicGETRoute() { routeBuilder.GET().route("/index"); assertTrue(buildRoute(routeBuilder).matches("GET", "/index")); } @Test public void basicPOSTRoute() { routeBuilder.POST().route("/index"); assertTrue(buildRoute(routeBuilder).matches("POST", "/index")); } @Test public void basicPUTRoute() { routeBuilder.PUT().route("/index"); assertTrue(buildRoute(routeBuilder).matches("PUT", "/index")); } @Test public void basicRoutes() { routeBuilder.OPTIONS().route("/index"); assertTrue(buildRoute(routeBuilder).matches("OPTIONS", "/index")); } @Test public void basisHEAD() { routeBuilder.HEAD().route("/index"); assertTrue(buildRoute(routeBuilder).matches("HEAD", "/index")); } @Test public void basicAnyHttpMethod() { routeBuilder.METHOD("PROPFIND").route("/index"); assertTrue(buildRoute(routeBuilder).matches("PROPFIND", "/index")); } @Test public void basicRoutesWithRegex() { routeBuilder.GET().route("/.*"); Route route = buildRoute(routeBuilder); // make sure the route catches everything assertTrue(route.matches("GET", "/index")); assertTrue(route.matches("GET", "/stylesheet.css")); assertTrue(route.matches("GET", "/public/stylesheet.css")); assertTrue(route.matches("GET", "/public/bootstrap.js")); } @Test public void basicPlaceholersAndParameters() { // ///////////////////////////////////////////////////////////////////// // One parameter: // ///////////////////////////////////////////////////////////////////// routeBuilder = new RouteBuilderImpl(ninjaProperties, ninjaBaseDirectoryResolver); routeBuilder.GET().route("/{name}/dashboard"); Route route = buildRoute(routeBuilder); assertFalse(route.matches("GET", "/dashboard")); assertTrue(route.matches("GET", "/John/dashboard")); Map<String, String> map = route .getPathParametersEncoded("/John/dashboard"); assertEquals(1, map.entrySet().size()); assertEquals("John", map.get("name")); // ///////////////////////////////////////////////////////////////////// // More parameters // ///////////////////////////////////////////////////////////////////// routeBuilder = new RouteBuilderImpl(ninjaProperties, ninjaBaseDirectoryResolver); routeBuilder.GET().route("/{name}/{id}/dashboard"); route = buildRoute(routeBuilder); assertFalse(route.matches("GET", "/dashboard")); assertTrue(route.matches("GET", "/John/20/dashboard")); map = route.getPathParametersEncoded("/John/20/dashboard"); assertEquals(2, map.entrySet().size()); assertEquals("John", map.get("name")); assertEquals("20", map.get("id")); } @Test public void basicPlaceholersParametersAndRegex() { // test that parameter parsing works in conjunction with // regex expressions... routeBuilder.GET().route("/John/{id}/.*"); Route route = buildRoute(routeBuilder); assertTrue(route.matches("GET", "/John/20/dashboard")); Map<String, String> map = route .getPathParametersEncoded("/John/20/dashboard"); assertEquals(1, map.entrySet().size()); assertEquals("20", map.get("id")); assertTrue(route.matches("GET", "/John/20/admin")); map = route.getPathParametersEncoded("/John/20/admin"); assertEquals(1, map.entrySet().size()); assertEquals("20", map.get("id")); assertTrue(route.matches("GET", "/John/20/mock")); map = route.getPathParametersEncoded("/John/20/mock"); assertEquals(1, map.entrySet().size()); assertEquals("20", map.get("id")); } @Test public void basicPlaceholersParametersAndRegexInsideVariableParts() { // test that parameter parsing works in conjunction with // regex expressions... routeBuilder.GET().route("/assets/{file: .*}"); Route route = buildRoute(routeBuilder); String pathUnderTest = "/assets/css/app.css"; assertTrue(route.matches("GET", pathUnderTest)); Map<String, String> map = route.getPathParametersEncoded(pathUnderTest); assertEquals(1, map.entrySet().size()); assertEquals("css/app.css", map.get("file")); pathUnderTest = "/assets/javascripts/main.js"; assertTrue(route.matches("GET", pathUnderTest)); map = route.getPathParametersEncoded(pathUnderTest); assertEquals(1, map.entrySet().size()); assertEquals("javascripts/main.js", map.get("file")); pathUnderTest = "/assets/robots.txt"; assertTrue(route.matches("GET", pathUnderTest)); map = route.getPathParametersEncoded(pathUnderTest); assertEquals(1, map.entrySet().size()); assertEquals("robots.txt", map.get("file")); // multiple parameter parsing with regex expressions routeBuilder = new RouteBuilderImpl(ninjaProperties, ninjaBaseDirectoryResolver); routeBuilder.GET().route("/{name: .+}/photos/{id: [0-9]+}"); route = buildRoute(routeBuilder); pathUnderTest = "/John/photos/2201"; assertTrue(route.matches("GET", pathUnderTest)); assertFalse(route.matches("GET", "John/photos/first")); map = route.getPathParametersEncoded(pathUnderTest); assertEquals(2, map.size()); assertEquals("John", map.get("name")); assertEquals("2201", map.get("id")); } @Test public void parametersDontCrossSlashes() { routeBuilder.GET().route("/blah/{id}/{id2}/{id3}/morestuff/at/the/end"); Route route = buildRoute(routeBuilder); // this must match assertTrue(route .matches("GET", "/blah/id/id2/id3/morestuff/at/the/end")); // this should not match as the last "end" is missing assertFalse(route.matches("GET", "/blah/id/id2/id3/morestuff/at/the")); } @Test public void pointsInRegexDontCrashRegexInTheMiddleOfTheRoute() { routeBuilder.GET().route("/blah/{id}/myname"); Route route = buildRoute(routeBuilder); // the "." in the route should not make any trouble: String routeFromServer = "/blah/my.id/myname"; assertTrue(route.matches("GET", routeFromServer)); assertEquals(1, route.getPathParametersEncoded(routeFromServer) .entrySet().size()); assertEquals("my.id", route.getPathParametersEncoded(routeFromServer) .get("id")); // and another slightly different route routeFromServer = "/blah/my.id/myname/should_not_match"; assertFalse(route.matches("GET", routeFromServer)); assertEquals(0, route.getPathParametersEncoded(routeFromServer) .entrySet().size()); } @Test public void pointsInRegexDontCrashRegexAtEnd() { routeBuilder.GET().route("/blah/{id}"); Route route = buildRoute(routeBuilder); // the "." in the route should not make any trouble: // even if it's the last part of the route String routeFromServer = "/blah/my.id"; assertTrue(route.matches("GET", "/blah/my.id")); assertEquals(1, route.getPathParametersEncoded(routeFromServer) .entrySet().size()); assertEquals("my.id", route.getPathParametersEncoded(routeFromServer) .get("id")); } @Test public void regexInRouteWorksWithEscapes() { // Test escaped constructs in regex // regex with escaped construct in a route routeBuilder.GET().route("/customers/\\d+"); Route route = buildRoute(routeBuilder); assertTrue(route.matches("GET", "/customers/1234")); assertFalse(route.matches("GET", "/customers/12ab")); // regex with escaped construct in a route with variable parts routeBuilder.GET().route("/customers/{id: \\d+}"); route = buildRoute(routeBuilder); assertTrue(route.matches("GET", "/customers/1234")); assertFalse(route.matches("GET", "/customers/12x")); Map<String, String> map = route.getPathParametersEncoded("/customers/1234"); assertEquals(1, map.size()); assertEquals("1234", map.get("id")); } @Test public void regexInRouteWorksWithoutSlashAtTheEnd() { routeBuilder.GET().route("/blah/{id}/.*"); Route route = buildRoute(routeBuilder); // the "." in the real route should work without any problems: String routeFromServer = "/blah/my.id/and/some/more/stuff"; assertTrue(route.matches("GET", routeFromServer)); assertEquals(1, route.getPathParametersEncoded(routeFromServer) .entrySet().size()); assertEquals("my.id", route.getPathParametersEncoded(routeFromServer) .get("id")); // another slightly different route. routeFromServer = "/blah/my.id/"; assertTrue(route.matches("GET", "/blah/my.id/")); assertEquals(1, route.getPathParametersEncoded(routeFromServer) .entrySet().size()); assertEquals("my.id", route.getPathParametersEncoded(routeFromServer) .get("id")); assertFalse(route.matches("GET", "/blah/my.id")); } @Test public void routeWithUrlEncodedSlashGetsChoppedCorrectly() { routeBuilder.GET().route("/blah/{id}/.*"); Route route = buildRoute(routeBuilder); // Just a simple test to make sure everything works on a not encoded // uri: // decoded this would be /blah/my/id/and/some/more/stuff String routeFromServer = "/blah/my%2fid/and/some/more/stuff"; assertTrue(route.matches("GET", routeFromServer)); assertEquals(1, route.getPathParametersEncoded(routeFromServer) .entrySet().size()); assertEquals("my%2fid", route.getPathParametersEncoded(routeFromServer) .get("id")); } @Test public void routeWithResult() { Context context = mock(Context.class); String template = "/directly_result/stuff"; routeBuilder.GET().route("/directly_result/route").with(Results.html().template(template)); Route route = routeBuilder.buildRoute(injector); assertTrue(route.matches("GET", "/directly_result/route")); Result result = route.getFilterChain().next(context); assertEquals(result.getTemplate(), template); } @Test public void failedControllerRegistration() { routeBuilder.GET().route("/failure").with(MockController.class, "DoesNotExist"); try { Route route = routeBuilder.buildRoute(injector); assertTrue(route == null); } catch (Exception e) { assertTrue(e instanceof IllegalStateException); } } @Test public void routeWithMethodReference() throws Exception { routeBuilder.GET().route("/method_reference").with(new MethodReference(MockController.class, "execute")); Route route = routeBuilder.buildRoute(injector); assertTrue(route.matches("GET", "/method_reference")); assertThat(route.getControllerClass(), is(MockController.class)); } private Route buildRoute(RouteBuilderImpl builder) { builder.with(MockController.class, "execute"); return builder.buildRoute(injector); } public static class MockController { public Result execute() { return null; } public Result execute2(Context context) { return null; } static public Result execute3(Context context) { return null; } } @Test public void routeToAnyInstanceMethodReference() throws Exception { routeBuilder.GET().route("/execute").with(MockController::execute); Route route = routeBuilder.buildRoute(injector); assertTrue(route.matches("GET", "/execute")); assertThat(route.getControllerClass(), is(MockController.class)); assertThat(route.getControllerMethod().getName(), is("execute")); } @Test public void routeToSpecificInstanceMethodReference() throws Exception { MockController controller = new MockController(); routeBuilder.GET().route("/execute").with(controller::execute); Route route = routeBuilder.buildRoute(injector); assertTrue(route.matches("GET", "/execute")); assertThat(route.getControllerClass().getCanonicalName(), startsWith(this.getClass().getCanonicalName())); assertThat(route.getControllerMethod().getName(), is("apply")); } @Test public void routeToStaticMethodReference() throws Exception { routeBuilder.GET().route("/execute").with(MockController::execute3); Route route = routeBuilder.buildRoute(injector); assertTrue(route.matches("GET", "/execute")); assertThat(route.getControllerClass(), is(MockController.class)); assertThat(route.getControllerMethod().getName(), is("execute3")); } @Test @SuppressWarnings("Convert2Lambda") public void routeToAnonymousClassReference() throws Exception { routeBuilder.GET().route("/execute").with(new ControllerMethods.ControllerMethod0() { @Override public Result apply() { return Results.redirect("/"); } }); Route route = routeBuilder.buildRoute(injector); assertTrue(route.matches("GET", "/execute")); assertThat(route.getControllerClass().isAnonymousClass(), is(true)); assertThat(route.getControllerMethod().getName(), is("apply")); } @Test public void routeToAnonymousMethodReference() throws Exception { routeBuilder.GET().route("/execute").with(() -> Results.redirect("/")); Route route = routeBuilder.buildRoute(injector); assertTrue(route.matches("GET", "/execute")); // should be a class within this test class as a real lambda assertThat(route.getControllerClass().getCanonicalName(), startsWith(this.getClass().getCanonicalName())); assertThat(route.getControllerMethod().getName(), is("apply")); } private class DummyFilter implements Filter { int executed = 0; @Override public Result filter(FilterChain filterChain, Context context) { executed++; return filterChain.next(context); } } private class DummyFilter2 extends DummyFilter {} @Test public void testGlobalFilters() throws Exception { // given // different setup that uses com.example packages and thus reads the Filters there Mockito.when(ninjaProperties.get(NinjaConstant.APPLICATION_MODULES_BASE_PACKAGE)) .thenReturn("com.example"); this.ninjaBaseDirectoryResolver = new NinjaBaseDirectoryResolver(ninjaProperties); this.routeBuilder = new RouteBuilderImpl(ninjaProperties, ninjaBaseDirectoryResolver); DummyFilter dummyFilter = new DummyFilter(); Result expectedResult = Mockito.mock(Result.class); Context context = Mockito.mock(Context.class); Provider filterProvider = Mockito.mock(Provider.class); com.example.conf.Filters filters = new com.example.conf.Filters(DummyFilter.class); Mockito.when(injector.getInstance(com.example.conf.Filters.class)).thenReturn(filters); Mockito.when(injector.getProvider(DummyFilter.class)).thenReturn(filterProvider); Mockito.when(filterProvider.get()).thenReturn(dummyFilter); routeBuilder.GET().route("/").with(() -> expectedResult); Route route = routeBuilder.buildRoute(injector); FilterChain filterChain = route.getFilterChain(); // when Result result = filterChain.next(context); // then Mockito.verify(injector).getInstance(com.example.conf.Filters.class); assertThat(dummyFilter.executed, Matchers.equalTo(1)); assertThat(result, org.hamcrest.Matchers.equalTo(expectedResult)); } @Test public void testThatGlobalFiltersInRouteReplaceGlobalFiltersInConfFilters() throws Exception { // DummyFilter is defined in conf.Filters, but .globalFilters(DummyFilter2.class) should // override that. // given // different setup that uses com.example packages and thus reads the Filters there Mockito.when(ninjaProperties.get(NinjaConstant.APPLICATION_MODULES_BASE_PACKAGE)) .thenReturn("com.example"); this.ninjaBaseDirectoryResolver = new NinjaBaseDirectoryResolver(ninjaProperties); this.routeBuilder = new RouteBuilderImpl(ninjaProperties, ninjaBaseDirectoryResolver); DummyFilter2 dummyFilter2 = new DummyFilter2(); Result expectedResult = Mockito.mock(Result.class); Context context = Mockito.mock(Context.class); Provider filterProvider = Mockito.mock(Provider.class); Mockito.when(injector.getProvider(DummyFilter2.class)).thenReturn(filterProvider); Mockito.when(filterProvider.get()).thenReturn(dummyFilter2); routeBuilder.GET().route("/").globalFilters(DummyFilter2.class).with(() -> expectedResult); Route route = routeBuilder.buildRoute(injector); FilterChain filterChain = route.getFilterChain(); // when Result result = filterChain.next(context); // then Mockito.verify(injector, Mockito.never()).getProvider(DummyFilter.class); Mockito.verify(injector).getProvider(DummyFilter2.class); assertThat(dummyFilter2.executed, Matchers.equalTo(1)); assertThat(result, org.hamcrest.Matchers.equalTo(expectedResult)); } @Test public void testWithFiltersClass() throws Exception { // given DummyFilter dummyFilter = new DummyFilter(); Result expectedResult = Mockito.mock(Result.class); Context context = Mockito.mock(Context.class); Provider filterProvider = Mockito.mock(Provider.class); Mockito.when(injector.getProvider(DummyFilter.class)).thenReturn(filterProvider); Mockito.when(filterProvider.get()).thenReturn(dummyFilter); routeBuilder.GET().route("/").filters(DummyFilter.class).with(() -> expectedResult); Route route = routeBuilder.buildRoute(injector); FilterChain filterChain = route.getFilterChain(); // when Result result = filterChain.next(context); // then assertThat(dummyFilter.executed, Matchers.equalTo(1)); assertThat(result, org.hamcrest.Matchers.equalTo(expectedResult)); } @Test public void testWithFiltersList() throws Exception { // given DummyFilter dummyFilter = new DummyFilter(); Result expectedResult = Mockito.mock(Result.class); Context context = Mockito.mock(Context.class); Provider filterProvider = Mockito.mock(Provider.class); Mockito.when(injector.getProvider(DummyFilter.class)).thenReturn(filterProvider); Mockito.when(filterProvider.get()).thenReturn(dummyFilter); routeBuilder.GET().route("/").filters(Lists.newArrayList(DummyFilter.class)).with(() -> expectedResult); Route route = routeBuilder.buildRoute(injector); FilterChain filterChain = route.getFilterChain(); // when Result result = filterChain.next(context); // then assertThat(dummyFilter.executed, Matchers.equalTo(1)); assertThat(result, org.hamcrest.Matchers.equalTo(expectedResult)); } @Test public void testWithGlobalFiltersClass() throws Exception { // given DummyFilter dummyFilter = new DummyFilter(); Result expectedResult = Mockito.mock(Result.class); Context context = Mockito.mock(Context.class); Provider filterProvider = Mockito.mock(Provider.class); Mockito.when(injector.getProvider(DummyFilter.class)).thenReturn(filterProvider); Mockito.when(filterProvider.get()).thenReturn(dummyFilter); routeBuilder.GET().route("/").globalFilters(DummyFilter.class).with(() -> expectedResult); Route route = routeBuilder.buildRoute(injector); FilterChain filterChain = route.getFilterChain(); // when Result result = filterChain.next(context); // then assertThat(dummyFilter.executed, Matchers.equalTo(1)); assertThat(result, org.hamcrest.Matchers.equalTo(expectedResult)); } @Test public void testWithGlobalFiltersList() throws Exception { // given DummyFilter dummyFilter = new DummyFilter(); Result expectedResult = Mockito.mock(Result.class); Context context = Mockito.mock(Context.class); Provider filterProvider = Mockito.mock(Provider.class); Mockito.when(injector.getProvider(DummyFilter.class)).thenReturn(filterProvider); Mockito.when(filterProvider.get()).thenReturn(dummyFilter); routeBuilder.GET().route("/").globalFilters(Lists.newArrayList(DummyFilter.class)).with(() -> expectedResult); Route route = routeBuilder.buildRoute(injector); FilterChain filterChain = route.getFilterChain(); // when Result result = filterChain.next(context); // then assertThat(dummyFilter.executed, Matchers.equalTo(1)); assertThat(result, org.hamcrest.Matchers.equalTo(expectedResult)); } }