/**
 * Copyright Microsoft Corporation
 * <p/>
 * 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
 * <p/>
 * 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.microsoft.azure.storage.analytics;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageLocation;
import com.microsoft.azure.storage.TestRunners.CloudTests;
import com.microsoft.azure.storage.TestRunners.DevFabricTests;
import com.microsoft.azure.storage.TestRunners.DevStoreTests;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlockBlob;
import com.microsoft.azure.storage.blob.ListBlobItem;
import com.microsoft.azure.storage.table.CloudTable;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;

import static org.junit.Assert.*;

/**
 * Analytics Client Tests
 */
@Category({DevFabricTests.class, DevStoreTests.class, CloudTests.class})
public class CloudAnalyticsClientTests {

    protected CloudAnalyticsClient client;
    protected CloudBlobContainer container;

    @Before
    public void analyticsTestMethodSetUp() throws URISyntaxException, StorageException {
        this.container = AnalyticsTestHelper.getRandomContainerReference();
        this.client = AnalyticsTestHelper.createCloudAnalyticsClient();
    }

    @After
    public void analyticsTestMethodTearDown() throws StorageException {
        this.container.deleteIfExists();
    }

    /**
     * Test table getters.
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    public void testCloudAnalyticsClientGetTables() throws URISyntaxException, StorageException {
        CloudTable blobHourPrimary = this.client.getHourMetricsTable(StorageService.BLOB);
        CloudTable blobHourSecondary = this.client.getHourMetricsTable(StorageService.BLOB, StorageLocation.SECONDARY);
        CloudTable fileHourPrimary = this.client.getHourMetricsTable(StorageService.FILE);
        CloudTable fileHourSecondary = this.client.getHourMetricsTable(StorageService.FILE, StorageLocation.SECONDARY);
        CloudTable queueHourPrimary = this.client.getHourMetricsTable(StorageService.QUEUE, StorageLocation.PRIMARY);
        CloudTable queueHourSecondary = this.client.getHourMetricsTable(StorageService.QUEUE, StorageLocation.SECONDARY);
        CloudTable tableHourPrimary = this.client.getHourMetricsTable(StorageService.TABLE, StorageLocation.PRIMARY);
        CloudTable tableHourSecondary = this.client.getHourMetricsTable(StorageService.TABLE, StorageLocation.SECONDARY);

        CloudTable blobMinutePrimary = this.client.getMinuteMetricsTable(StorageService.BLOB);
        CloudTable blobMinuteSecondary = this.client.getMinuteMetricsTable(StorageService.BLOB, StorageLocation.SECONDARY);
        CloudTable fileMinutePrimary = this.client.getMinuteMetricsTable(StorageService.FILE);
        CloudTable fileMinuteSecondary = this.client.getMinuteMetricsTable(StorageService.FILE, StorageLocation.SECONDARY);
        CloudTable queueMinutePrimary = this.client.getMinuteMetricsTable(StorageService.QUEUE, StorageLocation.PRIMARY);
        CloudTable queueMinuteSecondary = this.client.getMinuteMetricsTable(StorageService.QUEUE, StorageLocation.SECONDARY);
        CloudTable tableMinutePrimary = this.client.getMinuteMetricsTable(StorageService.TABLE, StorageLocation.PRIMARY);
        CloudTable tableMinuteSecondary = this.client.getMinuteMetricsTable(StorageService.TABLE, StorageLocation.SECONDARY);

        CloudTable capacity = this.client.getCapacityTable();

        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_BLOB, blobHourPrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_BLOB, blobHourSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_FILE, fileHourPrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_FILE, fileHourSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_QUEUE, queueHourPrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_QUEUE, queueHourSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_PRIMARY_TRANSACTIONS_TABLE, tableHourPrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_HOUR_SECONDARY_TRANSACTIONS_TABLE, tableHourSecondary.getName());

        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_BLOB, blobMinutePrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_BLOB, blobMinuteSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_FILE, fileMinutePrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_FILE, fileMinuteSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_QUEUE, queueMinutePrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_QUEUE, queueMinuteSecondary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_PRIMARY_TRANSACTIONS_TABLE, tableMinutePrimary.getName());
        assertEquals(Constants.AnalyticsConstants.METRICS_MINUTE_SECONDARY_TRANSACTIONS_TABLE, tableMinuteSecondary.getName());

        assertEquals(Constants.AnalyticsConstants.METRICS_CAPACITY_BLOB, capacity.getName());
    }

    /**
     * List all logs
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    public void testCloudAnalyticsClientListLogs() throws URISyntaxException, StorageException, IOException {
        this.container.create();
        this.client.LogContainer = this.container.getName();
        int numBlobs = 13;
        Calendar now = new GregorianCalendar();

        now.add(GregorianCalendar.MONTH, -13);
        List<String> blobNames = AnalyticsTestHelper.CreateLogs(this.container, StorageService.BLOB, 13, now,
                Granularity.MONTH);

        assertEquals(numBlobs, blobNames.size());

        for (ListBlobItem blob : this.client.listLogBlobs(StorageService.BLOB)) {
            assertEquals(CloudBlockBlob.class, blob.getClass());
            assertTrue(blobNames.remove(((CloudBlockBlob) blob).getName()));
        }
        assertTrue(blobNames.size() == 0);
    }

    /**
     * List Logs with open ended time range
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientListLogsStartTime() throws URISyntaxException, StorageException, IOException {
        this.container.create();
        this.client.LogContainer = this.container.getName();
        int numBlobs = 48;
        Calendar now = new GregorianCalendar();
        now.add(GregorianCalendar.DAY_OF_MONTH, -2);
        List<String> blobNames = AnalyticsTestHelper.CreateLogs(this.container, StorageService.BLOB, 48, now,
                Granularity.HOUR);

        assertEquals(numBlobs, blobNames.size());

        Calendar start = new GregorianCalendar();
        start.add(GregorianCalendar.DAY_OF_MONTH, -1);
        for (ListBlobItem blob : this.client.listLogBlobs(StorageService.BLOB, start.getTime(), null, null, null, null,
                null)) {
            assertEquals(CloudBlockBlob.class, blob.getClass());
            assertTrue(blobNames.remove(((CloudBlockBlob) blob).getName()));
        }
        assertTrue(blobNames.size() == 24);
    }

    /**
     * List Logs with well defined time range
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientListLogsStartEndTime() throws URISyntaxException, StorageException, IOException {
        this.container.create();
        this.client.LogContainer = this.container.getName();
        int numBlobs = 72;
        Calendar now = new GregorianCalendar();
        now.add(GregorianCalendar.DAY_OF_MONTH, -3);
        List<String> blobNames = AnalyticsTestHelper.CreateLogs(this.container, StorageService.BLOB, 72, now,
                Granularity.HOUR);

        assertEquals(numBlobs, blobNames.size());

        Calendar start = new GregorianCalendar();
        start.add(GregorianCalendar.DAY_OF_MONTH, -2);
        Calendar end = new GregorianCalendar();
        end.add(GregorianCalendar.DAY_OF_MONTH, -1);
        for (ListBlobItem blob : this.client.listLogBlobs(StorageService.BLOB, start.getTime(), end.getTime(), null,
                null, null, null)) {
            assertEquals(CloudBlockBlob.class, blob.getClass());
            assertTrue(blobNames.remove(((CloudBlockBlob) blob).getName()));
        }
        assertTrue(blobNames.size() == 48);
    }

    /**
     * Validate Log Parser
     *
     * @throws ParseException
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientParseExLogs() throws ParseException, URISyntaxException, StorageException,
            IOException {
        String logText = "1.0;2011-08-09T18:52:40.9241789Z;GetBlob;AnonymousSuccess;200;18;10;anonymous;;myaccount;blob;\"https://myaccount.blob.core.windows.net/thumb&amp;nails/lake.jpg?timeout=30000\";\"/myaccount/thumbnails/lake.jpg\";a84aa705-8a85-48c5-b064-b43bd22979c3;0;123.100.2.10;2009-09-19;252;0;265;100;0;;;\"0x8CE1B6EA95033D5\";Tuesday, 09-Aug-11 18:52:40 GMT;;;;\"8/9/2011 6:52:40 PM ba98eb12-700b-4d53-9230-33a3330571fc\""
                + '\n'
                + "1.0;2011-08-09T18:02:40.6271789Z;PutBlob;Success;201;28;21;authenticated;myaccount;myaccount;blob;\"https://myaccount.blob.core.windows.net/thumbnails/lake.jpg?timeout=30000\";\"/myaccount/thumbnails/lake.jpg\";fb658ee6-6123-41f5-81e2-4bfdc178fea3;0;201.9.10.20;2009-09-19;438;100;223;0;100;;\"66CbMXKirxDeTr82SXBKbg==\";\"0x8CE1B67AD25AA05\";Tuesday, 09-Aug-11 18:02:40 GMT;;;;\"8/9/2011 6:02:40 PM ab970a57-4a49-45c4-baa9-20b687941e32\""
                + '\n';
        this.container.createIfNotExists();
        CloudBlockBlob blob = this.container.getBlockBlobReference("blob1");
        blob.uploadText(logText);
        Iterator<LogRecord> iterator = CloudAnalyticsClient.parseLogBlob(blob).iterator();

        assertTrue(iterator.hasNext());
        LogRecord actualItemOne = iterator.next();
        assertTrue(iterator.hasNext());
        LogRecord actualItemTwo = iterator.next();

        LogRecord expectedItemOne = new LogRecord();
        expectedItemOne.setVersionNumber("1.0");
        expectedItemOne.setRequestStartTime(LogRecord.REQUEST_START_TIME_FORMAT.parse("2011-08-09T18:52:40.9241789Z"));
        expectedItemOne.setOperationType("GetBlob");
        expectedItemOne.setRequestStatus("AnonymousSuccess");
        expectedItemOne.setHttpStatusCode("200");
        expectedItemOne.setEndToEndLatencyInMS(18);
        expectedItemOne.setServerLatencyInMS(10);
        expectedItemOne.setAuthenticationType("anonymous");
        expectedItemOne.setRequesterAccountName(null);
        expectedItemOne.setOwnerAccountName("myaccount");
        expectedItemOne.setServiceType("blob");
        expectedItemOne.setRequestUrl(new URI(
                "https://myaccount.blob.core.windows.net/thumb&nails/lake.jpg?timeout=30000"));
        expectedItemOne.setRequestedObjectKey("/myaccount/thumbnails/lake.jpg");
        expectedItemOne.setRequestIdHeader(UUID.fromString("a84aa705-8a85-48c5-b064-b43bd22979c3"));
        expectedItemOne.setOperationCount(0);
        expectedItemOne.setRequesterIPAddress("123.100.2.10");
        expectedItemOne.setRequestVersionHeader("2009-09-19");
        expectedItemOne.setRequestHeaderSize(252L);
        expectedItemOne.setRequestPacketSize(0L);
        expectedItemOne.setResponseHeaderSize(265L);
        expectedItemOne.setResponsePacketSize(100L);
        expectedItemOne.setRequestContentLength(0L);
        expectedItemOne.setRequestMD5(null);
        expectedItemOne.setServerMD5(null);
        expectedItemOne.setETagIdentifier("0x8CE1B6EA95033D5");
        expectedItemOne.setLastModifiedTime(LogRecord.LAST_MODIFIED_TIME_FORMAT
                .parse("Tuesday, 09-Aug-11 18:52:40 GMT"));
        expectedItemOne.setConditionsUsed(null);
        expectedItemOne.setUserAgentHeader(null);
        expectedItemOne.setReferrerHeader(null);
        expectedItemOne.setClientRequestId("8/9/2011 6:52:40 PM ba98eb12-700b-4d53-9230-33a3330571fc");

        LogRecord expectedItemTwo = new LogRecord();
        expectedItemTwo.setVersionNumber("1.0");
        expectedItemTwo.setRequestStartTime(LogRecord.REQUEST_START_TIME_FORMAT.parse("2011-08-09T18:02:40.6271789Z"));
        expectedItemTwo.setOperationType("PutBlob");
        expectedItemTwo.setRequestStatus("Success");
        expectedItemTwo.setHttpStatusCode("201");
        expectedItemTwo.setEndToEndLatencyInMS(28);
        expectedItemTwo.setServerLatencyInMS(21);
        expectedItemTwo.setAuthenticationType("authenticated");
        expectedItemTwo.setRequesterAccountName("myaccount");
        expectedItemTwo.setOwnerAccountName("myaccount");
        expectedItemTwo.setServiceType("blob");
        expectedItemTwo.setRequestUrl(new URI(
                "https://myaccount.blob.core.windows.net/thumbnails/lake.jpg?timeout=30000"));
        expectedItemTwo.setRequestedObjectKey("/myaccount/thumbnails/lake.jpg");
        expectedItemTwo.setRequestIdHeader(UUID.fromString("fb658ee6-6123-41f5-81e2-4bfdc178fea3"));
        expectedItemTwo.setOperationCount(0);
        expectedItemTwo.setRequesterIPAddress("201.9.10.20");
        expectedItemTwo.setRequestVersionHeader("2009-09-19");
        expectedItemTwo.setRequestHeaderSize(438L);
        expectedItemTwo.setRequestPacketSize(100L);
        expectedItemTwo.setResponseHeaderSize(223L);
        expectedItemTwo.setResponsePacketSize(0L);
        expectedItemTwo.setRequestContentLength(100L);
        expectedItemTwo.setRequestMD5(null);
        expectedItemTwo.setServerMD5("66CbMXKirxDeTr82SXBKbg==");
        expectedItemTwo.setETagIdentifier("0x8CE1B67AD25AA05");
        expectedItemTwo.setLastModifiedTime(LogRecord.LAST_MODIFIED_TIME_FORMAT
                .parse("Tuesday, 09-Aug-11 18:02:40 GMT"));
        expectedItemTwo.setConditionsUsed(null);
        expectedItemTwo.setUserAgentHeader(null);
        expectedItemTwo.setReferrerHeader(null);
        expectedItemTwo.setClientRequestId("8/9/2011 6:02:40 PM ab970a57-4a49-45c4-baa9-20b687941e32");

        CloudAnalyticsClientTests.assertLogItemsEqual(expectedItemOne, actualItemOne);
        CloudAnalyticsClientTests.assertLogItemsEqual(expectedItemTwo, actualItemTwo);
    }

    /**
     * Validate Log Parser with prod data
     *
     * @throws ParseException
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientParseProdLogs() throws ParseException, URISyntaxException, StorageException,
            IOException {

        Calendar startTime = new GregorianCalendar();
        startTime.add(GregorianCalendar.HOUR_OF_DAY, -2);

        Iterator<LogRecord> logRecordsIterator = (this.client.listLogRecords(StorageService.BLOB, startTime.getTime(),
                null, null, null)).iterator();

        while (logRecordsIterator.hasNext()) {
            // Makes sure there's no exceptions thrown and that no records are null.
            // Primarily a sanity check.
            LogRecord rec = logRecordsIterator.next();
            System.out.println(rec.getRequestUrl());
            assertNotNull(rec);
        }
    }

    /**
     * Log parser error cases.
     *
     * @throws ParseException
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    public void testCloudAnalyticsClientParseLogErrors() throws ParseException, URISyntaxException, StorageException,
            IOException {
        this.container.createIfNotExists();

        String v2Entry = "2.0;2011-08-09T18:02:40.6271789Z;PutBlob;Success;201;28;21;authenticated;myaccount;myaccount;blob;\"https://myaccount.blob.core.windows.net/thumbnails/lake.jpg?timeout=30000\";\"/myaccount/thumbnails/lake.jpg\";fb658ee6-6123-41f5-81e2-4bfdc178fea3;0;201.9.10.20;2009-09-19;438;100;223;0;100;;\"66CbMXKirxDeTr82SXBKbg==\";\"0x8CE1B67AD25AA05\";Tuesday, 09-Aug-11 18:02:40 GMT;;;;\"8/9/2011 6:02:40 PM ab970a57-4a49-45c4-baa9-20b687941e32\"" + '\n';
        CloudBlockBlob v2Blob = this.container.getBlockBlobReference("v2Blob");
        v2Blob.uploadText(v2Entry);
        Iterator<LogRecord> v2Iterator = CloudAnalyticsClient.parseLogBlob(v2Blob).iterator();
        try {
            v2Iterator.next();
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals(e.getMessage(), "A storage log version of 2.0 is unsupported.");
        }

        // Note that if this non log data ('%s') contains a semicolon, we'll get an unfortunate failure
        // saying that a log version of %s is not supported. Otherwise, we'll see the behavior below.
        String nonLogData = "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG 1234567890";
        CloudBlockBlob nonLogDataBlob = this.container.getBlockBlobReference("nonLogBlob");
        nonLogDataBlob.uploadText(nonLogData);
        Iterator<LogRecord> nonLogDataIterator = CloudAnalyticsClient.parseLogBlob(nonLogDataBlob).iterator();
        try {
            nonLogDataIterator.next();
            fail();
        } catch (NoSuchElementException e) {
            assertEquals(e.getMessage(),
                    "An error occurred while enumerating the result, check the original exception for details.");
            assertEquals(e.getCause().getMessage(), "Error parsing log record: unexpected end of stream.");
        }

        CloudBlockBlob nullBlob1 = this.container.getBlockBlobReference("nullBlob1");
        CloudBlockBlob nullBlob2 = this.container.getBlockBlobReference("nullBlob2");
        CloudBlockBlob nullBlob3 = this.container.getBlockBlobReference("nullBlob3");
        ArrayList<ListBlobItem> nullBlobIterator = new ArrayList<ListBlobItem>();
        nullBlobIterator.add(nullBlob1);
        nullBlobIterator.add(nullBlob2);
        nullBlobIterator.add(nullBlob3);
        Iterator<LogRecord> nullLogIterator = CloudAnalyticsClient.parseLogBlobs(nullBlobIterator).iterator();
        try {
            nullLogIterator.next();
            fail();
        } catch (NoSuchElementException e) {
            assertEquals(e.getMessage(),
                    "An error occurred while enumerating the result, check the original exception for details.");
            assertEquals(e.getCause().getMessage(), "The specified blob does not exist.");
        }

        try {
            Iterator<LogRecord> emptyIterator = CloudAnalyticsClient.parseLogBlobs(new ArrayList<ListBlobItem>()).iterator();
            assertFalse(emptyIterator.hasNext());
            emptyIterator.next();
        } catch (NoSuchElementException e) {
            assertEquals(e.getMessage(), "There are no more elements in this enumeration.");
        }

        try {
            CloudAnalyticsClient.parseLogBlobs(null);
        } catch (IllegalArgumentException e) {
            assertEquals(e.getMessage(), "The argument must not be null or an empty string. Argument name: logBlobs.");
        }
    }

    public static void assertLogItemsEqual(LogRecord expected, LogRecord actual) {
        assertEquals(expected.getVersionNumber(), actual.getVersionNumber());

        assertEquals(expected.getRequestStartTime(), actual.getRequestStartTime());
        assertEquals(expected.getOperationType(), actual.getOperationType());
        assertEquals(expected.getRequestStatus(), actual.getRequestStatus());
        assertEquals(expected.getHttpStatusCode(), actual.getHttpStatusCode());
        assertEquals(expected.getEndToEndLatencyInMS(), actual.getEndToEndLatencyInMS());
        assertEquals(expected.getServerLatencyInMS(), actual.getServerLatencyInMS());
        assertEquals(expected.getAuthenticationType(), actual.getAuthenticationType());
        assertEquals(expected.getRequesterAccountName(), actual.getRequesterAccountName());
        assertEquals(expected.getOwnerAccountName(), actual.getOwnerAccountName());
        assertEquals(expected.getServiceType(), actual.getServiceType());
        assertEquals(expected.getRequestUrl(), actual.getRequestUrl());
        assertEquals(expected.getRequestedObjectKey(), actual.getRequestedObjectKey());
        assertEquals(expected.getRequestIdHeader(), actual.getRequestIdHeader());
        assertEquals(expected.getOperationCount(), actual.getOperationCount());
        assertEquals(expected.getRequesterIPAddress(), actual.getRequesterIPAddress());
        assertEquals(expected.getRequestVersionHeader(), actual.getRequestVersionHeader());
        assertEquals(expected.getRequestHeaderSize(), actual.getRequestHeaderSize());
        assertEquals(expected.getRequestPacketSize(), actual.getRequestPacketSize());
        assertEquals(expected.getResponseHeaderSize(), actual.getResponseHeaderSize());
        assertEquals(expected.getResponsePacketSize(), actual.getResponsePacketSize());
        assertEquals(expected.getRequestContentLength(), actual.getRequestContentLength());
        assertEquals(expected.getRequestMD5(), actual.getRequestMD5());
        assertEquals(expected.getServerMD5(), actual.getServerMD5());
        assertEquals(expected.getETagIdentifier(), actual.getETagIdentifier());
        assertEquals(expected.getLastModifiedTime(), actual.getLastModifiedTime());
        assertEquals(expected.getConditionsUsed(), actual.getConditionsUsed());
        assertEquals(expected.getUserAgentHeader(), actual.getUserAgentHeader());
        assertEquals(expected.getReferrerHeader(), actual.getReferrerHeader());
        assertEquals(expected.getClientRequestId(), actual.getClientRequestId());
    }
}