/* * Copyright (C) 2015 Strand Life Sciences. * * 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 com.strandls.alchemy.rest.client; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.SecureRandom; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.UUID; import javax.ws.rs.NotFoundException; import javax.ws.rs.ProcessingException; import javax.ws.rs.client.Client; import javax.ws.rs.core.Application; import lombok.Cleanup; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.media.multipart.BodyPart; import org.glassfish.jersey.media.multipart.BodyPartEntity; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataMultiPart; import org.glassfish.jersey.media.multipart.FormDataParam; import org.glassfish.jersey.media.multipart.MultiPart; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.media.multipart.file.FileDataBodyPart; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.junit.Before; import org.junit.Test; import org.reflections.ReflectionUtils; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; import com.google.common.base.Predicate; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Provides; import com.google.inject.name.Names; import com.strandls.alchemy.rest.client.reader.VoidMessageBodyReader; /** * Unit tests for {@link AlchemyRestClientFactory}. * * @author Ashish Shinde * */ public class AlchemyRestClientFactoryTest extends JerseyTest { /** * The client side guice module. * * @author Ashish Shinde * */ public class ClientModule extends AbstractModule { public ClientModule() { super(); } /* * (non-Javadoc) * @see com.google.inject.AbstractModule#configure() */ @Override protected void configure() { // bind the URI. bind(String.class).annotatedWith( Names.named(AlchemyRestClientFactory.BASE_URI_NAMED_PARAM)).toInstance( getBaseUri().toString()); } /** * @return the jersey client to use behind the proxy. */ @Provides Client getClient() { return client(); } } /** * The client factory. */ private AlchemyRestClientFactory clientFactory; /* * (non-Javadoc) * @see org.glassfish.jersey.test.JerseyTest#configure() */ @Override protected Application configure() { final ResourceConfig application = new ResourceConfig(TestWebserviceWithPath.class, TestWebserviceWithPutDelete.class, TestWebserviceMultipart.class, TestWebserviceExceptionHandling.class, JacksonJsonProvider.class); final Injector injector = Guice.createInjector(new ClientModule(), new ExceptionObjectMapperModule()); // register multi part feature. application.register(MultiPartFeature.class); // register the application mapper. application.register(injector.getInstance(TestExceptionMapper.class)); return application; } /* * (non-Javadoc) * @see * org.glassfish.jersey.test.JerseyTest#configureClient(org.glassfish.jersey * .client.ClientConfig) */ @Override protected void configureClient(final ClientConfig config) { super.configureClient(config); config.register(VoidMessageBodyReader.class); } /** * @param testFile * @return * @throws FileNotFoundException */ private FileInputStream getInputStream(final String testFile) throws FileNotFoundException { return new FileInputStream(new File(testFile)); } /** * Convert multi part body into a map of key value pairs. * * @param multiPart * @return * @throws IOException */ private Map<String, String> multipartToMap(final MultiPart multiPart) throws IOException { final Map<String, String> map = new HashMap<>(); for (final BodyPart part : multiPart.getBodyParts()) { Object value = part.getEntity(); if (value instanceof BodyPartEntity) { value = IOUtils.toString(((BodyPartEntity) value).getInputStream()); } if (value instanceof File) { value = IOUtils.toString(new FileInputStream((File) value)); } final String fieldName = part.getHeaders().get("Content-Disposition").get(0) .split("form-data;\\s*.*name=\"")[1].replaceFirst("\"$", ""); map.put(fieldName, ObjectUtils.toString(value)); } return map; } /** * Setup jackson as json provider. */ @Before public void setup() { client().register(new JacksonJsonProvider()); client().register(MultiPartFeature.class); final Injector injector = Guice.createInjector(new ClientModule(), new ExceptionObjectMapperModule()); clientFactory = injector.getInstance(AlchemyRestClientFactory.class); } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * Test {@link ProcessingException}s are not touched. * * @throws Exception */ @Test(expected = NotFoundException.class) public void testException404() throws Exception { final TestWebserviceExceptionHandling service = clientFactory.getInstance(TestWebserviceExceptionHandling.class); service.failInternal404(); fail("Should have thrown an exception"); } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * Test generic exception is relayed correctly without loosing message. * * @throws Exception */ @Test public void testExceptionMapping() throws Exception { final TestWebserviceExceptionHandling service = clientFactory.getInstance(TestWebserviceExceptionHandling.class); try { service.fail(); fail("Should have thrown an exception"); } catch (final Exception e) { assertEquals(TestWebserviceExceptionHandling.EXCEPTION_STRING, e.getMessage()); // ensure the mixin got applied assertNull(e.getCause()); // ensure server stack trace is masked. // will have local stack trace though. final StackTraceElement[] stackTrace = e.getStackTrace(); for (final StackTraceElement stackTraceElement : stackTrace) { assertFalse(stackTraceElement.toString().contains( TestWebserviceExceptionHandling.class.getName() + ".fail")); } assertEquals(0, e.getSuppressed().length); } } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * Test custom exception is relayed correctly without loosing message and * internal fields. * * @throws Exception */ @Test public void testExceptionMappingCustomException() throws Exception { final TestWebserviceExceptionHandling service = clientFactory.getInstance(TestWebserviceExceptionHandling.class); try { service.failWithACustomException(); fail("Should have thrown an exception"); } catch (final TestCustomException e) { assertEquals(10, e.getStatusCode()); // ensure the mixin got applied assertNull(e.getCause()); // ensure server stack trace is masked. // will have local stack trace though. final StackTraceElement[] stackTrace = e.getStackTrace(); for (final StackTraceElement stackTraceElement : stackTrace) { assertFalse(stackTraceElement.toString().contains( TestWebserviceExceptionHandling.class.getName() + ".failWithACustomException")); } assertEquals(0, e.getSuppressed().length); } } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * Test {@link FormDataParam} handling with upload and download of files. * * @throws Exception */ @Test public void testGetInstanceFormDataParam() throws Exception { final TestWebserviceMultipart webserviceClient = clientFactory.getInstance(TestWebserviceMultipart.class); // test input stream upload with content disposition final boolean enabled = new SecureRandom().nextInt() % 2 == 0; final String secret = UUID.randomUUID().toString(); final String testFile = "src/test/resources/UploadTest.txt"; final FormDataContentDisposition disposition = FormDataContentDisposition.name("file").fileName(testFile).build(); @Cleanup final FileInputStream inputStream = getInputStream(testFile); final Map<String, String> result = webserviceClient.multipartMapEcho(enabled, secret, inputStream, disposition); assertEquals(result.get("secret"), secret); assertEquals(result.get("enabled"), ObjectUtils.toString(enabled)); @Cleanup final FileInputStream inputStream1 = getInputStream(testFile); assertEquals(result.get("file"), IOUtils.toString(inputStream1)); assertEquals(result.get("filename"), testFile); assertEquals(result.get("filetype"), "form-data"); // test input stream upload without content disposition final boolean enabled1 = new SecureRandom().nextInt() % 2 == 0; final String secret1 = UUID.randomUUID().toString(); final String testFile1 = "src/test/resources/UploadTest.txt"; final Map<String, String> result1 = webserviceClient.multipartMapEchoWithoutDisposition(enabled1, secret1, getInputStream(testFile1)); assertEquals(result1.get("secret"), secret1); assertEquals(result1.get("enabled"), ObjectUtils.toString(enabled1)); @Cleanup final FileInputStream inputStream2 = getInputStream(testFile); assertEquals(result1.get("file"), IOUtils.toString(inputStream2)); // test simple params final Map<String, String> result11 = webserviceClient.multipartMapEchoPrimitive(enabled1, secret1); assertEquals(result11.get("secret"), secret1); assertEquals(result11.get("enabled"), ObjectUtils.toString(enabled1)); } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * Test get and post methods with a combination of parameter types (query, * path, header, cookie, matrix) * * @throws Exception */ @Test public void testGetInstanceGetAndPost() throws Exception { final TestWebserviceWithPath service = clientFactory.getInstance(TestWebserviceWithPath.class); final Random r = new Random(); final int[] args = new int[] { r.nextInt(), r.nextInt(), r.nextInt() }; @SuppressWarnings("unchecked") final Set<Method> methods = ReflectionUtils.getAllMethods(TestWebserviceWithPath.class, new Predicate<Method>() { @Override public boolean apply(final Method input) { return Modifier.isPublic(input.getModifiers()); } }); for (final Method method : methods) { System.out.println("Invoking : " + method); if (method.getParameterTypes().length == 3) { assertArrayEquals("Invocation failed on " + method, args, (int[]) method.invoke(service, args[0], args[1], args[2])); } else if (method.getParameterTypes().length == 0) { method.invoke(service); } else { assertArrayEquals("Invocation failed on " + method, args, (int[]) method.invoke(service, args)); } System.out.println("Done invoking : " + method); } } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * Test multipart form data handling with upload and download of files. * * @throws Exception */ @Test public void testGetInstanceMultipartBody() throws Exception { final TestWebserviceMultipart webserviceClient = clientFactory.getInstance(TestWebserviceMultipart.class); final FormDataMultiPart multiPartEntity = new FormDataMultiPart(); final String testFile = "src/test/resources/UploadTest.txt"; multiPartEntity.field("enabled", "true").field("secret", UUID.randomUUID().toString()) .bodyPart(new FileDataBodyPart("file", new File(testFile))); final MultiPart result = webserviceClient.multipartEcho(multiPartEntity); assertEquals(multipartToMap(multiPartEntity), multipartToMap(result)); } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * @throws Exception */ @Test(expected = NotRestInterfaceException.class) public void testGetInstanceOnBadClass() throws Exception { clientFactory.getInstance(Object.class); } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * @throws Exception */ @Test public void testGetInstancePutAndDelete() throws Exception { final TestWebserviceWithPutDelete service = clientFactory.getInstance(TestWebserviceWithPutDelete.class); assertTrue(service.put("someURI")); assertTrue(service.delete("someURI")); } /** * Test method for * {@link com.strandls.alchemy.rest.client.AlchemyRestClientFactory#getInstance(java.lang.Class, java.lang.String, javax.ws.rs.client.Client)} * . * * Test get and post methods with a combination of parameter types (query, * path, header, cookie, matrix) * * @throws Exception */ @Test public void testGetInstanceStub() throws Exception { final TestWebserviceWithPathStub service = clientFactory.getInstance(TestWebserviceWithPathStub.class); final Random r = new Random(); final int[] args = new int[] { r.nextInt(), r.nextInt(), r.nextInt() }; @SuppressWarnings("unchecked") final Set<Method> methods = ReflectionUtils.getAllMethods(TestWebserviceWithPathStub.class, new Predicate<Method>() { @Override public boolean apply(final Method input) { return Modifier.isPublic(input.getModifiers()); } }); for (final Method method : methods) { System.out.println("Invoking : " + method); if (method.getParameterTypes().length == 3) { assertArrayEquals("Invocation failed on " + method, args, (int[]) method.invoke(service, args[0], args[1], args[2])); } else { assertArrayEquals("Invocation failed on " + method, args, (int[]) method.invoke(service, args)); } System.out.println("Done invoking : " + method); } } }