// 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>
#include <cstdint>
#else
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#endif

static void* my_gballoc_malloc(size_t size)
{
    return malloc(size);
}

static void my_gballoc_free(void* ptr)
{
    free(ptr);
}

#include "testrunnerswitcher.h"
#include "umock_c/umock_c.h"
#include "umock_c/umocktypes_charptr.h"
#include "umock_c/umocktypes_stdint.h"
#include "umock_c/umocktypes_bool.h"
#include "umock_c/umock_c_negative_tests.h"
#include "azure_macro_utils/macro_utils.h"

#include "iothub_transport_ll.h"

#define ENABLE_MOCKS
#include "azure_c_shared_utility/threadapi.h"
#include "azure_c_shared_utility/lock.h"
#include "azure_c_shared_utility/vector.h"
#include "iothub_client_core_common.h"

#include "iothub_client_core.h"
#include "internal/iothub_client_private.h"
#undef ENABLE_MOCKS

#include "internal/iothub_transport_ll_private.h"
#include "internal/iothubtransport.h"

#define ENABLE_MOCKS
#include "azure_c_shared_utility/gballoc.h"
#include "umock_c/umock_c_prod.h"
#include "iothub_client_core_ll.h"

MOCKABLE_FUNCTION(, int, FAKE_IoTHubTransport_Subscribe_DeviceMethod, IOTHUB_DEVICE_HANDLE, handle);
MOCKABLE_FUNCTION(, void, FAKE_IoTHubTransport_Unsubscribe_DeviceMethod, IOTHUB_DEVICE_HANDLE, handle);
MOCKABLE_FUNCTION(, int, FAKE_IoTHubTransport_Subscribe_DeviceTwin, IOTHUB_DEVICE_HANDLE, handle);
MOCKABLE_FUNCTION(, void, FAKE_IoTHubTransport_Unsubscribe_DeviceTwin, IOTHUB_DEVICE_HANDLE, handle);
MOCKABLE_FUNCTION(, IOTHUB_CLIENT_RESULT, FAKE_IoTHubTransport_GetTwinAsync, IOTHUB_DEVICE_HANDLE, handle, IOTHUB_CLIENT_DEVICE_TWIN_CALLBACK, completionCallback, void*, callbackContext);
MOCKABLE_FUNCTION(, IOTHUB_CLIENT_RESULT, FAKE_IoTHubTransport_SendMessageDisposition, IOTHUB_DEVICE_HANDLE, handle, IOTHUB_MESSAGE_HANDLE, message, IOTHUBMESSAGE_DISPOSITION_RESULT, disposition);
MOCKABLE_FUNCTION(, IOTHUB_PROCESS_ITEM_RESULT, FAKE_IoTHubTransport_ProcessItem, TRANSPORT_LL_HANDLE, handle, IOTHUB_IDENTITY_TYPE, item_type, IOTHUB_IDENTITY_INFO*, iothub_item);
MOCKABLE_FUNCTION(, STRING_HANDLE, FAKE_IoTHubTransport_GetHostname, TRANSPORT_LL_HANDLE, handle);
MOCKABLE_FUNCTION(, IOTHUB_CLIENT_RESULT, FAKE_IoTHubTransport_SetOption, TRANSPORT_LL_HANDLE, handle, const char*, optionName, const void*, value);
MOCKABLE_FUNCTION(, void, FAKE_IoTHubTransport_Destroy, TRANSPORT_LL_HANDLE, handle);
MOCKABLE_FUNCTION(, IOTHUB_DEVICE_HANDLE, FAKE_IoTHubTransport_Register, TRANSPORT_LL_HANDLE, handle, const IOTHUB_DEVICE_CONFIG*, device, PDLIST_ENTRY, waitingToSend);
MOCKABLE_FUNCTION(, void, FAKE_IoTHubTransport_Unregister, IOTHUB_DEVICE_HANDLE, handle);
MOCKABLE_FUNCTION(, int, FAKE_IoTHubTransport_Subscribe, TRANSPORT_LL_HANDLE, handle);
MOCKABLE_FUNCTION(, void, FAKE_IoTHubTransport_Unsubscribe, TRANSPORT_LL_HANDLE, handle);
MOCKABLE_FUNCTION(, void, FAKE_IoTHubTransport_DoWork, TRANSPORT_LL_HANDLE, handle);
MOCKABLE_FUNCTION(, int, FAKE_IoTHubTransport_SetRetryPolicy, TRANSPORT_LL_HANDLE, handle, IOTHUB_CLIENT_RETRY_POLICY, retryPolicy, size_t, retryTimeoutLimitinSeconds);
MOCKABLE_FUNCTION(, IOTHUB_CLIENT_RESULT, FAKE_IoTHubTransport_GetSendStatus, TRANSPORT_LL_HANDLE, handle, IOTHUB_CLIENT_STATUS*, iotHubClientStatus);
MOCKABLE_FUNCTION(, int, FAKE_IoTHubTransport_DeviceMethod_Response, IOTHUB_DEVICE_HANDLE, handle, METHOD_HANDLE, methodId, const unsigned char*, response, size_t, resp_size, int, status_response);
MOCKABLE_FUNCTION(, TRANSPORT_LL_HANDLE, FAKE_IoTHubTransport_Create, const IOTHUBTRANSPORT_CONFIG*, config, TRANSPORT_CALLBACKS_INFO*, cb_info, void*, ctx);
MOCKABLE_FUNCTION(, int, FAKE_IoTHubTransport_Subscribe_InputQueue, IOTHUB_DEVICE_HANDLE, handle);
MOCKABLE_FUNCTION(, void, FAKE_IoTHubTransport_Unsubscribe_InputQueue, IOTHUB_DEVICE_HANDLE, handle);
MOCKABLE_FUNCTION(, int, FAKE_IoTHubTransport_SetCallbackContext, TRANSPORT_LL_HANDLE, handle, void*, ctx);

#undef ENABLE_MOCKS

#ifdef __cplusplus
extern "C" {
#endif

    extern VECTOR_HANDLE real_VECTOR_create(size_t elementSize);
    extern VECTOR_HANDLE real_VECTOR_move(VECTOR_HANDLE handle);
    extern void real_VECTOR_destroy(VECTOR_HANDLE handle);
    extern int real_VECTOR_push_back(VECTOR_HANDLE handle, const void* elements, size_t numElements);
    extern void real_VECTOR_erase(VECTOR_HANDLE handle, void* elements, size_t numElements);
    extern void real_VECTOR_clear(VECTOR_HANDLE handle);
    extern void* real_VECTOR_element(VECTOR_HANDLE handle, size_t index);
    extern void* real_VECTOR_front(VECTOR_HANDLE handle);
    extern void* real_VECTOR_back(VECTOR_HANDLE handle);
    extern void* real_VECTOR_find_if(VECTOR_HANDLE handle, PREDICATE_FUNCTION pred, const void* value);
    extern size_t real_VECTOR_size(VECTOR_HANDLE handle);
#ifdef __cplusplus
}
#endif

TEST_DEFINE_ENUM_TYPE(IOTHUB_CLIENT_RESULT, IOTHUB_CLIENT_RESULT_VALUES);
IMPLEMENT_UMOCK_C_ENUM_TYPE(IOTHUB_CLIENT_RESULT, IOTHUB_CLIENT_RESULT_VALUES);

#define TEST_DEVICE_ID "theidofTheDevice"
#define TEST_DEVICE_KEY "theKeyoftheDevice"
#define TEST_IOTHUBNAME "theNameoftheIotHub"
#define TEST_IOTHUBSUFFIX "theSuffixoftheIotHubHostname"
#define TEST_AUTHORIZATIONKEY "theAuthorizationKey"
static const IOTHUB_CLIENT_CORE_HANDLE TEST_IOTHUB_CLIENT_CORE_HANDLE1 = (IOTHUB_CLIENT_CORE_HANDLE)0xDEAD;
#define TEST_IOTHUB_CLIENT_CORE_HANDLE2 (IOTHUB_CLIENT_CORE_HANDLE)0xDEAF
#define TEST_CLIENTS_LOCK_HANDLE (LOCK_HANDLE)0x4445
#define TEST_THREAD_HANDLE (THREAD_HANDLE)0x4442

static const TRANSPORT_LL_HANDLE TEST_TRANSPORT_LL_HANDLE = (TRANSPORT_LL_HANDLE)0x112233;
static const LOCK_HANDLE TEST_LOCK_HANDLE = (LOCK_HANDLE)0x4443;
static const VECTOR_HANDLE TEST_VECTOR_HANDLE = (VECTOR_HANDLE)0x4444;

#define TEST_HOSTNAME_TOKEN "HostName"
#define TEST_HOSTNAME_VALUE "theNameoftheIotHub.theSuffixoftheIotHubHostname"

#define TEST_DEVICEID_TOKEN "DeviceId"
#define TEST_DEVICEKEY_TOKEN "SharedAccessKey"
#define TEST_PROTOCOL_GATEWAY_HOST_NAME_TOKEN "GatewayHostName"

#define TEST_DEVICEMESSAGE_HANDLE (IOTHUB_MESSAGE_HANDLE)0x52
#define TEST_DEVICEMESSAGE_HANDLE_2 (IOTHUB_MESSAGE_HANDLE)0x53
#define TEST_IOTHUB_CLIENT_CORE_LL_HANDLE    (IOTHUB_CLIENT_CORE_LL_HANDLE)0x4242

#define TEST_STRING_HANDLE (STRING_HANDLE)0x46
static const char* TEST_CHAR = "TestChar";
static THREAD_START_FUNC threadFunc = NULL;
static void* threadFuncArg = NULL;
static size_t g_num_of_calls = 0;
static size_t g_how_many_dowork_calls = 0;
static TRANSPORT_HANDLE g_transport_handle = NULL;

static const TRANSPORT_PROVIDER* provideFAKE(void);

static const IOTHUB_CLIENT_CONFIG TEST_CONFIG =
{
    provideFAKE,            /* IOTHUB_CLIENT_TRANSPORT_PROVIDER protocol;   */
    TEST_DEVICE_ID,         /* const char* deviceId;                        */
    TEST_DEVICE_KEY,        /* const char* deviceKey;                       */
    NULL,                   /* const char* deviceSasToken                   */
    TEST_IOTHUBNAME,        /* const char* iotHubName;                      */
    TEST_IOTHUBSUFFIX,      /* const char* iotHubSuffix;                    */
};

static const IOTHUB_CLIENT_CONFIG TEST_CONFIG_NULL_protocol =
{
    NULL,                   /* IOTHUB_CLIENT_TRANSPORT_PROVIDER protocol;   */
    TEST_DEVICE_ID,         /* const char* deviceId;                        */
    TEST_DEVICE_KEY,        /* const char* deviceKey;                       */
    NULL,                   /* const char* deviceSasToken                   */
    TEST_IOTHUBNAME,        /* const char* iotHubName;                      */
    TEST_IOTHUBSUFFIX,      /* const char* iotHubSuffix;                    */
};

#define FAKE_TRANSPORT_HANDLE (TRANSPORT_LL_HANDLE)0xDEAD

static const IOTHUB_CLIENT_DEVICE_CONFIG TEST_DEVICE_CONFIG =
{
    provideFAKE,
    FAKE_TRANSPORT_HANDLE,
    TEST_DEVICE_ID,
    TEST_DEVICE_KEY
};

static TRANSPORT_PROVIDER FAKE_transport_provider =
{
    FAKE_IoTHubTransport_SendMessageDisposition,
    FAKE_IoTHubTransport_Subscribe_DeviceMethod,
    FAKE_IoTHubTransport_Unsubscribe_DeviceMethod,
    FAKE_IoTHubTransport_DeviceMethod_Response,
    FAKE_IoTHubTransport_Subscribe_DeviceTwin,
    FAKE_IoTHubTransport_Unsubscribe_DeviceTwin,
    FAKE_IoTHubTransport_ProcessItem,
    FAKE_IoTHubTransport_GetHostname,
    FAKE_IoTHubTransport_SetOption,
    FAKE_IoTHubTransport_Create,
    FAKE_IoTHubTransport_Destroy,
    FAKE_IoTHubTransport_Register,
    FAKE_IoTHubTransport_Unregister,
    FAKE_IoTHubTransport_Subscribe,
    FAKE_IoTHubTransport_Unsubscribe,
    FAKE_IoTHubTransport_DoWork,
    FAKE_IoTHubTransport_SetRetryPolicy,
    FAKE_IoTHubTransport_GetSendStatus,
    FAKE_IoTHubTransport_Subscribe_InputQueue,
    FAKE_IoTHubTransport_Unsubscribe_InputQueue,
    FAKE_IoTHubTransport_SetCallbackContext,
    FAKE_IoTHubTransport_GetTwinAsync
};

static const TRANSPORT_PROVIDER* provideFAKE(void)
{
    return &FAKE_transport_provider; /*by convention... */
}

MU_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", MU_ENUM_TO_STRING(UMOCK_C_ERROR_CODE, error_code));
    ASSERT_FAIL(temp_str);
}

static size_t clientDoWork_calls = 0;
static void clientDoWork(void* clientHandle)
{
    (void)clientHandle;
    clientDoWork_calls++;
}

static LOCK_HANDLE my_Lock_Init(void)
{
    return (LOCK_HANDLE)my_gballoc_malloc(1);
}

static LOCK_RESULT my_Lock_Deinit(LOCK_HANDLE handle)
{
    my_gballoc_free(handle);
    return LOCK_OK;
}

static VECTOR_HANDLE my_VECTOR_create(size_t elementSize)
{
    (void)elementSize;
    return (VECTOR_HANDLE)my_gballoc_malloc(1);
}

static void my_VECTOR_destroy(VECTOR_HANDLE handle)
{
    my_gballoc_free(handle);
}

static THREADAPI_RESULT my_ThreadAPI_Create(THREAD_HANDLE* threadHandle, THREAD_START_FUNC func, void* arg)
{
    *threadHandle = TEST_THREAD_HANDLE;
    threadFunc = func;
    threadFuncArg = arg;
    return THREADAPI_OK;
}

static int my_VECTOR_push_back(VECTOR_HANDLE handle, const void* elements, size_t numElements)
{
    (void)handle;
    (void)elements;
    (void)numElements;
    return 0;
}

static void my_ThreadAPI_Sleep(unsigned int milliseconds)
{
    (void)milliseconds;
}

static void my_FAKE_IoTHubTransport_DoWork(TRANSPORT_LL_HANDLE handle)
{
    (void)handle;
    if ((g_transport_handle != NULL) && (g_num_of_calls >= g_how_many_dowork_calls))
    {
        (void)IoTHubTransport_SignalEndWorkerThread(g_transport_handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1);
    }
    g_num_of_calls++;
}

static TEST_MUTEX_HANDLE g_testByTest;

BEGIN_TEST_SUITE(iothubtransport_ut)

TEST_SUITE_INITIALIZE(suite_init)
{
    g_testByTest = TEST_MUTEX_CREATE();
    ASSERT_IS_NOT_NULL(g_testByTest);

    (void)umock_c_init(on_umock_c_error);
    (void)umocktypes_bool_register_types();
    (void)umocktypes_stdint_register_types();

    REGISTER_UMOCK_ALIAS_TYPE(TRANSPORT_LL_HANDLE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(LOCK_HANDLE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(VECTOR_HANDLE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(THREAD_HANDLE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(THREAD_START_FUNC, void*);
    REGISTER_UMOCK_ALIAS_TYPE(PREDICATE_FUNCTION, void*);
    REGISTER_UMOCK_ALIAS_TYPE(IOTHUB_CLIENT_CORE_LL_HANDLE, void*);
    REGISTER_UMOCK_ALIAS_TYPE(LOCK_RESULT, int);

    REGISTER_GLOBAL_MOCK_HOOK(gballoc_malloc, my_gballoc_malloc);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(gballoc_malloc, NULL);
    REGISTER_GLOBAL_MOCK_HOOK(gballoc_free, my_gballoc_free);

    REGISTER_GLOBAL_MOCK_RETURN(FAKE_IoTHubTransport_Create, TEST_TRANSPORT_LL_HANDLE);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(FAKE_IoTHubTransport_Create, NULL);
    REGISTER_GLOBAL_MOCK_HOOK(FAKE_IoTHubTransport_DoWork, my_FAKE_IoTHubTransport_DoWork);

    REGISTER_GLOBAL_MOCK_RETURN(IoTHubClientCore_LL_GetTransportCallbacks, 0);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(IoTHubClientCore_LL_GetTransportCallbacks, __LINE__);

    REGISTER_GLOBAL_MOCK_HOOK(Lock_Init, my_Lock_Init);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(Lock_Init, NULL);
    REGISTER_GLOBAL_MOCK_HOOK(Lock_Deinit, my_Lock_Deinit);
    REGISTER_GLOBAL_MOCK_RETURN(Lock, LOCK_OK);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(Lock, LOCK_ERROR);

    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_create, real_VECTOR_create);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(VECTOR_create, NULL);
    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_move, real_VECTOR_move);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(VECTOR_move, NULL);
    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_push_back, real_VECTOR_push_back);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(VECTOR_push_back, MU_FAILURE);
    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_element, real_VECTOR_element);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(VECTOR_element, NULL);
    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_find_if, real_VECTOR_find_if);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(VECTOR_find_if, NULL);
    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_erase, real_VECTOR_erase);
    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_clear, real_VECTOR_clear);
    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_destroy, real_VECTOR_destroy);
    REGISTER_GLOBAL_MOCK_HOOK(VECTOR_size, real_VECTOR_size);

    REGISTER_GLOBAL_MOCK_HOOK(ThreadAPI_Create, my_ThreadAPI_Create);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(ThreadAPI_Create, THREADAPI_ERROR);
    REGISTER_GLOBAL_MOCK_RETURN(ThreadAPI_Join, THREADAPI_OK);
    REGISTER_GLOBAL_MOCK_FAIL_RETURN(ThreadAPI_Join, THREADAPI_ERROR);
    REGISTER_GLOBAL_MOCK_HOOK(ThreadAPI_Sleep, my_ThreadAPI_Sleep);
}

TEST_SUITE_CLEANUP(suite_cleanup)
{
    umock_c_deinit();

    TEST_MUTEX_DESTROY(g_testByTest);
}

TEST_FUNCTION_INITIALIZE(method_init)
{
    if (TEST_MUTEX_ACQUIRE(g_testByTest))
    {
        ASSERT_FAIL("Could not acquire test serialization mutex.");
    }
    umock_c_reset_all_calls();

    clientDoWork_calls = 0;
    threadFunc = NULL;
    threadFuncArg = NULL;
    g_num_of_calls = 0;
    g_how_many_dowork_calls = 0;
    g_transport_handle = NULL;

}

TEST_FUNCTION_CLEANUP(method_cleanup)
{
    TEST_MUTEX_RELEASE(g_testByTest);
}

static int should_skip_index(size_t current_index, const size_t skip_array[], size_t length)
{
    int result = 0;
    for (size_t index = 0; index < length; index++)
    {
        if (current_index == skip_array[index])
        {
            result = __LINE__;
            break;
        }
    }
    return result;
}

static void setup_IoTHubTransport_Create(void)
{
    STRICT_EXPECTED_CALL(IoTHubClientCore_LL_GetTransportCallbacks(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(gballoc_malloc(IGNORED_NUM_ARG));
    STRICT_EXPECTED_CALL(FAKE_IoTHubTransport_Create(IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Lock_Init());
    STRICT_EXPECTED_CALL(Lock_Init());
    STRICT_EXPECTED_CALL(VECTOR_create(IGNORED_NUM_ARG));
}

TEST_FUNCTION(IoTHubTransport_Create_provider_NULL_fail)
{
    TRANSPORT_HANDLE handle = NULL;

    //arrange

    //act
    handle = IoTHubTransport_Create(NULL, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_Create_iothub_name_NULL_fail)
{
    TRANSPORT_HANDLE handle = NULL;

    //arrange

    //act
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, NULL, TEST_CONFIG.iotHubSuffix);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_Create_iothubsuffix_NULL_fail)
{
    TRANSPORT_HANDLE handle = NULL;

    //arrange

    //act
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, NULL);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_Create_success)
{
    TRANSPORT_HANDLE handle = NULL;

    //arrange
    setup_IoTHubTransport_Create();

    //act
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);

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

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_Create_fails)
{
    int negativeTestsInitResult = umock_c_negative_tests_init();
    ASSERT_ARE_EQUAL(int, 0, negativeTestsInitResult);

    setup_IoTHubTransport_Create();

    umock_c_negative_tests_snapshot();

    size_t count = umock_c_negative_tests_call_count();
    for (size_t index = 0; index < count; index++)
    {
        umock_c_negative_tests_reset();
        umock_c_negative_tests_fail_call(index);

        char tmp_msg[128];
        sprintf(tmp_msg, "IoTHubTransport_Create failure in test %lu/%lu", (unsigned long)index, (unsigned long)count);

        TRANSPORT_HANDLE handle = NULL;

        //act
        handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);

        //assert
        ASSERT_IS_NULL(handle, tmp_msg);
    }

    //cleanup
    umock_c_negative_tests_deinit();
}

TEST_FUNCTION(IoTHubTransport_Destroy_handle_NULL_fail)
{
    //arrange

    //act
    IoTHubTransport_Destroy(NULL);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_Destroy_success)
{
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    umock_c_reset_all_calls();

    //arrange
    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Lock_Deinit(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(FAKE_IoTHubTransport_Destroy(TEST_TRANSPORT_LL_HANDLE));
    STRICT_EXPECTED_CALL(VECTOR_destroy(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Lock_Deinit(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));

    //act
    IoTHubTransport_Destroy(handle);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_Destroy_worker_thread_success)
{
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);
    umock_c_reset_all_calls();

    //arrange
    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(ThreadAPI_Join(IGNORED_PTR_ARG, IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Lock_Deinit(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(FAKE_IoTHubTransport_Destroy(TEST_TRANSPORT_LL_HANDLE));
    STRICT_EXPECTED_CALL(VECTOR_destroy(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Lock_Deinit(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));

    //act
    IoTHubTransport_Destroy(handle);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_Destroy_fails)
{
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);
    umock_c_reset_all_calls();

    //arrange
    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG)).SetReturn(LOCK_ERROR);
    STRICT_EXPECTED_CALL(ThreadAPI_Join(IGNORED_PTR_ARG, IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Lock_Deinit(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(FAKE_IoTHubTransport_Destroy(TEST_TRANSPORT_LL_HANDLE));
    STRICT_EXPECTED_CALL(VECTOR_destroy(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Lock_Deinit(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(gballoc_free(IGNORED_PTR_ARG));

    //act
    IoTHubTransport_Destroy(handle);

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

    //cleanup
}
TEST_FUNCTION(IoTHubTransport_StartWorkerThread_handle_NULL_fail)
{
    //arrange

    //act
    IOTHUB_CLIENT_RESULT result = IoTHubTransport_StartWorkerThread(NULL, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);

    //assert
    ASSERT_ARE_NOT_EQUAL(IOTHUB_CLIENT_RESULT, result, IOTHUB_CLIENT_OK);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_StartWorkerThread_core_handle_NULL_fail)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    umock_c_reset_all_calls();

    //act
    IOTHUB_CLIENT_RESULT result = IoTHubTransport_StartWorkerThread(handle, NULL, clientDoWork);

    //assert
    ASSERT_ARE_NOT_EQUAL(IOTHUB_CLIENT_RESULT, result, IOTHUB_CLIENT_OK);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_StartWorkerThread_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(ThreadAPI_Create(IGNORED_PTR_ARG, IGNORED_PTR_ARG, handle));
    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_size(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_push_back(IGNORED_PTR_ARG, IGNORED_PTR_ARG, 1));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));

    //act
    IOTHUB_CLIENT_RESULT result = IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);

    //assert
    ASSERT_ARE_EQUAL(IOTHUB_CLIENT_RESULT, result, IOTHUB_CLIENT_OK);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_StartWorkerThread_client_call_twice_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_size(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_find_if(IGNORED_PTR_ARG, IGNORED_PTR_ARG, TEST_IOTHUB_CLIENT_CORE_HANDLE1));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));

    //act
    IOTHUB_CLIENT_RESULT result = IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);

    //assert
    ASSERT_ARE_EQUAL(IOTHUB_CLIENT_RESULT, result, IOTHUB_CLIENT_OK);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_StartWorkerThread_client_call_new_client_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_size(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_find_if(IGNORED_PTR_ARG, IGNORED_PTR_ARG, TEST_IOTHUB_CLIENT_CORE_HANDLE2));
    STRICT_EXPECTED_CALL(VECTOR_push_back(IGNORED_PTR_ARG, IGNORED_PTR_ARG, 1));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));

    //act
    IOTHUB_CLIENT_RESULT result = IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE2, clientDoWork);

    //assert
    ASSERT_ARE_EQUAL(IOTHUB_CLIENT_RESULT, result, IOTHUB_CLIENT_OK);
    ASSERT_ARE_EQUAL(char_ptr, umock_c_get_expected_calls(), umock_c_get_actual_calls());

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_GetLLTransport_handle_NULL_fail)
{
    //act
    TRANSPORT_LL_HANDLE ll_transport = IoTHubTransport_GetLLTransport(NULL);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_GetLLTransport_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    umock_c_reset_all_calls();

    //act
    TRANSPORT_LL_HANDLE ll_transport = IoTHubTransport_GetLLTransport(handle);

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

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_SignalEndWorkerThread_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_find_if(IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_erase(IGNORED_PTR_ARG, IGNORED_PTR_ARG, 1));
    STRICT_EXPECTED_CALL(VECTOR_size(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));

    //act
    bool result = IoTHubTransport_SignalEndWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1);

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

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_SignalEndWorkerThread_2_clients_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE2, clientDoWork);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(VECTOR_find_if(IGNORED_PTR_ARG, IGNORED_PTR_ARG, TEST_IOTHUB_CLIENT_CORE_HANDLE1));
    STRICT_EXPECTED_CALL(VECTOR_erase(IGNORED_PTR_ARG, IGNORED_PTR_ARG, 1));
    STRICT_EXPECTED_CALL(VECTOR_size(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));

    //act
    bool result = IoTHubTransport_SignalEndWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1);

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

    //cleanup
    (void)IoTHubTransport_SignalEndWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE2);
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_GetLock_handle_NULL_fail)
{
    //arrange

    //act
    LOCK_HANDLE lock = IoTHubTransport_GetLock(NULL);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_GetLock_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    umock_c_reset_all_calls();

    //act
    LOCK_HANDLE lock = IoTHubTransport_GetLock(handle);

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

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_worker_thread_runs_every_1_ms)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);
    g_transport_handle = handle;
    umock_c_reset_all_calls();

    g_how_many_dowork_calls = 2;

    for (size_t index = 0; index < g_how_many_dowork_calls+1; index++)
    {
        STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
        STRICT_EXPECTED_CALL(FAKE_IoTHubTransport_DoWork(IGNORED_PTR_ARG));
        if (index == g_how_many_dowork_calls)
        {
            // For stopping the threading
            STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
            STRICT_EXPECTED_CALL(VECTOR_find_if(IGNORED_PTR_ARG, IGNORED_PTR_ARG, IGNORED_PTR_ARG));
            STRICT_EXPECTED_CALL(VECTOR_erase(IGNORED_PTR_ARG, IGNORED_PTR_ARG, 1));
            STRICT_EXPECTED_CALL(VECTOR_size(IGNORED_PTR_ARG));
            STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
            STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));
            STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));
        }
        STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));
        STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
        STRICT_EXPECTED_CALL(VECTOR_size(IGNORED_PTR_ARG));
        if (index != g_how_many_dowork_calls)
        {
            STRICT_EXPECTED_CALL(VECTOR_element(IGNORED_PTR_ARG, IGNORED_NUM_ARG));
        }
        STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));
        STRICT_EXPECTED_CALL(ThreadAPI_Sleep(1));
    }
    STRICT_EXPECTED_CALL(Lock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(Unlock(IGNORED_PTR_ARG));
    STRICT_EXPECTED_CALL(ThreadAPI_Exit(IGNORED_NUM_ARG));

    //act
    threadFunc(threadFuncArg);

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

    //cleanup
    (void)IoTHubTransport_SignalEndWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1);
    (void)IoTHubTransport_SignalEndWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE2);
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_JoinWorkerThread_handle_NULL_fail)
{
    //arrange

    //act
    IoTHubTransport_JoinWorkerThread(NULL, TEST_IOTHUB_CLIENT_CORE_HANDLE1);

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

    //cleanup
}

TEST_FUNCTION(IoTHubTransport_JoinWorkerThread_client_NULL_fail)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    umock_c_reset_all_calls();

    //act
    IoTHubTransport_JoinWorkerThread(handle, NULL);

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

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_JoinWorkerThread_no_workers_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    umock_c_reset_all_calls();

    //act
    IoTHubTransport_JoinWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1);

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

    //cleanup
    IoTHubTransport_Destroy(handle);
}

TEST_FUNCTION(IoTHubTransport_JoinWorkerThread_success)
{
    //arrange
    TRANSPORT_HANDLE handle = NULL;
    handle = IoTHubTransport_Create(TEST_CONFIG.protocol, TEST_CONFIG.iotHubName, TEST_CONFIG.iotHubSuffix);
    (void)IoTHubTransport_StartWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1, clientDoWork);
    umock_c_reset_all_calls();

    STRICT_EXPECTED_CALL(ThreadAPI_Join(IGNORED_PTR_ARG, IGNORED_PTR_ARG));

    //act
    IoTHubTransport_JoinWorkerThread(handle, TEST_IOTHUB_CLIENT_CORE_HANDLE1);

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

    //cleanup
    IoTHubTransport_Destroy(handle);
}

END_TEST_SUITE(iothubtransport_ut)
