// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#ifdef __cplusplus
#include <cstdlib>
#include <cstddef>
#else
#include <stdlib.h>
#include <stddef.h>
#endif
#include "testrunnerswitcher.h"
#include "umock_c.h"
#include "umocktypes_charptr.h"
#include "umock_c_negative_tests.h"

static TEST_MUTEX_HANDLE g_testByTest;

#if defined _MSC_VER
#pragma warning(disable: 4054) /* MSC incorrectly fires this */
#endif

#ifdef __cplusplus
extern "C"
{
#endif
    void* real_malloc(size_t size)
    {
        return malloc(size);
    }

    void* real_realloc(void* ptr, size_t size)
    {
        return realloc(ptr, size);
    }

    void real_free(void* ptr)
    {
        free(ptr);
    }

    int real_mallocAndStrcpy_s(char** destination, const char* source);

#ifdef __cplusplus
}
#endif

#define ENABLE_MOCKS

#include "azure_c_shared_utility/gballoc.h"
#include "azure_c_shared_utility/optionhandler.h"
#include "azure_c_shared_utility/crt_abstractions.h"
#include "azure_c_shared_utility/xio.h"
#include "azure_c_shared_utility/socketio.h"
#include "azure_c_shared_utility/strings.h"
#include "azure_c_shared_utility/base64.h"

IMPLEMENT_UMOCK_C_ENUM_TYPE(IO_OPEN_RESULT, IO_OPEN_RESULT_VALUES);
IMPLEMENT_UMOCK_C_ENUM_TYPE(IO_SEND_RESULT, IO_SEND_RESULT_VALUES);

#define TEST_OPTION_HANDLER                     (OPTIONHANDLER_HANDLE)0x4244
#define TEST_SOCKETIO_INTERFACE_DESCRIPTION     (const IO_INTERFACE_DESCRIPTION*)0x4242
#define TEST_IO_HANDLE                          (XIO_HANDLE)0x4243
#define TEST_STRING_HANDLE                      (STRING_HANDLE)0x4244

MOCK_FUNCTION_WITH_CODE(, void, test_on_io_open_complete, void*, context, IO_OPEN_RESULT, open_result)
MOCK_FUNCTION_END();
MOCK_FUNCTION_WITH_CODE(, void, test_on_bytes_received, void*, context, const unsigned char*, buffer, size_t, size)
MOCK_FUNCTION_END();
MOCK_FUNCTION_WITH_CODE(, void, test_on_io_error, void*, context)
MOCK_FUNCTION_END();
MOCK_FUNCTION_WITH_CODE(, void, test_on_io_close_complete, void*, context)
MOCK_FUNCTION_END();
MOCK_FUNCTION_WITH_CODE(, void, test_on_send_complete, void*, context, IO_SEND_RESULT, send_result)
MOCK_FUNCTION_END();

#undef ENABLE_MOCKS

static char* umocktypes_stringify_const_SOCKETIO_CONFIG_ptr(const SOCKETIO_CONFIG** value)
{
    char* result = NULL;
    char temp_buffer[1024];
    int length;
    length = sprintf(temp_buffer, "{ hostname = %s, port = %d, accepted_socket = %p }",
        (*value)->hostname,
        (*value)->port,
        (*value)->accepted_socket);

    if (length > 0)
    {
        result = (char*)real_malloc(strlen(temp_buffer) + 1);
        if (result != NULL)
        {
            (void)memcpy(result, temp_buffer, strlen(temp_buffer) + 1);
        }
    }

    return result;
}

static int umocktypes_are_equal_const_SOCKETIO_CONFIG_ptr(const SOCKETIO_CONFIG** left, const SOCKETIO_CONFIG** right)
{
    int result;

    if ((left == NULL) ||
        (right == NULL))
    {
        result = -1;
    }
    else
    {
        result = (*left)->port == (*right)->port;
        result = result && ((*left)->accepted_socket == (*right)->accepted_socket);
        if ((*right)->hostname == NULL)
        {
            result = result && ((*left)->hostname == (*right)->hostname);
        }
        else
        {
            result = result && (strcmp((*left)->hostname, (*right)->hostname) == 0);
        }
    }

    return result;
}

static int copy_string(char** destination, const char* source)
{
    int result;

    if (source == NULL)
    {
        *destination = NULL;
        result = 0;
    }
    else
    {
        size_t length = strlen(source);
        *destination = (char*)real_malloc(length + 1);
        if (*destination == NULL)
        {
            result = __LINE__;
        }
        else
        {
            (void)memcpy(*destination, source, length + 1);
            result = 0;
        }
    }

    return result;
}

static int umocktypes_copy_const_SOCKETIO_CONFIG_ptr(SOCKETIO_CONFIG** destination, const SOCKETIO_CONFIG** source)
{
    int result;

    *destination = (SOCKETIO_CONFIG*)real_malloc(sizeof(SOCKETIO_CONFIG));
    if (*destination == NULL)
    {
        result = __LINE__;
    }
    else
    {
        if (copy_string((char**)&(*destination)->hostname, (*source)->hostname) != 0)
        {
            result = __LINE__;
        }
        else
        {
            (*destination)->port = (*source)->port;
            (*destination)->accepted_socket = (*source)->accepted_socket;

            result = 0;
        }
    }

    return result;
}

static void umocktypes_free_const_SOCKETIO_CONFIG_ptr(SOCKETIO_CONFIG** value)
{
    real_free((void*)(*value)->hostname);
    real_free(*value);
}

#include "azure_c_shared_utility/http_proxy_io.h"

#ifdef __cplusplus
extern "C"
{
#endif

pfCloneOption tlsio_cyclonessl_clone_option;
pfDestroyOption tlsio_cyclonessl_destroy_option;

OPTIONHANDLER_HANDLE my_OptionHandler_Create(pfCloneOption cloneOption, pfDestroyOption destroyOption, pfSetOption setOption)
{
    (void)setOption;

    tlsio_cyclonessl_clone_option = cloneOption;
    tlsio_cyclonessl_destroy_option = destroyOption;
    return TEST_OPTION_HANDLER;
}

static ON_IO_OPEN_COMPLETE g_on_io_open_complete;
static void* g_on_io_open_complete_context;
static ON_SEND_COMPLETE g_on_io_send_complete;
static void* g_on_io_send_complete_context;
static ON_BYTES_RECEIVED g_on_bytes_received;
static void* g_on_bytes_received_context;
static ON_IO_ERROR g_on_io_error;
static void* g_on_io_error_context;
static ON_IO_CLOSE_COMPLETE g_on_io_close_complete;
static void* g_on_io_close_complete_context;

static int my_xio_open(XIO_HANDLE xio, ON_IO_OPEN_COMPLETE on_io_open_complete, void* on_io_open_complete_context, ON_BYTES_RECEIVED on_bytes_received, void* on_bytes_received_context, ON_IO_ERROR on_io_error, void* on_io_error_context)
{
    (void)xio;
    g_on_io_open_complete = on_io_open_complete;
    g_on_io_open_complete_context = on_io_open_complete_context;
    g_on_bytes_received = on_bytes_received;
    g_on_bytes_received_context = on_bytes_received_context;
    g_on_io_error = on_io_error;
    g_on_io_error_context = on_io_error_context;
    return 0;
}

static int my_xio_close(XIO_HANDLE xio, ON_IO_CLOSE_COMPLETE on_io_close_complete, void* callback_context)
{
    (void)xio;
    g_on_io_close_complete = on_io_close_complete;
    g_on_io_close_complete_context = callback_context;
    return 0;
}

static const char connect_response[] = "HTTP/1.1 200\r\n\r\n";
static const HTTP_PROXY_IO_CONFIG default_http_proxy_io_config = {
    "test_host",
    443,
    "a_proxy",
    4444,
    "test_user",
    "shhhh"
};

static const HTTP_PROXY_IO_CONFIG http_proxy_io_config_no_username = {
    "test_host",
    443,
    "a_proxy",
    4444,
    NULL,
    NULL
    };

static const HTTP_PROXY_IO_CONFIG http_proxy_io_config_with_username = {
    "another_test_host",
    445,
    "another_proxy",
    8888,
    "le_user",
    "le_password"
    };

static const HTTP_PROXY_IO_CONFIG http_proxy_io_config_with_username_cased = {
    "another_test_host",
    445,
    "another_proxy",
    8888,
    "lE_uSeR",
    "lE_pAsSwOrD"
    };

static const SOCKETIO_CONFIG socketio_config =
{
    "a_proxy",
    4444,
    NULL
};

#ifdef __cplusplus
}
#endif

DEFINE_ENUM_STRINGS(UMOCK_C_ERROR_CODE, UMOCK_C_ERROR_CODE_VALUES)

static void on_umock_c_error(UMOCK_C_ERROR_CODE error_code)
{
    char temp_str[256];
    (void)snprintf(temp_str, sizeof(temp_str), "umock_c reported error :%s", ENUM_TO_STRING(UMOCK_C_ERROR_CODE, error_code));
    ASSERT_FAIL(temp_str);
}

BEGIN_TEST_SUITE(http_proxy_io_unittests)

TEST_SUITE_INITIALIZE(suite_init)
{
    int result;

    g_testByTest = TEST_MUTEX_CREATE();
    ASSERT_IS_NOT_NULL(g_testByTest);

    result = umock_c_init(on_umock_c_error);
    ASSERT_ARE_EQUAL(int, 0, result);
    result = umocktypes_charptr_register_types();
    ASSERT_ARE_EQUAL(int, 0, result);

    REGISTER_GLOBAL_MOCK_HOOK(gballoc_malloc, real_malloc);
    REGISTER_GLOBAL_MOCK_HOOK(gballoc_realloc, real_realloc);
    REGISTER_GLOBAL_MOCK_HOOK(gballoc_free, real_free);
    REGISTER_GLOBAL_MOCK_HOOK(mallocAndStrcpy_s, real_mallocAndStrcpy_s);
    REGISTER_GLOBAL_MOCK_HOOK(OptionHandler_Create, my_OptionHandler_Create);
    REGISTER_GLOBAL_MOCK_HOOK(xio_open, my_xio_open);
    REGISTER_GLOBAL_MOCK_HOOK(xio_close, my_xio_close);
    REGISTER_GLOBAL_MOCK_RETURN(OptionHandler_Create, TEST_OPTION_HANDLER);
    REGISTER_GLOBAL_MOCK_RETURN(socketio_get_interface_description, TEST_SOCKETIO_INTERFACE_DESCRIPTION);
    REGISTER_GLOBAL_MOCK_RETURN(xio_create, TEST_IO_HANDLE);
    REGISTER_GLOBAL_MOCK_RETURN(xio_retrieveoptions, TEST_OPTION_HANDLER);
    REGISTER_GLOBAL_MOCK_RETURN(Base64_Encode_Bytes, TEST_STRING_HANDLE);
    REGISTER_GLOBAL_MOCK_RETURN(STRING_c_str, "test_str");
    REGISTER_TYPE(IO_OPEN_RESULT, IO_OPEN_RESULT);
    REGISTER_TYPE(IO_SEND_RESULT, IO_SEND_RESULT);
    REGISTER_TYPE(const SOCKETIO_CONFIG*, const_SOCKETIO_CONFIG_ptr);
    REGISTER_UMOCK_ALIAS_TYPE(pfCloneOption, void*);
    REGISTER_UMOCK_ALIAS_TYPE(pfDestroyOption, void*);
    REGISTER_UMOCK_ALIAS_TYPE(pfSetOption, void*);
    REGISTER_UMOCK_ALIAS_TYPE(OPTIONHANDLER_HANDLE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(SOCKETIO_CONFIG*, const SOCKETIO_CONFIG*);
    REGISTER_UMOCK_ALIAS_TYPE(XIO_HANDLE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(ON_IO_OPEN_COMPLETE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(ON_BYTES_RECEIVED, void*);
    REGISTER_UMOCK_ALIAS_TYPE(ON_IO_ERROR, void*);
    REGISTER_UMOCK_ALIAS_TYPE(ON_IO_CLOSE_COMPLETE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(ON_SEND_COMPLETE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(STRING_HANDLE, void*);
    REGISTER_UMOCKC_PAIRED_CREATE_DESTROY_CALLS(xio_create, xio_destroy);
}

TEST_SUITE_CLEANUP(suite_cleanup)
{
    umock_c_deinit();

    TEST_MUTEX_DESTROY(g_testByTest);
}

TEST_FUNCTION_INITIALIZE(TestMethodInitialize)
{
    if (TEST_MUTEX_ACQUIRE(g_testByTest))
    {
        ASSERT_FAIL("our mutex is ABANDONED. Failure in test framework");
    }

    umock_c_reset_all_calls();
}

TEST_FUNCTION_CLEANUP(TestMethodCleanup)
{
    umock_c_negative_tests_deinit();

    TEST_MUTEX_RELEASE(g_testByTest);
}

/* http_proxy_io_create */

/* Tests_SRS_HTTP_PROXY_IO_01_001: [ `http_proxy_io_create` shall create a new instance of the HTTP proxy IO. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_003: [ `io_create_parameters` shall be used as an `HTTP_PROXY_IO_CONFIG*`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_005: [ `http_proxy_io_create` shall copy the `hostname`, `port`, `username` and `password` values for later use when the actual CONNECT is performed. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_006: [ `hostname` and `proxy_hostname`, `username` and `password` shall be copied by calling `mallocAndStrcpy_s`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_009: [ `http_proxy_io_create` shall create a new socket IO by calling `xio_create` with the arguments: ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_010: [ - `io_interface_description` shall be set to the result of `socketio_get_interface_description`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_011: [ - `xio_create_parameters` shall be set to a `SOCKETIO_CONFIG*` where `hostname` is set to the `proxy_hostname` member of `io_create_parameters` and `port` is set to the `proxy_port` member of `io_create_parameters`. ]*/
TEST_FUNCTION(http_proxy_io_create_succeeds)
{
    // arrange
    HTTP_PROXY_IO_CONFIG http_proxy_io_config;
    CONCRETE_IO_HANDLE http_io;

    http_proxy_io_config.hostname = "test_host";
    http_proxy_io_config.port = 443;
    http_proxy_io_config.proxy_hostname = "a_proxy";
    http_proxy_io_config.proxy_port = 4444;
    http_proxy_io_config.username = "test_user";
    http_proxy_io_config.password = "shhhh";

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "test_host"))
        .IgnoreArgument_destination();
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "a_proxy"))
        .IgnoreArgument_destination();
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "test_user"))
        .IgnoreArgument_destination();
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "shhhh"))
        .IgnoreArgument_destination();
    STRICT_EXPECTED_CALL(socketio_get_interface_description());
    STRICT_EXPECTED_CALL(xio_create(TEST_SOCKETIO_INTERFACE_DESCRIPTION, &socketio_config))
        .ValidateArgumentValue_io_create_parameters_AsType(UMOCK_TYPE(SOCKETIO_CONFIG*));

    // act
    http_io = http_proxy_io_get_interface_description()->concrete_io_create(&http_proxy_io_config);

    // assert
    ASSERT_IS_NOT_NULL(http_io);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_094: [ `username` and `password` shall be optional. ]*/
TEST_FUNCTION(http_proxy_io_create_with_NULL_username_and_password_succeeds)
{
    // arrange
    HTTP_PROXY_IO_CONFIG http_proxy_io_config;
    CONCRETE_IO_HANDLE http_io;

    http_proxy_io_config.hostname = "test_host";
    http_proxy_io_config.port = 443;
    http_proxy_io_config.proxy_hostname = "a_proxy";
    http_proxy_io_config.proxy_port = 4444;
    http_proxy_io_config.username = NULL;
    http_proxy_io_config.password = NULL;

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "test_host"))
        .IgnoreArgument_destination();
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "a_proxy"))
        .IgnoreArgument_destination();
    STRICT_EXPECTED_CALL(socketio_get_interface_description());
    STRICT_EXPECTED_CALL(xio_create(TEST_SOCKETIO_INTERFACE_DESCRIPTION, &socketio_config))
        .ValidateArgumentValue_io_create_parameters_AsType(UMOCK_TYPE(SOCKETIO_CONFIG*));

    // act
    http_io = http_proxy_io_get_interface_description()->concrete_io_create(&http_proxy_io_config);

    // assert
    ASSERT_IS_NOT_NULL(http_io);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_095: [ If one of the fields `username` and `password` is non-NULL, then the other has to be also non-NULL, otherwise `http_proxy_io_create` shall fail and return NULL. ]*/
TEST_FUNCTION(http_proxy_io_create_with_NULL_username_and_non_NULL_password_fails)
{
    // arrange
    HTTP_PROXY_IO_CONFIG http_proxy_io_config;
    CONCRETE_IO_HANDLE http_io;

    http_proxy_io_config.hostname = "test_host";
    http_proxy_io_config.port = 443;
    http_proxy_io_config.proxy_hostname = "a_proxy";
    http_proxy_io_config.proxy_port = 4444;
    http_proxy_io_config.username = NULL;
    http_proxy_io_config.password = "a";

    // act
    http_io = http_proxy_io_get_interface_description()->concrete_io_create(&http_proxy_io_config);

    // assert
    ASSERT_IS_NULL(http_io);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_095: [ If one of the fields `username` and `password` is non-NULL, then the other has to be also non-NULL, otherwise `http_proxy_io_create` shall fail and return NULL. ]*/
TEST_FUNCTION(http_proxy_io_create_with_non_NULL_username_and_NULL_password_fails)
{
    // arrange
    HTTP_PROXY_IO_CONFIG http_proxy_io_config;
    CONCRETE_IO_HANDLE http_io;

    http_proxy_io_config.hostname = "test_host";
    http_proxy_io_config.port = 443;
    http_proxy_io_config.proxy_hostname = "a_proxy";
    http_proxy_io_config.proxy_port = 4444;
    http_proxy_io_config.username = "a";
    http_proxy_io_config.password = NULL;

    // act
    http_io = http_proxy_io_get_interface_description()->concrete_io_create(&http_proxy_io_config);

    // assert
    ASSERT_IS_NULL(http_io);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_002: [ If `io_create_parameters` is NULL, `http_proxy_io_create` shall fail and return NULL. ]*/
TEST_FUNCTION(http_proxy_io_create_with_NULL_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    // act
    http_io = http_proxy_io_get_interface_description()->concrete_io_create(NULL);

    // assert
    ASSERT_IS_NULL(http_io);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_004: [ If the `hostname` or `proxy_hostname` member is NULL, then `http_proxy_io_create` shall fail and return NULL. ]*/
TEST_FUNCTION(http_proxy_io_create_with_NULL_hostname_fails)
{
    // arrange
    HTTP_PROXY_IO_CONFIG http_proxy_io_config;
    CONCRETE_IO_HANDLE http_io;

    http_proxy_io_config.hostname = NULL;
    http_proxy_io_config.port = 443;
    http_proxy_io_config.proxy_hostname = "a_proxy";
    http_proxy_io_config.proxy_port = 4444;
    http_proxy_io_config.username = "test_user";
    http_proxy_io_config.password = "shhhh";

    // act
    http_io = http_proxy_io_get_interface_description()->concrete_io_create(&http_proxy_io_config);

    // assert
    ASSERT_IS_NULL(http_io);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_004: [ If the `hostname` or `proxy_hostname` member is NULL, then `http_proxy_io_create` shall fail and return NULL. ]*/
TEST_FUNCTION(http_proxy_io_create_with_NULL_proxy_hostname_fails)
{
    // arrange
    HTTP_PROXY_IO_CONFIG http_proxy_io_config;
    CONCRETE_IO_HANDLE http_io;

    http_proxy_io_config.hostname = "a_hostname";
    http_proxy_io_config.port = 443;
    http_proxy_io_config.proxy_hostname = NULL;
    http_proxy_io_config.proxy_port = 4444;
    http_proxy_io_config.username = "test_user";
    http_proxy_io_config.password = "shhhh";

    // act
    http_io = http_proxy_io_get_interface_description()->concrete_io_create(&http_proxy_io_config);

    // assert
    ASSERT_IS_NULL(http_io);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_051: [ If allocating memory for the new instance fails, `http_proxy_io_create` shall fail and return NULL. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_007: [ If `mallocAndStrcpy_s` fails then `http_proxy_io_create` shall fail and return NULL. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_050: [ If `socketio_get_interface_description` fails, `http_proxy_io_create` shall fail and return NULL. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_012: [ If `xio_create` fails, `http_proxy_io_create` shall fail and return NULL. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_008: [ When `http_proxy_io_create` fails, all allocated resources up to that point shall be freed. ]*/
TEST_FUNCTION(when_a_call_made_by_http_proxy_io_create_fails_then_http_proxy_io_create_fails)
{
    // arrange
    size_t i;
    int negativeTestsInitResult = umock_c_negative_tests_init();
    ASSERT_ARE_EQUAL(int, 0, negativeTestsInitResult);

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG))
        .SetFailReturn(NULL);
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "test_host"))
        .IgnoreArgument_destination()
        .SetFailReturn(1);
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "a_proxy"))
        .IgnoreArgument_destination()
        .SetFailReturn(1);
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "test_user"))
        .IgnoreArgument_destination()
        .SetFailReturn(1);
    STRICT_EXPECTED_CALL(mallocAndStrcpy_s(IGNORED_PTR_ARG, "shhhh"))
        .IgnoreArgument_destination()
        .SetFailReturn(1);
    STRICT_EXPECTED_CALL(socketio_get_interface_description())
        .SetFailReturn(NULL);
    STRICT_EXPECTED_CALL(xio_create(TEST_SOCKETIO_INTERFACE_DESCRIPTION, &socketio_config))
        .ValidateArgumentValue_io_create_parameters_AsType(UMOCK_TYPE(SOCKETIO_CONFIG*))
        .SetFailReturn(NULL);

    umock_c_negative_tests_snapshot();

    for (i = 0; i < umock_c_negative_tests_call_count() - 1; i++)
    {
        char temp_str[128];
        CONCRETE_IO_HANDLE http_io;

        umock_c_negative_tests_reset();
        umock_c_negative_tests_fail_call(i);

        (void)sprintf(temp_str, "On failed call %zu", i);

        // act
        http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);

        // assert
        ASSERT_IS_NULL(http_io, temp_str);
    }
}

/* http_proxy_io_destroy */

/* Tests_SRS_HTTP_PROXY_IO_01_013: [ `http_proxy_io_destroy` shall free the HTTP proxy IO instance indicated by `http_proxy_io`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_016: [ `http_proxy_io_destroy` shall destroy the underlying IO created in `http_proxy_io_create` by calling `xio_destroy`. ]*/
TEST_FUNCTION(http_proxy_io_destroy_frees_the_resources)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_destroy(TEST_IO_HANDLE));
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));

    // act
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_014: [ If `http_proxy_io` is NULL, `http_proxy_io_destroy` shall do nothing. ]*/
TEST_FUNCTION(http_proxy_io_destroy_with_NULL_does_nothing)
{
    // arrange

    // act
    http_proxy_io_get_interface_description()->concrete_io_destroy(NULL);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* http_proxy_io_open */

/* Tests_SRS_HTTP_PROXY_IO_01_017: [ `http_proxy_io_open` shall open the HTTP proxy IO and on success it shall return 0. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_019: [ `http_proxy_io_open` shall open the underlying IO by calling `xio_open` on the underlying IO handle created in `http_proxy_io_create`, while passing to it the callbacks `on_underlying_io_open_complete`, `on_underlying_io_bytes_received` and `on_underlying_io_error`. ]*/
TEST_FUNCTION(http_proxy_io_open_opens_the_underlying_IO)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, http_io, test_on_bytes_received, http_io, test_on_io_error, http_io);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_020: [ If `xio_open` fails, then `http_proxy_io_open` shall return a non-zero value. ]*/
TEST_FUNCTION(when_the_underlying_xio_open_fails_http_proxy_io_open_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context()
        .SetReturn(1);

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, http_io, test_on_bytes_received, http_io, test_on_io_error, http_io);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_018: [ If any of the arguments `http_proxy_io`, `on_io_open_complete`, `on_bytes_received` or `on_io_error` are NULL then `http_proxy_io_open` shall return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_open_with_NULL_open_complete_callback_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, NULL, http_io, test_on_bytes_received, http_io, test_on_io_error, http_io);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_018: [ If any of the arguments `http_proxy_io`, `on_io_open_complete`, `on_bytes_received` or `on_io_error` are NULL then `http_proxy_io_open` shall return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_open_with_NULL_bytes_received_callback_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, http_io, NULL, http_io, test_on_io_error, http_io);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_018: [ If any of the arguments `http_proxy_io`, `on_io_open_complete`, `on_bytes_received` or `on_io_error` are NULL then `http_proxy_io_open` shall return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_open_with_NULL_on_io_error_callback_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, http_io, test_on_bytes_received, http_io, NULL, http_io);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_018: [ If any of the arguments `http_proxy_io`, `on_io_open_complete`, `on_bytes_received` or `on_io_error` are NULL then `http_proxy_io_open` shall return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_open_with_NULL_handle_fails)
{
    // arrange
    int result;

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(NULL, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4242, test_on_io_error, (void*)0x4242);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_021: [ If `http_proxy_io_open` is called while the IO was already open, `http_proxy_io_open` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_open_after_open_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_051: [ The arguments `on_io_open_complete_context`, `on_bytes_received_context` and `on_io_error_context` shall be allowed to be NULL. ]*/
TEST_FUNCTION(http_proxy_io_open_with_NULL_contexts_is_allowed)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, NULL, test_on_bytes_received, NULL, test_on_io_error, NULL);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* http_proxy_io_close */

/* Tests_SRS_HTTP_PROXY_IO_01_022: [ `http_proxy_io_close` shall close the HTTP proxy IO and on success it shall return 0. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_024: [ `http_proxy_io_close` shall close the underlying IO by calling `xio_close` on the IO handle create in `http_proxy_io_create`, while passing to it the `on_underlying_io_close_complete` callback. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_026: [ The `on_io_close_complete` and `on_io_close_complete_context` arguments shall be saved for later use. ]*/
TEST_FUNCTION(http_proxy_io_close_closes_the_IO)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_close_complete()
        .IgnoreArgument_callback_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4245);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_023: [ If the argument `http_proxy_io` is NULL, `http_proxy_io_close` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_close_with_NULL_handle_fails)
{
    // arrange
    int result;

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(NULL, test_on_io_close_complete, (void*)0x4245);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_025: [ If `xio_close` fails, `http_proxy_io_close` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(when_xio_close_fails_http_proxy_io_close_also_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_close_complete()
        .IgnoreArgument_callback_context()
        .SetReturn(1);

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4245);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_027: [ If `http_proxy_io_close` is called when not open, `http_proxy_io_close` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_close_when_not_open_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4245);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_027: [ If `http_proxy_io_close` is called when not open, `http_proxy_io_close` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_close_when_already_closed_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    (void)http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4245);
    g_on_io_close_complete(g_on_io_close_complete_context);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4245);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_028: [ `on_io_close_complete` shall be allowed to be NULL. ]*/
TEST_FUNCTION(http_proxy_io_close_with_NULL_close_complete_callback_is_allowed)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_close_complete()
        .IgnoreArgument_callback_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, NULL, (void*)0x4245);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_052: [ `on_io_close_complete_context` shall be allowed to be NULL. ]*/
TEST_FUNCTION(http_proxy_io_close_with_NULL_close_complete_callback_context_is_allowed)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_close_complete()
        .IgnoreArgument_callback_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, NULL);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_053: [ `http_proxy_io_close` while OPENING shall trigger the `on_io_open_complete` callback with `IO_OPEN_CANCELLED`. ]*/
TEST_FUNCTION(http_proxy_io_close_while_opening_indicates_open_as_cancelled)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_CANCELLED));

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4242);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_053: [ `http_proxy_io_close` while OPENING shall trigger the `on_io_open_complete` callback with `IO_OPEN_CANCELLED`. ]*/
TEST_FUNCTION(http_proxy_io_close_while_opening_waiting_for_reply_indicates_open_as_cancelled)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_CANCELLED));

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4242);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_054: [ `http_proxy_io_close` while OPENING shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_close_while_closing_indicates_open_as_cancelled)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    (void)http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4242);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4242);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* http_proxy_io_send */

/* Tests_SRS_HTTP_PROXY_IO_01_029: [ `http_proxy_io_send` shall send the `size` bytes pointed to by `buffer` and on success it shall return 0. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_033: [ `http_proxy_io_send` shall send the bytes by calling `xio_send` on the underlying IO created in `http_proxy_io_create` and passing `buffer` and `size` as arguments. ]*/
TEST_FUNCTION(http_proxy_io_send_calls_send_on_the_underlying_IO)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_send(TEST_IO_HANDLE, IGNORED_PTR_ARG, sizeof(test_buffer), test_on_send_complete, (void*)0x4247))
        .ValidateArgumentBuffer(2, test_buffer, sizeof(test_buffer));

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, sizeof(test_buffer), test_on_send_complete, (void*)0x4247);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_030: [ If any of the arguments `http_proxy_io` or `buffer` is NULL, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_send_with_NULL_handle_fails)
{
    // arrange
    int result;
    unsigned char test_buffer[] = { 0x42 };

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(NULL, test_buffer, sizeof(test_buffer), test_on_send_complete, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_030: [ If any of the arguments `http_proxy_io` or `buffer` is NULL, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_send_with_NULL_buffer_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, NULL, 1, test_on_send_complete, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_031: [ If `size` is 0, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_send_with_zero_size_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, 0, test_on_send_complete, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_032: [ `on_send_complete` shall be allowed to be NULL. ]*/
TEST_FUNCTION(http_proxy_io_send_with_NULL_send_complete_callback_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_send(TEST_IO_HANDLE, IGNORED_PTR_ARG, sizeof(test_buffer), NULL, (void*)0x4247))
        .ValidateArgumentBuffer(2, test_buffer, sizeof(test_buffer));

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, sizeof(test_buffer), NULL, (void*)0x4247);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_034: [ If `http_proxy_io_send` is called when the IO is not open, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_send_when_waiting_for_connect_reply_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, sizeof(test_buffer), NULL, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_034: [ If `http_proxy_io_send` is called when the IO is not open, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_send_when_opening_underlying_IO_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, sizeof(test_buffer), NULL, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_034: [ If `http_proxy_io_send` is called when the IO is not open, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_send_when_open_not_called_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, sizeof(test_buffer), NULL, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_034: [ If `http_proxy_io_send` is called when the IO is not open, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_send_when_closing_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    (void)http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4247);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, sizeof(test_buffer), NULL, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_035: [ If the IO is in an error state (an error was reported through the `on_io_error` callback), `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_send_when_IO_is_in_error_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    g_on_io_error(g_on_io_error_context);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, sizeof(test_buffer), NULL, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_055: [ If `xio_send` fails, `http_proxy_io_send` shall fail and return a non-zero value. ]*/
TEST_FUNCTION(when_xio_send_fails_http_proxy_io_send_also_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;
    unsigned char test_buffer[] = { 0x42 };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_send(TEST_IO_HANDLE, IGNORED_PTR_ARG, sizeof(test_buffer), NULL, (void*)0x4247))
        .ValidateArgumentBuffer(2, test_buffer, sizeof(test_buffer))
        .SetReturn(1);

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_send(http_io, test_buffer, sizeof(test_buffer), NULL, (void*)0x4247);

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* http_proxy_io_dowork */

/* Tests_SRS_HTTP_PROXY_IO_01_037: [ `http_proxy_io_dowork` shall call `xio_dowork` on the underlying IO created in `http_proxy_io_create`. ]*/
TEST_FUNCTION(http_proxy_io_dowork_calls_the_underlying_IO_dowork)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_dowork(TEST_IO_HANDLE));

    // act
    http_proxy_io_get_interface_description()->concrete_io_dowork(http_io);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_038: [ If the `http_proxy_io` argument is NULL, `http_proxy_io_dowork` shall do nothing. ]*/
TEST_FUNCTION(http_proxy_io_dowork_with_NULL_handle_does_nothing)
{
    // arrange

    // act
    http_proxy_io_get_interface_description()->concrete_io_dowork(NULL);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_039: [ If the IO is not open (no open has been called or the IO has been closed) then `http_proxy_io_dowork` shall do nothing. ]*/
TEST_FUNCTION(http_proxy_io_dowork_when_not_open_does_nothing)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    // act
    http_proxy_io_get_interface_description()->concrete_io_dowork(http_io);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_039: [ If the IO is not open (no open has been called or the IO has been closed) then `http_proxy_io_dowork` shall do nothing. ]*/
TEST_FUNCTION(http_proxy_io_dowork_when_closed_does_nothing)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    (void)http_proxy_io_get_interface_description()->concrete_io_close(http_io, NULL, NULL);
    g_on_io_close_complete(g_on_io_close_complete_context);
    umock_c_reset_all_calls();

    // act
    http_proxy_io_get_interface_description()->concrete_io_dowork(http_io);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* http_proxy_io_set_option */

/* Tests_SRS_HTTP_PROXY_IO_01_040: [ If any of the arguments `http_proxy_io` or `option_name` is NULL, `http_proxy_io_set_option` shall return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_set_option_with_NULL_option_name_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_setoption(http_io, NULL, "test");

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_040: [ If any of the arguments `http_proxy_io` or `option_name` is NULL, `http_proxy_io_set_option` shall return a non-zero value. ]*/
TEST_FUNCTION(http_proxy_io_set_option_with_NULL_handle_fails)
{
    // arrange
    int result;

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_setoption(NULL, "option_1", "test");

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_042: [ If the option was handled by `http_proxy_io_set_option` or the underlying IO, then `http_proxy_io_set_option` shall return 0. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_043: [ If the `option_name` argument indicates an option that is not handled by `http_proxy_io_set_option`, then `xio_setoption` shall be called on the underlying IO created in `http_proxy_io_create`, passing the option name and value to it. ]*/
TEST_FUNCTION(when_the_underlying_IO_handles_the_option_http_proxy_io_set_option_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_setoption(TEST_IO_HANDLE, "option_1", "test"))
        .ValidateArgumentValue_value_AsType(UMOCK_TYPE(char*));

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_setoption(http_io, "option_1", "test");

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_044: [ if `xio_setoption` fails, `http_proxy_io_set_option` shall return a non-zero value. ]*/
TEST_FUNCTION(when_the_underlying_xio_setoption_fails_http_proxy_io_set_option_also_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_setoption(TEST_IO_HANDLE, "option_1", "test"))
        .ValidateArgumentValue_value_AsType(UMOCK_TYPE(char*))
        .SetReturn(1);

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_setoption(http_io, "option_1", "test");

    // assert
    ASSERT_ARE_NOT_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_056: [ The `value` argument shall be allowed to be NULL. ]*/
TEST_FUNCTION(http_proxy_io_set_option_with_NULL_value_is_allowed)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_setoption(TEST_IO_HANDLE, "option_2", NULL))
        .ValidateArgumentValue_value_AsType(UMOCK_TYPE(char*));

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_setoption(http_io, "option_2", NULL);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* http_proxy_io_retrieve_options */

/* Tests_SRS_HTTP_PROXY_IO_01_046: [ `http_proxy_io_retrieve_options` shall return an `OPTIONHANDLER_HANDLE` obtained by calling `xio_retrieveoptions` on the underlying IO created in `http_proxy_io_create`. ]*/
TEST_FUNCTION(http_proxy_io_retrieve_options_calls_the_underlying_retrieve_options)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    OPTIONHANDLER_HANDLE result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_retrieveoptions(TEST_IO_HANDLE));

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_retrieveoptions(http_io);

    // assert
    ASSERT_ARE_EQUAL(void_ptr, TEST_OPTION_HANDLER, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_047: [ If the parameter `http_proxy_io` is NULL then `http_proxy_io_retrieve_options` shall fail and return NULL. ]*/
TEST_FUNCTION(http_proxy_io_retrieve_options_with_NULL_handle_fails)
{
    // arrange
    OPTIONHANDLER_HANDLE result;

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_retrieveoptions(NULL);

    // assert
    ASSERT_IS_NULL(result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());
}

/* Tests_SRS_HTTP_PROXY_IO_01_048: [ If `xio_retrieveoptions` fails, `http_proxy_io_retrieve_options` shall return NULL. ]*/
TEST_FUNCTION(when_xio_retrieveoptions_fails_then_http_proxy_io_retrieve_options_fails)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    OPTIONHANDLER_HANDLE result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&default_http_proxy_io_config);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_retrieveoptions(TEST_IO_HANDLE))
        .SetReturn(NULL);

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_retrieveoptions(http_io);

    // assert
    ASSERT_IS_NULL(result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* http_proxy_io_get_interface_description */

/* Tests_SRS_HTTP_PROXY_IO_01_049: [ `http_proxy_io_get_interface_description` shall return a pointer to an `IO_INTERFACE_DESCRIPTION` structure that contains pointers to the functions: `http_proxy_io_retrieve_options`, `http_proxy_io_retrieve_create`, `http_proxy_io_destroy`, `http_proxy_io_open`, `http_proxy_io_close`, `http_proxy_io_send` and `http_proxy_io_dowork`. ]*/
TEST_FUNCTION(http_proxy_io_get_interface_description_returns_a_structure_with_non_NULL_members)
{
    // arrange
    const IO_INTERFACE_DESCRIPTION* io_interface;

    // act
    io_interface = http_proxy_io_get_interface_description();

    // assert
    ASSERT_IS_NOT_NULL(io_interface);
    ASSERT_IS_NOT_NULL(io_interface->concrete_io_create);
    ASSERT_IS_NOT_NULL(io_interface->concrete_io_destroy);
    ASSERT_IS_NOT_NULL(io_interface->concrete_io_open);
    ASSERT_IS_NOT_NULL(io_interface->concrete_io_close);
    ASSERT_IS_NOT_NULL(io_interface->concrete_io_send);
    ASSERT_IS_NOT_NULL(io_interface->concrete_io_setoption);
    ASSERT_IS_NOT_NULL(io_interface->concrete_io_retrieveoptions);
}

/* on_underlying_io_open_complete */

/* Tests_SRS_HTTP_PROXY_IO_01_081: [ `on_underlying_io_open_complete` called with NULL context shall do nothing. ]*/
TEST_FUNCTION(underlying_io_open_complete_with_NULL_does_nothing)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_no_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    // act
    g_on_io_open_complete(NULL, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_057: [ When `on_underlying_io_open_complete` is called, the `http_proxy_io` shall send the CONNECT request constructed per RFC 2817: ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_075: [ The Request-URI portion of the Request-Line is always an 'authority' as defined by URI Generic Syntax [2], which is to say the host name and port number destination of the requested connection separated by a colon: ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_063: [ The request shall be sent by calling `xio_send` and passing NULL as `on_send_complete` callback. ]*/
TEST_FUNCTION(when_the_underlying_io_open_complete_is_called_the_CONNECT_request_is_sent)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    const char connect_request[] = "CONNECT test_host:443 HTTP/1.1\r\nHost:test_host:443\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_no_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_send(TEST_IO_HANDLE, IGNORED_PTR_ARG, sizeof(connect_request) - 1, IGNORED_PTR_ARG, NULL))
        .ValidateArgumentBuffer(2, connect_request, sizeof(connect_request) - 1);
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_064: [ If `xio_send` fails, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(when_xio_send_fails_on_open_complete_is_triggered_with_IO_OPEN_ERROR)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    const char connect_request[] = "CONNECT test_host:443 HTTP/1.1\r\nHost:test_host:443\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_no_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_send(TEST_IO_HANDLE, IGNORED_PTR_ARG, sizeof(connect_request) - 1, IGNORED_PTR_ARG, NULL))
        .ValidateArgumentBuffer(2, connect_request, sizeof(connect_request) - 1)
        .SetReturn(1);
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_017: [ `http_proxy_io_open` shall open the HTTP proxy IO and on success it shall return 0. ]*/
TEST_FUNCTION(http_proxy_io_open_after_CONNECT_request_send_error_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    const char connect_request[] = "CONNECT test_host:443 HTTP/1.1\r\nHost:test_host:443\r\n\r\n";
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_no_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_send(TEST_IO_HANDLE, IGNORED_PTR_ARG, sizeof(connect_request) - 1, IGNORED_PTR_ARG, NULL))
        .ValidateArgumentBuffer(2, connect_request, sizeof(connect_request) - 1)
        .SetReturn(1);
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));

    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(when_allocating_memory_for_the_connect_request_fails_on_open_complete_is_triggered_with_IO_OPEN_ERROR)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_no_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG))
        .SetReturn(NULL);
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_017: [ `http_proxy_io_open` shall open the HTTP proxy IO and on success it shall return 0. ]*/
TEST_FUNCTION(http_proxy_io_open_after_CONNECT_request_allocation_error_error_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_no_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG))
        .SetReturn(NULL);

    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_059: [ - If `username` and `password` have been specified in the arguments passed to `http_proxy_io_create`, then the header `Proxy-Authorization` shall be added to the request. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_061: [ Encoding to Base64 shall be done by calling `Base64_Encode_Bytes`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_060: [ - The value of `Proxy-Authorization` shall be the constructed according to RFC 2617. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_091: [ To receive authorization, the client sends the userid and password, separated by a single colon (":") character, within a base64 [7] encoded string in the credentials. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_092: [ A client MAY preemptively send the corresponding Authorization header with requests for resources in that space without receipt of another challenge from the server. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_093: [ Userids might be case sensitive. ]*/
TEST_FUNCTION(when_the_underlying_io_open_complete_is_called_the_CONNECT_request_with_auth_is_sent)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    const char connect_request[] = "CONNECT another_test_host:445 HTTP/1.1\r\nHost:another_test_host:445\r\nProxy-authorization: Basic __encoded_base64__\r\n\r\n";
    const char plain_auth_string[] = "le_user:le_password";
    const char base64encoded[] = "__encoded_base64__";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // auth
    STRICT_EXPECTED_CALL(Base64_Encode_Bytes(IGNORED_NUM_ARG, sizeof(plain_auth_string) - 1))
        .ValidateArgumentBuffer(1, plain_auth_string, sizeof(plain_auth_string) - 1);
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); // auth
    STRICT_EXPECTED_CALL(STRING_c_str(TEST_STRING_HANDLE))
        .SetReturn(base64encoded);
    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_send(TEST_IO_HANDLE, IGNORED_PTR_ARG, sizeof(connect_request) - 1, IGNORED_PTR_ARG, NULL))
        .ValidateArgumentBuffer(2, connect_request, sizeof(connect_request) - 1);
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(STRING_delete(TEST_STRING_HANDLE));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_059: [ - If `username` and `password` have been specified in the arguments passed to `http_proxy_io_create`, then the header `Proxy-Authorization` shall be added to the request. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_061: [ Encoding to Base64 shall be done by calling `Base64_Encode_Bytes`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_060: [ - The value of `Proxy-Authorization` shall be the constructed according to RFC 2617. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_091: [ To receive authorization, the client sends the userid and password, separated by a single colon (":") character, within a base64 [7] encoded string in the credentials. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_092: [ A client MAY preemptively send the corresponding Authorization header with requests for resources in that space without receipt of another challenge from the server. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_093: [ Userids might be case sensitive. ]*/
TEST_FUNCTION(when_the_underlying_io_open_complete_is_called_the_CONNECT_request_with_auth_is_sent_cased)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    const char connect_request[] = "CONNECT another_test_host:445 HTTP/1.1\r\nHost:another_test_host:445\r\nProxy-authorization: Basic __encoded_base64__\r\n\r\n";
    const char plain_auth_string[] = "lE_uSeR:lE_pAsSwOrD";
    const char base64encoded[] = "__encoded_base64__";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username_cased);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // auth
    STRICT_EXPECTED_CALL(Base64_Encode_Bytes(IGNORED_NUM_ARG, sizeof(plain_auth_string) - 1))
        .ValidateArgumentBuffer(1, plain_auth_string, sizeof(plain_auth_string) - 1);
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); // auth
    STRICT_EXPECTED_CALL(STRING_c_str(TEST_STRING_HANDLE))
        .SetReturn(base64encoded);
    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_send(TEST_IO_HANDLE, IGNORED_PTR_ARG, sizeof(connect_request) - 1, IGNORED_PTR_ARG, NULL))
        .ValidateArgumentBuffer(2, connect_request, sizeof(connect_request) - 1);
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(STRING_delete(TEST_STRING_HANDLE));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(when_Base64_Encode_Bytes_fails_on_open_complete_is_triggered_with_IO_OPEN_ERROR)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    const char plain_auth_string[] = "le_user:le_password";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // auth
    STRICT_EXPECTED_CALL(Base64_Encode_Bytes(IGNORED_NUM_ARG, sizeof(plain_auth_string) - 1))
        .ValidateArgumentBuffer(1, plain_auth_string, sizeof(plain_auth_string) - 1)
        .SetReturn(NULL);
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));
    EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG)); // auth

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(after_Base64_Encode_Bytes_fails_http_proxy_io_open_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    const char plain_auth_string[] = "le_user:le_password";
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG)); // auth
    STRICT_EXPECTED_CALL(Base64_Encode_Bytes(IGNORED_NUM_ARG, sizeof(plain_auth_string) - 1))
        .ValidateArgumentBuffer(1, plain_auth_string, sizeof(plain_auth_string) - 1)
        .SetReturn(NULL);

    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(when_allocating_memory_for_the_plain_auth_string_fails_on_open_complete_is_triggered_with_IO_OPEN_ERROR)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG))
        .SetReturn(NULL); // auth
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_062: [ If any failure is encountered while constructing the request, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(after_allocating_memory_for_the_plain_auth_string_fails_http_proxy_io_open_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG))
        .SetReturn(NULL); // auth

    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_078: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_ERROR`, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(on_underlying_io_open_complete_with_error_yields_an_error)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_ERROR);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_078: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_ERROR`, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(after_on_underlying_io_open_complete_with_error_http_proxy_io_open_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_ERROR);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_079: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_CANCELLED`, the `on_open_complete` callback shall be triggered with `IO_OPEN_CANCELLED`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(on_underlying_io_open_complete_with_cancelled_yields_an_error)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_CANCELLED));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_CANCELLED);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_079: [ When `on_underlying_io_open_complete` is called with `IO_OPEN_CANCELLED`, the `on_open_complete` callback shall be triggered with `IO_OPEN_CANCELLED`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(after_on_underlying_io_open_complete_with_cancelled_http_proxy_io_open_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_CANCELLED);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_076: [ When `on_underlying_io_open_complete` is called while waiting for the CONNECT reply, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(when_on_underlying_io_open_complete_is_called_when_waiting_for_connect_reply_an_error_is_indicated)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_076: [ When `on_underlying_io_open_complete` is called while waiting for the CONNECT reply, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(after_on_underlying_io_open_complete_is_called_when_waiting_for_connect_reply_http_proxy_io_open_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_077: [ When `on_underlying_io_open_complete` is called in after OPEN has completed, the `on_io_error` callback shall be triggered passing the `on_io_error_context` argument as `context`. ]*/
TEST_FUNCTION(on_underlying_io_open_complete_in_OPEN_indicates_an_error)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(test_on_io_error((void*)0x4244));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_077: [ When `on_underlying_io_open_complete` is called in after OPEN has completed, the `on_io_error` callback shall be triggered passing the `on_io_error_context` argument as `context`. ]*/
TEST_FUNCTION(on_underlying_io_open_complete_in_CLOSING_indicates_an_error)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_io_open_complete_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    (void)http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4246);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(test_on_io_error((void*)0x4244));

    // act
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* on_underlying_io_bytes_received */

/* Tests_SRS_HTTP_PROXY_IO_01_065: [ When bytes are received and the response to the CONNECT request was not yet received, the bytes shall be accumulated until a double new-line is detected. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_1_byte_buffers_the_received_bytes)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_065: [ When bytes are received and the response to the CONNECT request was not yet received, the bytes shall be accumulated until a double new-line is detected. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_2_times_1_byte_buffers_the_received_bytes)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, 1);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response + 1, 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_066: [ When a double new-line is detected the response shall be parsed in order to extract the status code. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_069: [ Any successful (2xx) response to a CONNECT request indicates that the proxy has established a connection to the requested host and port, and has switched to tunneling the current connection to that server connection. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_070: [ When a success status code is parsed, the `on_open_complete` callback shall be triggered with `IO_OPEN_OK`, passing also the `on_open_complete_context` argument as `context`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_073: [ Once a success status code was parsed, the IO shall be OPEN. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_a_good_reply_indicates_OPEN_OK)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_OK));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_066: [ When a double new-line is detected the response shall be parsed in order to extract the status code. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_a_good_reply_in_2_chunks_indicates_OPEN_OK)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 2);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_OK));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response + sizeof(connect_response) - 2, 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_067: [ If allocating memory for the buffered bytes fails, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(when_allocating_memory_for_cached_data_in_on_underlying_io_bytes_an_error_is_triggered)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG))
        .SetReturn(NULL);
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_066: [ When a double new-line is detected the response shall be parsed in order to extract the status code. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_069: [ Any successful (2xx) response to a CONNECT request indicates that the proxy has established a connection to the requested host and port, and has switched to tunneling the current connection to that server connection. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_070: [ When a success status code is parsed, the `on_open_complete` callback shall be triggered with `IO_OPEN_OK`, passing also the `on_open_complete_context` argument as `context`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_073: [ Once a success status code was parsed, the IO shall be OPEN. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_a_good_reply_status_code_201_indicates_OPEN_OK)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char connect_response_201[] = "HTTP/1.1 201\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_OK));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response_201, sizeof(connect_response_201) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_066: [ When a double new-line is detected the response shall be parsed in order to extract the status code. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_069: [ Any successful (2xx) response to a CONNECT request indicates that the proxy has established a connection to the requested host and port, and has switched to tunneling the current connection to that server connection. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_070: [ When a success status code is parsed, the `on_open_complete` callback shall be triggered with `IO_OPEN_OK`, passing also the `on_open_complete_context` argument as `context`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_073: [ Once a success status code was parsed, the IO shall be OPEN. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_a_good_reply_status_code_299_indicates_OPEN_OK)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char connect_response_299[] = "HTTP/1.1 299\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_OK));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response_299, sizeof(connect_response_299) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_066: [ When a double new-line is detected the response shall be parsed in order to extract the status code. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_069: [ Any successful (2xx) response to a CONNECT request indicates that the proxy has established a connection to the requested host and port, and has switched to tunneling the current connection to that server connection. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_070: [ When a success status code is parsed, the `on_open_complete` callback shall be triggered with `IO_OPEN_OK`, passing also the `on_open_complete_context` argument as `context`. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_073: [ Once a success status code was parsed, the IO shall be OPEN. ]*/
/* Tests_SRS_HTTP_PROXY_IO_01_090: [ Any successful (2xx) response to a CONNECT request indicates that the proxy has established a connection to the requested host and port, and has switched to tunneling the current connection to that server connection. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_a_good_reply_status_code_200_and_some_text_indicates_OPEN_OK)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char connect_response_200[] = "HTTP/1.1 200 Blah blah\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_OK));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response_200, sizeof(connect_response_200) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_071: [ If the status code is not successful, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_a_199_code_indicates_an_error)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char connect_response_199[] = "HTTP/1.1 199\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response_199, sizeof(connect_response_199) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_071: [ If the status code is not successful, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_a_300_code_indicates_an_error)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char connect_response_300[] = "HTTP/1.1 300\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response_300, sizeof(connect_response_300) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_071: [ If the status code is not successful, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(after_a_bad_status_code_http_proxy_io_open_succeeds)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char connect_response_300[] = "HTTP/1.1 300\r\n\r\n";
    int result;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response_300, sizeof(connect_response_300) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_open(TEST_IO_HANDLE, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG))
        .IgnoreArgument_on_io_open_complete()
        .IgnoreArgument_on_io_open_complete_context()
        .IgnoreArgument_on_bytes_received()
        .IgnoreArgument_on_bytes_received_context()
        .IgnoreArgument_on_io_error()
        .IgnoreArgument_on_io_error_context();

    // act
    result = http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);

    // assert
    ASSERT_ARE_EQUAL(int, 0, result);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_072: [ Any bytes that are extra (not consumed by the CONNECT response), shall be indicated as received by calling the `on_bytes_received` callback and passing the `on_bytes_received_context` as context argument. ]*/
TEST_FUNCTION(one_extra_byte_gets_indicated_as_received)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char connect_response_with_byte[] = "HTTP/1.1 200\r\n\r\nA";
    static const unsigned char expected_bytes[] = { 'A' };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_OK));
    STRICT_EXPECTED_CALL(test_on_bytes_received((void*)0x4243, IGNORED_PTR_ARG, sizeof(expected_bytes)))
        .ValidateArgumentBuffer(2, expected_bytes, sizeof(expected_bytes));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response_with_byte, sizeof(connect_response_with_byte) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_072: [ Any bytes that are extra (not consumed by the CONNECT response), shall be indicated as received by calling the `on_bytes_received` callback and passing the `on_bytes_received_context` as context argument. ]*/
TEST_FUNCTION(three_extra_byte_get_indicated_as_received)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char connect_response_with_byte[] = "HTTP/1.1 200\r\n\r\nABC";
    static const unsigned char expected_bytes[] = { 'A', 'B', 'C' };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_OK));
    STRICT_EXPECTED_CALL(test_on_bytes_received((void*)0x4243, IGNORED_PTR_ARG, sizeof(expected_bytes)))
        .ValidateArgumentBuffer(2, expected_bytes, sizeof(expected_bytes));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response_with_byte, sizeof(connect_response_with_byte) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_074: [ If `on_underlying_io_bytes_received` is called while OPEN, all bytes shall be indicated as received by calling the `on_bytes_received` callback and passing the `on_bytes_received_context` as context argument. ]*/
TEST_FUNCTION(bytes_indicated_as_received_in_OPEN_get_bubbled_up)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const unsigned char expected_bytes[] = { 'A', 'B', 'C' };

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(test_on_bytes_received((void*)0x4243, IGNORED_PTR_ARG, sizeof(expected_bytes)))
        .ValidateArgumentBuffer(2, expected_bytes, sizeof(expected_bytes));

    // act
    g_on_bytes_received(g_on_bytes_received_context, expected_bytes, sizeof(expected_bytes));

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_080: [ If `on_underlying_io_bytes_received` is called while the underlying IO is being opened, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(if_bytes_are_indicated_as_received_while_opening_the_underlying_IO_an_error_is_indicated_in_the_open_complete_callback)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_068: [ If parsing the CONNECT response fails, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(a_bad_reply_triggers_an_error_in_open_complete_callback)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char bad_reply[] = "HTTP/1.1 \r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)bad_reply, sizeof(bad_reply) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_068: [ If parsing the CONNECT response fails, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(a_bad_reply_malformed_char_triggers_an_error_in_open_complete_callback)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char bad_reply[] = "HYTP/1.1 200\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)bad_reply, sizeof(bad_reply) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_068: [ If parsing the CONNECT response fails, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(a_bad_reply_only_one_char_triggers_an_error_in_open_complete_callback)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;
    static const char bad_reply[] = "H\r\n\r\n";

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    EXPECTED_CALL(gballoc_realloc(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)bad_reply, sizeof(bad_reply) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_082: [ `on_underlying_io_bytes_received` called with NULL context shall do nothing. ]*/
TEST_FUNCTION(on_underlying_io_bytes_received_with_NULL_does_nothing)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    // act
    g_on_bytes_received(NULL, (const unsigned char*)connect_response, sizeof(connect_response) - 1);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* on_underlying_io_close_complete */

/* Tests_SRS_HTTP_PROXY_IO_01_083: [ `on_underlying_io_close_complete` while CLOSING shall call the `on_io_close_complete` callback, passing to it the `on_io_close_complete_context` as `context` argument. ]*/
TEST_FUNCTION(on_underlying_io_close_complete_in_CLOSING_triggers_the_close_complete_callback)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    (void)http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4245);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(test_on_io_close_complete((void*)0x4245));

    // act
    g_on_io_close_complete(g_on_io_close_complete_context);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_084: [ `on_underlying_io_close_complete` called with NULL context shall do nothing. ]*/
TEST_FUNCTION(on_underlying_io_close_complete_in_OPEN_does_nothing)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    (void)http_proxy_io_get_interface_description()->concrete_io_close(http_io, test_on_io_close_complete, (void*)0x4245);
    umock_c_reset_all_calls();

    // act
    g_on_io_close_complete(NULL);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_086: [ If the `on_io_close_complete` callback passed to `http_proxy_io_close` was NULL, no callback shall be triggered. ]*/
TEST_FUNCTION(on_underlying_io_close_complete_in_CLOSING_with_NULL_callback_does_not_trigger_any_callback)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    (void)http_proxy_io_get_interface_description()->concrete_io_close(http_io, NULL, (void*)0x4245);
    umock_c_reset_all_calls();

    // act
    g_on_io_close_complete(g_on_io_close_complete_context);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* on_underlying_io_error */

/* Tests_SRS_HTTP_PROXY_IO_01_088: [ `on_underlying_io_error` called with NULL context shall do nothing. ]*/
TEST_FUNCTION(on_underlying_io_error_with_NULL_handle_does_nothing)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    // act
    g_on_io_error(NULL);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_089: [ If the `on_underlying_io_error` callback is called while the IO is OPEN, the `on_io_error` callback shall be called with the `on_io_error_context` argument as `context`. ]*/
TEST_FUNCTION(when_on_underlying_io_error_is_called_in_OPEN_the_error_is_indicated_up)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    g_on_bytes_received(g_on_bytes_received_context, (const unsigned char*)connect_response, sizeof(connect_response) - 1);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(test_on_io_error((void*)0x4244));

    // act
    g_on_io_error(g_on_io_error_context);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_087: [ If the `on_underlying_io_error` callback is called while OPENING, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(when_on_underlying_io_error_is_called_while_waiting_for_CONNECT_response_the_error_is_indicated_via_open_complete)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    g_on_io_open_complete(g_on_io_open_complete_context, IO_OPEN_OK);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_io_error(g_on_io_error_context);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

/* Tests_SRS_HTTP_PROXY_IO_01_087: [ If the `on_underlying_io_error` callback is called while OPENING, the `on_open_complete` callback shall be triggered with `IO_OPEN_ERROR`, passing also the `on_open_complete_context` argument as `context`. ]*/
TEST_FUNCTION(when_on_underlying_io_error_is_called_while_waiting_for_underlying_IO_to_open_the_error_is_indicated_via_open_complete)
{
    // arrange
    CONCRETE_IO_HANDLE http_io;

    http_io = http_proxy_io_get_interface_description()->concrete_io_create((void*)&http_proxy_io_config_with_username);
    (void)http_proxy_io_get_interface_description()->concrete_io_open(http_io, test_on_io_open_complete, (void*)0x4242, test_on_bytes_received, (void*)0x4243, test_on_io_error, (void*)0x4244);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(xio_close(TEST_IO_HANDLE, NULL, NULL));
    STRICT_EXPECTED_CALL(test_on_io_open_complete((void*)0x4242, IO_OPEN_ERROR));

    // act
    g_on_io_error(g_on_io_error_context);

    // assert
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    // cleanup
    http_proxy_io_get_interface_description()->concrete_io_destroy(http_io);
}

END_TEST_SUITE(http_proxy_io_unittests)
