com.strandls.alchemy.rest.client.AlchemyRestClientFactoryTest.java Source code

Java tutorial

Introduction

Here is the source code for com.strandls.alchemy.rest.client.AlchemyRestClientFactoryTest.java

Source

/*
 * 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);
        }
    }
}