com.opentok.test.OpenTokTest.java Source code

Java tutorial

Introduction

Here is the source code for com.opentok.test.OpenTokTest.java

Source

/**
 * OpenTok Java SDK
 * Copyright (C) 2016 TokBox, Inc.
 * http://www.tokbox.com
 *
 * Licensed under The MIT License (MIT). See LICENSE file for more information.
 */
package com.opentok.test;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.any;
import static com.github.tomakehurst.wiremock.client.WireMock.delete;
import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.RequestPatternBuilder;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.ProxySettings;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.opentok.Archive;
import com.opentok.Archive.OutputMode;
import com.opentok.constants.Version;
import com.opentok.ArchiveList;
import com.opentok.ArchiveMode;
import com.opentok.ArchiveProperties;
import com.opentok.MediaMode;
import com.opentok.OpenTok;
import com.opentok.Role;
import com.opentok.Session;
import com.opentok.SessionProperties;
import com.opentok.TokenOptions;
import com.opentok.exception.InvalidArgumentException;
import com.opentok.exception.OpenTokException;

public class OpenTokTest {

    private int apiKey = 123456;
    private String apiSecret = "1234567890abcdef1234567890abcdef1234567890";
    private String apiUrl = "http://localhost:8080";
    private OpenTok sdk;

    @Rule
    public WireMockRule wireMockRule = new WireMockRule(8080);

    @Before
    public void setUp() {

        // read system properties for integration testing
        int anApiKey = 0;
        boolean useMockKey = false;
        String anApiKeyProp = System.getProperty("apiKey");
        String anApiSecret = System.getProperty("apiSecret");
        try {
            anApiKey = Integer.parseInt(anApiKeyProp);
        } catch (NumberFormatException e) {
            useMockKey = true;
        }

        if (!useMockKey && anApiSecret != null && !anApiSecret.isEmpty()) {
            // TODO: figure out when to turn mocking off based on this
            apiKey = anApiKey;
            apiSecret = anApiSecret;
        }
        sdk = new OpenTok(apiKey, apiSecret, apiUrl);
    }

    @Test
    public void testCreateDefaultSession() throws OpenTokException {
        String sessionId = "SESSIONID";
        stubFor(post(urlEqualTo("/session/create")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "text/xml")
                .withBody("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><sessions><Session><"
                        + "session_id>" + sessionId + "</session_id><partner_id>123456</partner_id><create_dt>"
                        + "Mon Mar 17 00:41:31 PDT 2014</create_dt></Session></sessions>")));

        Session session = sdk.createSession();

        assertNotNull(session);
        assertEquals(this.apiKey, session.getApiKey());
        assertEquals(sessionId, session.getSessionId());
        assertEquals(MediaMode.RELAYED, session.getProperties().mediaMode());
        assertEquals(ArchiveMode.MANUAL, session.getProperties().archiveMode());
        assertNull(session.getProperties().getLocation());

        verify(postRequestedFor(urlMatching("/session/create"))
                .withRequestBody(matching(".*p2p.preference=enabled.*"))
                .withRequestBody(matching(".*archiveMode=manual.*")));
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    @Test
    public void testCreateRoutedSession() throws OpenTokException {
        String sessionId = "SESSIONID";
        stubFor(post(urlEqualTo("/session/create")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "text/xml")
                .withBody("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><sessions><Session><"
                        + "session_id>" + sessionId + "</session_id><partner_id>123456</partner_id><create_dt>"
                        + "Mon Mar 17 00:41:31 PDT 2014</create_dt></Session></sessions>")));

        SessionProperties properties = new SessionProperties.Builder().mediaMode(MediaMode.ROUTED).build();
        Session session = sdk.createSession(properties);

        assertNotNull(session);
        assertEquals(this.apiKey, session.getApiKey());
        assertEquals(sessionId, session.getSessionId());
        assertEquals(MediaMode.ROUTED, session.getProperties().mediaMode());
        assertNull(session.getProperties().getLocation());

        verify(postRequestedFor(urlMatching("/session/create"))
                // NOTE: this is a pretty bad way to verify, ideally we can decode the body and then query the object
                .withRequestBody(matching(".*p2p.preference=disabled.*")));
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    @Test
    public void testCreateLocationHintSession() throws OpenTokException {
        String sessionId = "SESSIONID";
        String locationHint = "12.34.56.78";
        stubFor(post(urlEqualTo("/session/create")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "text/xml")
                .withBody("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><sessions><Session><"
                        + "session_id>" + sessionId + "</session_id><partner_id>123456</partner_id><create_dt>"
                        + "Mon Mar 17 00:41:31 PDT 2014</create_dt></Session></sessions>")));

        SessionProperties properties = new SessionProperties.Builder().location(locationHint).build();
        Session session = sdk.createSession(properties);

        assertNotNull(session);
        assertEquals(this.apiKey, session.getApiKey());
        assertEquals(sessionId, session.getSessionId());
        assertEquals(MediaMode.RELAYED, session.getProperties().mediaMode());
        assertEquals(locationHint, session.getProperties().getLocation());

        verify(postRequestedFor(urlMatching("/session/create"))
                // TODO: this is a pretty bad way to verify, ideally we can decode the body and then query the object
                .withRequestBody(matching(".*location=" + locationHint + ".*")));
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    @Test
    public void testCreateAlwaysArchivedSession() throws OpenTokException {
        String sessionId = "SESSIONID";
        String locationHint = "12.34.56.78";
        stubFor(post(urlEqualTo("/session/create")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "text/xml")
                .withBody("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><sessions><Session><"
                        + "session_id>" + sessionId + "</session_id><partner_id>123456</partner_id><create_dt>"
                        + "Mon Mar 17 00:41:31 PDT 2014</create_dt></Session></sessions>")));

        SessionProperties properties = new SessionProperties.Builder().archiveMode(ArchiveMode.ALWAYS).build();
        Session session = sdk.createSession(properties);

        assertNotNull(session);
        assertEquals(this.apiKey, session.getApiKey());
        assertEquals(sessionId, session.getSessionId());
        assertEquals(ArchiveMode.ALWAYS, session.getProperties().archiveMode());

        verify(postRequestedFor(urlMatching("/session/create"))
                // TODO: this is a pretty bad way to verify, ideally we can decode the body and then query the object
                .withRequestBody(matching(".*archiveMode=always.*")));
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    @Test(expected = InvalidArgumentException.class)
    public void testCreateBadSession() throws OpenTokException {
        SessionProperties properties = new SessionProperties.Builder().location("NOT A VALID IP").build();
    }

    //    This is not part of the API because it would introduce a backwards incompatible change.
    //    @Test(expected = InvalidArgumentException.class)
    //    public void testCreateInvalidAlwaysArchivedAndRelayedSession() throws OpenTokException {
    //        SessionProperties properties = new SessionProperties.Builder()
    //                .mediaMode(MediaMode.RELAYED)
    //                .archiveMode(ArchiveMode.ALWAYS)
    //                .build();
    //    }

    // TODO: test session creation conditions that result in errors

    @Test
    public void testTokenDefault() throws OpenTokException, UnsupportedEncodingException, NoSuchAlgorithmException,
            SignatureException, InvalidKeyException {

        int apiKey = 123456;
        String apiSecret = "1234567890abcdef1234567890abcdef1234567890";
        OpenTok opentok = new OpenTok(apiKey, apiSecret);
        String sessionId = "1_MX4xMjM0NTZ-flNhdCBNYXIgMTUgMTQ6NDI6MjMgUERUIDIwMTR-MC40OTAxMzAyNX4";

        String token = opentok.generateToken(sessionId);

        assertNotNull(token);
        assertTrue(Helpers.verifyTokenSignature(token, apiSecret));

        Map<String, String> tokenData = Helpers.decodeToken(token);
        assertEquals(Integer.toString(apiKey), tokenData.get("partner_id"));
        assertNotNull(tokenData.get("create_time"));
        assertNotNull(tokenData.get("nonce"));
    }

    @Test
    public void testTokenRoles() throws OpenTokException, UnsupportedEncodingException, NoSuchAlgorithmException,
            SignatureException, InvalidKeyException {

        int apiKey = 123456;
        String apiSecret = "1234567890abcdef1234567890abcdef1234567890";
        OpenTok opentok = new OpenTok(apiKey, apiSecret);
        String sessionId = "1_MX4xMjM0NTZ-flNhdCBNYXIgMTUgMTQ6NDI6MjMgUERUIDIwMTR-MC40OTAxMzAyNX4";
        Role role = Role.SUBSCRIBER;

        String defaultToken = opentok.generateToken(sessionId);
        String roleToken = opentok.generateToken(sessionId, new TokenOptions.Builder().role(role).build());

        assertNotNull(defaultToken);
        assertNotNull(roleToken);
        assertTrue(Helpers.verifyTokenSignature(defaultToken, apiSecret));
        assertTrue(Helpers.verifyTokenSignature(roleToken, apiSecret));

        Map<String, String> defaultTokenData = Helpers.decodeToken(defaultToken);
        assertEquals("publisher", defaultTokenData.get("role"));
        Map<String, String> roleTokenData = Helpers.decodeToken(roleToken);
        assertEquals(role.toString(), roleTokenData.get("role"));
    }

    @Test
    public void testTokenExpireTime() throws OpenTokException, SignatureException, NoSuchAlgorithmException,
            InvalidKeyException, UnsupportedEncodingException {

        int apiKey = 123456;
        String apiSecret = "1234567890abcdef1234567890abcdef1234567890";
        String sessionId = "1_MX4xMjM0NTZ-flNhdCBNYXIgMTUgMTQ6NDI6MjMgUERUIDIwMTR-MC40OTAxMzAyNX4";
        OpenTok opentok = new OpenTok(apiKey, apiSecret);
        long now = System.currentTimeMillis() / 1000L;
        long inOneHour = now + (60 * 60);
        long inOneDay = now + (60 * 60 * 24);
        long inThirtyDays = now + (60 * 60 * 24 * 30);
        ArrayList<Exception> exceptions = new ArrayList<Exception>();

        String defaultToken = opentok.generateToken(sessionId);
        String oneHourToken = opentok.generateToken(sessionId,
                new TokenOptions.Builder().expireTime(inOneHour).build());
        try {
            String earlyExpireTimeToken = opentok.generateToken(sessionId,
                    new TokenOptions.Builder().expireTime(now - 10).build());
        } catch (Exception exception) {
            exceptions.add(exception);
        }
        try {
            String lateExpireTimeToken = opentok.generateToken(sessionId,
                    new TokenOptions.Builder().expireTime(inThirtyDays + (60 * 60 * 24) /* 31 days */).build());
        } catch (Exception exception) {
            exceptions.add(exception);
        }

        assertNotNull(defaultToken);
        assertNotNull(oneHourToken);
        assertTrue(Helpers.verifyTokenSignature(defaultToken, apiSecret));
        assertTrue(Helpers.verifyTokenSignature(oneHourToken, apiSecret));

        Map<String, String> defaultTokenData = Helpers.decodeToken(defaultToken);
        assertEquals(Long.toString(inOneDay), defaultTokenData.get("expire_time"));
        Map<String, String> oneHourTokenData = Helpers.decodeToken(oneHourToken);
        assertEquals(Long.toString(inOneHour), oneHourTokenData.get("expire_time"));
        assertEquals(2, exceptions.size());
        for (Exception e : exceptions) {
            assertEquals(InvalidArgumentException.class, e.getClass());
        }

    }

    @Test
    public void testTokenConnectionData() throws OpenTokException, SignatureException, NoSuchAlgorithmException,
            InvalidKeyException, UnsupportedEncodingException {

        int apiKey = 123456;
        String apiSecret = "1234567890abcdef1234567890abcdef1234567890";
        String sessionId = "1_MX4xMjM0NTZ-flNhdCBNYXIgMTUgMTQ6NDI6MjMgUERUIDIwMTR-MC40OTAxMzAyNX4";
        OpenTok opentok = new OpenTok(apiKey, apiSecret);
        // purposely contains some exotic characters
        String actualData = "{\"name\":\"%foo  &\"}";
        Exception tooLongException = null;

        String defaultToken = opentok.generateToken(sessionId);
        String dataBearingToken = opentok.generateToken(sessionId,
                new TokenOptions.Builder().data(actualData).build());
        try {
            String dataTooLongToken = opentok.generateToken(sessionId,
                    new TokenOptions.Builder().data(StringUtils.repeat("x", 1001)).build());
        } catch (InvalidArgumentException e) {
            tooLongException = e;
        }

        assertNotNull(defaultToken);
        assertNotNull(dataBearingToken);
        assertTrue(Helpers.verifyTokenSignature(defaultToken, apiSecret));
        assertTrue(Helpers.verifyTokenSignature(dataBearingToken, apiSecret));

        Map<String, String> defaultTokenData = Helpers.decodeToken(defaultToken);
        assertNull(defaultTokenData.get("connection_data"));
        Map<String, String> dataBearingTokenData = Helpers.decodeToken(dataBearingToken);
        assertEquals(actualData, dataBearingTokenData.get("connection_data"));
        assertEquals(InvalidArgumentException.class, tooLongException.getClass());
    }

    @Test
    public void testTokenBadSessionId() throws OpenTokException {
        int apiKey = 123456;
        String apiSecret = "1234567890abcdef1234567890abcdef1234567890";
        OpenTok opentok = new OpenTok(apiKey, apiSecret);
        ArrayList<Exception> exceptions = new ArrayList<Exception>();

        try {
            String nullSessionToken = opentok.generateToken(null);
        } catch (Exception e) {
            exceptions.add(e);
        }
        try {
            String emptySessionToken = opentok.generateToken("");
        } catch (Exception e) {
            exceptions.add(e);
        }
        try {
            String invalidSessionToken = opentok.generateToken("NOT A VALID SESSION ID");
        } catch (Exception e) {
            exceptions.add(e);
        }

        assertEquals(3, exceptions.size());
        for (Exception e : exceptions) {
            assertEquals(InvalidArgumentException.class, e.getClass());
        }
    }

    @Test
    public void testGetArchive() throws OpenTokException {
        String archiveId = "ARCHIVEID";
        stubFor(get(urlEqualTo("/v2/partner/" + this.apiKey + "/archive/" + archiveId)).willReturn(aResponse()
                .withStatus(200).withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395187836000,\n" + "          \"duration\" : 62,\n"
                        + "          \"id\" : \"" + archiveId + "\",\n" + "          \"name\" : \"\",\n"
                        + "          \"partnerId\" : 123456,\n" + "          \"reason\" : \"\",\n"
                        + "          \"sessionId\" : \"SESSIONID\",\n" + "          \"size\" : 8347554,\n"
                        + "          \"status\" : \"available\",\n"
                        + "          \"url\" : \"http://tokbox.com.archive2.s3.amazonaws.com/123456%2F" + archiveId
                        + "%2Farchive.mp4?Expires=1395194362&AWSAccessKeyId=AKIAI6LQCPIXYVWCQV6Q&Si"
                        + "gnature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n" + "        }")));

        Archive archive = sdk.getArchive(archiveId);

        assertNotNull(archive);
        assertEquals(this.apiKey, archive.getPartnerId());
        assertEquals(archiveId, archive.getId());
        assertEquals(1395187836000L, archive.getCreatedAt());
        assertEquals(62, archive.getDuration());
        assertEquals("", archive.getName());
        assertEquals("SESSIONID", archive.getSessionId());
        assertEquals(8347554, archive.getSize());
        assertEquals(Archive.Status.AVAILABLE, archive.getStatus());
        assertEquals(
                "http://tokbox.com.archive2.s3.amazonaws.com/123456%2F" + archiveId + "%2Farchive.mp4?Expires=13951"
                        + "94362&AWSAccessKeyId=AKIAI6LQCPIXYVWCQV6Q&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                archive.getUrl());

        verify(getRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive/" + archiveId)));
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    // TODO: test get archive failure scenarios

    @Test
    public void testListArchives() throws OpenTokException {

        stubFor(get(urlEqualTo("/v2/partner/" + this.apiKey + "/archive")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"count\" : 60,\n" + "          \"items\" : [ {\n"
                        + "            \"createdAt\" : 1395187930000,\n" + "            \"duration\" : 22,\n"
                        + "            \"id\" : \"ef546c5a-4fd7-4e59-ab3d-f1cfb4148d1d\",\n"
                        + "            \"name\" : \"\",\n" + "            \"partnerId\" : 123456,\n"
                        + "            \"reason\" : \"\",\n" + "            \"sessionId\" : \"SESSIONID\",\n"
                        + "            \"size\" : 2909274,\n" + "            \"status\" : \"available\",\n"
                        + "            \"url\" : \"http://tokbox.com.archive2.s3.amazonaws.com/123456%2Fef546c5"
                        + "a-4fd7-4e59-ab3d-f1cfb4148d1d%2Farchive.mp4?Expires=1395188695&AWSAccessKeyId=AKIAI6"
                        + "LQCPIXYVWCQV6Q&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n" + "          }, {\n"
                        + "            \"createdAt\" : 1395187910000,\n" + "            \"duration\" : 14,\n"
                        + "            \"id\" : \"5350f06f-0166-402e-bc27-09ba54948512\",\n"
                        + "            \"name\" : \"\",\n" + "            \"partnerId\" : 123456,\n"
                        + "            \"reason\" : \"\",\n" + "            \"sessionId\" : \"SESSIONID\",\n"
                        + "            \"size\" : 1952651,\n" + "            \"status\" : \"available\",\n"
                        + "            \"url\" : \"http://tokbox.com.archive2.s3.amazonaws.com/123456%2F5350f06"
                        + "f-0166-402e-bc27-09ba54948512%2Farchive.mp4?Expires=1395188695&AWSAccessKeyId=AKIAI6"
                        + "LQCPIXYVWCQV6Q&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n" + "          }, {\n"
                        + "            \"createdAt\" : 1395187836000,\n" + "            \"duration\" : 62,\n"
                        + "            \"id\" : \"f6e7ee58-d6cf-4a59-896b-6d56b158ec71\",\n"
                        + "            \"name\" : \"\",\n" + "            \"partnerId\" : 123456,\n"
                        + "            \"reason\" : \"\",\n" + "            \"sessionId\" : \"SESSIONID\",\n"
                        + "            \"size\" : 8347554,\n" + "            \"status\" : \"available\",\n"
                        + "            \"url\" : \"http://tokbox.com.archive2.s3.amazonaws.com/123456%2Ff6e7ee5"
                        + "8-d6cf-4a59-896b-6d56b158ec71%2Farchive.mp4?Expires=1395188695&AWSAccessKeyId=AKIAI6"
                        + "LQCPIXYVWCQV6Q&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n" + "          }, {\n"
                        + "            \"createdAt\" : 1395183243000,\n" + "            \"duration\" : 544,\n"
                        + "            \"id\" : \"30b3ebf1-ba36-4f5b-8def-6f70d9986fe9\",\n"
                        + "            \"name\" : \"\",\n" + "            \"partnerId\" : 123456,\n"
                        + "            \"reason\" : \"\",\n" + "            \"sessionId\" : \"SESSIONID\",\n"
                        + "            \"size\" : 78499758,\n" + "            \"status\" : \"available\",\n"
                        + "            \"url\" : \"http://tokbox.com.archive2.s3.amazonaws.com/123456%2F30b3ebf"
                        + "1-ba36-4f5b-8def-6f70d9986fe9%2Farchive.mp4?Expires=1395188695&AWSAccessKeyId=AKIAI6"
                        + "LQCPIXYVWCQV6Q&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n" + "          }, {\n"
                        + "            \"createdAt\" : 1394396753000,\n" + "            \"duration\" : 24,\n"
                        + "            \"id\" : \"b8f64de1-e218-4091-9544-4cbf369fc238\",\n"
                        + "            \"name\" : \"showtime again\",\n" + "            \"partnerId\" : 123456,\n"
                        + "            \"reason\" : \"\",\n" + "            \"sessionId\" : \"SESSIONID\",\n"
                        + "            \"size\" : 2227849,\n" + "            \"status\" : \"available\",\n"
                        + "            \"url\" : \"http://tokbox.com.archive2.s3.amazonaws.com/123456%2Fb8f64de"
                        + "1-e218-4091-9544-4cbf369fc238%2Farchive.mp4?Expires=1395188695&AWSAccessKeyId=AKIAI6"
                        + "LQCPIXYVWCQV6Q&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n" + "          }, {\n"
                        + "            \"createdAt\" : 1394321113000,\n" + "            \"duration\" : 1294,\n"
                        + "            \"id\" : \"832641bf-5dbf-41a1-ad94-fea213e59a92\",\n"
                        + "            \"name\" : \"showtime\",\n" + "            \"partnerId\" : 123456,\n"
                        + "            \"reason\" : \"\",\n" + "            \"sessionId\" : \"SESSIONID\",\n"
                        + "            \"size\" : 42165242,\n" + "            \"status\" : \"available\",\n"
                        + "            \"url\" : \"http://tokbox.com.archive2.s3.amazonaws.com/123456%2F832641b"
                        + "f-5dbf-41a1-ad94-fea213e59a92%2Farchive.mp4?Expires=1395188695&AWSAccessKeyId=AKIAI6"
                        + "LQCPIXYVWCQV6Q&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n" + "          } ]\n"
                        + "        }")));

        ArchiveList archives = sdk.listArchives();

        assertNotNull(archives);
        assertEquals(6, archives.size());
        assertEquals(60, archives.getTotalCount());
        assertThat(archives.get(0), instanceOf(Archive.class));
        assertEquals("ef546c5a-4fd7-4e59-ab3d-f1cfb4148d1d", archives.get(0).getId());

        verify(getRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive")));
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    // TODO: test list archives with count and offset

    // TODO: test list archives failure scenarios

    @Test
    public void testStartArchive() throws OpenTokException {
        String sessionId = "SESSIONID";

        stubFor(post(urlEqualTo("/v2/partner/" + this.apiKey + "/archive")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395183243556,\n" + "          \"duration\" : 0,\n"
                        + "          \"id\" : \"30b3ebf1-ba36-4f5b-8def-6f70d9986fe9\",\n"
                        + "          \"name\" : \"\",\n" + "          \"partnerId\" : 123456,\n"
                        + "          \"reason\" : \"\",\n" + "          \"sessionId\" : \"SESSIONID\",\n"
                        + "          \"size\" : 0,\n" + "          \"status\" : \"started\",\n"
                        + "          \"url\" : null\n" + "        }")));

        ArchiveProperties properties = new ArchiveProperties.Builder().name(null).build();
        Archive archive = sdk.startArchive(sessionId, properties);
        assertNotNull(archive);
        assertEquals(sessionId, archive.getSessionId());
        assertNotNull(archive.getId());

        verify(postRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive")));
        // TODO: find a way to match JSON without caring about spacing
        //.withRequestBody(matching(".*"+".*"))
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    @Test
    public void testStartArchiveWithName() throws OpenTokException {
        String sessionId = "SESSIONID";
        String name = "archive_name";

        stubFor(post(urlEqualTo("/v2/partner/" + this.apiKey + "/archive")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395183243556,\n" + "          \"duration\" : 0,\n"
                        + "          \"id\" : \"30b3ebf1-ba36-4f5b-8def-6f70d9986fe9\",\n"
                        + "          \"name\" : \"archive_name\",\n" + "          \"partnerId\" : 123456,\n"
                        + "          \"reason\" : \"\",\n" + "          \"sessionId\" : \"SESSIONID\",\n"
                        + "          \"size\" : 0,\n" + "          \"status\" : \"started\",\n"
                        + "          \"url\" : null\n" + "        }")));

        Archive archive = sdk.startArchive(sessionId, name);
        assertNotNull(archive);
        assertEquals(sessionId, archive.getSessionId());
        assertEquals(name, archive.getName());
        assertNotNull(archive.getId());

        verify(postRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive")));
        // TODO: find a way to match JSON without caring about spacing
        //.withRequestBody(matching(".*"+".*"))
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    @Test
    public void testStartVoiceOnlyArchive() throws OpenTokException {
        String sessionId = "SESSIONID";

        stubFor(post(urlEqualTo("/v2/partner/" + this.apiKey + "/archive")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395183243556,\n" + "          \"duration\" : 0,\n"
                        + "          \"id\" : \"30b3ebf1-ba36-4f5b-8def-6f70d9986fe9\",\n"
                        + "          \"name\" : \"\",\n" + "          \"partnerId\" : 123456,\n"
                        + "          \"reason\" : \"\",\n" + "          \"sessionId\" : \"SESSIONID\",\n"
                        + "          \"size\" : 0,\n" + "          \"status\" : \"started\",\n"
                        + "          \"url\" : null,\n" + "          \"hasVideo\" : false,\n"
                        + "          \"hasAudio\" : true\n" + "        }")));
        ArchiveProperties properties = new ArchiveProperties.Builder().hasVideo(false).build();

        Archive archive = sdk.startArchive(sessionId, properties);
        assertNotNull(archive);
        assertEquals(sessionId, archive.getSessionId());
        assertNotNull(archive.getId());

        verify(postRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive")));
        // TODO: find a way to match JSON without caring about spacing
        //.withRequestBody(matching(".*"+".*"))
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    @Test
    public void testStartComposedArchive() throws OpenTokException {
        String sessionId = "SESSIONID";

        stubFor(post(urlEqualTo("/v2/partner/" + this.apiKey + "/archive")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395183243556,\n" + "          \"duration\" : 0,\n"
                        + "          \"id\" : \"30b3ebf1-ba36-4f5b-8def-6f70d9986fe9\",\n"
                        + "          \"name\" : \"\",\n" + "          \"partnerId\" : 123456,\n"
                        + "          \"reason\" : \"\",\n" + "          \"sessionId\" : \"SESSIONID\",\n"
                        + "          \"size\" : 0,\n" + "          \"status\" : \"started\",\n"
                        + "          \"url\" : null,\n" + "          \"outputMode\" : \"composed\"\n"
                        + "        }")));
        ArchiveProperties properties = new ArchiveProperties.Builder().outputMode(OutputMode.COMPOSED).build();

        Archive archive = sdk.startArchive(sessionId, properties);
        assertNotNull(archive);
        assertEquals(sessionId, archive.getSessionId());
        assertNotNull(archive.getId());
        assertEquals(OutputMode.COMPOSED, archive.getOutputMode());

        verify(postRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive")));
        // TODO: find a way to match JSON without caring about spacing
        //.withRequestBody(matching(".*"+".*"))
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    @Test
    public void testStartIndividualArchive() throws OpenTokException {
        String sessionId = "SESSIONID";

        stubFor(post(urlEqualTo("/v2/partner/" + this.apiKey + "/archive")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395183243556,\n" + "          \"duration\" : 0,\n"
                        + "          \"id\" : \"30b3ebf1-ba36-4f5b-8def-6f70d9986fe9\",\n"
                        + "          \"name\" : \"\",\n" + "          \"partnerId\" : 123456,\n"
                        + "          \"reason\" : \"\",\n" + "          \"sessionId\" : \"SESSIONID\",\n"
                        + "          \"size\" : 0,\n" + "          \"status\" : \"started\",\n"
                        + "          \"url\" : null,\n" + "          \"outputMode\" : \"individual\"\n"
                        + "        }")));

        ArchiveProperties properties = new ArchiveProperties.Builder().outputMode(OutputMode.INDIVIDUAL).build();

        Archive archive = sdk.startArchive(sessionId, properties);
        assertNotNull(archive);
        assertEquals(sessionId, archive.getSessionId());
        assertNotNull(archive.getId());
        assertEquals(OutputMode.INDIVIDUAL, archive.getOutputMode());

        verify(postRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive")));
        // TODO: find a way to match JSON without caring about spacing
        //.withRequestBody(matching(".*"+".*"))
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    // TODO: test start archive with name

    // TODO: test start archive failure scenarios

    @Test
    public void testStopArchive() throws OpenTokException {
        String archiveId = "ARCHIVEID";

        stubFor(post(urlEqualTo("/v2/partner/" + this.apiKey + "/archive/" + archiveId + "/stop"))
                .willReturn(aResponse().withStatus(200).withHeader("Content-Type", "application/json")
                        .withBody("{\n" + "          \"createdAt\" : 1395183243000,\n"
                                + "          \"duration\" : 0,\n" + "          \"id\" : \"ARCHIVEID\",\n"
                                + "          \"name\" : \"\",\n" + "          \"partnerId\" : 123456,\n"
                                + "          \"reason\" : \"\",\n" + "          \"sessionId\" : \"SESSIONID\",\n"
                                + "          \"size\" : 0,\n" + "          \"status\" : \"stopped\",\n"
                                + "          \"url\" : null\n" + "        }")));

        Archive archive = sdk.stopArchive(archiveId);
        assertNotNull(archive);
        assertEquals("SESSIONID", archive.getSessionId());
        assertEquals(archiveId, archive.getId());

        verify(postRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive/" + archiveId + "/stop")));
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    // TODO: test stop archive failure scenarios

    @Test
    public void testDeleteArchive() throws OpenTokException {
        String archiveId = "ARCHIVEID";
        stubFor(delete(urlEqualTo("/v2/partner/" + this.apiKey + "/archive/" + archiveId))
                .willReturn(aResponse().withStatus(204).withHeader("Content-Type", "application/json")));

        sdk.deleteArchive(archiveId);

        verify(deleteRequestedFor(urlMatching("/v2/partner/" + this.apiKey + "/archive/" + archiveId)));
        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();
    }

    // TODO: test delete archive failure scenarios

    // NOTE: this test is pretty sloppy
    @Test
    public void testGetExpiredArchive() throws OpenTokException {
        String archiveId = "ARCHIVEID";
        stubFor(get(urlEqualTo("/v2/partner/" + this.apiKey + "/archive/" + archiveId)).willReturn(aResponse()
                .withStatus(200).withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395187836000,\n" + "          \"duration\" : 62,\n"
                        + "          \"id\" : \"" + archiveId + "\",\n" + "          \"name\" : \"\",\n"
                        + "          \"partnerId\" : 123456,\n" + "          \"reason\" : \"\",\n"
                        + "          \"sessionId\" : \"SESSIONID\",\n" + "          \"size\" : 8347554,\n"
                        + "          \"status\" : \"expired\",\n" + "          \"url\" : null\n" + "        }")));

        Archive archive = sdk.getArchive(archiveId);
        assertNotNull(archive);
        assertEquals(Archive.Status.EXPIRED, archive.getStatus());
    }

    // NOTE: this test is pretty sloppy
    @Test
    public void testGetPausedArchive() throws OpenTokException {
        String archiveId = "ARCHIVEID";
        stubFor(get(urlEqualTo("/v2/partner/" + this.apiKey + "/archive/" + archiveId)).willReturn(aResponse()
                .withStatus(200).withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395187836000,\n" + "          \"duration\" : 62,\n"
                        + "          \"id\" : \"" + archiveId + "\",\n" + "          \"name\" : \"\",\n"
                        + "          \"partnerId\" : 123456,\n" + "          \"reason\" : \"\",\n"
                        + "          \"sessionId\" : \"SESSIONID\",\n" + "          \"size\" : 8347554,\n"
                        + "          \"status\" : \"paused\",\n" + "          \"url\" : null\n" + "        }")));

        Archive archive = sdk.getArchive(archiveId);
        assertNotNull(archive);
        assertEquals(Archive.Status.PAUSED, archive.getStatus());
    }

    @Test
    public void testGetArchiveWithUnknownProperties() throws OpenTokException {
        String archiveId = "ARCHIVEID";
        stubFor(get(urlEqualTo("/v2/partner/" + this.apiKey + "/archive/" + archiveId)).willReturn(aResponse()
                .withStatus(200).withHeader("Content-Type", "application/json")
                .withBody("{\n" + "          \"createdAt\" : 1395187836000,\n" + "          \"duration\" : 62,\n"
                        + "          \"id\" : \"" + archiveId + "\",\n" + "          \"name\" : \"\",\n"
                        + "          \"partnerId\" : 123456,\n" + "          \"reason\" : \"\",\n"
                        + "          \"sessionId\" : \"SESSIONID\",\n" + "          \"size\" : 8347554,\n"
                        + "          \"status\" : \"expired\",\n" + "          \"url\" : null,\n"
                        + "          \"thisisnotaproperty\" : null\n" + "        }")));

        Archive archive = sdk.getArchive(archiveId);

        assertNotNull(archive);
    }

    @Test
    public void testCreateSessionWithProxy() throws OpenTokException, UnknownHostException {
        WireMockConfiguration proxyConfig = WireMockConfiguration.wireMockConfig();
        proxyConfig.dynamicPort();
        WireMockServer proxyingService = new WireMockServer(proxyConfig);
        proxyingService.start();
        WireMock proxyingServiceAdmin = new WireMock(proxyingService.port());

        String targetServiceBaseUrl = "http://localhost:" + wireMockRule.port();
        proxyingServiceAdmin.register(
                any(urlMatching(".*")).atPriority(10).willReturn(aResponse().proxiedFrom(targetServiceBaseUrl)));

        String sessionId = "SESSIONID";
        Proxy proxy = new Proxy(Proxy.Type.HTTP,
                new InetSocketAddress(InetAddress.getLocalHost(), proxyingService.port()));

        sdk = new OpenTok(apiKey, apiSecret, apiUrl, proxy);
        stubFor(post(urlEqualTo("/session/create")).willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "text/xml")
                .withBody("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><sessions><Session><"
                        + "session_id>" + sessionId + "</session_id><partner_id>123456</partner_id><create_dt>"
                        + "Mon Mar 17 00:41:31 PDT 2014</create_dt></Session></sessions>")));

        Session session = sdk.createSession();

        assertNotNull(session);
        assertEquals(this.apiKey, session.getApiKey());
        assertEquals(sessionId, session.getSessionId());

        verify(postRequestedFor(urlMatching("/session/create")));

        Helpers.verifyPartnerAuth(this.apiKey, this.apiSecret);
        Helpers.verifyUserAgent();

    }

}