All Downloads are FREE. Search and download functionalities are using the official Maven repository.

test.java.com.cloudant.tests.CloudantClientTests Maven / Gradle / Ivy

There is a newer version: 2.20.1
Show newest version
/*
 * Copyright © 2015, 2016 IBM Corp. 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 com.cloudant.tests;

import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.createPost;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import com.cloudant.client.api.ClientBuilder;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
import com.cloudant.client.api.model.ApiKey;
import com.cloudant.client.api.model.Membership;
import com.cloudant.client.api.model.Task;
import com.cloudant.client.org.lightcouch.CouchDbException;
import com.cloudant.client.org.lightcouch.NoDocumentException;
import com.cloudant.client.org.lightcouch.PreconditionFailedException;
import com.cloudant.http.interceptors.BasicAuthInterceptor;
import com.cloudant.http.internal.interceptors.UserAgentInterceptor;
import com.cloudant.test.main.RequiresCloudant;
import com.cloudant.test.main.RequiresCloudantService;
import com.cloudant.test.main.RequiresDB;
import com.cloudant.tests.util.CloudantClientResource;
import com.cloudant.tests.util.MockWebServerResources;
import com.cloudant.tests.util.TestLog;
import com.cloudant.tests.util.Utils;

import org.apache.commons.io.IOUtils;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.net.ServerSocketFactory;

public class CloudantClientTests {

    @ClassRule
    public static final TestLog TEST_LOG = new TestLog();

    @ClassRule
    public static CloudantClientResource clientResource = new CloudantClientResource();
    private CloudantClient account = clientResource.get();

    @Test
    @Category(RequiresCloudantService.class)
    public void apiKey() {
        ApiKey key = account.generateApiKey();
        assertNotNull(key);
        assertNotNull(key.getKey());
        assertNotNull(key.getPassword());
    }

    @Test
    @Category(RequiresCloudant.class)
    public void activeTasks() {
        List tasks = account.getActiveTasks();
        assertNotNull(tasks);
    }

    @Test
    @Category(RequiresCloudant.class)
    public void membership() {
        Membership mship = account.getMembership();
        assertNotNull(mship);
        assertNotNull(mship.getClusterNodes());
        assertNotNull(mship.getClusterNodes().hasNext());
        assertNotNull(mship.getAllNodes());
        assertNotNull(mship.getAllNodes().hasNext());
    }

    @Test
    @Category(RequiresCloudant.class)
    public void cookieTest() {

        Membership membership = account.getMembership();
        assertNotNull(membership);
    }

    // java-cloudant/n.n.n or java-cloudant/unknown followed by 4 groups of /anything
    private final String userAgentRegex = "java-cloudant/(?:(?:\\d+.\\d+.\\d+))" +
            "(?:/{1}[^/]+){4}";

    private final String userAgentUnknownRegex = "cloudant-http/(?:(?:unknown))" +
            "(?:/{1}[^/]+){4}";


    private final String userAgentFormat = "java-cloudant/version/jvm.version/jvm.vendor/os" +
            ".name/os.arch";

    /**
     * Assert that the User-Agent header is of the expected form.
     */
    @Test
    public void testUserAgentHeaderString() throws Exception {

        // This doesn't read the a properties file, since the tests do not run from the published jars.
        String userAgentHeader = new UserAgentInterceptor(UserAgentInterceptor.class.getClassLoader(),
                "META-INF/com.cloudant.client.properties").getUserAgent();
        assertTrue("The value of the User-Agent header: " + userAgentHeader + " should match the " +
                        "format: " + userAgentFormat,
                userAgentHeader.matches(userAgentUnknownRegex));
    }

    @Test
    public void testUserAgentHeaderStringFromFile() throws Exception {
        // This doesn't read the a properties file, since the tests do not run from the published jars.
        // Point to the built classes, it's a bit awkward but we need to load the class cleanly
        File f = new File("../cloudant-http/build/classes/main/");
        String userAgentHeader = new UserAgentInterceptor(new URLClassLoader(new URL[]{f.toURI().toURL()}){

            @Override
            public InputStream getResourceAsStream(String name) {
                if (name.equals("META-INF/com.cloudant.client.properties")){
                    try {
                        return new ByteArrayInputStream("user.agent.name=java-cloudant\nuser.agent.version=1.6.1".getBytes("UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                }
                return super.getResourceAsStream(name);
            }
        }, "META-INF/com.cloudant.client.properties").getUserAgent();
        assertTrue("The value of the User-Agent header: " + userAgentHeader + " should match the " +
                        "format: " + userAgentFormat,
                userAgentHeader.matches(userAgentRegex));
    }

    /**
     * Assert that requests have the User-Agent header added. This test runs a local HTTP server
     * process that can handle a single request to receive the request and validate the header.
     */
    @Test
    public void testUserAgentHeaderIsAddedToRequest() throws Exception {

        MockWebServer server = new MockWebServer();
        server.start();
        //send back an OK 200
        server.enqueue(new MockResponse());
        try {

            //instantiating the client performs a single post request
            CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(server)
                    .build();
            String response = client.executeRequest(createPost(client.getBaseUri(), null,
                    "application/json")).responseAsString();
            assertTrue("There should be no response body on the mock response", response.isEmpty());

            //assert that the request had the expected header
            String userAgentHeader = server.takeRequest(10, TimeUnit.SECONDS)
                    .getHeader("User-Agent");
            assertNotNull("The User-Agent header should be present on the request",
                    userAgentHeader);
            assertTrue("The value of the User-Agent header " + userAgentHeader + " on the request" +
                            " should match the format " + userAgentFormat,
                    userAgentHeader.matches(userAgentUnknownRegex));
        } finally {
            server.shutdown();
        }
    }

    /**
     * Test a NoDocumentException is thrown when trying an operation on a DB that doesn't exist
     */
    @Test(expected = NoDocumentException.class)
    @Category(RequiresDB.class)
    public void nonExistentDatabaseException() {
        //try and get a DB that doesn't exist
        Database db = account.database("not_really_there", false);
        //try an operation against the non-existant DB
        db.info();
    }

    /**
     * Validate that no exception bubbles up when trying to create a DB that already exists
     */
    @Test
    @Category(RequiresDB.class)
    public void existingDatabaseCreateException() {
        String id = Utils.generateUUID();
        String dbName = "existing" + id;
        try {
            //create a DB for this test
            account.createDB(dbName);

            // Get a database instance using create true for the already existing DB
            account.database(dbName, true);
        } finally {
            //clean up the DB created by this test
            account.deleteDB(dbName);
        }
    }

    /**
     * Validate that a PreconditionFailedException is thrown when using the createDB method to
     * create a database that already exists.
     */
    @Test(expected = PreconditionFailedException.class)
    @Category(RequiresDB.class)
    public void existingDatabaseCreateDBException() {
        String id = Utils.generateUUID();
        String dbName = "existing" + id;
        try {
            //create a DB for this test
            account.createDB(dbName);

            //do a get with create true for the already existing DB
            account.createDB(dbName);

        } finally {
            //clean up the DB created by this test
            account.deleteDB(dbName);
        }
    }

    @Test
    public void testDefaultPorts() throws Exception {
        CloudantClient c = null;

        c = CloudantClientHelper.newTestAddressClient().build();

        assertEquals("The http port should be 80", 80, c.getBaseUri().getPort());


        c = CloudantClientHelper.newHttpsTestAddressClient().build();

        assertEquals("The http port should be 443", 443, c.getBaseUri().getPort());
    }

    /**
     * Check that the connection timeout throws a SocketTimeoutException when it can't connect
     * within the timeout.
     */
    @Test(expected = SocketTimeoutException.class)
    public void connectionTimeout() throws Throwable {

        ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0, 1);

        //block the single connection to our server
        Socket socket = new Socket();
        socket.connect(serverSocket.getLocalSocketAddress());

        //now try to connect, but should timeout because there is no connection available
        try {
            CloudantClient c = ClientBuilder.url(new URL("http://127.0.0.1:" + serverSocket
                    .getLocalPort()))
                    .connectTimeout(100, TimeUnit.MILLISECONDS).build();

            // Make a request
            c.getAllDbs();
        } catch (CouchDbException e) {
            //unwrap the CouchDbException
            if (e.getCause() != null) {
                //whilst it would be really nice to actually assert that this was a connect
                //exception and not some other SocketTimeoutException there are JVM differences in
                //this respect (i.e. OpenJDK does not appear to distinguish between read/connect)
                //in its exception messages
                throw e.getCause();
            } else {
                throw e;
            }
        } finally {
            //make sure we close the sockets
            IOUtils.closeQuietly(serverSocket);
            IOUtils.closeQuietly(socket);
        }
    }

    /**
     * Checks that the read timeout works. The test sets a read timeout of 0.25 s and the mock
     * server thread sleeps for twice the duration of the read timeout. If things are working
     * correctly then the client should see a SocketTimeoutException for the read.
     */
    @Test(expected = SocketTimeoutException.class)
    public void readTimeout() throws Throwable {

        final Long READ_TIMEOUT = 250l;

        //start a simple http server
        MockWebServer server = new MockWebServer();
        server.setDispatcher(new MockWebServerResources.SleepingDispatcher(READ_TIMEOUT * 2,
                TimeUnit.MILLISECONDS));

        try {
            server.start();

            try {
                CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(server)
                        .readTimeout(READ_TIMEOUT, TimeUnit.MILLISECONDS).build();

                //do a call that expects a response
                c.getAllDbs();
            } catch (CouchDbException e) {
                //unwrap the CouchDbException
                if (e.getCause() != null) {
                    throw e.getCause();
                } else {
                    throw e;
                }
            }
        } finally {
            server.shutdown();
        }
    }

    /**
     * This tests that a CouchDbException is thrown if the user is null, but the password is
     * supplied.
     */
    @Test(expected = CouchDbException.class)
    public void nullUser() throws Exception {
        CloudantClientHelper.newTestAddressClient()
                .password(":0-myPassword")
                .build();

    }

    /**
     * This tests that a CouchDbException is thrown if the user is supplied, but the password is
     * null.
     */
    @Test(expected = CouchDbException.class)
    public void nullPassword() throws Exception {
        CloudantClientHelper.newTestAddressClient()
                .username("user")
                .build();
    }

    /**
     * Test that user info provided in a url is correctly removed and made into user name and
     * password fields.
     */
    @Test
    public void testUserInfoInUrl() throws Exception {
        ClientBuilder b = ClientBuilder.url(new URL("https://user:[email protected]"));

        //reflectively check (not nice, but better than having a bug)
        Field user = b.getClass().getDeclaredField("username");
        user.setAccessible(true);
        assertEquals("The username should match the one provided in the URL", "user", user.get(b));
        Field pass = b.getClass().getDeclaredField("password");
        pass.setAccessible(true);
        assertEquals("The password should match the one provided in the URL", "password", pass
                .get(b));

        CloudantClient c = b.build();

        assertFalse("The URL should not contain the username", c.getBaseUri().toString().contains
                ("user"));
        assertFalse("The URL should not contain the password", c.getBaseUri().toString().contains
                ("password"));

        //ensure that building a URL from it does not throw any exceptions
        new URL(c.getBaseUri().toString());
    }

    @Test
    public void sessionDeleteOnShutdown() throws Exception {
        MockWebServer server = new MockWebServer();
        // Mock a 200 OK for the _session DELETE request
        server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"ok\":\"true\"}"));

        CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(server).build();
        c.shutdown();

        RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS);
        assertEquals("The request method should be DELETE", "DELETE", request.getMethod());
        assertEquals("The request should be to the _session path", "/_session", request.getPath());
    }

    /**
     * Test that adding the Basic Authentication interceptor to CloudantClient works.
     */
    @Test
    @Category(RequiresCloudant.class)
    public void testBasicAuth() throws IOException {
        BasicAuthInterceptor interceptor =
                new BasicAuthInterceptor(CloudantClientHelper.COUCH_USERNAME
                        + ":" + CloudantClientHelper.COUCH_PASSWORD);

        CloudantClient client = ClientBuilder.account(CloudantClientHelper.COUCH_USERNAME)
                .interceptors(interceptor).build();

        // Test passes if there are no exceptions
        client.getAllDbs();
    }

    @Test
    public void gatewayStyleURL() throws Exception {

        final String gatewayPath = "/gateway";

        MockWebServer server = new MockWebServer();
        // Set a dispatcher that returns 200 if the requests have the correct path /gateway/_all_dbs
        // Otherwise return 400.
        server.setDispatcher(new Dispatcher() {
            @Override
            public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
                if (request.getPath().equals(gatewayPath + "/_all_dbs")) {
                    return new MockResponse();
                } else {
                    return new MockResponse().setResponseCode(400);
                }
            }
        });

        // Build a client with a URL that includes a path
        CloudantClient c = ClientBuilder.url(new URL(server.url(gatewayPath).toString())).build();
        // If the request path is wrong this call will return 400 and throw an exception failing the
        // test.
        c.getAllDbs();

        // Build a client with a URL that includes a path with a trailing /
        c = ClientBuilder.url(new URL(server.url(gatewayPath + "/").toString())).build();
        // If the request path is wrong this call will return 400 and throw an exception failing the
        // test.
        c.getAllDbs();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy