com.neiljbrown.brighttalk.channels.reportingapi.client.spring.SpringApiClientImplIntegrationTest.java Source code

Java tutorial

Introduction

Here is the source code for com.neiljbrown.brighttalk.channels.reportingapi.client.spring.SpringApiClientImplIntegrationTest.java

Source

/*
 * Copyright 2014-present the original author or authors.
 *
 * 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.neiljbrown.brighttalk.channels.reportingapi.client.spring;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withBadRequest;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withUnauthorizedRequest;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplate;

import com.neiljbrown.brighttalk.channels.reportingapi.client.ApiErrorResponseException;
import com.neiljbrown.brighttalk.channels.reportingapi.client.PageCriteria;
import com.neiljbrown.brighttalk.channels.reportingapi.client.common.ApiDateTimeFormatter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.ChannelResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.ChannelSubscriberResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.ChannelSubscribersResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.ChannelsResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.EmbedXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.LinkXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.QuestionXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.SubscriberWebcastActivityResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.SubscribersWebcastActivityResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.SurveyResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.SurveyResponseResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.SurveyResponsesResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.SurveysResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.UserXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.WebcastRegistrationResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.WebcastRegistrationsResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.WebcastResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.WebcastViewingResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.WebcastViewingsResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.marshall.WebcastsResourceXStreamConverter;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.ApiError;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.ChannelResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.ChannelSubscriberResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.ChannelSubscribersResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.ChannelsResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.Embed;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.Link;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.Question;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.SubscriberWebcastActivityResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.SubscribersWebcastActivityResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.SurveyResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.SurveyResponseResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.SurveyResponsesResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.SurveysResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.User;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.WebcastRegistrationResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.WebcastRegistrationsResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.WebcastResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.WebcastStatus;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.WebcastViewingResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.WebcastViewingsResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.resource.WebcastsResource;
import com.neiljbrown.brighttalk.channels.reportingapi.client.spring.AppConfig;
import com.neiljbrown.brighttalk.channels.reportingapi.client.spring.SpringApiClientImpl;
import com.neiljbrown.brighttalk.channels.reportingapi.client.support.LinkRelationType;
import com.neiljbrown.brighttalk.channels.reportingapi.client.support.Links;
import com.thoughtworks.xstream.XStream;

/**
 * Integration tests for {@link SpringApiClientImpl}.
 * <p>
 * These tests load the application's Spring application context using the {@link SpringJUnit4ClassRunner}. In addition
 * to testing the Spring bean configuration, this tests the use of the production configured instance of the
 * RestTemplate.
 * <p>
 * The test case uses the Spring MVC Test frameworks {@link MockRestServiceServer} to provide a dynamically mocked
 * implementation of the BrightTALK Reporting API service, assert the expected API requests, and specify stubbed
 * responses. When using the MockRestServiceServer, the RestTemplate is exercised exactly as it would be if it were
 * making HTTP requests to the real API service. The test coverage provided by these tests therefore include marshalling
 * and unmarshalling the HTTP request and response bodies, exercising the API resource objects (DTOs) and their
 * configured marshallers and marshalling annotations in the process.
 * <p>
 * The MockRestServiceServer works by substituting the Spring {@link ClientHttpRequestFactory} for a mock
 * implementation. As a consequence, the coverage of these tests does NOT extend as far as testing the production HTTP
 * client and its configuration (e.g. support for basic auth and compression). This requires a separate set of,
 * (end-to-end integration or functional) tests.
 * 
 * @author Neil Brown
 * @see SpringApiClientImplStubbedApiServiceIntegrationTest
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { AppConfig.class })
// Avoid reuse of injected RestTemplate in later tests as it's configured to use a MockClientHttpRequestFactory
@DirtiesContext
public class SpringApiClientImplIntegrationTest {

    private static final Logger logger = LoggerFactory.getLogger(SpringApiClientImplIntegrationTest.class);

    /** Instance of class under test */
    @Autowired
    private SpringApiClientImpl apiClient;

    /**
     * Instance of {@link RestTemplate} that both the instance of the {@link SpringApiClientImpl} class under test, and
     * the {@link #mockReportingApiService} are configured to use.
     */
    @Autowired
    @Qualifier("apiClientRestTemplate")
    private RestTemplate restTemplate;

    /**
     * Dynamic mock implementation of the BrightTALK Reporting API service, implemented using the Spring MVC Test
     * frameworks {@link MockRestServiceServer}.
     */
    private MockRestServiceServer mockReportingApiService;

    /** Instance of {@link XStream} used to unmarshall (deserialise) canned API response payloads used by tests. */
    private XStream xstream;

    /**
     * @throws Exception If an unexpected exception occurs.
     */
    @Before
    public void setUp() throws Exception {
        this.mockReportingApiService = MockRestServiceServer.createServer(this.restTemplate);
        this.initXStream();
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} in the case where HTTP response code 401 Unauthorized is returned.
     * This can occur if the API credentials that the API client is configured to use are deemed invalid by the API
     * service.
     */
    @Test
    public void getMyChannelsWhenAuthenticationFails() {
        String expectedRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.MY_CHANNELS_RELATIVE_URI_TEMPLATE;

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withUnauthorizedRequest());

        try {
            this.apiClient.getMyChannels(null);
            fail("Expected exception to be thrown.");
        } catch (ApiErrorResponseException e) {
            assertThat(e.getStatusCode(), is(HttpStatus.UNAUTHORIZED.value()));
        }

        this.mockReportingApiService.verify();
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} when the API user has zero channels.
     */
    @Test
    public void getMyChannelsWhenZeroChannels() {
        String expectedRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.MY_CHANNELS_RELATIVE_URI_TEMPLATE;

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<channels/>", MediaType.APPLICATION_XML));

        // Perform the test
        ChannelsResource channelsResource = this.apiClient.getMyChannels(null);

        this.mockReportingApiService.verify();
        assertThat(channelsResource, notNullValue());
        assertThat(channelsResource.getChannels(), hasSize(0));
        assertThat(channelsResource.getLinks(), hasSize(0));
    }

    /**
     * Tests an API call, such as {@link SpringApiClientImpl#getMyChannels}, when page criteria is supplied without a next
     * page link, i.e. it is criteria for the first page of resources which is only used to specificy a non-default page
     * size.
     */
    @Test
    public void getMyChannelsWhenPageCriteriaSuppliedForFirstPage() {
        int pageSize = 100;
        PageCriteria firstPageCriteria = new PageCriteria(pageSize);
        String expectedRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.MY_CHANNELS_RELATIVE_URI_TEMPLATE + "?pageSize=" + pageSize;

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<channels/>", MediaType.APPLICATION_XML));

        // Perform the test
        ChannelsResource channelsResource = this.apiClient.getMyChannels(firstPageCriteria);

        this.mockReportingApiService.verify();
        assertThat(channelsResource, notNullValue());
        assertThat(channelsResource.getChannels(), hasSize(0));
        assertThat(channelsResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} when the first page of returned resources contains multiple
     * channels, and the total no. of channels is greater than the API page size, resulting in a 'next' page link.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getMyChannelsWhenMultipleChannelsWithNextPage() throws Exception {
        String expectedRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.MY_CHANNELS_RELATIVE_URI_TEMPLATE;

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getMyChannelsMultipleChannelsWithNextPage-response.xml", this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        ChannelsResource channelsResource = this.apiClient.getMyChannels(null);

        this.mockReportingApiService.verify();
        assertThat(channelsResource, notNullValue());
        assertThat(channelsResource.getChannels(), hasSize(2));

        // Assert all fields in the first of the returned Resource
        ChannelsResource expectedChannelsResource = (ChannelsResource) this.xstream
                .fromXML(responseBody.getInputStream());
        ChannelResource expectedChannel = expectedChannelsResource.getChannels().get(0);
        // Relies on overridden ChannelResource.equals() to test for equality by value
        assertThat(channelsResource.getChannels().get(0), is(expectedChannel));

        // For the second of the two returned Resource just check its not the same as the first
        assertThat(channelsResource.getChannels().get(0).getId(),
                not(channelsResource.getChannels().get(1).getId()));

        assertThat(channelsResource.getLinks(), hasSize(1));
        assertThat(channelsResource.getLinks().get(0), is(expectedChannelsResource.getLinks().get(0)));
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} with paging criteria that includes a next page link and a
     * non-default page size. The API responds with the final (last) page containing a single Channel.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getMyChannelsWhenNextPageWithNonDefaultPageSizeReturnsLastPage() throws Exception {
        int pageSize = 50;
        String expectedRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.MY_CHANNELS_RELATIVE_URI_TEMPLATE + "?cursor=1234&pageSize=" + pageSize;
        PageCriteria pageCriteria = createPageCriteria(pageSize, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getMyChannelsNextPageWithNonDefaultPageSizeReturnsLastPageSingleChannel-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        ChannelsResource channelsResource = this.apiClient.getMyChannels(pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(channelsResource, notNullValue());
        assertThat(channelsResource.getChannels(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        ChannelsResource expectedChannelsResource = (ChannelsResource) this.xstream
                .fromXML(responseBody.getInputStream());
        ChannelResource expectedChannel = expectedChannelsResource.getChannels().get(0);
        // Relies on overridden ChannelResource.equals() to test for equality by value
        assertThat(channelsResource.getChannels().get(0), is(expectedChannel));

        assertThat(channelsResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} in the case where the API service returns a response containing a
     * field value which cannot be converted to the expected type as defined in the API resource class. In this case,
     * return of a {@link ChannelResource} with a non-numeric identifier is tested.
     */
    @Test
    public void getMyChannelsInvalidResponseTypeMismatchChannelId() {
        String expectedRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.MY_CHANNELS_RELATIVE_URI_TEMPLATE;

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getMyChannelsInvalidResponseTypeMismatchChannelId-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Default JAXB behaviour is to treat this as a non-fatal unmarshalling error, resulting in a default value field
        ChannelsResource channelsResource = this.apiClient.getMyChannels(null);

        this.mockReportingApiService.verify();
        assertThat(channelsResource, notNullValue());
        assertThat(channelsResource.getChannels(), hasSize(1));
        assertThat(channelsResource.getChannels().get(0).getId(), is(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} in the case where the API service returns a response containing a
     * field value which cannot be converted to the expected type as defined in the API resource class. In this case,
     * return of a {@link ChannelResource} with created date/time string which can't be converted to date is tested.
     */
    @Test
    public void getMyChannelsInvalidResponseTypeMismatchChannelCreatedDate() {
        String expectedRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.MY_CHANNELS_RELATIVE_URI_TEMPLATE;

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getMyChannelsInvalidResponseTypeMismatchChannelCreatedDate-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        ChannelsResource channelsResource = this.apiClient.getMyChannels(null);

        // Default JAXB behaviour is to treat this as a non-fatal unmarshalling error, resulting in a null field
        this.mockReportingApiService.verify();
        assertThat(channelsResource, notNullValue());
        assertThat(channelsResource.getChannels().get(0), notNullValue());
        assertThat(channelsResource.getChannels().get(0).getCreated(), nullValue());
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} in the case where the API service returns an HTTP 500 Internal
     * Server error, as a result of an unexpected system error.
     */
    @Test
    public void getMyChannelsWhenInternalServerError() {
        String expectedRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.MY_CHANNELS_RELATIVE_URI_TEMPLATE;

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withServerError());

        try {
            this.apiClient.getMyChannels(null);
            fail("Expected exception to be thrown.");
        } catch (ApiErrorResponseException e) {
            assertThat(e.getStatusCode(), is(HttpStatus.INTERNAL_SERVER_ERROR.value()));
        }

        this.mockReportingApiService.verify();
    }

    /**
     * Test {@link SpringApiClientImpl#getUserChannels(int, PageCriteria)} in the case where the identified user has zero
     * channels.
     */
    @Test
    public void getUserChanelsWhenZeroChannels() {
        int userId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelsResource.USER_CHANNELS_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(userId).toString();

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<channels/>", MediaType.APPLICATION_XML));

        // Perform the test
        ChannelsResource channelsResource = this.apiClient.getUserChannels(userId, null);

        this.mockReportingApiService.verify();
        assertThat(channelsResource, notNullValue());
        assertThat(channelsResource.getChannels(), hasSize(0));
        assertThat(channelsResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} in the case where HTTP response code 403 Forbidden is returned.
     * This can occur if the requesting API user is not permitted to access the requested BrightTALK Channel.
     */
    @Test
    public void getChannelSubscribersWhenAuthorisationFails() {
        int channelId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelSubscribersResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        ApiError apiError = new ApiError("NotAuthorisedForChannel",
                "Not authorised for channel [" + channelId + "].");
        String responseBody = apiErrorToXml(apiError);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(
                        withStatus(HttpStatus.FORBIDDEN).body(responseBody).contentType(MediaType.APPLICATION_XML));

        try {
            this.apiClient.getChannelSubscribers(channelId, null, null, null, null);
            fail("Expected exception to be thrown.");
        } catch (ApiErrorResponseException e) {
            assertThat(e.getStatusCode(), is(HttpStatus.FORBIDDEN.value()));
            assertThat(e.getApiError(), is(apiError));
        }

        this.mockReportingApiService.verify();
    }

    /**
     * Test {@link SpringApiClientImpl#getChannelSubscribers} in the case where there identified channel has zero
     * subscribers.
     */
    @Test
    public void getChannelSubscribersWhenZeroSubscribers() {
        int channelId = 1;
        Boolean subscribed = false;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelSubscribersResource.RELATIVE_URI_TEMPLATE + "?subscribed=" + subscribed;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<channelSubscribers/>", MediaType.APPLICATION_XML));

        // Perform the test
        ChannelSubscribersResource subscribersResource = this.apiClient.getChannelSubscribers(channelId, subscribed,
                null, null, null);

        this.mockReportingApiService.verify();
        assertThat(subscribersResource, notNullValue());
        assertThat(subscribersResource.getChannelSubscribers(), hasSize(0));
        assertThat(subscribersResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} when retrieving all current and past subscribers and the first page
     * of returned resources contains multiple subscribers, and the total no. of subscribers is greater than the API page
     * size, resulting in a 'next' page link.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getChannelSubscribersWhenMultipleCurrentAndPastSusbcribersWithNextPage() throws Exception {
        int channelId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelSubscribersResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getChannelSubscribersWhenMultipleCurrentAndPastSusbcribersWithNextPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        ChannelSubscribersResource subscribersResource = this.apiClient.getChannelSubscribers(channelId, null, null,
                null, null);

        this.mockReportingApiService.verify();
        assertThat(subscribersResource, notNullValue());
        assertThat(subscribersResource.getChannelSubscribers(), hasSize(2));

        // Assert all fields in the first of the returned Resource
        ChannelSubscribersResource expectedSubscribersResource = (ChannelSubscribersResource) this.xstream
                .fromXML(responseBody.getInputStream());
        ChannelSubscriberResource expectedSubscriber = expectedSubscribersResource.getChannelSubscribers().get(0);
        // Relies on overridden ChannelResource.equals() to test for equality by value
        assertThat(subscribersResource.getChannelSubscribers().get(0), is(expectedSubscriber));

        // For the second of the two returned Resource just check its not the same as the first
        assertThat(subscribersResource.getChannelSubscribers().get(0).getId(),
                not(subscribersResource.getChannelSubscribers().get(1).getId()));

        assertThat(subscribersResource.getLinks(), hasSize(1));
        assertThat(subscribersResource.getLinks().get(0), is(expectedSubscribersResource.getLinks().get(0)));
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} when a request is made for the next page of all of the current
     * subscribers that subscribed since a specified date/time.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getChannelSubscribersWhenSubscribedSinceNextPage() throws Exception {
        int channelId = 1;
        Boolean subscribed = true;
        String subscribedSinceAsString = "2014-06-14T19:26:10Z";
        Date subscribedSinceDate = new ApiDateTimeFormatter().parse(subscribedSinceAsString);
        int pageSize = 200;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelSubscribersResource.RELATIVE_URI_TEMPLATE + "?subscribed=" + subscribed
                + "&subscribedSince=" + subscribedSinceAsString + "&cursor=1234&pageSize=" + pageSize;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();
        PageCriteria pageCriteria = createPageCriteria(pageSize, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getChannelSubscribersWhenSubscribedSinceNextPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        ChannelSubscribersResource subscribersResource = this.apiClient.getChannelSubscribers(channelId, subscribed,
                subscribedSinceDate, null, pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(subscribersResource, notNullValue());
        assertThat(subscribersResource.getChannelSubscribers(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        ChannelSubscribersResource expectedSubscribersResource = (ChannelSubscribersResource) this.xstream
                .fromXML(responseBody.getInputStream());
        ChannelSubscriberResource expectedSubscriberResource = expectedSubscribersResource.getChannelSubscribers()
                .get(0);
        // Relies on overridden ChannelResource.equals() to test for equality by value
        assertThat(subscribersResource.getChannelSubscribers().get(0), is(expectedSubscriberResource));

        assertThat(subscribersResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getMyChannels} when a request is made for the next page of all of the past
     * subscribers that have unsubscribed since a specified date/time.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getChannelSubscribersWhenUnsubscribedSinceNextPage() throws Exception {
        int channelId = 1;
        String unsubscribedSinceAsString = "2014-06-16T22:06:40Z";
        Date unsubscribedSinceDate = new ApiDateTimeFormatter().parse(unsubscribedSinceAsString);
        int pageSize = 200;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelSubscribersResource.RELATIVE_URI_TEMPLATE + "?unsubscribedSince="
                + unsubscribedSinceAsString + "&cursor=1234&pageSize=" + pageSize;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();
        PageCriteria pageCriteria = createPageCriteria(pageSize, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getChannelSubscribersWhenSubscribedSinceNextPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        ChannelSubscribersResource subscribersResource = this.apiClient.getChannelSubscribers(channelId, null, null,
                unsubscribedSinceDate, pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(subscribersResource, notNullValue());
        assertThat(subscribersResource.getChannelSubscribers(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        ChannelSubscribersResource expectedSubscribersResource = (ChannelSubscribersResource) this.xstream
                .fromXML(responseBody.getInputStream());
        ChannelSubscriberResource expectedSubscriberResource = expectedSubscribersResource.getChannelSubscribers()
                .get(0);
        // Relies on overridden ChannelResource.equals() to test for equality by value
        assertThat(subscribersResource.getChannelSubscribers().get(0), is(expectedSubscriberResource));

        assertThat(subscribersResource.getLinks(), hasSize(0));
    }

    /**
     * Test {@link SpringApiClientImpl#getChannelSubscribers} in the case when the requested channel does not exist.
     */
    @Test
    public void getChannelSubscribersWhenChannelNotFound() {
        int channelId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + ChannelSubscribersResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        // Configure mock API service to respond to API call with a canned collection of Channel resources read from file
        ApiError apiError = new ApiError("ChannelNotFound", "Channel [" + channelId + "] not found");
        String responseBody = apiErrorToXml(apiError);
        MediaType mediaType = MediaType.APPLICATION_XML;
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withBadRequest().body(responseBody).contentType(mediaType));

        // Perform the test
        try {
            this.apiClient.getChannelSubscribers(channelId, null, null, null, null);
            fail("Expected exception to be thrown.");
        } catch (ApiErrorResponseException e) {
            assertThat(e.getStatusCode(), is(HttpStatus.BAD_REQUEST.value()));
            assertThat(e.getApiError(), is(apiError));
        }

        this.mockReportingApiService.verify();
    }

    /**
     * Test {@link SpringApiClientImpl#getSubscribersWebcastActivityForWebcast} in the case where there is no activity for
     * the identified webcast.
     */
    @Test
    public void getSubscribersWebcastActivityForWebcastWhenZeroActivity() {
        int channelId = 1;
        int webcastId = 2;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SubscribersWebcastActivityResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<subscribersWebcastActivity/>", MediaType.APPLICATION_XML));

        // Perform the test
        SubscribersWebcastActivityResource subscribersWebcastActivityResource = this.apiClient
                .getSubscribersWebcastActivityForWebcast(channelId, webcastId, null, null, null);

        this.mockReportingApiService.verify();
        assertThat(subscribersWebcastActivityResource, notNullValue());
        assertThat(subscribersWebcastActivityResource.getSubscriberWebcastActivities(), hasSize(0));
        assertThat(subscribersWebcastActivityResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getSubscribersWebcastActivityForWebcast} when the first page of returned resources
     * contains multiple Subscriber Webcast Activity, and the total no. of activities is greater than the API page size,
     * resulting in a 'next' page link.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getSubscribersWebcastActivityForWebcastWhenMultipleActivitiesWithNextPage() throws Exception {
        int channelId = 1;
        int webcastId = 2;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SubscribersWebcastActivityResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getChannelSubscribersWebcastActivityForWebcastWhenMultipleActivitiesWithNextPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SubscribersWebcastActivityResource subscribersWebcastActivityResource = this.apiClient
                .getSubscribersWebcastActivityForWebcast(channelId, webcastId, null, null, null);

        this.mockReportingApiService.verify();
        assertThat(subscribersWebcastActivityResource, notNullValue());
        assertThat(subscribersWebcastActivityResource.getSubscriberWebcastActivities(), hasSize(2));

        // Assert all fields in the first of the returned Resource
        SubscribersWebcastActivityResource expectedsubscribersWebcastActivityResource = (SubscribersWebcastActivityResource) this.xstream
                .fromXML(responseBody.getInputStream());
        SubscriberWebcastActivityResource expectedSWA = expectedsubscribersWebcastActivityResource
                .getSubscriberWebcastActivities().get(0);
        // Relies on overridden SubscriberWebcastActivityResource.equals() to test for equality by value
        assertThat(subscribersWebcastActivityResource.getSubscriberWebcastActivities().get(0), is(expectedSWA));

        // For the second of the two returned Resource just check its not the same as the first
        assertThat(subscribersWebcastActivityResource.getSubscriberWebcastActivities().get(0).getId(),
                not(subscribersWebcastActivityResource.getSubscriberWebcastActivities().get(1).getId()));

        assertThat(subscribersWebcastActivityResource.getLinks(), hasSize(1));
        assertThat(subscribersWebcastActivityResource.getLinks().get(0),
                is(expectedsubscribersWebcastActivityResource.getLinks().get(0)));
    }

    /**
     * Tests {@link SpringApiClientImpl#getSubscribersWebcastActivityForWebcast} with paging criteria that includes a next
     * page link and a non-default page size. The API responds with the final (last) page containing a single Subscriber
     * Webcast Activity.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getSubscribersWebcastActivityForWebcastWhenNextPageWithNonDefaultPageSizeReturnsLastPage()
            throws Exception {
        int channelId = 1;
        int webcastId = 2;
        int pageSize = 200;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SubscribersWebcastActivityResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE + "?cursor=1234&pageSize="
                + pageSize;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();
        PageCriteria pageCriteria = createPageCriteria(pageSize, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getChannelSubscribersWebcastActivityForWebcastWhenNextPageWithNonDefaultPageSizeReturnsLastPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SubscribersWebcastActivityResource subscribersWebcastActivityResource = this.apiClient
                .getSubscribersWebcastActivityForWebcast(channelId, webcastId, null, null, pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(subscribersWebcastActivityResource, notNullValue());
        assertThat(subscribersWebcastActivityResource.getSubscriberWebcastActivities(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        SubscribersWebcastActivityResource expectedsubscribersWebcastActivityResource = (SubscribersWebcastActivityResource) this.xstream
                .fromXML(responseBody.getInputStream());
        SubscriberWebcastActivityResource expectedSWA = expectedsubscribersWebcastActivityResource
                .getSubscriberWebcastActivities().get(0);
        // Relies on overridden SubscriberWebcastActivityResource.equals() to test for equality by value
        assertThat(subscribersWebcastActivityResource.getSubscriberWebcastActivities().get(0), is(expectedSWA));

        assertThat(subscribersWebcastActivityResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getSubscribersWebcastActivityForWebcast} when a request is made for the next page
     * of Subscriber Webcast Activity which have been created or updated since a specified date/time, and the Activity
     * should be expanded to include the subscriber's channel survey response, if they have one.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getSubscribersWebcastActivityForWebcastWhenSinceNextPageAndExpandChannelSurveyResponse()
            throws Exception {
        int channelId = 1;
        int webcastId = 2;
        String sinceString = "2014-06-28T21:24:59Z";
        boolean expandChannelSurveyResponse = true;
        Date sinceDate = new ApiDateTimeFormatter().parse(sinceString);
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SubscribersWebcastActivityResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE + "?since=" + sinceString
                + "&expand=channelSurveyResponse&cursor=1234";
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();
        PageCriteria pageCriteria = createPageCriteria(null, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getChannelSubscribersWebcastActivityForWebcastWhenSinceNextPageAndExpandChannelSurveyResponse-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SubscribersWebcastActivityResource subscribersWebcastActivityResource = this.apiClient
                .getSubscribersWebcastActivityForWebcast(channelId, webcastId, sinceDate,
                        expandChannelSurveyResponse, pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(subscribersWebcastActivityResource, notNullValue());

        // Assert all fields in the first of the returned Resource
        SubscribersWebcastActivityResource expectedsubscribersWebcastActivityResource = (SubscribersWebcastActivityResource) this.xstream
                .fromXML(responseBody.getInputStream());
        SubscriberWebcastActivityResource expectedSWA = expectedsubscribersWebcastActivityResource
                .getSubscriberWebcastActivities().get(0);
        // Relies on overridden SubscriberWebcastActivityResource.equals() to test for equality by value
        assertThat(subscribersWebcastActivityResource.getSubscriberWebcastActivities().get(0), is(expectedSWA));

        assertThat(subscribersWebcastActivityResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getSubscribersWebcastActivityForChannel} when a request is made for the next page
     * of Subscriber Webcast Activity which have been created or updated since a specified date/time, and the Activity
     * should be expanded to include the subscriber's channel survey response, if they have one.
     * 
     * @throws Exception If an unexpected error occurs.
     * @see #getChannelSubscribersWebcastActivityForWebcastWhenSinceNextPageAndExpandChannelSurveyResponse
     */
    @Test
    public void getSubscribersWebcastActivityForChannelWhenSinceNextPageAndExpandChannelSurveyResponse()
            throws Exception {
        int channelId = 1;
        String sinceString = "2014-06-28T21:24:59Z";
        boolean expandChannelSurveyResponse = true;
        Date sinceDate = new ApiDateTimeFormatter().parse(sinceString);
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SubscribersWebcastActivityResource.FOR_CHANNEL_RELATIVE_URI_TEMPLATE + "?since=" + sinceString
                + "&expand=channelSurveyResponse&cursor=1234";
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();
        PageCriteria pageCriteria = createPageCriteria(null, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getChannelSubscribersWebcastActivityForChannelWhenSinceNextPageAndExpandChannelSurveyResponse-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SubscribersWebcastActivityResource subscribersWebcastActivityResource = this.apiClient
                .getSubscribersWebcastActivityForChannel(channelId, sinceDate, expandChannelSurveyResponse,
                        pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(subscribersWebcastActivityResource, notNullValue());

        // Assert all fields in the first of the returned Resource
        SubscribersWebcastActivityResource expectedsubscribersWebcastActivityResource = (SubscribersWebcastActivityResource) this.xstream
                .fromXML(responseBody.getInputStream());
        SubscriberWebcastActivityResource expectedSWA = expectedsubscribersWebcastActivityResource
                .getSubscriberWebcastActivities().get(0);
        // Relies on overridden SubscriberWebcastActivityResource.equals() to test for equality by value
        assertThat(subscribersWebcastActivityResource.getSubscriberWebcastActivities().get(0), is(expectedSWA));

        assertThat(subscribersWebcastActivityResource.getLinks(), hasSize(0));
    }

    /**
     * Test {@link SpringApiClientImpl#getSurveysForChannel} when the identified channel does not have a survey.
     */
    @Test
    public void getSurveysForChannelWhenNoSurveyFound() {
        int channelId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SurveysResource.FOR_CHANNELS_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<surveys/>", MediaType.APPLICATION_XML));

        // Perform the test
        SurveysResource surveysResource = this.apiClient.getSurveysForChannel(channelId);

        this.mockReportingApiService.verify();
        assertThat(surveysResource, notNullValue());
        assertThat(surveysResource.getSurveys(), hasSize(0));
    }

    /**
     * Test {@link SpringApiClientImpl#getSurveysForChannel} when the identified channel has a single survey which
     * comprises multiple questions of every supported type, some with multiple options.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getSurveysForChannelWhenSingleSurveyMultipleQuestionsOfEveryType() throws Exception {
        int channelId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SurveysResource.FOR_CHANNELS_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getSurveysForChannelWhenSingleSurveyMultipleQuestionsOfEveryType-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SurveysResource surveysResource = this.apiClient.getSurveysForChannel(channelId);

        this.mockReportingApiService.verify();
        assertThat(surveysResource, notNullValue());
        assertThat(surveysResource.getSurveys(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        SurveysResource expectedSurveysResource = (SurveysResource) this.xstream
                .fromXML(responseBody.getInputStream());
        SurveyResource expectedSurveyResource = expectedSurveysResource.getSurveys().get(0);
        // Relies on overridden SurveyResource.equals() to test for equality by value
        assertThat(surveysResource.getSurveys().get(0), is(expectedSurveyResource));
    }

    /**
     * Test {@link SpringApiClientImpl#getSurveys} when the identified survey exists, is inactive, and comprises multiple
     * questions of every supported type, some with multiple options.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getSurveyWhenInactiveSurveyMultipleQuestionsOfEveryType() throws Exception {
        int surveyId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SurveyResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(surveyId).toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getSurveyWhenInactiveSurveyMultipleQuestionsOfEveryType-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SurveyResource surveyResource = this.apiClient.getSurvey(surveyId);

        this.mockReportingApiService.verify();
        assertThat(surveyResource, notNullValue());

        // Assert all fields in the first of the returned Resource
        SurveyResource expectedSurveyResource = (SurveyResource) this.xstream
                .fromXML(responseBody.getInputStream());
        // Relies on overridden SurveyResource.equals() to test for equality by value
        assertThat(surveyResource, is(expectedSurveyResource));
    }

    /**
     * Tests {@link SpringApiClientImpl#getSurvey} in the case where the API service returns a response containing a field
     * value which cannot be converted to the expected type as defined in the API resource class. In this case, return of
     * a {@link SurveyResource} with an active field which can't be converted to a boolean is tested.
     */
    @Test
    public void getSurveyWhenInvalidResponseTypeMismatchActive() {
        int surveyId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SurveyResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(surveyId).toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getSurveyWhenInvalidResponseTypeMismatchActive-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SurveyResource surveyResource = this.apiClient.getSurvey(surveyId);

        // Current behaviour, as exhibited by the JAXB RI, is to suppress the error, resulting in default field value
        this.mockReportingApiService.verify();
        assertThat(surveyResource, notNullValue());
        assertThat(surveyResource.isActive(), is(false));
    }

    /**
     * Tests {@link SpringApiClientImpl#getSurveyResponses} when the request is for the first page of all the survey
     * responses (no filter criteria is specified in the request), and no responses are found.
     */
    @Test
    public void getSurveyResponsesWhenZeroResponses() {
        int surveyId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SurveyResponsesResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(surveyId).toString();

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<surveyResponses/>", MediaType.APPLICATION_XML));

        // Perform the test
        SurveyResponsesResource surveyResponsesResource = this.apiClient.getSurveyResponses(surveyId, null, null);

        this.mockReportingApiService.verify();
        assertThat(surveyResponsesResource, notNullValue());
        assertThat(surveyResponsesResource.getSurveyResponses(), hasSize(0));
        assertThat(surveyResponsesResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getSurveyResponses} for a survey with multiple questions, when there are responses
     * for more than one user/subscriber with, in some cases, multiple answers to a question, and a next page link.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getSurveyResponsesWhenMultipleResponsesWithMultipleQuestionsAndAnswersAndNextPage()
            throws Exception {
        int surveyId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SurveyResponsesResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(surveyId).toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getSurveyResponsesWhenMultipleResponsesWithMultipleQuestionsAndAnswersAndNextPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SurveyResponsesResource surveyResponsesResource = this.apiClient.getSurveyResponses(surveyId, null, null);

        this.mockReportingApiService.verify();
        assertThat(surveyResponsesResource, notNullValue());
        assertThat(surveyResponsesResource.getSurveyResponses(), hasSize(2));

        // Assert all fields in the first of the returned Resource
        SurveyResponsesResource expectedSurveyResponsesResource = (SurveyResponsesResource) this.xstream
                .fromXML(responseBody.getInputStream());
        SurveyResponseResource expectedSurveyResponseResource = expectedSurveyResponsesResource.getSurveyResponses()
                .get(0);
        // Relies on overridden SurveyResponseResource.equals() to test for equality by value
        assertThat(surveyResponsesResource.getSurveyResponses().get(0), is(expectedSurveyResponseResource));

        assertThat(surveyResponsesResource.getLinks(), hasSize(1));
    }

    /**
     * Tests {@link SpringApiClientImpl#getSurveyResponses} when the request is for the next page of responses since a
     * specified date/time, and the response is the last page containing a single response.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getSurveyResponsesWhenSinceAndNextPageWithLastPageSingleResponse() throws Exception {
        int surveyId = 1;
        String sinceString = "2014-06-28T21:24:59Z";
        Date sinceDate = new ApiDateTimeFormatter().parse(sinceString);
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + SurveyResponsesResource.RELATIVE_URI_TEMPLATE + "?since=" + sinceString + "&cursor=1234";
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(surveyId).toString();
        PageCriteria pageCriteria = createPageCriteria(null, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getSurveyResponsesWhenSinceAndNextPageWithLastPageSingleResponse-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        SurveyResponsesResource surveyResponsesResource = this.apiClient.getSurveyResponses(surveyId, sinceDate,
                pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(surveyResponsesResource, notNullValue());
        assertThat(surveyResponsesResource.getSurveyResponses(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        SurveyResponsesResource expectedSurveyResponsesResource = (SurveyResponsesResource) this.xstream
                .fromXML(responseBody.getInputStream());
        SurveyResponseResource expectedSurveyResponseResource = expectedSurveyResponsesResource.getSurveyResponses()
                .get(0);
        // Relies on overridden SurveyResponseResource.equals() to test for equality by value
        assertThat(surveyResponsesResource.getSurveyResponses().get(0), is(expectedSurveyResponseResource));

        assertThat(surveyResponsesResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastsForChannel} when the request is for the first page of all the webcasts
     * in the identified channel (no filter criteria is specified in the request), and none exist.
     */
    @Test
    public void getWebcastsForChannelWhenZeroResponses() {
        int channelId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastsResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<webcasts/>", MediaType.APPLICATION_XML));

        // Perform the test
        WebcastsResource webcastsResource = this.apiClient.getWebcastsForChannel(channelId, null, null);

        this.mockReportingApiService.verify();
        assertThat(webcastsResource, notNullValue());
        assertThat(webcastsResource.getWebcasts(), hasSize(0));
        assertThat(webcastsResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastsForChannel} when the request is for the first page of all the webcasts
     * in the identified channel (no filter criteria is specified in the request), and there are more than a pageful of
     * webcasts, and hence a next page link.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getWebcastsForChannelWhenMultipleWebastsAndNextPage() throws Exception {
        int channelId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastsResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastsForChannelWhenMultipleWebastsAndNextPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        WebcastsResource webcastsResource = this.apiClient.getWebcastsForChannel(channelId, null, null);

        this.mockReportingApiService.verify();
        assertThat(webcastsResource, notNullValue());
        assertThat(webcastsResource.getWebcasts(), hasSize(2));

        // Assert all fields in the first of the returned Resource
        WebcastsResource expectedWebcastsResource = (WebcastsResource) this.xstream
                .fromXML(responseBody.getInputStream());
        WebcastResource expectedWebcastResource = expectedWebcastsResource.getWebcasts().get(0);
        // Relies on overridden WebcastResource.equals() to test for equality by value
        assertThat(webcastsResource.getWebcasts().get(0), is(expectedWebcastResource));

        assertThat(webcastsResource.getLinks(), hasSize(1));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastsForChannel} when the request is for the next page of webcasts since a
     * specified date/time, and the response is the last page containing a single webcast.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getWebcastsForChannelWhenSinceAndNextPageWithLastPageSingleWebcast() throws Exception {
        int channelId = 1;
        String sinceString = "2014-06-28T21:24:59Z";
        Date sinceDate = new ApiDateTimeFormatter().parse(sinceString);
        int pageSize = 200;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastsResource.RELATIVE_URI_TEMPLATE + "?since=" + sinceString + "&cursor=1234&pageSize="
                + pageSize;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();
        PageCriteria pageCriteria = createPageCriteria(pageSize, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastsForChannelWhenSinceAndNextPageWithLastPageSingleWebcast-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        WebcastsResource webcastsResource = this.apiClient.getWebcastsForChannel(channelId, sinceDate,
                pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(webcastsResource, notNullValue());
        assertThat(webcastsResource.getWebcasts(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        WebcastsResource expectedWebcastsResource = (WebcastsResource) this.xstream
                .fromXML(responseBody.getInputStream());
        WebcastResource expectedWebcastResource = expectedWebcastsResource.getWebcasts().get(0);
        // Relies on overridden WebcastResource.equals() to test for equality by value
        assertThat(webcastsResource.getWebcasts().get(0), is(expectedWebcastResource));

        assertThat(webcastsResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastsForChannel} when there are multiple {@link WebcastsResource webcasts}
     * for the channel to be reported, and these are requested and returned across separate pages.
     * <p>
     * Primarily written to provide an example of how to use the API client to page through a collection of resources,
     * using {@link PageCriteria} and the supporting {@link Links} class.
     */
    @Test
    public void getWebcastsForChannelWhenMultiplePagesToRetrieve() {
        final int channelId = 1;
        final int pageSize = 1;
        final Date since = null;

        // Configure mock API service to respond to API call for first page of resources
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastsResource.RELATIVE_URI_TEMPLATE + "?pageSize=" + pageSize;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();
        Resource responseBodyPage1 = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastsForChannelWhenMultiplePages-page1-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBodyPage1, MediaType.APPLICATION_XML));

        // Configure mock API service to respond to API call for second (and final) page of resources
        expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri() + WebcastsResource.RELATIVE_URI_TEMPLATE
                + "?cursor=5-1376595689&pageSize=" + pageSize;
        expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();
        Resource responseBodyPage2 = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastsForChannelWhenMultiplePages-page2-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBodyPage2, MediaType.APPLICATION_XML));

        // Retrieve all pages of resources (until next page link is not present)
        List<WebcastResource> webcastResources = new ArrayList<>();
        Link nextPageLink = null;
        do {
            if (nextPageLink == null) {
                logger.debug("Retrieving first page of resources.");
            } else {
                logger.debug("Retrieving next page of resources using link [{}].", nextPageLink);
            }

            WebcastsResource webcastsResource = this.apiClient.getWebcastsForChannel(channelId, since,
                    new PageCriteria(pageSize, nextPageLink));

            if (webcastsResource.getWebcasts().size() > 0) {
                // One or more resources returned

                // Unwrap returned page of resource collection 'webcasts' to get to member 'webcast'
                webcastResources.addAll(webcastsResource.getWebcasts());

                // Find optional next page link in last retrieved collection of resources
                nextPageLink = Links.findNextPageLink(webcastsResource.getLinks());
            } else if (nextPageLink != null) {
                logger.warn("Zero resources returned for previously returned next page link [{}]", nextPageLink);
                nextPageLink = null;
            }
        } while (nextPageLink != null);

        this.mockReportingApiService.verify();
        assertThat(webcastResources, notNullValue());
        assertThat(webcastResources, hasSize(2));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcast} in the case where the requested webcast exists, and contains all
     * optional fields.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getWebcastExistsFullyPopulated() throws Exception {
        int channelId = 1;
        int webcastId = 2;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastResource.RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();

        // Configure mock API service to respond to API call with a canned API resource read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastExistsFullyPopulated-response.xml", this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        WebcastResource webcastResource = this.apiClient.getWebcast(channelId, webcastId);

        this.mockReportingApiService.verify();
        assertThat(webcastResource, notNullValue());

        // Assert all fields in the returned Resource
        WebcastResource expectedWebcastResource = (WebcastResource) this.xstream
                .fromXML(responseBody.getInputStream());
        // Relies on overridden WebcastResource.equals() to test for equality by value
        assertThat(webcastResource, is(expectedWebcastResource));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastRegistrationsForWebcast} when the request is for the first page of all
     * the registrations for an identified webcast (no filter criteria is specified in the request), and none exist.
     */
    @Test
    public void getWebcastRegistrationsForWebcastWhenZeroFound() {
        int channelId = 1;
        int webcastId = 2;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastRegistrationsResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<webcastRegistrations/>", MediaType.APPLICATION_XML));

        // Perform the test
        WebcastRegistrationsResource preregistrationsResource = this.apiClient
                .getWebcastRegistrationsForWebcast(channelId, webcastId, null, null, null);

        this.mockReportingApiService.verify();
        assertThat(preregistrationsResource, notNullValue());
        assertThat(preregistrationsResource.getWebcastRegistrations(), hasSize(0));
        assertThat(preregistrationsResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastRegistrationsForWebcast} when the request is for the first page of all
     * the pre-registrations for the identified webcast (no filter criteria is specified in the request), and there are
     * more than a pageful of pre-registrations, and hence a next page link.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getWebcastRegistrationsForWebcastWhenMultipleRegistrationsAndNextPage() throws Exception {
        int channelId = 1;
        int webcastId = 2;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastRegistrationsResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastRegistrationsForWebcastWhenMultipleRegistrationsAndNextPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        WebcastRegistrationsResource webcastRegistrationsResource = this.apiClient
                .getWebcastRegistrationsForWebcast(channelId, webcastId, null, null, null);

        this.mockReportingApiService.verify();
        assertThat(webcastRegistrationsResource, notNullValue());
        assertThat(webcastRegistrationsResource.getWebcastRegistrations(), hasSize(2));

        // Assert all fields in the first of the returned Resource
        WebcastRegistrationsResource expectedWebcastRegistrationsResource = (WebcastRegistrationsResource) this.xstream
                .fromXML(responseBody.getInputStream());
        WebcastRegistrationResource expectedWebcastRegistrationResource = expectedWebcastRegistrationsResource
                .getWebcastRegistrations().get(0);
        // Relies on overridden WebcastResource.equals() to test for equality by value
        assertThat(webcastRegistrationsResource.getWebcastRegistrations().get(0),
                is(expectedWebcastRegistrationResource));

        assertThat(webcastRegistrationsResource.getLinks(), hasSize(1));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastRegistrationsForWebcast} when the request is for the next page of
     * pre-registrations, for which the registrant has also subsequently viewed, since a specified date/time, and the
     * response is the last page containing a single pre-registration.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getWebcastRegistrationsForWebcastWhenViewedAndSinceAndNextPageWithLastPage() throws Exception {
        int channelId = 1;
        int webcastId = 2;
        Boolean viewed = true;
        String sinceString = "2013-07-10T12:23:22Z";
        Date sinceDate = new ApiDateTimeFormatter().parse(sinceString);
        int pageSize = 200;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastRegistrationsResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE + "?since=" + sinceString
                + "&viewed=" + viewed + "&cursor=1234&pageSize=" + pageSize;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();
        PageCriteria pageCriteria = createPageCriteria(pageSize, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastRegistrationsForWebcastWhenViewedOnlySinceAndNextPageWithLastPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        WebcastRegistrationsResource webcastRegistrationsResource = this.apiClient
                .getWebcastRegistrationsForWebcast(channelId, webcastId, sinceDate, viewed, pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(webcastRegistrationsResource, notNullValue());
        assertThat(webcastRegistrationsResource.getWebcastRegistrations(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        WebcastRegistrationsResource expectedWebcastRegistrationsResource = (WebcastRegistrationsResource) this.xstream
                .fromXML(responseBody.getInputStream());
        WebcastRegistrationResource expectedWebcastRegistrationResource = expectedWebcastRegistrationsResource
                .getWebcastRegistrations().get(0);
        // Relies on overridden WebcastRegistrationResource.equals() to test for equality by value
        assertThat(webcastRegistrationsResource.getWebcastRegistrations().get(0),
                is(expectedWebcastRegistrationResource));

        assertThat(webcastRegistrationsResource.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastViewingsForChannel} when the request is for the first page of all the
     * viewings in an identified channel (no filter criteria is specified in the request), and none exist.
     */
    @Test
    public void getWebcastViewingsForChannelWhenZeroFound() {
        int channelId = 1;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastViewingsResource.FOR_CHANNEL_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId).toString();

        // Configure mock API service to respond to API call
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess("<webcastViewings/>", MediaType.APPLICATION_XML));

        // Perform the test
        WebcastViewingsResource webcastViewings = this.apiClient.getWebcastViewingsForChannel(channelId, null, null,
                null);

        this.mockReportingApiService.verify();
        assertThat(webcastViewings, notNullValue());
        assertThat(webcastViewings.getWebcastViewings(), hasSize(0));
        assertThat(webcastViewings.getLinks(), hasSize(0));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastViewingsForChannel} in the error case where the requested webcast status
     * is not one of the status that the API supports.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getWebcastViewingsForChannelWhenViewingStatusNotSupported() throws Exception {
        int channelId = 1;
        WebcastStatus unsupportedWebcastStatus = WebcastStatus.PROCESSING;

        try {
            this.apiClient.getWebcastViewingsForChannel(channelId, null, unsupportedWebcastStatus, null);
            fail("Expected an exception to be thrown for an unsupported webcast status.");
        } catch (IllegalArgumentException e) {
            assertTrue("Unexpected exception message [" + e.getMessage() + "].",
                    e.getMessage().matches(".*webcast status.*" + unsupportedWebcastStatus.toString() + ".*"));
        }
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastViewingsForWebcast} when the request is for the first page of all the
     * viewings for the identified webcast (no filter criteria is specified in the request), and there are more than a
     * pageful of viewings, and hence a next page link.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getWebcastViewingsForWebcastWhenMultipleRegistrationsAndNextPage() throws Exception {
        int channelId = 1;
        int webcastId = 2;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastViewingsResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastViewingsForWebcastWhenMultipleViewingsAndNextPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        WebcastViewingsResource webcastViewingsResource = this.apiClient.getWebcastViewingsForWebcast(channelId,
                webcastId, null, null, null);

        this.mockReportingApiService.verify();
        assertThat(webcastViewingsResource, notNullValue());
        assertThat(webcastViewingsResource.getWebcastViewings(), hasSize(2));

        // Assert all fields in the first of the returned Resource
        WebcastViewingsResource expectedWebcastViewingsResource = (WebcastViewingsResource) this.xstream
                .fromXML(responseBody.getInputStream());
        WebcastViewingResource expectedWebcastViewingResource = expectedWebcastViewingsResource.getWebcastViewings()
                .get(0);
        // Relies on overridden WebcastViewingResource.equals() to test for equality by value
        assertThat(webcastViewingsResource.getWebcastViewings().get(0), is(expectedWebcastViewingResource));

        assertThat(webcastViewingsResource.getLinks(), hasSize(1));
    }

    /**
     * Tests {@link SpringApiClientImpl#getWebcastViewingsForWebcast} when the request is for the next page of viewings,
     * matching a specified webcast status, since a specified date/time, and the response is the last page containing a
     * single viewing.
     * 
     * @throws Exception If an unexpected error occurs.
     */
    @Test
    public void getWebcastViewingsForWebcastWhenViewingStatusRecordedAndSinceAndNextPageWithLastPage()
            throws Exception {
        int channelId = 1;
        int webcastId = 2;
        String sinceString = "2013-07-11T19:16:36Z";
        Date sinceDate = new ApiDateTimeFormatter().parse(sinceString);
        WebcastStatus webcastStatus = WebcastStatus.RECORDED;
        int pageSize = 200;
        String expectedTemplateRequestUrl = this.apiClient.getApiServiceBaseUri()
                + WebcastViewingsResource.FOR_WEBCAST_RELATIVE_URI_TEMPLATE + "?since=" + sinceString
                + "&webcastStatus=" + webcastStatus.toString() + "&cursor=1234&pageSize=" + pageSize;
        String expectedRequestUrl = new UriTemplate(expectedTemplateRequestUrl).expand(channelId, webcastId)
                .toString();
        PageCriteria pageCriteria = createPageCriteria(pageSize, expectedRequestUrl);

        // Configure mock API service to respond to API call with a canned collection of API resources read from file
        Resource responseBody = new ClassPathResource(
                "SpringApiClientImplTest.getWebcastViewingsForWebcastViewingStatusRecordedAndSinceAndNextPageWithLastPage-response.xml",
                this.getClass());
        this.mockReportingApiService.expect(method(HttpMethod.GET)).andExpect(requestTo(expectedRequestUrl))
                .andRespond(withSuccess(responseBody, MediaType.APPLICATION_XML));

        // Perform the test
        WebcastViewingsResource webcastViewingsResource = this.apiClient.getWebcastViewingsForWebcast(channelId,
                webcastId, sinceDate, webcastStatus, pageCriteria);

        this.mockReportingApiService.verify();
        assertThat(webcastViewingsResource, notNullValue());
        assertThat(webcastViewingsResource.getWebcastViewings(), hasSize(1));

        // Assert all fields in the first of the returned Resource
        WebcastViewingsResource expectedWebcastViewingsResource = (WebcastViewingsResource) this.xstream
                .fromXML(responseBody.getInputStream());
        WebcastViewingResource expectedWebcastViewingResource = expectedWebcastViewingsResource.getWebcastViewings()
                .get(0);
        // Relies on overridden WebcastRegistrationResource.equals() to test for equality by value
        assertThat(webcastViewingsResource.getWebcastViewings().get(0), is(expectedWebcastViewingResource));

        assertThat(webcastViewingsResource.getLinks(), hasSize(0));
    }

    private static PageCriteria createPageCriteria(Integer pageSize, String nextPageUrl) {
        return new PageCriteria(pageSize, new Link(nextPageUrl, LinkRelationType.next.name()));
    }

    private static String apiErrorToXml(ApiError apiError) {
        return "<?xml version='1.0' encoding='UTF-8'?><error><code>" + apiError.getCode() + "</code><message>"
                + apiError.getMessage() + "</message></error>";
    }

    /**
     * Configures the {@link XStream} instance the test uses to unamrshall (deserialise) canned API response payloads.
     */
    private void initXStream() {
        this.xstream = new XStream();
        this.xstream.alias("channels", ChannelsResource.class);
        this.xstream.registerConverter(new ChannelsResourceXStreamConverter());
        this.xstream.alias("channel", ChannelResource.class);
        this.xstream.registerConverter(new ChannelResourceXStreamConverter());
        this.xstream.alias("link", Link.class);
        this.xstream.registerConverter(new LinkXStreamConverter());
        this.xstream.alias("channelSubscribers", ChannelSubscribersResource.class);
        this.xstream.registerConverter(new ChannelSubscribersResourceXStreamConverter());
        this.xstream.alias("channelSubscriber", ChannelSubscriberResource.class);
        this.xstream.registerConverter(new ChannelSubscriberResourceXStreamConverter());
        this.xstream.alias("embed", Embed.class);
        this.xstream.registerConverter(new EmbedXStreamConverter());
        this.xstream.alias("user", User.class);
        this.xstream.registerConverter(new UserXStreamConverter());
        this.xstream.alias("subscribersWebcastActivity", SubscribersWebcastActivityResource.class);
        this.xstream.registerConverter(new SubscribersWebcastActivityResourceXStreamConverter());
        this.xstream.alias("subscriberWebcastActivity", SubscriberWebcastActivityResource.class);
        this.xstream.registerConverter(new SubscriberWebcastActivityResourceXStreamConverter());
        this.xstream.alias("surveyResponse", SurveyResponseResource.class);
        this.xstream.registerConverter(new SurveyResponseResourceXStreamConverter());
        this.xstream.alias("question", Question.class);
        this.xstream.registerConverter(new QuestionXStreamConverter());
        this.xstream.alias("surveys", SurveysResource.class);
        this.xstream.registerConverter(new SurveysResourceXStreamConverter());
        this.xstream.alias("survey", SurveyResource.class);
        this.xstream.registerConverter(new SurveyResourceXStreamConverter());
        this.xstream.alias("surveyResponses", SurveyResponsesResource.class);
        this.xstream.registerConverter(new SurveyResponsesResourceXStreamConverter());
        this.xstream.alias("webcasts", WebcastsResource.class);
        this.xstream.registerConverter(new WebcastsResourceXStreamConverter());
        this.xstream.alias("webcast", WebcastResource.class);
        this.xstream.registerConverter(new WebcastResourceXStreamConverter());
        this.xstream.alias("webcastRegistrations", WebcastRegistrationsResource.class);
        this.xstream.registerConverter(new WebcastRegistrationsResourceXStreamConverter());
        this.xstream.alias("webcastRegistration", WebcastRegistrationResource.class);
        this.xstream.registerConverter(new WebcastRegistrationResourceXStreamConverter());
        this.xstream.alias("webcastViewings", WebcastViewingsResource.class);
        this.xstream.registerConverter(new WebcastViewingsResourceXStreamConverter());
        this.xstream.alias("webcastViewing", WebcastViewingResource.class);
        this.xstream.registerConverter(new WebcastViewingResourceXStreamConverter());
    }
}