/* * Copyright 2014 Rick Grashel. * * 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 net.sourceforge.stripes.action; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import net.sourceforge.stripes.FilterEnabledTestBase; import net.sourceforge.stripes.mock.MockRoundtrip; import net.sourceforge.stripes.util.Log; import net.sourceforge.stripes.validation.SimpleError; import net.sourceforge.stripes.validation.TypeConverter; import net.sourceforge.stripes.validation.Validate; import net.sourceforge.stripes.validation.ValidateNestedProperties; import net.sourceforge.stripes.validation.ValidationError; import net.sourceforge.stripes.validation.ValidationErrors; import net.sourceforge.stripes.validation.ValidationMethod; import org.testng.Assert; import org.testng.annotations.Test; /** * This is a series of tests for Stripes REST action beans. */ @RestActionBean @UrlBinding("/test/{person}") public class RestActionBeanTest extends FilterEnabledTestBase implements ActionBean { private static final Log log = Log.getInstance(RestActionBeanTest.class); @Validate(on = "head", required = true) private String id; @ValidateNestedProperties( { @Validate( field = "id", required = true, on = "boundPersonEvent" ) }) @Validate(converter = PersonTypeConverter.class) private Person person; public Resolution customHttpVerb() { return new JsonResolution("Yay! This is a custom HTTP verb!"); } public Resolution get() { Map< String, Object> response = new HashMap< String, Object>(); response.put("foo", "bar"); response.put("hello", "world"); response.put("person", new Person()); Map< String, Number> nested = new HashMap< String, Number>(); nested.put("one", 1); nested.put("two", 2); response.put("numbers", nested); return new JsonResolution(response); } @POST public Resolution boundPersonEvent() { return new JsonResolution(getPerson()); } public Resolution jsonResolutionWithExclusion() { Person p = new Person(); return new JsonResolution(p, "firstName"); } @ValidationMethod(on = "head") public void validateHeadCall(ValidationErrors errors) { errors.addGlobalError(new SimpleError("The head request was not valid for whatever custom reason.")); } public Resolution head() { return new JsonResolution("Successful head!"); } public Resolution testUnhandledExceptionEvent() { throw new RuntimeException("Some Unhandled Exception Occurred!"); } @POST public Resolution onlySupportsPost() { return new JsonResolution("Successful onlySupportsPost()!"); } public void setId(String id) { this.id = id; } public String getId() { return this.id; } public void setPerson(Person person) { this.person = person; } public Person getPerson() { return this.person; } private ActionBeanContext context; public ActionBeanContext getContext() { return this.context; } public void setContext(ActionBeanContext context) { this.context = context; } @Test(groups = "fast") public void testGetAttemptOnPostMethod() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); trip.getRequest().setMethod("GET"); trip.execute("onlySupportsPost"); Assert.assertEquals(trip.getResponse().getStatus(), HttpURLConnection.HTTP_BAD_METHOD); logTripResponse(trip); } @Test(groups = "fast") public void testPostAttemptOnPostMethod() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); trip.getRequest().setMethod("POST"); trip.execute("onlySupportsPost"); Assert.assertEquals(trip.getResponse().getStatus(), HttpURLConnection.HTTP_OK); logTripResponse(trip); } @Test(groups = "fast") public void testSuccessfulGet() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); trip.getRequest().setMethod("GET"); trip.execute(); Assert.assertEquals(trip.getResponse().getStatus(), HttpURLConnection.HTTP_OK); logTripResponse(trip); } @Test(groups = "fast") public void testFailedPost() throws Exception { // Since no event is specified, this should default to the post() method and attempt // to execute it. Since one doesn't exist, it should throw a 404. MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); trip.getRequest().setMethod("POST"); trip.execute(); Assert.assertEquals(trip.getResponse().getStatus(), HttpURLConnection.HTTP_NOT_FOUND); logTripResponse(trip); } @Test(groups = "fast") public void testMissingRequiredParameterOnHead() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); trip.getRequest().setMethod("HEAD"); trip.execute(); Assert.assertTrue(trip.getValidationErrors().hasFieldErrors() && trip.getValidationErrors().size() == 1); logTripResponse(trip); } @Test(groups = "fast") public void testFailedCustomValidationOnHead() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); trip.setParameter("id", "SOME_ID"); trip.getRequest().setMethod("HEAD"); trip.execute(); Assert.assertTrue(!trip.getValidationErrors().hasFieldErrors() && trip.getValidationErrors().size() == 1); logTripResponse(trip); } @Test(groups = "fast") public void testCustomHTTPVerb() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); trip.getRequest().setMethod("customHttpVerb"); trip.execute(); Assert.assertEquals(trip.getResponse().getStatus(), HttpURLConnection.HTTP_OK); logTripResponse(trip); } @Test(groups = "fast") public void testJsonResolutionWithPropertyExclusion() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), getClass()); trip.getRequest().setMethod("jsonResolutionWithExclusion"); trip.execute(); Assert.assertFalse(trip.getResponse().getOutputString().toUpperCase().contains("FIRSTNAME")); logTripResponse(trip); } @Test(groups = "fast") public void testJsonBindingFromRequestBody() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), "/test/1" ); trip.getRequest().addHeader("Content-Type", "application/json"); trip.getRequest().setMethod("POST"); String json = "{ \"person\" : { \"firstName\":\"Jane\", \"lastName\":\"Johnson\", \"favoriteFoods\" : [\"Snickers\",\"Scotch\",\"Pizza\"], \"children\" : [{ \"firstName\": \"Jackie\"},{\"firstName\":\"Janie\"}]}}"; trip.getRequest().setRequestBody(json); trip.execute("boundPersonEvent"); RestActionBeanTest bean = trip.getActionBean( getClass() ); Assert.assertEquals( bean.getPerson().getId(), "1" ); Assert.assertEquals( bean.getPerson().getFirstName(), "Jane" ); Assert.assertEquals( bean.getPerson().getLastName(), "Johnson" ); Assert.assertEquals( bean.getPerson().getChildren().size(), 2); List< String > favoriteFoods = new ArrayList< String >(); favoriteFoods.add( "Snickers" ); favoriteFoods.add( "Scotch" ); favoriteFoods.add( "Pizza" ); Assert.assertEquals( bean.getPerson().getFavoriteFoods(), favoriteFoods ); logTripResponse(trip); } @Test(groups = "fast") public void testThereIsNoJsonBindingWithoutRequestBody() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), "/test/1" ); trip.getRequest().addHeader("Content-Type", "application/json"); trip.getRequest().setMethod("POST"); String emptyBody = ""; trip.getRequest().setRequestBody(emptyBody); trip.execute("boundPersonEvent"); RestActionBeanTest bean = trip.getActionBean( getClass() ); Assert.assertEquals( bean.getPerson().getId(), "1" ); Assert.assertEquals( bean.getPerson().getFirstName(), "John" ); Assert.assertEquals( bean.getPerson().getLastName(), "Doe" ); Assert.assertEquals( bean.getPerson().getChildren().size(), 0); Assert.assertEquals( bean.getPerson().getFavoriteFoods().size(), 0 ); logTripResponse(trip); } @Test(groups = "fast") /** * This tests to make sure that a JSON request that is bound to an event * has its validation handled properly. In this case, the person.id is * a required field and is not bound. */ public void testJsonBindingFromRequestBodyWithValidationError() throws Exception { MockRoundtrip trip = new MockRoundtrip(getMockServletContext(), "/test" ); trip.getRequest().addHeader("Content-Type", "application/json"); trip.getRequest().setMethod("POST"); String json = "{ \"person\" : { \"firstName\":\"Jane\", \"lastName\":\"Johnson\", \"favoriteFoods\" : [\"Snickers\",\"Scotch\",\"Pizza\"], \"children\" : [{ \"firstName\": \"Jackie\"},{\"firstName\":\"Janie\"}]}}"; trip.getRequest().setRequestBody(json); trip.execute("boundPersonEvent"); Assert.assertTrue( trip.getValidationErrors().hasFieldErrors() ); logTripResponse(trip); } private void logTripResponse(MockRoundtrip trip) { log.debug("TRIP RESPONSE: [Event=" + trip.getActionBean(getClass()).getContext().getEventName() + "] [Status=" + trip.getResponse().getStatus() + "] [Message=" + trip.getResponse().getOutputString() + "] [Error Message=" + trip.getResponse().getErrorMessage() + "]"); } public static class Person { String id = null; String firstName = "John"; String lastName = "Doe"; List< String> favoriteFoods = new ArrayList< String>(); List< Person> children = new ArrayList<Person>(); public void setChildren( List< Person > children ) { this.children = children; } public List< Person > getChildren() { return this.children; } public void setId( String id ) { this.id = id; } public String getId() { return this.id; } public void setFavoriteFoods(List< String> favoriteFoods) { this.favoriteFoods = favoriteFoods; } public List< String> getFavoriteFoods() { return this.favoriteFoods; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } } public static class PersonTypeConverter implements TypeConverter<Person> { public void setLocale(Locale locale) { } public Locale getLocale() { return Locale.getDefault(); } public Person convert(String input, Class<? extends Person> targetType, Collection<ValidationError> errors) { Person p = new Person(); p.setId(input); return p; } } }