From 17f1036876ab083e9ad86bea21345441afef92be Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Sun, 10 Mar 2019 21:35:59 -0700 Subject: [PATCH 01/19] stash --- .../cpp/src/signalrclient/connection_impl.cpp | 120 +++++---- .../default_websocket_client.cpp | 73 ++++- .../signalrclient/default_websocket_client.h | 13 +- .../cpp/src/signalrclient/transport.cpp | 20 +- .../clients/cpp/src/signalrclient/transport.h | 21 +- .../src/signalrclient/transport_factory.cpp | 8 +- .../cpp/src/signalrclient/transport_factory.h | 6 +- .../cpp/src/signalrclient/websocket_client.h | 11 +- .../src/signalrclient/websocket_transport.cpp | 254 +++++++++--------- .../src/signalrclient/websocket_transport.h | 19 +- 10 files changed, 309 insertions(+), 236 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 26d40e67970f..bdd3b59d3bf3 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -224,52 +224,57 @@ namespace signalr const auto& disconnect_cts = m_disconnect_cts; const auto& logger = m_logger; - auto process_response_callback = - [weak_connection, disconnect_cts, logger](const std::string& response) mutable - { - // When a connection is stopped we don't wait for its transport to stop. As a result if the same connection - // is immediately re-started the old transport can still invoke this callback. To prevent this we capture - // the disconnect_cts by value which allows distinguishing if the message is for the running connection - // or for the one that was already stopped. If this is the latter we just ignore it. - if (disconnect_cts.get_token().is_canceled()) - { - logger.log(trace_level::info, - std::string{ "ignoring stray message received after connection was restarted. message: " } - .append(response)); - return; - } + auto transport = connection->m_transport_factory->create_transport( + transport_type::websockets, connection->m_logger, connection->m_signalr_client_config); - auto connection = weak_connection.lock(); - if (connection) + transport->on_receive([disconnect_cts, connect_request_tce, logger, weak_connection](std::string message, std::exception_ptr exception) + { + if (exception != nullptr) { - connection->process_response(response); - } - }; + try + { + std::rethrow_exception(exception); + } + catch (const std::exception & e) + { + // When a connection is stopped we don't wait for its transport to stop. As a result if the same connection + // is immediately re-started the old transport can still invoke this callback. To prevent this we capture + // the disconnect_cts by value which allows distinguishing if the error is for the running connection + // or for the one that was already stopped. If this is the latter we just ignore it. + if (disconnect_cts.get_token().is_canceled()) + { + logger.log(trace_level::info, + std::string{ "ignoring stray error received after connection was restarted. error: " } + .append(e.what())); + return; + } - auto error_callback = - [weak_connection, connect_request_tce, disconnect_cts, logger](const std::exception &e) mutable - { - // When a connection is stopped we don't wait for its transport to stop. As a result if the same connection - // is immediately re-started the old transport can still invoke this callback. To prevent this we capture - // the disconnect_cts by value which allows distinguishing if the error is for the running connection - // or for the one that was already stopped. If this is the latter we just ignore it. - if (disconnect_cts.get_token().is_canceled()) + // no op after connection started successfully + connect_request_tce.set_exception(exception); + } + } + else { - logger.log(trace_level::info, - std::string{ "ignoring stray error received after connection was restarted. error: " } - .append(e.what())); + // When a connection is stopped we don't wait for its transport to stop. As a result if the same connection + // is immediately re-started the old transport can still invoke this callback. To prevent this we capture + // the disconnect_cts by value which allows distinguishing if the message is for the running connection + // or for the one that was already stopped. If this is the latter we just ignore it. + if (disconnect_cts.get_token().is_canceled()) + { + logger.log(trace_level::info, + std::string{ "ignoring stray message received after connection was restarted. message: " } + .append(message)); + return; + } - return; + auto connection = weak_connection.lock(); + if (connection) + { + connection->process_response(message); + } } - - // no op after connection started successfully - connect_request_tce.set_exception(e); - }; - - auto transport = connection->m_transport_factory->create_transport( - transport_type::websockets, connection->m_logger, connection->m_signalr_client_config, - process_response_callback, error_callback); + }); pplx::create_task([connect_request_tce, disconnect_cts, weak_connection]() { @@ -300,12 +305,14 @@ namespace signalr auto query_string = "id=" + m_connection_id; auto connect_url = url_builder::build_connect(url, transport->get_transport_type(), query_string); - transport->connect(connect_url) - .then([transport, connect_request_tce, logger](pplx::task connect_task) + transport->start(connect_url, [transport, connect_request_tce, logger](std::exception_ptr exception) mutable { try { - connect_task.get(); + if (exception != nullptr) + { + std::rethrow_exception(exception); + } connect_request_tce.set(); } catch (const std::exception& e) @@ -368,12 +375,16 @@ namespace signalr logger.log(trace_level::info, std::string("sending data: ").append(data)); - return transport->send(data) - .then([logger](pplx::task send_task) + pplx::task_completion_event event; + transport->send(data, [logger, event](std::exception_ptr exception) mutable { try { - send_task.get(); + if (exception != nullptr) + { + std::rethrow_exception(exception); + } + event.set(); } catch (const std::exception &e) { @@ -382,9 +393,11 @@ namespace signalr std::string("error sending data: ") .append(e.what())); - throw; + event.set_exception(exception); } }); + + return pplx::create_task(event); } pplx::task connection_impl::stop() @@ -471,7 +484,20 @@ namespace signalr change_state(connection_state::disconnecting); } - return m_transport->disconnect(); + pplx::task_completion_event tce; + m_transport->stop([tce](std::exception_ptr exception) + { + if (exception != nullptr) + { + tce.set_exception(exception); + } + else + { + tce.set(); + } + }); + + return pplx::create_task(tce); } connection_state connection_impl::get_connection_state() const noexcept diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp index 2bf07b93f9f9..197f26233dcc 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp @@ -21,30 +21,77 @@ namespace signalr : m_underlying_client(create_client_config(signalr_client_config)) { } - pplx::task default_websocket_client::connect(const std::string& url) + void default_websocket_client::start(std::string url, std::function callback) { - return m_underlying_client.connect(utility::conversions::to_string_t(url)); + m_underlying_client.connect(utility::conversions::to_string_t(url)) + .then([callback](pplx::task task) + { + try + { + task.get(); + callback(nullptr); + } + catch (...) + { + callback(std::current_exception()); + } + }); } - pplx::task default_websocket_client::send(const std::string &message) + void default_websocket_client::stop(std::function callback) { - web::websockets::client::websocket_outgoing_message msg; - msg.set_utf8_message(message); - return m_underlying_client.send(msg); + m_underlying_client.close() + .then([callback](pplx::task task) + { + try + { + callback(nullptr); + } + catch (...) + { + callback(std::current_exception()); + } + }); } - pplx::task default_websocket_client::receive() + void default_websocket_client::on_close(std::function) { - // the caller is responsible for observing exceptions - return m_underlying_client.receive() - .then([](web::websockets::client::websocket_incoming_message msg) + } + + void default_websocket_client::send(std::string payload, std::function callback) + { + web::websockets::client::websocket_outgoing_message msg; + msg.set_utf8_message(payload); + m_underlying_client.send(msg) + .then([callback](pplx::task task) { - return msg.extract_string(); + try + { + task.get(); + callback(nullptr); + } + catch (...) + { + callback(std::current_exception()); + } }); } - pplx::task default_websocket_client::close() + void default_websocket_client::receive(std::function callback) { - return m_underlying_client.close(); + m_underlying_client.receive() + .then([callback](pplx::task task) + { + try + { + auto response = task.get(); + auto msg = response.extract_string().get(); + callback(msg, nullptr); + } + catch (...) + { + callback("", std::current_exception()); + } + }); } } diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h index 7a7386db9dcc..9cef8041f284 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h @@ -14,14 +14,11 @@ namespace signalr public: explicit default_websocket_client(const signalr_client_config& signalr_client_config = {}) noexcept; - pplx::task connect(const std::string& url) override; - - pplx::task send(const std::string& message) override; - - pplx::task receive() override; - - pplx::task close() override; - + void start(std::string url, std::function callback) override; + void stop(std::function callback) override; + void on_close(std::function) override; + void send(std::string payload, std::function callback) override; + void receive(std::function callback) override; private: web::websockets::client::websocket_client m_underlying_client; }; diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/transport.cpp index 89de95eeaea0..ddc64531698a 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/transport.cpp @@ -7,9 +7,8 @@ namespace signalr { - transport::transport(const logger& logger, const std::function& process_response_callback, - std::function error_callback) - : m_logger(logger), m_process_response_callback(process_response_callback), m_error_callback(error_callback) + transport::transport(const logger& logger) + : m_logger(logger) {} // Do NOT remove this destructor. Letting the compiler generate and inline the default dtor may lead to @@ -17,13 +16,18 @@ namespace signalr transport::~transport() { } - void transport::process_response(const std::string &message) + void transport::on_receive(std::function callback) { - m_process_response_callback(message); + m_process_response_callback = callback;; } - void transport::error(const std::exception& e) + void transport::process_response(std::string message) { - m_error_callback(e); + m_process_response_callback(message, nullptr); } -} \ No newline at end of file + + void transport::process_response(std::exception_ptr exception) + { + m_process_response_callback("", exception); + } +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport.h b/src/SignalR/clients/cpp/src/signalrclient/transport.h index 3316b2af03c1..aa0eec9b719b 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/transport.h @@ -12,27 +12,28 @@ namespace signalr class transport { public: - virtual pplx::task connect(const std::string &url) = 0; + virtual transport_type get_transport_type() const = 0; - virtual pplx::task send(const std::string &data) = 0; + virtual ~transport(); - virtual pplx::task disconnect() = 0; + virtual void start(const std::string& url, /*format,*/ std::function callback) = 0; + virtual void stop(/*format,*/ std::function callback) = 0; + virtual void on_close(std::function callback) = 0; - virtual transport_type get_transport_type() const = 0; + virtual void send(std::string payload, std::function callback) = 0; - virtual ~transport(); + void on_receive(std::function callback); protected: - transport(const logger& logger, const std::function& process_response_callback, - std::function error_callback); + transport(const logger& logger); - void process_response(const std::string &message); - void error(const std::exception &e); + void process_response(std::string message); + void process_response(std::exception_ptr exception); logger m_logger; private: - std::function m_process_response_callback; + std::function m_process_response_callback; std::function m_error_callback; }; diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport_factory.cpp b/src/SignalR/clients/cpp/src/signalrclient/transport_factory.cpp index 125b5b3bacc9..8c74b6aeed28 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport_factory.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/transport_factory.cpp @@ -8,15 +8,13 @@ namespace signalr { std::shared_ptr transport_factory::create_transport(transport_type transport_type, const logger& logger, - const signalr_client_config& signalr_client_config, - std::function process_response_callback, - std::function error_callback) + const signalr_client_config& signalr_client_config) { if (transport_type == signalr::transport_type::websockets) { return websocket_transport::create( [signalr_client_config](){ return std::make_shared(signalr_client_config); }, - logger, process_response_callback, error_callback); + logger); } throw std::runtime_error("not implemented"); @@ -24,4 +22,4 @@ namespace signalr transport_factory::~transport_factory() { } -} \ No newline at end of file +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport_factory.h b/src/SignalR/clients/cpp/src/signalrclient/transport_factory.h index a6c80df4c31a..3d55b83a9285 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport_factory.h +++ b/src/SignalR/clients/cpp/src/signalrclient/transport_factory.h @@ -14,10 +14,8 @@ namespace signalr { public: virtual std::shared_ptr create_transport(transport_type transport_type, const logger& logger, - const signalr_client_config& signalr_client_config, - std::function process_response_callback, - std::function error_callback); + const signalr_client_config& signalr_client_config); virtual ~transport_factory(); }; -} \ No newline at end of file +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h b/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h index 9be26a27e8c3..5224d803dfb8 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h @@ -10,14 +10,15 @@ namespace signalr class websocket_client { public: - virtual pplx::task connect(const std::string& url) = 0; + virtual ~websocket_client() {}; - virtual pplx::task send(const std::string& message) = 0; + virtual void start(std::string url, /*transfer_format format,*/ std::function callback) = 0; - virtual pplx::task receive() = 0; + virtual void stop(std::function callback) = 0; + virtual void on_close(std::function) = 0; - virtual pplx::task close() = 0; + virtual void send(std::string payload, std::function callback) = 0; - virtual ~websocket_client() {}; + virtual void receive(std::function callback) = 0; }; } diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp index 96812decc931..707edfad3e5b 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp @@ -5,21 +5,20 @@ #include "websocket_transport.h" #include "logger.h" #include "signalrclient/signalr_exception.h" +#include namespace signalr { std::shared_ptr websocket_transport::create(const std::function()>& websocket_client_factory, - const logger& logger, const std::function& process_response_callback, - std::function error_callback) + const logger& logger) { return std::shared_ptr( - new websocket_transport(websocket_client_factory, logger, process_response_callback, error_callback)); + new websocket_transport(websocket_client_factory, logger)); } websocket_transport::websocket_transport(const std::function()>& websocket_client_factory, - const logger& logger, const std::function& process_response_callback, - std::function error_callback) - : transport(logger, process_response_callback, error_callback), m_websocket_client_factory(websocket_client_factory) + const logger& logger) + : transport(logger), m_websocket_client_factory(websocket_client_factory) { // we use this cts to check if the receive loop is running so it should be // initially cancelled to indicate that the receive loop is not running @@ -30,7 +29,8 @@ namespace signalr { try { - disconnect().get(); + // TODO: wait + stop([](std::exception_ptr) { }); } catch (...) // must not throw from the destructor {} @@ -41,7 +41,99 @@ namespace signalr return transport_type::websockets; } - pplx::task websocket_transport::connect(const std::string& url) + // Note that the connection assumes that the error callback won't be fired when the result is being processed. This + // may no longer be true when we replace the `receive_loop` with "on_message_received" and "on_close" events if they + // can be fired on different threads in which case we will have to lock before setting groups token and message id. + void websocket_transport::receive_loop(pplx::cancellation_token_source cts) + { + auto this_transport = shared_from_this(); + auto logger = this_transport->m_logger; + + // Passing the `std::weak_ptr` prevents from a memory leak where we would capture the shared_ptr to + // the transport in the continuation lambda and as a result as long as the loop runs the ref count would never get to + // zero. Now we capture the weak pointer and get the shared pointer only when the continuation runs so the ref count is + // incremented when the shared pointer is acquired and then decremented when it goes out of scope of the continuation. + auto weak_transport = std::weak_ptr(this_transport); + + auto websocket_client = this_transport->safe_get_websocket_client(); + + // There are two cases when we exit the loop. The first case is implicit - we pass the cancellation_token + // to `then` (note this is after the lambda body) and if the token is cancelled the continuation will not + // run at all. The second - explicit - case happens if the token gets cancelled after the continuation has + // been started in which case we just stop the loop by not scheduling another receive task. + websocket_client->receive([weak_transport, cts, logger, websocket_client](std::string message, std::exception_ptr exception) + { + if (exception != nullptr) + { + try + { + std::rethrow_exception(exception); + } + catch (const std::exception & e) + { + cts.cancel(); + + logger.log( + trace_level::errors, + std::string("[websocket transport] error receiving response from websocket: ") + .append(e.what())); + + websocket_client->stop([](std::exception_ptr exception) + { + }); + + auto transport = weak_transport.lock(); + if (transport) + { + transport->process_response(exception); + } + } + catch (...) + { + cts.cancel(); + + logger.log( + trace_level::errors, + std::string("[websocket transport] unknown error occurred when receiving response from websocket")); + + websocket_client->stop([](std::exception_ptr) + { + }); + + auto transport = weak_transport.lock(); + if (transport) + { + transport->process_response(std::make_exception_ptr(signalr_exception("unknown error"))); + } + } + + return; + } + + auto transport = weak_transport.lock(); + if (transport) + { + transport->process_response(message); + + if (!cts.get_token().is_canceled()) + { + transport->receive_loop(cts); + } + } + }); + } + + std::shared_ptr websocket_transport::safe_get_websocket_client() + { + { + const std::lock_guard lock(m_websocket_client_lock); + auto websocket_client = m_websocket_client; + + return websocket_client; + } + } + + void websocket_transport::start(const std::string& url, /*format,*/ std::function callback) { web::uri uri(utility::conversions::to_string_t(url)); _ASSERTE(uri.scheme() == _XPLATSTR("ws") || uri.scheme() == _XPLATSTR("wss")); @@ -66,20 +158,21 @@ namespace signalr } pplx::cancellation_token_source receive_loop_cts; - pplx::task_completion_event connect_tce; auto transport = shared_from_this(); - websocket_client->connect(url) - .then([transport, connect_tce, receive_loop_cts](pplx::task connect_task) + websocket_client->start(url, [transport, receive_loop_cts, callback](std::exception_ptr exception) { try { - connect_task.get(); + if (exception != nullptr) + { + std::rethrow_exception(exception); + } transport->receive_loop(receive_loop_cts); - connect_tce.set(); + callback(nullptr); } - catch (const std::exception &e) + catch (const std::exception & e) { transport->m_logger.log( trace_level::errors, @@ -87,23 +180,15 @@ namespace signalr .append(e.what())); receive_loop_cts.cancel(); - connect_tce.set_exception(std::current_exception()); + callback(std::current_exception()); } }); m_receive_loop_cts = receive_loop_cts; - - return pplx::create_task(connect_tce); } } - pplx::task websocket_transport::send(const std::string &data) - { - // send will return a faulted task if client has disconnected - return safe_get_websocket_client()->send(data); - } - - pplx::task websocket_transport::disconnect() + void websocket_transport::stop(/*format,*/ std::function callback) { std::shared_ptr websocket_client = nullptr; @@ -112,7 +197,8 @@ namespace signalr if (m_receive_loop_cts.get_token().is_canceled()) { - return pplx::task_from_result(); + callback(nullptr); + return; } m_receive_loop_cts.cancel(); @@ -122,125 +208,45 @@ namespace signalr auto logger = m_logger; - return websocket_client->close() - .then([logger](pplx::task close_task) - mutable { + websocket_client->stop([logger, callback](std::exception_ptr exception) + { try { - close_task.get(); + if (exception != nullptr) + { + std::rethrow_exception(exception); + } + callback(nullptr); } - catch (const std::exception &e) + catch (const std::exception & e) { logger.log( trace_level::errors, std::string("[websocket transport] exception when closing websocket: ") .append(e.what())); + + callback(exception); } }); } - // Note that the connection assumes that the error callback won't be fired when the result is being processed. This - // may no longer be true when we replace the `receive_loop` with "on_message_received" and "on_close" events if they - // can be fired on different threads in which case we will have to lock before setting groups token and message id. - void websocket_transport::receive_loop(pplx::cancellation_token_source cts) + void websocket_transport::on_close(std::function callback) { - auto this_transport = shared_from_this(); - auto logger = this_transport->m_logger; - - // Passing the `std::weak_ptr` prevents from a memory leak where we would capture the shared_ptr to - // the transport in the continuation lambda and as a result as long as the loop runs the ref count would never get to - // zero. Now we capture the weak pointer and get the shared pointer only when the continuation runs so the ref count is - // incremented when the shared pointer is acquired and then decremented when it goes out of scope of the continuation. - auto weak_transport = std::weak_ptr(this_transport); - auto websocket_client = this_transport->safe_get_websocket_client(); + } - websocket_client->receive() - // There are two cases when we exit the loop. The first case is implicit - we pass the cancellation_token - // to `then` (note this is after the lambda body) and if the token is cancelled the continuation will not - // run at all. The second - explicit - case happens if the token gets cancelled after the continuation has - // been started in which case we just stop the loop by not scheduling another receive task. - .then([weak_transport, cts](std::string message) + void websocket_transport::send(std::string payload, std::function callback) + { + safe_get_websocket_client()->send(payload, [callback](std::exception_ptr exception) { - auto transport = weak_transport.lock(); - if (transport) - { - transport->process_response(message); - - if (!cts.get_token().is_canceled()) - { - transport->receive_loop(cts); - } - } - }, cts.get_token()) - // this continuation is used to observe exceptions from the previous tasks. It will run always - even if one of - // the previous continuations throws or was not scheduled due to the cancellation token being set to cancelled - .then([weak_transport, logger, websocket_client, cts](pplx::task task) - mutable { - try + if (exception != nullptr) { - task.get(); + callback(exception); } - catch (const pplx::task_canceled&) + else { - cts.cancel(); - - logger.log(trace_level::info, - std::string("[websocket transport] receive task canceled.")); - } - catch (const std::exception& e) - { - cts.cancel(); - - logger.log( - trace_level::errors, - std::string("[websocket transport] error receiving response from websocket: ") - .append(e.what())); - - websocket_client->close() - .then([](pplx::task task) - { - try { task.get(); } - catch (...) {} - }); - - auto transport = weak_transport.lock(); - if (transport) - { - transport->error(e); - } - } - catch (...) - { - cts.cancel(); - - logger.log( - trace_level::errors, - std::string("[websocket transport] unknown error occurred when receiving response from websocket")); - - websocket_client->close() - .then([](pplx::task task) - { - try { task.get(); } - catch (...) {} - }); - - auto transport = weak_transport.lock(); - if (transport) - { - transport->error(signalr_exception("unknown error")); - } + callback(nullptr); } }); } - - std::shared_ptr websocket_transport::safe_get_websocket_client() - { - { - const std::lock_guard lock(m_websocket_client_lock); - auto websocket_client = m_websocket_client; - - return websocket_client; - } - } } diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h index 6176bcc35843..80d1f62607fa 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h @@ -16,8 +16,7 @@ namespace signalr { public: static std::shared_ptr create(const std::function()>& websocket_client_factory, - const logger& logger, const std::function& process_response_callback, - std::function error_callback); + const logger& logger); ~websocket_transport(); @@ -25,18 +24,17 @@ namespace signalr websocket_transport& operator=(const websocket_transport&) = delete; - pplx::task connect(const std::string& url) override; - - pplx::task send(const std::string &data) override; + transport_type get_transport_type() const noexcept override; - pplx::task disconnect() override; + void start(const std::string& url, /*format,*/ std::function callback) override; + void stop(/*format,*/ std::function callback) override; + void on_close(std::function callback) override; - transport_type get_transport_type() const noexcept override; + void send(std::string payload, std::function callback) override; private: websocket_transport(const std::function()>& websocket_client_factory, - const logger& logger, const std::function& process_response_callback, - std::function error_callback); + const logger& logger); std::function()> m_websocket_client_factory; std::shared_ptr m_websocket_client; @@ -47,9 +45,6 @@ namespace signalr void receive_loop(pplx::cancellation_token_source cts); - void handle_receive_error(const std::exception &e, pplx::cancellation_token_source cts, - logger logger, std::weak_ptr weak_transport); - std::shared_ptr safe_get_websocket_client(); }; } From 418559a263d6987b7d7d1661ae01a51462b7dba5 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 11 Mar 2019 10:40:56 -0700 Subject: [PATCH 02/19] cleanup --- .../HubConnectionSample.cpp | 19 +++++++------------ .../default_websocket_client.cpp | 4 ---- .../signalrclient/default_websocket_client.h | 1 - .../cpp/src/signalrclient/transport.cpp | 15 --------------- .../clients/cpp/src/signalrclient/transport.h | 12 ++---------- .../cpp/src/signalrclient/websocket_client.h | 1 - .../src/signalrclient/websocket_transport.cpp | 18 +++++++++++++----- .../src/signalrclient/websocket_transport.h | 4 ++++ 8 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp index c67e9f1a2543..9c72b12cc092 100644 --- a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp +++ b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp @@ -17,14 +17,13 @@ class logger : public signalr::log_writer } }; -void send_message(signalr::hub_connection& connection, const std::string& name, const std::string& message) +void send_message(signalr::hub_connection& connection, const std::string& message) { web::json::value args{}; - args[0] = web::json::value::string(utility::conversions::to_string_t(name)); - args[1] = web::json::value(utility::conversions::to_string_t(message)); + args[0] = web::json::value(utility::conversions::to_string_t(message)); // if you get an internal compiler error uncomment the lambda below or install VS Update 4 - connection.invoke("Invoke", args/*, [](const web::json::value&){}*/) + connection.invoke("Send", args/*, [](const web::json::value&){}*/) .then([](pplx::task invoke_task) // fire and forget but we need to observe exceptions { try @@ -39,7 +38,7 @@ void send_message(signalr::hub_connection& connection, const std::string& name, }); } -void chat(const std::string& name) +void chat() { signalr::hub_connection connection("http://localhost:5000/default", signalr::trace_level::all, std::make_shared()); connection.on("Send", [](const web::json::value& m) @@ -48,7 +47,7 @@ void chat(const std::string& name) }); connection.start() - .then([&connection, name]() + .then([&connection]() { ucout << U("Enter your message:"); for (;;) @@ -61,7 +60,7 @@ void chat(const std::string& name) break; } - send_message(connection, name, message); + send_message(connection, message); } }) .then([&connection]() // fine to capture by reference - we are blocking so it is guaranteed to be valid @@ -84,11 +83,7 @@ void chat(const std::string& name) int main() { - ucout << U("Enter your name: "); - std::string name; - std::getline(std::cin, name); - - chat(name); + chat(); return 0; } diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp index 197f26233dcc..fe703c63af2e 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp @@ -54,10 +54,6 @@ namespace signalr }); } - void default_websocket_client::on_close(std::function) - { - } - void default_websocket_client::send(std::string payload, std::function callback) { web::websockets::client::websocket_outgoing_message msg; diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h index 9cef8041f284..be1609b28a60 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h @@ -16,7 +16,6 @@ namespace signalr void start(std::string url, std::function callback) override; void stop(std::function callback) override; - void on_close(std::function) override; void send(std::string payload, std::function callback) override; void receive(std::function callback) override; private: diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/transport.cpp index ddc64531698a..4f235e16f438 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/transport.cpp @@ -15,19 +15,4 @@ namespace signalr // undefinded behavior since we are using an incomplete type. More details here: http://herbsutter.com/gotw/_100/ transport::~transport() { } - - void transport::on_receive(std::function callback) - { - m_process_response_callback = callback;; - } - - void transport::process_response(std::string message) - { - m_process_response_callback(message, nullptr); - } - - void transport::process_response(std::exception_ptr exception) - { - m_process_response_callback("", exception); - } } diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport.h b/src/SignalR/clients/cpp/src/signalrclient/transport.h index aa0eec9b719b..41a7dbe8ee58 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/transport.h @@ -17,24 +17,16 @@ namespace signalr virtual ~transport(); virtual void start(const std::string& url, /*format,*/ std::function callback) = 0; - virtual void stop(/*format,*/ std::function callback) = 0; + virtual void stop(std::function callback) = 0; virtual void on_close(std::function callback) = 0; virtual void send(std::string payload, std::function callback) = 0; - void on_receive(std::function callback); + virtual void on_receive(std::function callback) = 0; protected: transport(const logger& logger); - void process_response(std::string message); - void process_response(std::exception_ptr exception); - logger m_logger; - - private: - std::function m_process_response_callback; - - std::function m_error_callback; }; } diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h b/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h index 5224d803dfb8..216bcfbaf7fa 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h @@ -15,7 +15,6 @@ namespace signalr virtual void start(std::string url, /*transfer_format format,*/ std::function callback) = 0; virtual void stop(std::function callback) = 0; - virtual void on_close(std::function) = 0; virtual void send(std::string payload, std::function callback) = 0; diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp index 707edfad3e5b..c2dc86dc217e 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp @@ -18,7 +18,7 @@ namespace signalr websocket_transport::websocket_transport(const std::function()>& websocket_client_factory, const logger& logger) - : transport(logger), m_websocket_client_factory(websocket_client_factory) + : transport(logger), m_websocket_client_factory(websocket_client_factory), m_close_callback([](std::exception_ptr) {}) { // we use this cts to check if the receive loop is running so it should be // initially cancelled to indicate that the receive loop is not running @@ -85,7 +85,7 @@ namespace signalr auto transport = weak_transport.lock(); if (transport) { - transport->process_response(exception); + transport->m_close_callback(exception); } } catch (...) @@ -103,7 +103,7 @@ namespace signalr auto transport = weak_transport.lock(); if (transport) { - transport->process_response(std::make_exception_ptr(signalr_exception("unknown error"))); + transport->m_close_callback(std::make_exception_ptr(signalr_exception("unknown error"))); } } @@ -113,7 +113,7 @@ namespace signalr auto transport = weak_transport.lock(); if (transport) { - transport->process_response(message); + transport->m_process_response_callback(message, nullptr); if (!cts.get_token().is_canceled()) { @@ -207,8 +207,9 @@ namespace signalr } auto logger = m_logger; + auto close_callback = m_close_callback; - websocket_client->stop([logger, callback](std::exception_ptr exception) + websocket_client->stop([logger, callback, close_callback](std::exception_ptr exception) { try { @@ -227,12 +228,19 @@ namespace signalr callback(exception); } + + close_callback(exception); }); } void websocket_transport::on_close(std::function callback) { + m_close_callback = callback; + } + void websocket_transport::on_receive(std::function callback) + { + m_process_response_callback = callback; } void websocket_transport::send(std::string payload, std::function callback) diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h index 80d1f62607fa..f316342c3693 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h @@ -32,6 +32,8 @@ namespace signalr void send(std::string payload, std::function callback) override; + void on_receive(std::function) override; + private: websocket_transport(const std::function()>& websocket_client_factory, const logger& logger); @@ -40,6 +42,8 @@ namespace signalr std::shared_ptr m_websocket_client; std::mutex m_websocket_client_lock; std::mutex m_start_stop_lock; + std::function m_process_response_callback; + std::function m_close_callback; pplx::cancellation_token_source m_receive_loop_cts; From 7d2f900155975f5b1f4afbefbd2664b8dffa4faf Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 11 Mar 2019 14:32:40 -0700 Subject: [PATCH 03/19] http --- .../Build/VS/signalrclient.vcxproj | 9 +- .../Build/VS/signalrclient.vcxproj.filters | 17 +++- .../cpp/src/signalrclient/connection_impl.cpp | 7 +- .../cpp/src/signalrclient/connection_impl.h | 2 + .../src/signalrclient/default_http_client.cpp | 68 ++++++++++++++ .../src/signalrclient/default_http_client.h | 16 ++++ .../cpp/src/signalrclient/http_client.h | 40 ++++++++ .../cpp/src/signalrclient/negotiate.cpp | 92 +++++++++++++++++++ .../{request_sender.h => negotiate.h} | 6 +- .../cpp/src/signalrclient/request_sender.cpp | 72 --------------- .../Build/VS/signalrclientdll.vcxproj | 9 +- .../Build/VS/signalrclientdll.vcxproj.filters | 21 +++-- 12 files changed, 265 insertions(+), 94 deletions(-) create mode 100644 src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp create mode 100644 src/SignalR/clients/cpp/src/signalrclient/default_http_client.h create mode 100644 src/SignalR/clients/cpp/src/signalrclient/http_client.h create mode 100644 src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp rename src/SignalR/clients/cpp/src/signalrclient/{request_sender.h => negotiate.h} (76%) delete mode 100644 src/SignalR/clients/cpp/src/signalrclient/request_sender.cpp diff --git a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj index c74fbdc9a9ae..867f2661d836 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj +++ b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj @@ -53,14 +53,16 @@ + + - + @@ -75,12 +77,13 @@ + - + Create @@ -105,4 +108,4 @@ - + \ No newline at end of file diff --git a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters index eb4f38f008b6..b5945464dd11 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters +++ b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters @@ -45,9 +45,6 @@ Header Files - - Header Files - Header Files @@ -111,6 +108,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -164,7 +170,10 @@ Source Files - + + Source Files + + Source Files diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index bdd3b59d3bf3..4d88a232f3a5 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -6,11 +6,12 @@ #include #include "constants.h" #include "connection_impl.h" -#include "request_sender.h" +#include "negotiate.h" #include "url_builder.h" #include "trace_log_writer.h" #include "make_unique.h" #include "signalrclient/signalr_exception.h" +#include "default_http_client.h" namespace signalr { @@ -37,7 +38,7 @@ namespace signalr std::unique_ptr web_request_factory, std::unique_ptr transport_factory) : m_base_url(url), m_connection_state(connection_state::disconnected), m_logger(log_writer, trace_level), m_transport(nullptr), m_web_request_factory(std::move(web_request_factory)), m_transport_factory(std::move(transport_factory)), - m_message_received([](const std::string&) noexcept {}), m_disconnected([]() noexcept {}) + m_message_received([](const std::string&) noexcept {}), m_disconnected([]() noexcept {}), m_http_client(new default_http_client()) { } connection_impl::~connection_impl() @@ -105,7 +106,7 @@ namespace signalr { return pplx::task_from_exception("connection no longer exists"); } - return request_sender::negotiate(*connection->m_web_request_factory, url, connection->m_signalr_client_config); + return negotiate::negotiate(*connection->m_http_client, url, connection->m_signalr_client_config); }, m_disconnect_cts.get_token()) .then([weak_connection, start_tce, redirect_count, url](negotiation_response negotiation_response) { diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h index cd712e59217d..a593aa59eb71 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h @@ -6,6 +6,7 @@ #include #include #include "cpprest/http_client.h" +#include "http_client.h" #include "signalrclient/trace_level.h" #include "signalrclient/connection_state.h" #include "signalrclient/signalr_client_config.h" @@ -63,6 +64,7 @@ namespace signalr std::mutex m_stop_lock; event m_start_completed_event; std::string m_connection_id; + http_client* m_http_client; connection_impl(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, std::unique_ptr transport_factory); diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp new file mode 100644 index 000000000000..5653cde732f8 --- /dev/null +++ b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "default_http_client.h" + +namespace signalr +{ + void default_http_client::send(std::string url, http_request request, std::function callback) + { + web::http::method method; + if (request.method == http_method::GET) + { + method = U("GET"); + } + else if (request.method == http_method::POST) + { + method = U("POST"); + } + + web::http::http_request http_request; + http_request.set_method(method); + http_request.set_body(request.content); + if (request.headers.size() > 0) + { + web::http::http_headers headers; + for (auto& header : request.headers) + { + headers.add(utility::conversions::to_string_t(header.first), utility::conversions::to_string_t(header.second)); + } + http_request.headers() = headers; + } + + auto milliseconds = std::chrono::milliseconds(request.timeout).count(); + pplx::cancellation_token_source cts; + if (milliseconds != 0) + { + pplx::create_task([milliseconds, cts]() + { + pplx::wait((unsigned int)milliseconds); + cts.cancel(); + }); + } + + web::http::client::http_client client(utility::conversions::to_string_t(url)); + client.request(http_request, cts.get_token()) + .then([callback](pplx::task response_task) + { + try + { + auto http_response = response_task.get(); + auto status_code = http_response.status_code(); + http_response.extract_utf8string() + .then([callback, status_code](std::string response_body) + { + signalr::http_response response; + response.content = response_body; + response.status_code = status_code; + callback(response, nullptr); + }); + } + catch (...) + { + callback(signalr::http_response(), std::current_exception()); + } + }); + } +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_http_client.h b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.h new file mode 100644 index 000000000000..685213389357 --- /dev/null +++ b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.h @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +#include "http_client.h" +#include "cpprest/http_client.h" + +namespace signalr +{ + class default_http_client : public http_client + { + public: + void send(std::string url, http_request request, std::function callback) override; + }; +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/http_client.h b/src/SignalR/clients/cpp/src/signalrclient/http_client.h new file mode 100644 index 000000000000..9f8c84d875d9 --- /dev/null +++ b/src/SignalR/clients/cpp/src/signalrclient/http_client.h @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include +#include + +namespace signalr +{ + enum http_method + { + GET, + POST + }; + + class http_request + { + public: + http_method method; + std::map headers; + std::string content; + std::chrono::seconds timeout; + }; + + class http_response + { + public: + int status_code; + std::string content; + }; + + class http_client + { + public: + virtual void send(std::string url, http_request request, std::function callback) = 0; + }; +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp b/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp new file mode 100644 index 000000000000..3aebb458267e --- /dev/null +++ b/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "negotiate.h" +#include "http_sender.h" +#include "url_builder.h" +#include "signalrclient/signalr_exception.h" + +namespace signalr +{ + namespace negotiate + { + pplx::task negotiate(http_client& client, const std::string& base_url, + const signalr_client_config&) + { + auto negotiate_url = url_builder::build_negotiate(base_url); + + pplx::task_completion_event tce; + + // TODO: signalr_client_config + http_request request; + request.method = http_method::POST; + client.send(negotiate_url, request, [tce](http_response http_response, std::exception_ptr exception) + { + if (exception != nullptr) + { + tce.set_exception(exception); + return; + } + + try + { + auto negotiation_response_json = web::json::value::parse(utility::conversions::to_string_t(http_response.content)); + + negotiation_response response; + + if (negotiation_response_json.has_field(_XPLATSTR("error"))) + { + response.error = utility::conversions::to_utf8string(negotiation_response_json[_XPLATSTR("error")].as_string()); + tce.set(std::move(response)); + return; + } + + if (negotiation_response_json.has_field(_XPLATSTR("connectionId"))) + { + response.connectionId = utility::conversions::to_utf8string(negotiation_response_json[_XPLATSTR("connectionId")].as_string()); + } + + if (negotiation_response_json.has_field(_XPLATSTR("availableTransports"))) + { + for (auto transportData : negotiation_response_json[_XPLATSTR("availableTransports")].as_array()) + { + available_transport transport; + transport.transport = utility::conversions::to_utf8string(transportData[_XPLATSTR("transport")].as_string()); + + for (auto format : transportData[_XPLATSTR("transferFormats")].as_array()) + { + transport.transfer_formats.push_back(utility::conversions::to_utf8string(format.as_string())); + } + + response.availableTransports.push_back(transport); + } + } + + if (negotiation_response_json.has_field(_XPLATSTR("url"))) + { + response.url = utility::conversions::to_utf8string(negotiation_response_json[_XPLATSTR("url")].as_string()); + + if (negotiation_response_json.has_field(_XPLATSTR("accessToken"))) + { + response.accessToken = utility::conversions::to_utf8string(negotiation_response_json[_XPLATSTR("accessToken")].as_string()); + } + } + + if (negotiation_response_json.has_field(_XPLATSTR("ProtocolVersion"))) + { + tce.set_exception(signalr_exception("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.")); + } + + tce.set(std::move(response)); + } + catch (...) + { + tce.set_exception(std::current_exception()); + } + }); + + return pplx::create_task(tce); + } + } +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/request_sender.h b/src/SignalR/clients/cpp/src/signalrclient/negotiate.h similarity index 76% rename from src/SignalR/clients/cpp/src/signalrclient/request_sender.h rename to src/SignalR/clients/cpp/src/signalrclient/negotiate.h index 73becd3eb06d..9e5c8d04fd16 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/request_sender.h +++ b/src/SignalR/clients/cpp/src/signalrclient/negotiate.h @@ -7,13 +7,13 @@ #include "signalrclient/transport_type.h" #include "web_request_factory.h" #include "negotiation_response.h" - +#include "http_client.h" namespace signalr { - namespace request_sender + namespace negotiate { - pplx::task negotiate(web_request_factory& request_factory, const std::string& base_url, + pplx::task negotiate(http_client& client, const std::string& base_url, const signalr_client_config& signalr_client_config = signalr::signalr_client_config{}); } } diff --git a/src/SignalR/clients/cpp/src/signalrclient/request_sender.cpp b/src/SignalR/clients/cpp/src/signalrclient/request_sender.cpp deleted file mode 100644 index b3d2e99d3914..000000000000 --- a/src/SignalR/clients/cpp/src/signalrclient/request_sender.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#include "stdafx.h" -#include "request_sender.h" -#include "http_sender.h" -#include "url_builder.h" -#include "signalrclient/signalr_exception.h" - -namespace signalr -{ - namespace request_sender - { - pplx::task negotiate(web_request_factory& request_factory, const std::string& base_url, - const signalr_client_config& signalr_client_config) - { - auto negotiate_url = url_builder::build_negotiate(base_url); - - return http_sender::post(request_factory, negotiate_url, signalr_client_config) - .then([](std::string body) - { - auto negotiation_response_json = web::json::value::parse(utility::conversions::to_string_t(body)); - - negotiation_response response; - - if (negotiation_response_json.has_field(_XPLATSTR("error"))) - { - response.error = utility::conversions::to_utf8string(negotiation_response_json[_XPLATSTR("error")].as_string()); - return std::move(response); - } - - if (negotiation_response_json.has_field(_XPLATSTR("connectionId"))) - { - response.connectionId = utility::conversions::to_utf8string(negotiation_response_json[_XPLATSTR("connectionId")].as_string()); - } - - if (negotiation_response_json.has_field(_XPLATSTR("availableTransports"))) - { - for (auto transportData : negotiation_response_json[_XPLATSTR("availableTransports")].as_array()) - { - available_transport transport; - transport.transport = utility::conversions::to_utf8string(transportData[_XPLATSTR("transport")].as_string()); - - for (auto format : transportData[_XPLATSTR("transferFormats")].as_array()) - { - transport.transfer_formats.push_back(utility::conversions::to_utf8string(format.as_string())); - } - - response.availableTransports.push_back(transport); - } - } - - if (negotiation_response_json.has_field(_XPLATSTR("url"))) - { - response.url = utility::conversions::to_utf8string(negotiation_response_json[_XPLATSTR("url")].as_string()); - - if (negotiation_response_json.has_field(_XPLATSTR("accessToken"))) - { - response.accessToken = utility::conversions::to_utf8string(negotiation_response_json[_XPLATSTR("accessToken")].as_string()); - } - } - - if (negotiation_response_json.has_field(_XPLATSTR("ProtocolVersion"))) - { - throw signalr_exception("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details."); - } - - return std::move(response); - }); - } - } -} diff --git a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj index 2fba0aff2f18..3221b1455d98 100644 --- a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj +++ b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj @@ -54,13 +54,15 @@ + + + - @@ -76,13 +78,14 @@ + - + Create @@ -133,4 +136,4 @@ - + \ No newline at end of file diff --git a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters index 08233634b386..bc19072f4813 100644 --- a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters +++ b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters @@ -39,9 +39,6 @@ Header Files - - Header Files - Header Files @@ -105,6 +102,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -128,9 +134,6 @@ Source Files - - Source Files - Source Files @@ -164,6 +167,12 @@ Source Files + + Source Files + + + Source Files + From 4f32a79de349ad2dc74b6422898fcdc3674a232e Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 11 Mar 2019 15:12:15 -0700 Subject: [PATCH 04/19] transfer format --- .../cpp/include/signalrclient/transport_type.h | 2 +- .../signalrclient/Build/VS/signalrclient.vcxproj | 1 + .../Build/VS/signalrclient.vcxproj.filters | 3 +++ .../cpp/src/signalrclient/connection_impl.cpp | 2 +- .../src/signalrclient/default_websocket_client.cpp | 2 +- .../src/signalrclient/default_websocket_client.h | 2 +- .../clients/cpp/src/signalrclient/transfer_format.h | 13 +++++++++++++ .../clients/cpp/src/signalrclient/transport.h | 3 ++- .../cpp/src/signalrclient/websocket_client.h | 4 ++-- .../cpp/src/signalrclient/websocket_transport.cpp | 10 +++++----- .../cpp/src/signalrclient/websocket_transport.h | 2 +- .../Build/VS/signalrclientdll.vcxproj | 1 + .../Build/VS/signalrclientdll.vcxproj.filters | 3 +++ 13 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 src/SignalR/clients/cpp/src/signalrclient/transfer_format.h diff --git a/src/SignalR/clients/cpp/include/signalrclient/transport_type.h b/src/SignalR/clients/cpp/include/signalrclient/transport_type.h index 2db53381825c..bc30ee464602 100644 --- a/src/SignalR/clients/cpp/include/signalrclient/transport_type.h +++ b/src/SignalR/clients/cpp/include/signalrclient/transport_type.h @@ -10,4 +10,4 @@ namespace signalr long_polling, websockets }; -} \ No newline at end of file +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj index 867f2661d836..b39a81768a69 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj +++ b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj @@ -65,6 +65,7 @@ + diff --git a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters index b5945464dd11..6615d38f11d3 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters +++ b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters @@ -117,6 +117,9 @@ Header Files + + Header Files + diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 4d88a232f3a5..017dffb85ccc 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -306,7 +306,7 @@ namespace signalr auto query_string = "id=" + m_connection_id; auto connect_url = url_builder::build_connect(url, transport->get_transport_type(), query_string); - transport->start(connect_url, [transport, connect_request_tce, logger](std::exception_ptr exception) + transport->start(connect_url, transfer_format::text, [transport, connect_request_tce, logger](std::exception_ptr exception) mutable { try { diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp index fe703c63af2e..e8c683c141b5 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.cpp @@ -21,7 +21,7 @@ namespace signalr : m_underlying_client(create_client_config(signalr_client_config)) { } - void default_websocket_client::start(std::string url, std::function callback) + void default_websocket_client::start(std::string url, transfer_format, std::function callback) { m_underlying_client.connect(utility::conversions::to_string_t(url)) .then([callback](pplx::task task) diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h index be1609b28a60..8394882dc7c9 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h @@ -14,7 +14,7 @@ namespace signalr public: explicit default_websocket_client(const signalr_client_config& signalr_client_config = {}) noexcept; - void start(std::string url, std::function callback) override; + void start(std::string url, transfer_format format, std::function callback) override; void stop(std::function callback) override; void send(std::string payload, std::function callback) override; void receive(std::function callback) override; diff --git a/src/SignalR/clients/cpp/src/signalrclient/transfer_format.h b/src/SignalR/clients/cpp/src/signalrclient/transfer_format.h new file mode 100644 index 000000000000..8355d25309f1 --- /dev/null +++ b/src/SignalR/clients/cpp/src/signalrclient/transfer_format.h @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +namespace signalr +{ + enum class transfer_format + { + text, + binary + }; +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport.h b/src/SignalR/clients/cpp/src/signalrclient/transport.h index 41a7dbe8ee58..b6daa1703ded 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/transport.h @@ -5,6 +5,7 @@ #include "pplx/pplxtasks.h" #include "signalrclient/transport_type.h" +#include "transfer_format.h" #include "logger.h" namespace signalr @@ -16,7 +17,7 @@ namespace signalr virtual ~transport(); - virtual void start(const std::string& url, /*format,*/ std::function callback) = 0; + virtual void start(const std::string& url, transfer_format format, std::function callback) = 0; virtual void stop(std::function callback) = 0; virtual void on_close(std::function callback) = 0; diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h b/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h index 216bcfbaf7fa..b45e73995f3b 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h @@ -3,7 +3,7 @@ #pragma once -#include "pplx/pplxtasks.h" +#include "transfer_format.h" namespace signalr { @@ -12,7 +12,7 @@ namespace signalr public: virtual ~websocket_client() {}; - virtual void start(std::string url, /*transfer_format format,*/ std::function callback) = 0; + virtual void start(std::string url, transfer_format format, std::function callback) = 0; virtual void stop(std::function callback) = 0; diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp index c2dc86dc217e..4839d461a445 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp @@ -58,9 +58,9 @@ namespace signalr auto websocket_client = this_transport->safe_get_websocket_client(); // There are two cases when we exit the loop. The first case is implicit - we pass the cancellation_token - // to `then` (note this is after the lambda body) and if the token is cancelled the continuation will not - // run at all. The second - explicit - case happens if the token gets cancelled after the continuation has - // been started in which case we just stop the loop by not scheduling another receive task. + // to `then` (note this is after the lambda body) and if the token is cancelled the continuation will not + // run at all. The second - explicit - case happens if the token gets cancelled after the continuation has + // been started in which case we just stop the loop by not scheduling another receive task. websocket_client->receive([weak_transport, cts, logger, websocket_client](std::string message, std::exception_ptr exception) { if (exception != nullptr) @@ -133,7 +133,7 @@ namespace signalr } } - void websocket_transport::start(const std::string& url, /*format,*/ std::function callback) + void websocket_transport::start(const std::string& url, transfer_format format, std::function callback) { web::uri uri(utility::conversions::to_string_t(url)); _ASSERTE(uri.scheme() == _XPLATSTR("ws") || uri.scheme() == _XPLATSTR("wss")); @@ -161,7 +161,7 @@ namespace signalr auto transport = shared_from_this(); - websocket_client->start(url, [transport, receive_loop_cts, callback](std::exception_ptr exception) + websocket_client->start(url, format, [transport, receive_loop_cts, callback](std::exception_ptr exception) { try { diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h index f316342c3693..4335ccc1b6ef 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h @@ -26,7 +26,7 @@ namespace signalr transport_type get_transport_type() const noexcept override; - void start(const std::string& url, /*format,*/ std::function callback) override; + void start(const std::string& url, transfer_format format, std::function callback) override; void stop(/*format,*/ std::function callback) override; void on_close(std::function callback) override; diff --git a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj index 3221b1455d98..9219bdff9934 100644 --- a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj +++ b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj @@ -65,6 +65,7 @@ + diff --git a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters index bc19072f4813..c573648c8125 100644 --- a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters +++ b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters @@ -111,6 +111,9 @@ Header Files + + Header Files + From 388e40598ea026085ec9d63f48abe0b5e3889b2c Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Wed, 13 Mar 2019 16:19:31 -0700 Subject: [PATCH 05/19] stash tests --- .../connection_impl_tests.cpp | 67 ++- .../request_sender_tests.cpp | 104 ++-- .../test_transport_factory.cpp | 5 +- .../test/signalrclienttests/test_utils.cpp | 95 ---- .../cpp/test/signalrclienttests/test_utils.h | 22 - .../test_websocket_client.cpp | 53 -- .../test_websocket_client.h | 40 -- .../websocket_transport_tests.cpp | 451 ------------------ 8 files changed, 105 insertions(+), 732 deletions(-) delete mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp delete mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h delete mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp delete mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.h delete mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp index e95a78f66a7c..9e1283a80b4e 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp @@ -313,9 +313,9 @@ TEST(connection_impl_start, start_fails_if_negotiate_request_fails) }); auto websocket_client = std::make_shared(); - websocket_client->set_receive_function([]()->pplx::task + websocket_client->set_receive_function([](std::function callback) { - return pplx::task_from_result(std::string("{ }\x1e")); + callback("{ }\x1e", nullptr); }); auto connection = @@ -349,9 +349,14 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_has_error) pplx::task_completion_event tce; auto websocket_client = std::make_shared(); - websocket_client->set_connect_function([tce](const std::string&) mutable + websocket_client->set_connect_function([tce](const std::string&, std::function callback) { - return pplx::task(tce); + pplx::task(tce) + .then([callback](pplx::task prev_task) + { + prev_task.get(); + callback(nullptr); + }); }); auto connection = @@ -367,6 +372,8 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_has_error) { ASSERT_STREQ("bad negotiate", e.what()); } + + tce.set(); } TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_websockets) @@ -385,9 +392,14 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_webs pplx::task_completion_event tce; auto websocket_client = std::make_shared(); - websocket_client->set_connect_function([tce](const std::string&) mutable + websocket_client->set_connect_function([tce](const std::string&, std::function callback) { - return pplx::task(tce); + pplx::task(tce) + .then([callback](pplx::task prev_task) + { + prev_task.get(); + callback(nullptr); + }); }); auto connection = @@ -403,6 +415,8 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_webs { ASSERT_STREQ("The server does not support WebSockets which is currently the only transport supported by this client.", e.what()); } + + tce.set(); } TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_transports) @@ -421,9 +435,14 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_tran pplx::task_completion_event tce; auto websocket_client = std::make_shared(); - websocket_client->set_connect_function([tce](const std::string&) mutable + websocket_client->set_connect_function([tce](const std::string&, std::function callback) { - return pplx::task(tce); + pplx::task(tce) + .then([callback](pplx::task prev_task) + { + prev_task.get(); + callback(nullptr); + }); }); auto connection = @@ -439,6 +458,8 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_tran { ASSERT_STREQ("The server does not support WebSockets which is currently the only transport supported by this client.", e.what()); } + + tce.set(); } TEST(connection_impl_start, start_fails_if_negotiate_response_is_invalid) @@ -457,9 +478,14 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_is_invalid) pplx::task_completion_event tce; auto websocket_client = std::make_shared(); - websocket_client->set_connect_function([tce](const std::string&) mutable + websocket_client->set_connect_function([tce](const std::string&, std::function callback) { - return pplx::task(tce); + pplx::task(tce) + .then([callback](pplx::task prev_task) + { + prev_task.get(); + callback(nullptr); + }); }); auto connection = @@ -475,6 +501,8 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_is_invalid) { ASSERT_STREQ("* Line 1, Column 28 Syntax error: Malformed token", e.what()); } + + tce.set(); } TEST(connection_impl_start, negotiate_follows_redirect) @@ -503,10 +531,10 @@ TEST(connection_impl_start, negotiate_follows_redirect) auto websocket_client = std::make_shared(); std::string connectUrl; - websocket_client->set_connect_function([&connectUrl](const std::string& url) + websocket_client->set_connect_function([&connectUrl](const std::string& url, std::function callback) { connectUrl = url; - return pplx::task_from_result(); + callback(nullptr); }); auto connection = @@ -550,10 +578,10 @@ TEST(connection_impl_start, negotiate_redirect_uses_accessToken) auto websocket_client = std::make_shared(); std::string connectUrl; - websocket_client->set_connect_function([&connectUrl](const std::string& url) + websocket_client->set_connect_function([&connectUrl](const std::string& url, std::function callback) { connectUrl = url; - return pplx::task_from_result(); + callback(nullptr); }); auto connection = @@ -721,9 +749,14 @@ TEST(connection_impl_start, start_fails_if_connect_request_times_out) pplx::task_completion_event tce; auto websocket_client = std::make_shared(); - websocket_client->set_connect_function([tce](const std::string&) mutable + websocket_client->set_connect_function([tce](const std::string&, std::function callback) { - return pplx::task(tce); + pplx::task(tce) + .then([callback](pplx::task prev_task) + { + prev_task.get(); + callback(nullptr); + }); }); auto connection = @@ -739,6 +772,8 @@ TEST(connection_impl_start, start_fails_if_connect_request_times_out) { ASSERT_STREQ("transport timed out when trying to connect", e.what()); } + + tce.set(); } TEST(connection_impl_process_response, process_response_logs_messages) diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp index d332848f7729..a97789663812 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp @@ -1,53 +1,53 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - +//// Copyright (c) .NET Foundation. All rights reserved. +//// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// #include "stdafx.h" -#include "cpprest/details/basic_types.h" -#include "signalrclient/web_exception.h" -#include "request_sender.h" -#include "web_request_stub.h" -#include "test_web_request_factory.h" -#include "signalrclient/signalr_exception.h" - -using namespace signalr; - -TEST(request_sender_negotiate, request_created_with_correct_url) -{ - std::string requested_url; - auto request_factory = test_web_request_factory([&requested_url](const std::string& url) -> std::unique_ptr - { - std::string response_body( - "{ \"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " - "\"availableTransports\" : [] }"); - - requested_url = url; - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); - }); - - request_sender::negotiate(request_factory, "http://fake/signalr").get(); - - ASSERT_EQ("http://fake/signalr/negotiate", requested_url); -} - -TEST(request_sender_negotiate, negotiation_request_sent_and_response_serialized) -{ - auto request_factory = test_web_request_factory([](const std::string&) -> std::unique_ptr - { - std::string response_body( - "{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " - "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] }," - "{ \"transport\": \"ServerSentEvents\", \"transferFormats\": [ \"Text\" ] } ] }"); - - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); - }); - - auto response = request_sender::negotiate(request_factory, "http://fake/signalr").get(); - - ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", response.connectionId); - ASSERT_EQ(2u, response.availableTransports.size()); - ASSERT_EQ(2u, response.availableTransports[0].transfer_formats.size()); - ASSERT_EQ("Text", response.availableTransports[0].transfer_formats[0]); - ASSERT_EQ("Binary", response.availableTransports[0].transfer_formats[1]); - ASSERT_EQ(1u, response.availableTransports[1].transfer_formats.size()); - ASSERT_EQ("Text", response.availableTransports[1].transfer_formats[0]); -} +//#include "cpprest/details/basic_types.h" +//#include "signalrclient/web_exception.h" +//#include "request_sender.h" +//#include "web_request_stub.h" +//#include "test_web_request_factory.h" +//#include "signalrclient/signalr_exception.h" +// +//using namespace signalr; +// +//TEST(request_sender_negotiate, request_created_with_correct_url) +//{ +// std::string requested_url; +// auto request_factory = test_web_request_factory([&requested_url](const std::string& url) -> std::unique_ptr +// { +// std::string response_body( +// "{ \"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " +// "\"availableTransports\" : [] }"); +// +// requested_url = url; +// return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); +// }); +// +// request_sender::negotiate(request_factory, "http://fake/signalr").get(); +// +// ASSERT_EQ("http://fake/signalr/negotiate", requested_url); +//} +// +//TEST(request_sender_negotiate, negotiation_request_sent_and_response_serialized) +//{ +// auto request_factory = test_web_request_factory([](const std::string&) -> std::unique_ptr +// { +// std::string response_body( +// "{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " +// "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] }," +// "{ \"transport\": \"ServerSentEvents\", \"transferFormats\": [ \"Text\" ] } ] }"); +// +// return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); +// }); +// +// auto response = request_sender::negotiate(request_factory, "http://fake/signalr").get(); +// +// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", response.connectionId); +// ASSERT_EQ(2u, response.availableTransports.size()); +// ASSERT_EQ(2u, response.availableTransports[0].transfer_formats.size()); +// ASSERT_EQ("Text", response.availableTransports[0].transfer_formats[0]); +// ASSERT_EQ("Binary", response.availableTransports[0].transfer_formats[1]); +// ASSERT_EQ(1u, response.availableTransports[1].transfer_formats.size()); +// ASSERT_EQ("Text", response.availableTransports[1].transfer_formats[0]); +//} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.cpp index 1d4e3cbe00c5..1406dce8595f 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.cpp @@ -10,12 +10,11 @@ test_transport_factory::test_transport_factory(const std::shared_ptr test_transport_factory::create_transport(transport_type transport_type, const logger& logger, - const signalr_client_config&, std::function process_message_callback, - std::function error_callback) + const signalr_client_config&) { if (transport_type == signalr::transport_type::websockets) { - return websocket_transport::create([&](){ return m_websocket_client; }, logger, process_message_callback, error_callback); + return websocket_transport::create([&](){ return m_websocket_client; }, logger); } throw std::runtime_error("not supported"); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp deleted file mode 100644 index 39f4b0fb0224..000000000000 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#include "stdafx.h" -#include "test_utils.h" -#include "test_websocket_client.h" -#include "test_web_request_factory.h" - -using namespace signalr; - -std::string remove_date_from_log_entry(const std::string &log_entry) -{ - // dates are ISO 8601 (e.g. `2014-11-13T06:05:29.452066Z`) - auto date_end_index = log_entry.find_first_of("Z") + 1; - - // date is followed by a whitespace hence +1 - return log_entry.substr(date_end_index + 1); -} - -std::shared_ptr create_test_websocket_client(std::function()> receive_function, - std::function(const std::string& msg)> send_function, - std::function(const std::string& url)> connect_function, - std::function()> close_function) -{ - auto websocket_client = std::make_shared(); - websocket_client->set_receive_function(receive_function); - websocket_client->set_send_function(send_function); - websocket_client->set_connect_function(connect_function); - websocket_client->set_close_function(close_function); - - return websocket_client; -} - -std::unique_ptr create_test_web_request_factory() -{ - return std::make_unique([](const std::string& url) - { - auto response_body = - url.find_first_of("/negotiate") != 0 - ? "{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " - "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }" - : ""; - - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); - }); -} - -std::string create_uri() -{ - auto unit_test = ::testing::UnitTest::GetInstance(); - - // unit test will be null if this function is not called in a test - _ASSERTE(unit_test); - - return std::string("http://") - .append(unit_test->current_test_info()->name()); -} - -std::string create_uri(const std::string& query_string) -{ - auto unit_test = ::testing::UnitTest::GetInstance(); - - // unit test will be null if this function is not called in a test - _ASSERTE(unit_test); - - return std::string("http://") - .append(unit_test->current_test_info()->name()) - .append("?" + query_string); -} - -std::vector filter_vector(const std::vector& source, const std::string& string) -{ - std::vector filtered_entries; - std::copy_if(source.begin(), source.end(), std::back_inserter(filtered_entries), [&string](const std::string &e) - { - return e.find(string) != std::string::npos; - }); - return filtered_entries; -} - -std::string dump_vector(const std::vector& source) -{ - std::stringstream ss; - ss << "Number of entries: " << source.size() << std::endl; - for (const auto& e : source) - { - ss << e; - if (e.back() != '\n') - { - ss << std::endl; - } - } - - return ss.str(); -} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h deleted file mode 100644 index 4e007c657884..000000000000 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#pragma once - -#include "cpprest/details/basic_types.h" -#include "websocket_client.h" -#include "web_request_factory.h" - -std::string remove_date_from_log_entry(const std::string &log_entry); - -std::shared_ptr create_test_websocket_client( - std::function()> receive_function = [](){ return pplx::task_from_result(""); }, - std::function(const std::string& msg)> send_function = [](const std::string&){ return pplx::task_from_result(); }, - std::function(const std::string& url)> connect_function = [](const std::string&){ return pplx::task_from_result(); }, - std::function()> close_function = [](){ return pplx::task_from_result(); }); - -std::unique_ptr create_test_web_request_factory(); -std::string create_uri(); -std::string create_uri(const std::string& query_string); -std::vector filter_vector(const std::vector& source, const std::string& string); -std::string dump_vector(const std::vector& source); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp deleted file mode 100644 index fbf2e8ef9334..000000000000 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#include "stdafx.h" -#include "test_websocket_client.h" - -test_websocket_client::test_websocket_client() - : m_connect_function([](const std::string&){ return pplx::task_from_result(); }), - m_send_function ([](const std::string msg){ return pplx::task_from_result(); }), - m_receive_function([](){ return pplx::task_from_result(""); }), - m_close_function([](){ return pplx::task_from_result(); }) - -{ } - -pplx::task test_websocket_client::connect(const std::string& url) -{ - return m_connect_function(url); -} - -pplx::task test_websocket_client::send(const std::string &msg) -{ - return m_send_function(msg); -} - -pplx::task test_websocket_client::receive() -{ - return pplx::create_task([this]() { return m_receive_function(); }); -} - -pplx::task test_websocket_client::close() -{ - return m_close_function(); -} - -void test_websocket_client::set_connect_function(std::function(const std::string& url)> connect_function) -{ - m_connect_function = connect_function; -} - -void test_websocket_client::set_send_function(std::function(const std::string& msg)> send_function) -{ - m_send_function = send_function; -} - -void test_websocket_client::set_receive_function(std::function()> receive_function) -{ - m_receive_function = receive_function; -} - -void test_websocket_client::set_close_function(std::function()> close_function) -{ - m_close_function = close_function; -} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.h deleted file mode 100644 index bea66aded20d..000000000000 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#pragma once - -#include -#include "websocket_client.h" - -using namespace signalr; - -class test_websocket_client : public websocket_client -{ -public: - test_websocket_client(); - - pplx::task connect(const std::string& url) override; - - pplx::task send(const std::string& msg) override; - - pplx::task receive() override; - - pplx::task close() override; - - void set_connect_function(std::function(const std::string& url)> connect_function); - - void set_send_function(std::function(const std::string& msg)> send_function); - - void set_receive_function(std::function()> receive_function); - - void set_close_function(std::function()> close_function); - -private: - std::function(const std::string& url)> m_connect_function; - - std::function(const std::string&)> m_send_function; - - std::function()> m_receive_function; - - std::function()> m_close_function; -}; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp deleted file mode 100644 index 2c620a0ee1d0..000000000000 --- a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp +++ /dev/null @@ -1,451 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#include "stdafx.h" -#include "test_utils.h" -#include "trace_log_writer.h" -#include "test_websocket_client.h" -#include "websocket_transport.h" -#include "memory_log_writer.h" - -using namespace signalr; - -TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop) -{ - auto connect_called = false; - auto receive_called = std::make_shared(); - auto client = std::make_shared(); - - client->set_connect_function([&connect_called](const std::string&) -> pplx::task - { - connect_called = true; - return pplx::task_from_result(); - }); - - client->set_receive_function([receive_called]()->pplx::task - { - receive_called->set(); - return pplx::task_from_result(std::string("")); - }); - - std::shared_ptr writer(std::make_shared()); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::info), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://fakeuri.org/connect?param=42").get(); - - ASSERT_TRUE(connect_called); - ASSERT_FALSE(receive_called->wait(5000)); - - auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); - ASSERT_FALSE(log_entries.empty()); - - auto entry = remove_date_from_log_entry(log_entries[0]); - ASSERT_EQ("[info ] [websocket transport] connecting to: ws://fakeuri.org/connect?param=42\n", entry); -} - -TEST(websocket_transport_connect, connect_propagates_exceptions) -{ - auto client = std::make_shared(); - client->set_connect_function([](const std::string&)->pplx::task - { - return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); - }); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - try - { - ws_transport->connect("ws://fakeuri.org").get(); - ASSERT_TRUE(false); // exception not thrown - } - catch (const std::exception &e) - { - ASSERT_EQ(_XPLATSTR("connecting failed"), utility::conversions::to_string_t(e.what())); - } -} - -TEST(websocket_transport_connect, connect_logs_exceptions) -{ - auto client = std::make_shared(); - client->set_connect_function([](const std::string&) -> pplx::task - { - return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); - }); - - std::shared_ptr writer(std::make_shared()); - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::errors), - [](const std::string&){}, [](const std::exception&){}); - - try - { - ws_transport->connect("ws://fakeuri.org").wait(); - } - catch (...) - { } - - auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); - - ASSERT_FALSE(log_entries.empty()); - - auto entry = remove_date_from_log_entry(log_entries[0]); - - ASSERT_EQ( - "[error ] [websocket transport] exception when connecting to the server: connecting failed\n", - entry); -} - -TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_transport) -{ - auto client = std::make_shared(); - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://fakeuri.org").wait(); - - try - { - ws_transport->connect("ws://fakeuri.org").wait(); - ASSERT_TRUE(false); // exception not thrown - } - catch (const std::exception &e) - { - ASSERT_EQ(_XPLATSTR("transport already connected"), utility::conversions::to_string_t(e.what())); - } -} - -TEST(websocket_transport_connect, can_connect_after_disconnecting) -{ - auto client = std::make_shared(); - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://fakeuri.org").get(); - ws_transport->disconnect().get(); - ws_transport->connect("ws://fakeuri.org").get(); - // shouldn't throw or crash -} - -TEST(websocket_transport_send, send_creates_and_sends_websocket_messages) -{ - bool send_called = false; - - auto client = std::make_shared(); - - client->set_send_function([&send_called](const std::string&) -> pplx::task - { - send_called = true; - return pplx::task_from_result(); - }); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://url") - .then([ws_transport](){ return ws_transport->send("ABC"); }) - .wait(); - - ASSERT_TRUE(send_called); -} - -TEST(websocket_transport_disconnect, disconnect_closes_websocket) -{ - bool close_called = false; - - auto client = std::make_shared(); - - client->set_close_function([&close_called]() -> pplx::task - { - close_called = true; - return pplx::task_from_result(); - }); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://url") - .then([ws_transport]() - { - return ws_transport->disconnect(); - }).get(); - - ASSERT_TRUE(close_called); -} - -TEST(websocket_transport_disconnect, disconnect_does_not_throw) -{ - auto client = std::make_shared(); - - bool close_called = false; - client->set_close_function([&close_called]() -> pplx::task - { - close_called = true; - return pplx::task_from_exception(std::exception()); - }); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://url") - .then([ws_transport]() - { - return ws_transport->disconnect(); - }).get(); - - ASSERT_TRUE(close_called); -} - -TEST(websocket_transport_disconnect, disconnect_logs_exceptions) -{ - auto client = std::make_shared(); - client->set_close_function([]()->pplx::task - { - return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connection closing failed"))); - }); - - std::shared_ptr writer(std::make_shared()); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::errors), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://url") - .then([ws_transport]() - { - return ws_transport->disconnect(); - }).get(); - - auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); - - ASSERT_FALSE(log_entries.empty()); - - // disconnect cancels the receive loop by setting the cancellation token source to cancelled which results in writing - // to the log. Exceptions from close are also logged but this happens on a different thread. As a result the order - // of messages in the log is not deterministic and therefore we just use the "contains" idiom to find the message. - ASSERT_NE(std::find_if(log_entries.begin(), log_entries.end(), [](const std::string& entry) - { - return remove_date_from_log_entry(entry) == - "[error ] [websocket transport] exception when closing websocket: connection closing failed\n"; - }), - log_entries.end()); -} - -TEST(websocket_transport_disconnect, receive_not_called_after_disconnect) -{ - auto client = std::make_shared(); - - pplx::task_completion_event receive_task_tce; - pplx::task_completion_event receive_task_started_tce; - - // receive_task_tce is captured by reference since we assign it a new value after the first disconnect. This is - // safe here because we are blocking on disconnect and therefore we won't get into a state where we would be using - // an invalid reference because the tce went out of scope and was destroyed. - client->set_close_function([&receive_task_tce]() - { - // unblock receive - receive_task_tce.set(std::string("")); - return pplx::task_from_result(); - }); - - int num_called = 0; - - client->set_receive_function([&receive_task_tce, &receive_task_started_tce, &num_called]() -> pplx::task - { - num_called++; - receive_task_started_tce.set(); - return pplx::create_task(receive_task_tce); - }); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://fakeuri.org").get(); - pplx::create_task(receive_task_started_tce).get(); - ws_transport->disconnect().get(); - - receive_task_tce = pplx::task_completion_event(); - receive_task_started_tce = pplx::task_completion_event(); - ws_transport->connect("ws://fakeuri.org").get(); - pplx::create_task(receive_task_started_tce).get(); - ws_transport->disconnect().get(); - - ASSERT_EQ(2, num_called); -} - -TEST(websocket_transport_disconnect, disconnect_is_no_op_if_transport_not_started) -{ - auto client = std::make_shared(); - - auto close_called = false; - - client->set_close_function([&close_called]() - { - close_called = true; - return pplx::task_from_result(); - }); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->disconnect().get(); - - ASSERT_FALSE(close_called); -} - -TEST(websocket_transport_disconnect, exceptions_from_outstanding_receive_task_observed_after_websocket_transport_disconnected) -{ - auto client = std::make_shared(); - - auto receive_event = std::make_shared(); - client->set_receive_function([receive_event]() - { - return pplx::create_task([receive_event]() - { - receive_event->wait(); - return pplx::task_from_exception(std::runtime_error("exception from receive")); - }); - }); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://fakeuri.org").get(); - ws_transport->disconnect().get(); - - // at this point the cancellation token that closes the receive loop is set to cancelled so - // we can unblock the the receive task which throws an exception that should be observed otwherwise the test will crash - receive_event->set(); -} - -template -void receive_loop_logs_exception_runner(const T& e, const std::string& expected_message, trace_level trace_level); - -TEST(websocket_transport_receive_loop, receive_loop_logs_websocket_exceptions) -{ - receive_loop_logs_exception_runner( - web::websockets::client::websocket_exception(_XPLATSTR("receive failed")), - "[error ] [websocket transport] error receiving response from websocket: receive failed\n", - trace_level::errors); -} - -TEST(websocket_transport_receive_loop, receive_loop_logs_if_receive_task_canceled) -{ - receive_loop_logs_exception_runner( - pplx::task_canceled("canceled"), - "[info ] [websocket transport] receive task canceled.\n", - trace_level::info); -} - -TEST(websocket_transport_receive_loop, receive_loop_logs_std_exception) -{ - receive_loop_logs_exception_runner( - std::runtime_error("exception"), - "[error ] [websocket transport] error receiving response from websocket: exception\n", - trace_level::errors); -} - -template -void receive_loop_logs_exception_runner(const T& e, const std::string& expected_message, trace_level trace_level) -{ - event receive_event; - auto client = std::make_shared(); - - client->set_receive_function([&receive_event, &e]()->pplx::task - { - receive_event.set(); - return pplx::task_from_exception(e); - }); - - std::shared_ptr writer(std::make_shared()); - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level), - [](const std::string&){}, [](const std::exception&){}); - - ws_transport->connect("ws://url") - .then([&receive_event]() - { - receive_event.wait(); - }).get(); - - // this is race'y but there is nothing we can block on - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - - auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); - - ASSERT_NE(std::find_if(log_entries.begin(), log_entries.end(), - [&expected_message](std::string entry) { return remove_date_from_log_entry(entry) == expected_message; }), - log_entries.end()); -} - -TEST(websocket_transport_receive_loop, process_response_callback_called_when_message_received) -{ - auto client = std::make_shared(); - client->set_receive_function([]() -> pplx::task - { - return pplx::task_from_result(std::string("msg")); - }); - - auto process_response_event = std::make_shared(); - auto msg = std::make_shared(); - - auto process_response = [msg, process_response_event](const std::string& message) - { - *msg = message; - process_response_event->set(); - }; - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - process_response, [](const std::exception&){}); - - ws_transport->connect("ws://fakeuri.org").get(); - - process_response_event->wait(1000); - - ASSERT_EQ(std::string("msg"), *msg); -} - -TEST(websocket_transport_receive_loop, error_callback_called_when_exception_thrown) -{ - auto client = std::make_shared(); - client->set_receive_function([]() - { - return pplx::task_from_exception(std::runtime_error("error")); - }); - - auto close_invoked = std::make_shared(false); - client->set_close_function([close_invoked]() - { - *close_invoked = true; - return pplx::task_from_result(); - }); - - auto error_event = std::make_shared(); - auto exception_msg = std::make_shared(); - - auto error_callback = [exception_msg, error_event](const std::exception& e) - { - *exception_msg = e.what(); - error_event->set(); - }; - - auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none), - [](const std::string&){}, error_callback); - - ws_transport->connect("ws://fakeuri.org").get(); - - error_event->wait(1000); - - ASSERT_STREQ("error", exception_msg->c_str()); - ASSERT_TRUE(*close_invoked); -} - -TEST(websocket_transport_get_transport_type, get_transport_type_returns_websockets) -{ - auto ws_transport = websocket_transport::create( - [](){ return std::make_shared(); }, - logger(std::make_shared(), trace_level::none), - [](const std::string&){}, [](const std::exception&){}); - - ASSERT_EQ(transport_type::websockets, ws_transport->get_transport_type()); -} From 219763278958d569caa5129856416d6cd7079f30 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Thu, 14 Mar 2019 09:36:57 -0700 Subject: [PATCH 06/19] stash --- .../cpp/src/signalrclient/connection_impl.cpp | 29 ++++++++++++++----- .../cpp/src/signalrclient/connection_impl.h | 6 ++-- .../src/signalrclient/hub_connection_impl.cpp | 10 +++---- .../src/signalrclient/hub_connection_impl.h | 4 +-- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 017dffb85ccc..0071c589eb61 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -24,22 +24,31 @@ namespace signalr std::shared_ptr connection_impl::create(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer) { - return connection_impl::create(url, trace_level, log_writer, std::make_unique(), std::make_unique()); + return connection_impl::create(url, trace_level, log_writer, nullptr, std::make_unique()); } std::shared_ptr connection_impl::create(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - std::unique_ptr web_request_factory, std::unique_ptr transport_factory) + http_client* http_client, std::unique_ptr transport_factory) { return std::shared_ptr(new connection_impl(url, trace_level, - log_writer ? log_writer : std::make_shared(), std::move(web_request_factory), std::move(transport_factory))); + log_writer ? log_writer : std::make_shared(), http_client, std::move(transport_factory))); } connection_impl::connection_impl(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - std::unique_ptr web_request_factory, std::unique_ptr transport_factory) - : m_base_url(url), m_connection_state(connection_state::disconnected), m_logger(log_writer, trace_level), - m_transport(nullptr), m_web_request_factory(std::move(web_request_factory)), m_transport_factory(std::move(transport_factory)), - m_message_received([](const std::string&) noexcept {}), m_disconnected([]() noexcept {}), m_http_client(new default_http_client()) - { } + http_client* http_client, std::unique_ptr transport_factory) + : m_base_url(url), m_connection_state(connection_state::disconnected), m_logger(log_writer, trace_level), m_owned_http_client(false), + m_transport(nullptr), m_transport_factory(std::move(transport_factory)), m_message_received([](const std::string&) noexcept {}), m_disconnected([]() noexcept {}) + { + if (http_client != nullptr) + { + m_http_client = http_client; + } + else + { + m_http_client = new default_http_client(); + m_owned_http_client = true; + } + } connection_impl::~connection_impl() { @@ -64,6 +73,10 @@ namespace signalr m_transport = nullptr; change_state(connection_state::disconnected); + if (m_owned_http_client) + { + delete m_http_client; + } } pplx::task connection_impl::start() diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h index a593aa59eb71..8d8d2042c2b3 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h @@ -29,7 +29,7 @@ namespace signalr static std::shared_ptr create(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer); static std::shared_ptr create(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - std::unique_ptr web_request_factory, std::unique_ptr transport_factory); + http_client* http_client, std::unique_ptr transport_factory); connection_impl(const connection_impl&) = delete; @@ -53,7 +53,6 @@ namespace signalr std::atomic m_connection_state; logger m_logger; std::shared_ptr m_transport; - std::unique_ptr m_web_request_factory; std::unique_ptr m_transport_factory; std::function m_message_received; @@ -65,9 +64,10 @@ namespace signalr event m_start_completed_event; std::string m_connection_id; http_client* m_http_client; + bool m_owned_http_client; connection_impl(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - std::unique_ptr web_request_factory, std::unique_ptr transport_factory); + http_client* http_client, std::unique_ptr transport_factory); pplx::task> start_transport(const std::string& url); pplx::task send_connect_request(const std::shared_ptr& transport, diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index 8f9d7a029020..e5b385b5c4a0 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -24,15 +24,15 @@ namespace signalr const std::shared_ptr& log_writer) { return hub_connection_impl::create(url, trace_level, log_writer, - std::make_unique(), std::make_unique()); + nullptr, std::make_unique()); } std::shared_ptr hub_connection_impl::create(const std::string& url, trace_level trace_level, - const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, + const std::shared_ptr& log_writer, http_client* http_client, std::unique_ptr transport_factory) { auto connection = std::shared_ptr(new hub_connection_impl(url, trace_level, - log_writer ? log_writer : std::make_shared(), std::move(web_request_factory), std::move(transport_factory))); + log_writer ? log_writer : std::make_shared(), http_client, std::move(transport_factory))); connection->initialize(); @@ -40,10 +40,10 @@ namespace signalr } hub_connection_impl::hub_connection_impl(const std::string& url, trace_level trace_level, - const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, + const std::shared_ptr& log_writer, http_client* http_client, std::unique_ptr transport_factory) : m_connection(connection_impl::create(url, trace_level, log_writer, - std::move(web_request_factory), std::move(transport_factory))), m_logger(log_writer, trace_level), + http_client, std::move(transport_factory))), m_logger(log_writer, trace_level), m_callback_manager(json::value::parse(_XPLATSTR("{ \"error\" : \"connection went out of scope before invocation result was received\"}"))), m_disconnected([]() noexcept {}), m_handshakeReceived(false) { } diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h index 1548c2c4980a..e1871d27a406 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h @@ -24,7 +24,7 @@ namespace signalr const std::shared_ptr& log_writer); static std::shared_ptr create(const std::string& url, trace_level trace_level, - const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, + const std::shared_ptr& log_writer, http_client* http_client, std::unique_ptr transport_factory); hub_connection_impl(const hub_connection_impl&) = delete; @@ -46,7 +46,7 @@ namespace signalr private: hub_connection_impl(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - std::unique_ptr web_request_factory, std::unique_ptr transport_factory); + http_client* http_client, std::unique_ptr transport_factory); std::shared_ptr m_connection; logger m_logger; From a01c345523dc55d0d9338f61179cc19705579b93 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Thu, 14 Mar 2019 18:17:54 -0700 Subject: [PATCH 07/19] stash --- .../cpp/src/signalrclient/connection_impl.cpp | 19 +- .../cpp/src/signalrclient/connection_impl.h | 7 +- .../cpp/src/signalrclient/http_client.h | 2 +- .../src/signalrclient/hub_connection_impl.cpp | 8 +- .../src/signalrclient/hub_connection_impl.h | 4 +- .../Build/VS/signalrclienttests.vcxproj | 4 +- .../VS/signalrclienttests.vcxproj.filters | 9 +- .../connection_impl_tests.cpp | 207 +++++++++--------- .../signalrclienttests/test_http_client.cpp | 26 +++ .../signalrclienttests/test_http_client.h | 17 ++ .../test/signalrclienttests/test_utils.cpp | 110 ++++++++++ .../cpp/test/signalrclienttests/test_utils.h | 24 ++ 12 files changed, 307 insertions(+), 130 deletions(-) create mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.cpp create mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.h create mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp create mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 0071c589eb61..6008983473d2 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -28,25 +28,24 @@ namespace signalr } std::shared_ptr connection_impl::create(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - http_client* http_client, std::unique_ptr transport_factory) + std::unique_ptr http_client, std::unique_ptr transport_factory) { return std::shared_ptr(new connection_impl(url, trace_level, - log_writer ? log_writer : std::make_shared(), http_client, std::move(transport_factory))); + log_writer ? log_writer : std::make_shared(), std::move(http_client), std::move(transport_factory))); } connection_impl::connection_impl(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - http_client* http_client, std::unique_ptr transport_factory) - : m_base_url(url), m_connection_state(connection_state::disconnected), m_logger(log_writer, trace_level), m_owned_http_client(false), - m_transport(nullptr), m_transport_factory(std::move(transport_factory)), m_message_received([](const std::string&) noexcept {}), m_disconnected([]() noexcept {}) + std::unique_ptr http_client, std::unique_ptr transport_factory) + : m_base_url(url), m_connection_state(connection_state::disconnected), m_logger(log_writer, trace_level), m_transport(nullptr), + m_transport_factory(std::move(transport_factory)), m_message_received([](const std::string&) noexcept {}), m_disconnected([]() noexcept {}) { if (http_client != nullptr) { - m_http_client = http_client; + m_http_client = std::move(http_client); } else { - m_http_client = new default_http_client(); - m_owned_http_client = true; + m_http_client = std::unique_ptr(new default_http_client()); } } @@ -73,10 +72,6 @@ namespace signalr m_transport = nullptr; change_state(connection_state::disconnected); - if (m_owned_http_client) - { - delete m_http_client; - } } pplx::task connection_impl::start() diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h index 8d8d2042c2b3..83aacb662cc4 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h @@ -29,7 +29,7 @@ namespace signalr static std::shared_ptr create(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer); static std::shared_ptr create(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - http_client* http_client, std::unique_ptr transport_factory); + std::unique_ptr http_client, std::unique_ptr transport_factory); connection_impl(const connection_impl&) = delete; @@ -63,11 +63,10 @@ namespace signalr std::mutex m_stop_lock; event m_start_completed_event; std::string m_connection_id; - http_client* m_http_client; - bool m_owned_http_client; + std::unique_ptr m_http_client; connection_impl(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - http_client* http_client, std::unique_ptr transport_factory); + std::unique_ptr http_client, std::unique_ptr transport_factory); pplx::task> start_transport(const std::string& url); pplx::task send_connect_request(const std::shared_ptr& transport, diff --git a/src/SignalR/clients/cpp/src/signalrclient/http_client.h b/src/SignalR/clients/cpp/src/signalrclient/http_client.h index 9f8c84d875d9..0f2e617b4bb5 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/http_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/http_client.h @@ -28,7 +28,7 @@ namespace signalr class http_response { public: - int status_code; + int status_code = 0; std::string content; }; diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index e5b385b5c4a0..a8d9a83f2113 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -28,11 +28,11 @@ namespace signalr } std::shared_ptr hub_connection_impl::create(const std::string& url, trace_level trace_level, - const std::shared_ptr& log_writer, http_client* http_client, + const std::shared_ptr& log_writer, std::unique_ptr http_client, std::unique_ptr transport_factory) { auto connection = std::shared_ptr(new hub_connection_impl(url, trace_level, - log_writer ? log_writer : std::make_shared(), http_client, std::move(transport_factory))); + log_writer ? log_writer : std::make_shared(), std::move(http_client), std::move(transport_factory))); connection->initialize(); @@ -40,10 +40,10 @@ namespace signalr } hub_connection_impl::hub_connection_impl(const std::string& url, trace_level trace_level, - const std::shared_ptr& log_writer, http_client* http_client, + const std::shared_ptr& log_writer, std::unique_ptr http_client, std::unique_ptr transport_factory) : m_connection(connection_impl::create(url, trace_level, log_writer, - http_client, std::move(transport_factory))), m_logger(log_writer, trace_level), + std::move(http_client), std::move(transport_factory))), m_logger(log_writer, trace_level), m_callback_manager(json::value::parse(_XPLATSTR("{ \"error\" : \"connection went out of scope before invocation result was received\"}"))), m_disconnected([]() noexcept {}), m_handshakeReceived(false) { } diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h index e1871d27a406..ca3b6c586a8d 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h @@ -24,7 +24,7 @@ namespace signalr const std::shared_ptr& log_writer); static std::shared_ptr create(const std::string& url, trace_level trace_level, - const std::shared_ptr& log_writer, http_client* http_client, + const std::shared_ptr& log_writer, std::unique_ptr http_client, std::unique_ptr transport_factory); hub_connection_impl(const hub_connection_impl&) = delete; @@ -46,7 +46,7 @@ namespace signalr private: hub_connection_impl(const std::string& url, trace_level trace_level, const std::shared_ptr& log_writer, - http_client* http_client, std::unique_ptr transport_factory); + std::unique_ptr http_client, std::unique_ptr transport_factory); std::shared_ptr m_connection; logger m_logger; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj b/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj index 2ce3bed5f235..745f9c6226bd 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj +++ b/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj @@ -45,6 +45,7 @@ + @@ -65,6 +66,7 @@ Create + @@ -97,4 +99,4 @@ true Flaky, due to https://github.com/aspnet/AspNetCore/issues/8421 - + \ No newline at end of file diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj.filters b/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj.filters index ff4d349b3b19..b96cd7a1664d 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj.filters +++ b/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj.filters @@ -39,6 +39,9 @@ Header Files + + Header Files + @@ -98,8 +101,8 @@ Source Files - - - + + Source Files + \ No newline at end of file diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp index 9e1283a80b4e..222b9f9b311a 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp @@ -13,13 +13,14 @@ #include "cpprest/ws_client.h" #include "signalrclient/signalr_exception.h" #include "signalrclient/web_exception.h" +#include "test_http_client.h" using namespace signalr; static std::shared_ptr create_connection(std::shared_ptr websocket_client = create_test_websocket_client(), std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) { - return connection_impl::create(create_uri(), trace_level, log_writer, create_test_web_request_factory(), + return connection_impl::create(create_uri(), trace_level, log_writer, create_test_http_client(), std::make_unique(websocket_client)); } @@ -34,7 +35,7 @@ TEST(connection_impl_connection_state, initial_connection_state_is_disconnected) TEST(connection_impl_start, cannot_start_non_disconnected_exception) { auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client); connection->start().wait(); @@ -55,7 +56,7 @@ TEST(connection_impl_start, connection_state_is_connecting_when_connection_is_be std::shared_ptr writer(std::make_shared()); auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, /* send function */ [](const std::string){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, /* connect function */[](const std::string&) { @@ -84,7 +85,7 @@ TEST(connection_impl_start, connection_state_is_connecting_when_connection_is_be TEST(connection_impl_start, connection_state_is_connected_when_connection_established_succesfully) { auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr)); }); auto connection = create_connection(websocket_client); connection->start().get(); ASSERT_EQ(connection->get_connection_state(), connection_state::connected); @@ -92,14 +93,14 @@ TEST(connection_impl_start, connection_state_is_connected_when_connection_establ TEST(connection_impl_start, connection_state_is_disconnected_when_connection_cannot_be_established) { - auto web_request_factory = std::make_unique([](const std::string&) -> std::unique_ptr + auto http_client = std::make_unique([](const std::string&, http_request) { - return std::unique_ptr(new web_request_stub((unsigned short)404, "Bad request", "")); + return http_response { 404, "" }; }); auto connection = connection_impl::create(create_uri(), trace_level::none, std::make_shared(), - std::move(web_request_factory), std::make_unique()); + std::move(http_client), std::make_unique()); try { @@ -116,9 +117,9 @@ TEST(connection_impl_start, throws_for_invalid_uri) std::shared_ptr writer(std::make_shared()); auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); - auto connection = connection_impl::create(":1\t ä bad_uri&a=b", trace_level::errors, writer, create_test_web_request_factory(), std::make_unique(websocket_client)); + auto connection = connection_impl::create(":1\t ä bad_uri&a=b", trace_level::errors, writer, create_test_http_client(), std::make_unique(websocket_client)); try { @@ -139,7 +140,7 @@ TEST(connection_impl_start, start_sets_id_query_string) std::string query_string; auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, /* send function */ [](const std::string&) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, /* connect function */[&query_string](const std::string& url) { @@ -147,7 +148,7 @@ TEST(connection_impl_start, start_sets_id_query_string) return pplx::task_from_exception(web::websockets::client::websocket_exception("connecting failed")); }); - auto connection = connection_impl::create(create_uri(""), trace_level::errors, writer, create_test_web_request_factory(), std::make_unique(websocket_client)); + auto connection = connection_impl::create(create_uri(""), trace_level::errors, writer, create_test_http_client(), std::make_unique(websocket_client)); try { @@ -166,7 +167,7 @@ TEST(connection_impl_start, start_appends_id_query_string) std::string query_string; auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, /* send function */ [](const std::string) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, /* connect function */[&query_string](const std::string& url) { @@ -174,7 +175,7 @@ TEST(connection_impl_start, start_appends_id_query_string) return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); }); - auto connection = connection_impl::create(create_uri("a=b&c=d"), trace_level::errors, writer, create_test_web_request_factory(), std::make_unique(websocket_client)); + auto connection = connection_impl::create(create_uri("a=b&c=d"), trace_level::errors, writer, create_test_http_client(), std::make_unique(websocket_client)); try { @@ -191,14 +192,14 @@ TEST(connection_impl_start, start_logs_exceptions) { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string&) -> std::unique_ptr + auto http_client = std::make_unique([](const std::string&, http_request) { - return std::unique_ptr(new web_request_stub((unsigned short)404, "Bad request", "")); + return http_response{ 404, "" }; }); auto connection = connection_impl::create(create_uri(), trace_level::errors, writer, - std::move(web_request_factory), std::make_unique()); + std::move(http_client), std::make_unique()); try { @@ -217,14 +218,14 @@ TEST(connection_impl_start, start_logs_exceptions) TEST(connection_impl_start, start_propagates_exceptions_from_negotiate) { - auto web_request_factory = std::make_unique([](const std::string&) -> std::unique_ptr + auto http_client = std::make_unique([](const std::string&, http_request) { - return std::unique_ptr(new web_request_stub((unsigned short)404, "Bad request", "")); + return http_response{ 404, "" }; }); auto connection = connection_impl::create(create_uri(), trace_level::none, std::make_shared(), - std::move(web_request_factory), std::make_unique()); + std::move(http_client), std::make_unique()); try { @@ -242,7 +243,7 @@ TEST(connection_impl_start, start_fails_if_transport_connect_throws) std::shared_ptr writer(std::make_shared()); auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, /* send function */ [](const std::string){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, /* connect function */[](const std::string&) { @@ -274,7 +275,7 @@ TEST(connection_impl_send, send_fails_if_transport_fails_when_receiving_messages { std::shared_ptr writer(std::make_shared()); - auto websocket_client = create_test_websocket_client([]() { return pplx::task_from_result(std::string("")); }, + auto websocket_client = create_test_websocket_client([](std::function callback) { callback("", nullptr); }, /* send function */ [](const std::string &) { return pplx::task_from_exception(std::runtime_error("send error")); @@ -307,9 +308,9 @@ TEST(connection_impl_start, start_fails_if_negotiate_request_fails) { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string&) + auto http_client = std::make_unique([](const std::string&, http_request) { - return std::unique_ptr(new web_request_stub((unsigned short)400, "Bad Request")); + return http_response{ 400, "" }; }); auto websocket_client = std::make_shared(); @@ -320,7 +321,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_request_fails) auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); try { @@ -337,14 +338,14 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_has_error) { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { auto response_body = url.find("/negotiate") != std::string::npos ? "{ \"error\": \"bad negotiate\" }" : ""; - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; }); pplx::task_completion_event tce; @@ -361,7 +362,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_has_error) auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); try { @@ -380,14 +381,14 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_webs { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { auto response_body = url.find("/negotiate") != std::string::npos ? "{ \"availableTransports\": [ { \"transport\": \"ServerSentEvents\", \"transferFormats\": [ \"Text\" ] } ] }" : ""; - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; }); pplx::task_completion_event tce; @@ -404,7 +405,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_webs auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); try { @@ -423,14 +424,14 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_tran { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { auto response_body = url.find("/negotiate") != std::string::npos ? "{ \"availableTransports\": [ ] }" : ""; - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + http_response{ 200, response_body }; }); pplx::task_completion_event tce; @@ -447,7 +448,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_tran auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); try { @@ -466,14 +467,14 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_is_invalid) { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { auto response_body = url.find("/negotiate") != std::string::npos ? "{ \"availableTransports\": [ " : ""; - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; }); pplx::task_completion_event tce; @@ -490,7 +491,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_is_invalid) auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); try { @@ -509,7 +510,7 @@ TEST(connection_impl_start, negotiate_follows_redirect) { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { std::string response_body = ""; if (url.find("/negotiate") != std::string::npos) @@ -525,7 +526,7 @@ TEST(connection_impl_start, negotiate_follows_redirect) } } - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; }); auto websocket_client = std::make_shared(); @@ -539,7 +540,7 @@ TEST(connection_impl_start, negotiate_follows_redirect) auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); connection->start().get(); @@ -551,7 +552,7 @@ TEST(connection_impl_start, negotiate_redirect_uses_accessToken) std::shared_ptr writer(std::make_shared()); std::string accessToken; - auto web_request_factory = std::make_unique([&accessToken](const std::string& url) + auto http_client = std::make_unique([&accessToken](const std::string& url, http_request) { std::string response_body = ""; if (url.find("/negotiate") != std::string::npos) @@ -567,12 +568,12 @@ TEST(connection_impl_start, negotiate_redirect_uses_accessToken) } } - auto request = new web_request_stub((unsigned short)200, "OK", response_body); + /*auto request = new web_request_stub((unsigned short)200, "OK", response_body); request->on_get_response = [&accessToken](web_request_stub& stub) { accessToken = utility::conversions::to_utf8string(stub.m_signalr_client_config.get_http_headers()[_XPLATSTR("Authorization")]); - }; - return std::unique_ptr(request); + };*/ + return http_response{ 200, response_body }; }); auto websocket_client = std::make_shared(); @@ -586,7 +587,7 @@ TEST(connection_impl_start, negotiate_redirect_uses_accessToken) auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); connection->start().get(); @@ -598,7 +599,7 @@ TEST(connection_impl_start, negotiate_fails_after_too_many_redirects) { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { std::string response_body = ""; if (url.find("/negotiate") != std::string::npos) @@ -607,14 +608,14 @@ TEST(connection_impl_start, negotiate_fails_after_too_many_redirects) response_body = "{ \"url\": \"http://redirected\" }"; } - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; }); auto websocket_client = std::make_shared(); auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); try { @@ -630,7 +631,7 @@ TEST(connection_impl_start, negotiate_fails_if_ProtocolVersion_in_response) { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { std::string response_body = ""; if (url.find("/negotiate") != std::string::npos) @@ -638,14 +639,14 @@ TEST(connection_impl_start, negotiate_fails_if_ProtocolVersion_in_response) response_body = "{\"ProtocolVersion\" : \"\" }"; } - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + http_response{ 200, response_body }; }); auto websocket_client = std::make_shared(); auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); try { @@ -662,7 +663,7 @@ TEST(connection_impl_start, negotiate_redirect_does_not_overwrite_url) std::shared_ptr writer(std::make_shared()); int redirectCount = 0; - auto web_request_factory = std::make_unique([&redirectCount](const std::string& url) + auto http_client = std::make_unique([&redirectCount](const std::string& url, http_request) { std::string response_body = ""; if (url.find("/negotiate") != std::string::npos) @@ -679,14 +680,14 @@ TEST(connection_impl_start, negotiate_redirect_does_not_overwrite_url) } } - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; }); auto websocket_client = std::make_shared(); auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); connection->start().get(); ASSERT_EQ(1, redirectCount); @@ -701,7 +702,7 @@ TEST(connection_impl_start, negotiate_redirect_uses_own_query_string) std::string query_string; auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, /* send function */ [](const std::string) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, /* connect function */[&query_string](const std::string& url) { @@ -709,7 +710,7 @@ TEST(connection_impl_start, negotiate_redirect_uses_own_query_string) return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); }); - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { std::string response_body = ""; if (url.find("/negotiate") != std::string::npos) @@ -725,10 +726,10 @@ TEST(connection_impl_start, negotiate_redirect_uses_own_query_string) } } - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; }); - auto connection = connection_impl::create(create_uri("a=b&c=d"), trace_level::errors, writer, std::move(web_request_factory), std::make_unique(websocket_client)); + auto connection = connection_impl::create(create_uri("a=b&c=d"), trace_level::errors, writer, std::move(http_client), std::make_unique(websocket_client)); try { @@ -745,7 +746,7 @@ TEST(connection_impl_start, start_fails_if_connect_request_times_out) { std::shared_ptr writer(std::make_shared()); - auto web_request_factory = create_test_web_request_factory(); + auto http_client = create_test_http_client(); pplx::task_completion_event tce; auto websocket_client = std::make_shared(); @@ -761,7 +762,7 @@ TEST(connection_impl_start, start_fails_if_connect_request_times_out) auto connection = connection_impl::create(create_uri(), trace_level::messages, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); try { @@ -781,10 +782,10 @@ TEST(connection_impl_process_response, process_response_logs_messages) std::shared_ptr writer(std::make_shared()); auto wait_receive = std::make_shared(); auto websocket_client = create_test_websocket_client( - /* receive function */ [wait_receive]() + /* receive function */ [wait_receive](std::function callback) { wait_receive->set(); - return pplx::task_from_result(std::string("{ }")); + callback("{ }", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::messages); @@ -804,7 +805,7 @@ TEST(connection_impl_send, message_sent) std::string actual_message; auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, /* send function */ [&actual_message](const std::string& message) { actual_message = message; @@ -844,9 +845,9 @@ TEST(connection_impl_send, exceptions_from_send_logged_and_propagated) { std::shared_ptr writer(std::make_shared()); auto websocket_client = create_test_websocket_client( - /* receive function */ []() + /* receive function */ [](std::function callback) { - return pplx::task_from_result(std::string("{}")); + callback("{}", nullptr); }, /* send function */ [](const std::string&) { @@ -881,7 +882,7 @@ TEST(connection_impl_set_message_received, callback_invoked_when_message_receive { int call_number = -1; auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number]() + /* receive function */ [call_number](std::function callback) mutable { std::string responses[] { @@ -892,7 +893,7 @@ TEST(connection_impl_set_message_received, callback_invoked_when_message_receive call_number = std::min(call_number + 1, 2); - return pplx::task_from_result(responses[call_number]); + callback(responses[call_number], nullptr); }); auto connection = create_connection(websocket_client); @@ -924,7 +925,7 @@ TEST(connection_impl_set_message_received, exception_from_callback_caught_and_lo { int call_number = -1; auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number]() + /* receive function */ [call_number](std::function callback) mutable { std::string responses[] { @@ -935,7 +936,7 @@ TEST(connection_impl_set_message_received, exception_from_callback_caught_and_lo call_number = std::min(call_number + 1, 2); - return pplx::task_from_result(responses[call_number]); + callback(responses[call_number], nullptr); }); std::shared_ptr writer(std::make_shared()); @@ -970,7 +971,7 @@ TEST(connection_impl_set_message_received, non_std_exception_from_callback_caugh { int call_number = -1; auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number]() + /* receive function */ [call_number](std::function callback) mutable { std::string responses[] { @@ -981,7 +982,7 @@ TEST(connection_impl_set_message_received, non_std_exception_from_callback_caugh call_number = std::min(call_number + 1, 2); - return pplx::task_from_result(responses[call_number]); + callback(responses[call_number], nullptr); }); std::shared_ptr writer(std::make_shared()); @@ -1015,7 +1016,7 @@ TEST(connection_impl_set_message_received, non_std_exception_from_callback_caugh void can_be_set_only_in_disconnected_state(std::function callback, const char* expected_exception_message) { auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client); connection->start().get(); @@ -1065,7 +1066,7 @@ TEST(connection_impl_stop, stopping_disconnecting_connection_returns_cancelled_t auto writer = std::shared_ptr{std::make_shared()}; auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, /* send function */ [](const std::string){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, /* connect function */ [&close_event](const std::string&) { return pplx::task_from_result(); }, /* close function */ [&close_event]() @@ -1107,7 +1108,7 @@ TEST(connection_impl_stop, can_start_and_stop_connection) auto writer = std::shared_ptr{std::make_shared()}; auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); connection->start() @@ -1130,7 +1131,7 @@ TEST(connection_impl_stop, can_start_and_stop_connection_multiple_times) { auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); connection->start() @@ -1172,10 +1173,10 @@ TEST(connection_impl_stop, dtor_stops_the_connection) { auto websocket_client = create_test_websocket_client( - /* receive function */ []() + /* receive function */ [](std::function callback) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); - return pplx::task_from_result(std::string("{ }\x1e")); + callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); @@ -1205,10 +1206,10 @@ TEST(connection_impl_stop, stop_cancels_ongoing_start_request) auto disconnect_completed_event = std::make_shared(); auto websocket_client = create_test_websocket_client( - /* receive function */ [disconnect_completed_event]() + /* receive function */ [disconnect_completed_event](std::function callback) { disconnect_completed_event->wait(); - return pplx::task_from_result(std::string("{ }\x1e")); + callback("{ }\x1e", nullptr); }); auto writer = std::shared_ptr{std::make_shared()}; @@ -1242,7 +1243,7 @@ TEST(connection_impl_stop, stop_cancels_ongoing_start_request) TEST(connection_impl_stop, ongoing_start_request_canceled_if_connection_stopped_before_init_message_received) { - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { auto response_body = url.find("/negotiate") != std::string::npos @@ -1250,17 +1251,17 @@ TEST(connection_impl_stop, ongoing_start_request_canceled_if_connection_stopped_ "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }" : ""; - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; }); - auto websocket_client = create_test_websocket_client(/*receive function*/ []() + auto websocket_client = create_test_websocket_client(/*receive function*/ [](std::function callback) { - return pplx::task_from_result("{}"); + callback("{}", nullptr); }); auto writer = std::shared_ptr{std::make_shared()}; auto connection = connection_impl::create(create_uri(), trace_level::all, writer, - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); auto start_task = connection->start(); connection->stop().get(); @@ -1288,7 +1289,7 @@ TEST(connection_impl_stop, ongoing_start_request_canceled_if_connection_stopped_ TEST(connection_impl_stop, stop_invokes_disconnected_callback) { auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client); auto disconnected_invoked = false; @@ -1309,7 +1310,7 @@ TEST(connection_impl_stop, std_exception_for_disconnected_callback_caught_and_lo int call_number = -1; auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number]() + /* receive function */ [call_number](std::function callback) mutable { std::string responses[] { @@ -1319,7 +1320,7 @@ TEST(connection_impl_stop, std_exception_for_disconnected_callback_caught_and_lo call_number = std::min(call_number + 1, 1); - return pplx::task_from_result(responses[call_number]); + callback(responses[call_number], nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::errors); @@ -1342,7 +1343,7 @@ TEST(connection_impl_stop, exception_for_disconnected_callback_caught_and_logged int call_number = -1; auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number]() + /* receive function */ [call_number](std::function callback) mutable { std::string responses[] { @@ -1352,7 +1353,7 @@ TEST(connection_impl_stop, exception_for_disconnected_callback_caught_and_logged call_number = std::min(call_number + 1, 1); - return pplx::task_from_result(responses[call_number]); + callback(responses[call_number], nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::errors); @@ -1373,7 +1374,7 @@ TEST(connection_impl_config, custom_headers_set_in_requests) { auto writer = std::shared_ptr{std::make_shared()}; - auto web_request_factory = std::make_unique([](const std::string& url) + auto http_client = std::make_unique([](const std::string& url, http_request) { auto response_body = url.find("/negotiate") != std::string::npos @@ -1381,23 +1382,23 @@ TEST(connection_impl_config, custom_headers_set_in_requests) "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }" : ""; - auto request = new web_request_stub((unsigned short)200, "OK", response_body); + /*auto request = new web_request_stub((unsigned short)200, "OK", response_body); request->on_get_response = [](web_request_stub& request) { auto http_headers = request.m_signalr_client_config.get_http_headers(); ASSERT_EQ(1U, http_headers.size()); ASSERT_EQ(_XPLATSTR("42"), http_headers[_XPLATSTR("Answer")]); - }; + };*/ - return std::unique_ptr(request); + return http_response{ 200, response_body }; }); auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = connection_impl::create(create_uri(), trace_level::state_changes, - writer, std::move(web_request_factory), std::make_unique(websocket_client)); + writer, std::move(http_client), std::make_unique(websocket_client)); signalr::signalr_client_config signalr_client_config{}; auto http_headers = signalr_client_config.get_http_headers(); @@ -1428,7 +1429,7 @@ TEST(connection_impl_change_state, change_state_logs) { std::shared_ptr writer(std::make_shared()); auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); connection->start().wait(); @@ -1445,7 +1446,7 @@ TEST(connection_id, connection_id_is_set_if_start_fails_but_negotiate_request_su std::shared_ptr writer(std::make_shared()); auto websocket_client = create_test_websocket_client( - /* receive function */ [](){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* receive function */ [](std::function callback){ callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, /* send function */ [](const std::string){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, /* connect function */[](const std::string&) { @@ -1478,7 +1479,7 @@ TEST(connection_id, can_get_connection_id_when_connection_in_connected_state) auto writer = std::shared_ptr{ std::make_shared() }; auto websocket_client = create_test_websocket_client( - /* receive function */ [](){ return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback){ callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); std::string connection_id; @@ -1497,7 +1498,7 @@ TEST(connection_id, can_get_connection_id_after_connection_has_stopped) auto writer = std::shared_ptr{ std::make_shared() }; auto websocket_client = create_test_websocket_client( - /* receive function */ [](){ return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback){ callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client, writer, trace_level::state_changes); connection->start() @@ -1516,9 +1517,9 @@ TEST(connection_id, connection_id_reset_when_starting_connection) auto writer = std::shared_ptr{ std::make_shared() }; auto websocket_client = create_test_websocket_client( - /* receive function */ [](){ return pplx::task_from_result(std::string("{ }\x1e")); }); + /* receive function */ [](std::function callback){ callback("{ }\x1e", nullptr); }); - auto web_request_factory = std::make_unique([&fail_http_requests](const std::string &url) -> std::unique_ptr + auto http_client = std::make_unique([&fail_http_requests](const std::string& url, http_request) { if (!fail_http_requests) { auto response_body = @@ -1527,15 +1528,15 @@ TEST(connection_id, connection_id_reset_when_starting_connection) "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }" : ""; - return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + return http_response{ 200, response_body }; } - return std::unique_ptr(new web_request_stub((unsigned short)500, "Internal Server Error", "")); + return http_response{ 500, "" }; }); auto connection = connection_impl::create(create_uri(), trace_level::none, std::make_shared(), - std::move(web_request_factory), std::make_unique(websocket_client)); + std::move(http_client), std::make_unique(websocket_client)); connection->start() .then([connection]() diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.cpp new file mode 100644 index 000000000000..4f71fd74607b --- /dev/null +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.cpp @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "test_http_client.h" + +test_http_client::test_http_client(std::function create_http_response_fn) + : m_http_response(create_http_response_fn) +{ +} + +void test_http_client::send(std::string url, http_request request, std::function callback) +{ + http_response response; + std::exception_ptr exception; + try + { + response = m_http_response(url, request); + } + catch (...) + { + exception = std::current_exception(); + } + + callback(response, exception); +} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.h new file mode 100644 index 000000000000..ea8f8b55e20a --- /dev/null +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.h @@ -0,0 +1,17 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +#include "http_client.h" + +using namespace signalr; + +class test_http_client : public http_client +{ +public: + test_http_client(std::function create_http_response_fn); + void send(std::string url, http_request request, std::function callback) override; +private: + std::function m_http_response; +}; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp new file mode 100644 index 000000000000..0863492fc577 --- /dev/null +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp @@ -0,0 +1,110 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "test_utils.h" +#include "test_websocket_client.h" +#include "test_web_request_factory.h" +#include "test_http_client.h" + +using namespace signalr; + +std::string remove_date_from_log_entry(const std::string &log_entry) +{ + // dates are ISO 8601 (e.g. `2014-11-13T06:05:29.452066Z`) + auto date_end_index = log_entry.find_first_of("Z") + 1; + + // date is followed by a whitespace hence +1 + return log_entry.substr(date_end_index + 1); +} + +std::shared_ptr create_test_websocket_client(std::function)> receive_function, + std::function)> send_function, + std::function)> connect_function, + std::function)> close_function) +{ + auto websocket_client = std::make_shared(); + websocket_client->set_receive_function(receive_function); + websocket_client->set_send_function(send_function); + websocket_client->set_connect_function(connect_function); + websocket_client->set_close_function(close_function); + + return websocket_client; +} + +std::unique_ptr create_test_web_request_factory() +{ + return std::make_unique([](const std::string& url) + { + auto response_body = + url.find_first_of("/negotiate") != 0 + ? "{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " + "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }" + : ""; + + return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); + }); +} + +std::unique_ptr create_test_http_client() +{ + return std::make_unique([](const std::string & url, http_request request) + { + auto response_body = + url.find_first_of("/negotiate") != 0 + ? "{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " + "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }" + : ""; + + return http_response{ 200, response_body }; + }); +} + +std::string create_uri() +{ + auto unit_test = ::testing::UnitTest::GetInstance(); + + // unit test will be null if this function is not called in a test + _ASSERTE(unit_test); + + return std::string("http://") + .append(unit_test->current_test_info()->name()); +} + +std::string create_uri(const std::string& query_string) +{ + auto unit_test = ::testing::UnitTest::GetInstance(); + + // unit test will be null if this function is not called in a test + _ASSERTE(unit_test); + + return std::string("http://") + .append(unit_test->current_test_info()->name()) + .append("?" + query_string); +} + +std::vector filter_vector(const std::vector& source, const std::string& string) +{ + std::vector filtered_entries; + std::copy_if(source.begin(), source.end(), std::back_inserter(filtered_entries), [&string](const std::string &e) + { + return e.find(string) != std::string::npos; + }); + return filtered_entries; +} + +std::string dump_vector(const std::vector& source) +{ + std::stringstream ss; + ss << "Number of entries: " << source.size() << std::endl; + for (const auto& e : source) + { + ss << e; + if (e.back() != '\n') + { + ss << std::endl; + } + } + + return ss.str(); +} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h new file mode 100644 index 000000000000..604f71dc8a50 --- /dev/null +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +#include "cpprest/details/basic_types.h" +#include "websocket_client.h" +#include "web_request_factory.h" +#include "http_client.h" + +std::string remove_date_from_log_entry(const std::string &log_entry); + +std::shared_ptr create_test_websocket_client( + std::function)> receive_function = [](std::function callback) { callback("", nullptr); }, + std::function)> send_function = [](const std::string&, std::function callback) { callback(nullptr); }, + std::function)> connect_function = [](const std::string&, std::function callback) { callback(nullptr); }, + std::function)> close_function = [](std::function callback) { callback(nullptr); }); + +std::unique_ptr create_test_web_request_factory(); +std::unique_ptr create_test_http_client(); +std::string create_uri(); +std::string create_uri(const std::string& query_string); +std::vector filter_vector(const std::vector& source, const std::string& string); +std::string dump_vector(const std::vector& source); From cc5ba3c056b0d7c734a413a681e9e75cecbfdb4d Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Thu, 14 Mar 2019 22:47:15 -0700 Subject: [PATCH 08/19] progress --- .../src/signalrclient/websocket_transport.cpp | 10 +- .../src/signalrclient/websocket_transport.h | 2 +- .../connection_impl_tests.cpp | 64 +- .../hub_connection_impl_tests.cpp | 1474 ++++++++--------- .../test_websocket_client.cpp | 65 + .../websocket_transport_tests.cpp | 645 ++++++++ 6 files changed, 1485 insertions(+), 775 deletions(-) create mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp create mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp index 4839d461a445..28cc59508295 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp @@ -18,7 +18,8 @@ namespace signalr websocket_transport::websocket_transport(const std::function()>& websocket_client_factory, const logger& logger) - : transport(logger), m_websocket_client_factory(websocket_client_factory), m_close_callback([](std::exception_ptr) {}) + : transport(logger), m_websocket_client_factory(websocket_client_factory), m_close_callback([](std::exception_ptr) {}), + m_process_response_callback([](std::string, std::exception_ptr) {}) { // we use this cts to check if the receive loop is running so it should be // initially cancelled to indicate that the receive loop is not running @@ -29,8 +30,9 @@ namespace signalr { try { - // TODO: wait - stop([](std::exception_ptr) { }); + pplx::task_completion_event event; + stop([event](std::exception_ptr) { event.set(); }); + pplx::create_task(event).get(); } catch (...) // must not throw from the destructor {} @@ -188,7 +190,7 @@ namespace signalr } } - void websocket_transport::stop(/*format,*/ std::function callback) + void websocket_transport::stop(std::function callback) { std::shared_ptr websocket_client = nullptr; diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h index 4335ccc1b6ef..8dc362c696c4 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.h @@ -27,7 +27,7 @@ namespace signalr transport_type get_transport_type() const noexcept override; void start(const std::string& url, transfer_format format, std::function callback) override; - void stop(/*format,*/ std::function callback) override; + void stop(std::function callback) override; void on_close(std::function callback) override; void send(std::string payload, std::function callback) override; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp index 222b9f9b311a..636d719f7faa 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp @@ -57,11 +57,8 @@ TEST(connection_impl_start, connection_state_is_connecting_when_connection_is_be auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, - /* send function */ [](const std::string){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, - /* connect function */[](const std::string&) - { - return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); - }); + /* send function */ [](const std::string&, std::function callback) { callback(std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, + /* connect function */[](const std::string&, std::function callback) { callback(std::make_exception_ptr(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")))); }); auto connection = create_connection(websocket_client, writer, trace_level::errors); @@ -85,7 +82,7 @@ TEST(connection_impl_start, connection_state_is_connecting_when_connection_is_be TEST(connection_impl_start, connection_state_is_connected_when_connection_established_succesfully) { auto websocket_client = create_test_websocket_client( - /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr)); }); + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); auto connection = create_connection(websocket_client); connection->start().get(); ASSERT_EQ(connection->get_connection_state(), connection_state::connected); @@ -141,11 +138,11 @@ TEST(connection_impl_start, start_sets_id_query_string) auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, - /* send function */ [](const std::string&) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, - /* connect function */[&query_string](const std::string& url) + /* send function */ [](const std::string&, std::function callback) { callback(std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, + /* connect function */[&query_string](const std::string& url, std::function callback) { query_string = utility::conversions::to_utf8string(url.substr(url.find('?') + 1)); - return pplx::task_from_exception(web::websockets::client::websocket_exception("connecting failed")); + callback(std::make_exception_ptr(web::websockets::client::websocket_exception("connecting failed"))); }); auto connection = connection_impl::create(create_uri(""), trace_level::errors, writer, create_test_http_client(), std::make_unique(websocket_client)); @@ -168,11 +165,11 @@ TEST(connection_impl_start, start_appends_id_query_string) auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, - /* send function */ [](const std::string) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, - /* connect function */[&query_string](const std::string& url) + /* send function */ [](const std::string&, std::function callback) { callback(std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, + /* connect function */[&query_string](const std::string& url, std::function callback) { query_string = utility::conversions::to_utf8string(url.substr(url.find('?') + 1)); - return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); + callback(std::make_exception_ptr(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")))); }); auto connection = connection_impl::create(create_uri("a=b&c=d"), trace_level::errors, writer, create_test_http_client(), std::make_unique(websocket_client)); @@ -244,10 +241,10 @@ TEST(connection_impl_start, start_fails_if_transport_connect_throws) auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, - /* send function */ [](const std::string){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, - /* connect function */[](const std::string&) + /* send function */ [](const std::string&, std::function callback){ callback(std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, + /* connect function */[](const std::string&, std::function callback) { - return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); + callback(std::make_exception_ptr(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")))); }); auto connection = create_connection(websocket_client, writer, trace_level::errors); @@ -276,9 +273,9 @@ TEST(connection_impl_send, send_fails_if_transport_fails_when_receiving_messages std::shared_ptr writer(std::make_shared()); auto websocket_client = create_test_websocket_client([](std::function callback) { callback("", nullptr); }, - /* send function */ [](const std::string &) + /* send function */ [](const std::string &, std::function callback) { - return pplx::task_from_exception(std::runtime_error("send error")); + callback(std::make_exception_ptr(std::runtime_error("send error"))); }); auto connection = create_connection(websocket_client, writer, trace_level::errors); @@ -431,7 +428,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_tran ? "{ \"availableTransports\": [ ] }" : ""; - http_response{ 200, response_body }; + return http_response{ 200, response_body }; }); pplx::task_completion_event tce; @@ -639,7 +636,7 @@ TEST(connection_impl_start, negotiate_fails_if_ProtocolVersion_in_response) response_body = "{\"ProtocolVersion\" : \"\" }"; } - http_response{ 200, response_body }; + return http_response{ 200, response_body }; }); auto websocket_client = std::make_shared(); @@ -703,11 +700,11 @@ TEST(connection_impl_start, negotiate_redirect_uses_own_query_string) auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback) { callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, - /* send function */ [](const std::string) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, - /* connect function */[&query_string](const std::string& url) + /* send function */ [](const std::string&, std::function callback) { callback(std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, + /* connect function */[&query_string](const std::string& url, std::function callback) { query_string = url.substr(url.find('?') + 1); - return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); + callback(std::make_exception_ptr(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")))); }); auto http_client = std::make_unique([](const std::string& url, http_request) @@ -806,10 +803,10 @@ TEST(connection_impl_send, message_sent) auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, - /* send function */ [&actual_message](const std::string& message) + /* send function */ [&actual_message](const std::string& message, std::function callback) { actual_message = message; - return pplx::task_from_result(); + callback(nullptr); }); auto connection = create_connection(websocket_client); @@ -849,9 +846,9 @@ TEST(connection_impl_send, exceptions_from_send_logged_and_propagated) { callback("{}", nullptr); }, - /* send function */ [](const std::string&) + /* send function */ [](const std::string&, std::function callback) { - return pplx::task_from_exception(std::runtime_error("error")); + callback(std::make_exception_ptr(std::runtime_error("error"))); }); auto connection = create_connection(websocket_client, writer, trace_level::errors); @@ -1067,13 +1064,14 @@ TEST(connection_impl_stop, stopping_disconnecting_connection_returns_cancelled_t auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, - /* send function */ [](const std::string){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, - /* connect function */ [&close_event](const std::string&) { return pplx::task_from_result(); }, - /* close function */ [&close_event]() + /* send function */ [](const std::string, std::function callback){ callback(std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, + /* connect function */ [&close_event](const std::string&, std::function callback) { callback(nullptr); }, + /* close function */ [&close_event](std::function callback) { - return pplx::create_task([&close_event]() + pplx::create_task([&close_event, callback]() { close_event.wait(); + callback(nullptr); }); }); @@ -1447,10 +1445,10 @@ TEST(connection_id, connection_id_is_set_if_start_fails_but_negotiate_request_su auto websocket_client = create_test_websocket_client( /* receive function */ [](std::function callback){ callback("", std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, - /* send function */ [](const std::string){ return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, - /* connect function */[](const std::string&) + /* send function */ [](const std::string, std::function callback){ callback(std::make_exception_ptr(std::runtime_error("should not be invoked"))); }, + /* connect function */[](const std::string&, std::function callback) { - return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); + callback(std::make_exception_ptr(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")))); }); auto connection = create_connection(websocket_client, writer, trace_level::errors); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp index 20b1436da568..58a0199c96a3 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp @@ -1,738 +1,738 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - +//// Copyright (c) .NET Foundation. All rights reserved. +//// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// #include "stdafx.h" -#include "test_utils.h" -#include "test_transport_factory.h" -#include "test_web_request_factory.h" -#include "hub_connection_impl.h" -#include "trace_log_writer.h" -#include "memory_log_writer.h" -#include "signalrclient/hub_exception.h" -#include "signalrclient/signalr_exception.h" - -using namespace signalr; - -std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), - std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) -{ - return hub_connection_impl::create(create_uri(), trace_level, log_writer, - create_test_web_request_factory(), std::make_unique(websocket_client)); -} - -TEST(url, negotiate_appended_to_url) -{ - std::string base_urls[] = { "http://fakeuri", "http://fakeuri/" }; - - for (const auto& base_url : base_urls) - { - std::string requested_url; - auto web_request_factory = std::make_unique([&requested_url](const std::string& url) - { - requested_url = url; - return std::unique_ptr(new web_request_stub((unsigned short)404, "Bad request", "")); - }); - - auto hub_connection = hub_connection_impl::create(base_url, trace_level::none, - std::make_shared(), std::move(web_request_factory), - std::make_unique(create_test_websocket_client())); - - try - { - hub_connection->start().get(); - } - catch (const std::exception&) {} - - ASSERT_EQ("http://fakeuri/negotiate", requested_url); - } -} - -TEST(start, start_starts_connection) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); - auto hub_connection = create_hub_connection(websocket_client); - - hub_connection->start().get(); - - ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -} - -TEST(start, start_sends_handshake) -{ - auto message = std::make_shared(); - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, - /* send function */ [message](const std::string& msg) { *message = msg; return pplx::task_from_result(); }); - auto hub_connection = create_hub_connection(websocket_client); - - hub_connection->start().get(); - - ASSERT_EQ("{\"protocol\":\"json\",\"version\":1}\x1e", *message); - - ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -} - -TEST(start, start_waits_for_handshake_response) -{ - pplx::task_completion_event tce; - pplx::task_completion_event tceWaitForSend; - auto websocket_client = create_test_websocket_client( - /* receive function */ [tce, tceWaitForSend]() - { - tceWaitForSend.set(); - pplx::task(tce).get(); - return pplx::task_from_result(std::string("{ }\x1e")); - }); - auto hub_connection = create_hub_connection(websocket_client); - - auto startTask = hub_connection->start(); - pplx::task(tceWaitForSend).get(); - ASSERT_FALSE(startTask.is_done()); - tce.set(); - startTask.get(); - - ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -} - -TEST(start, start_fails_for_handshake_response_with_error) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{\"error\":\"bad things\"}\x1e")); }); - auto hub_connection = create_hub_connection(websocket_client); - - try - { - hub_connection->start().get(); - ASSERT_TRUE(false); - } - catch (std::exception ex) - { - ASSERT_STREQ("Received an error during handshake: bad things", ex.what()); - } - - ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -} - -TEST(start, start_fails_if_stop_called_before_handshake_response) -{ - pplx::task_completion_event tce; - pplx::task_completion_event tceWaitForSend; - auto websocket_client = create_test_websocket_client( - /* receive function */ [tce]() { return pplx::task(tce); }, - /* send function */ [tceWaitForSend](const std::string &) - { - tceWaitForSend.set(); - return pplx::task_from_result(); - }); - auto hub_connection = create_hub_connection(websocket_client); - - auto startTask = hub_connection->start(); - pplx::task(tceWaitForSend).get(); - hub_connection->stop(); - - try - { - startTask.get(); - ASSERT_TRUE(false); - } - catch (std::exception ex) - { - ASSERT_STREQ("connection closed while handshake was in progress.", ex.what()); - } - - ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -} - -TEST(stop, stop_stops_connection) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); - auto hub_connection = create_hub_connection(websocket_client); - - hub_connection->start().get(); - hub_connection->stop().get(); - - ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -} - -TEST(stop, disconnected_callback_called_when_hub_connection_stops) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); - auto hub_connection = create_hub_connection(websocket_client); - - auto disconnected_invoked = false; - hub_connection->set_disconnected([&disconnected_invoked]() { disconnected_invoked = true; }); - - hub_connection->start().get(); - hub_connection->stop().get(); - - ASSERT_TRUE(disconnected_invoked); -} - -TEST(stop, connection_stopped_when_going_out_of_scope) -{ - std::shared_ptr writer(std::make_shared()); - - { - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); - auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::state_changes); - - hub_connection->start().get(); - } - - auto memory_writer = std::dynamic_pointer_cast(writer); - - // The underlying connection_impl will be destroyed when the last reference to shared_ptr holding is released. This can happen - // on a different thread in which case the dtor will be invoked on a different thread so we need to wait for this - // to happen and if it does not the test will fail. There is nothing we can block on. - for (int wait_time_ms = 5; wait_time_ms < 100 && memory_writer->get_log_entries().size() < 4; wait_time_ms <<= 1) - { - std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); - } - - auto log_entries = memory_writer->get_log_entries(); - ASSERT_EQ(4U, log_entries.size()) << dump_vector(log_entries); - ASSERT_EQ("[state change] disconnected -> connecting\n", remove_date_from_log_entry(log_entries[0])); - ASSERT_EQ("[state change] connecting -> connected\n", remove_date_from_log_entry(log_entries[1])); - ASSERT_EQ("[state change] connected -> disconnecting\n", remove_date_from_log_entry(log_entries[2])); - ASSERT_EQ("[state change] disconnecting -> disconnected\n", remove_date_from_log_entry(log_entries[3])); -} - -TEST(stop, stop_cancels_pending_callbacks) -{ - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number]() - mutable { - std::string responses[] - { - "{ }\x1e", - "{}" - }; - - if (call_number < 1) - { - call_number++; - } - - return pplx::task_from_result(responses[call_number]); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - auto t = hub_connection->invoke("method", json::value::array()); - hub_connection->stop(); - - try - { - t.get(); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("\"connection was stopped before invocation result was received\"", e.what()); - } -} - -TEST(stop, pending_callbacks_finished_if_hub_connections_goes_out_of_scope) -{ - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number]() - mutable { - std::string responses[] - { - "{ }\x1e", - "{}" - }; - - if (call_number < 1) - { - call_number++; - } - - return pplx::task_from_result(responses[call_number]); - }); - - pplx::task t; - - { - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - t = hub_connection->invoke("method", json::value::array()); - } - - try - { - t.get(); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("\"connection went out of scope before invocation result was received\"", e.what()); - } -} - -TEST(hub_invocation, hub_connection_invokes_users_code_on_hub_invocations) -{ - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number]() - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 1, \"target\": \"BROADcast\", \"arguments\": [ \"message\", 1 ] }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - return pplx::task_from_result(responses[call_number]); - }); - - auto hub_connection = create_hub_connection(websocket_client); - - auto payload = std::make_shared(); - auto on_broadcast_event = std::make_shared(); - hub_connection->on("broadCAST", [on_broadcast_event, payload](const json::value& message) - { - *payload = utility::conversions::to_utf8string(message.serialize()); - on_broadcast_event->set(); - }); - - hub_connection->start().get(); - ASSERT_FALSE(on_broadcast_event->wait(5000)); - - ASSERT_EQ("[\"message\",1]", *payload); -} - -TEST(send, creates_correct_payload) -{ - std::string payload; - bool handshakeReceived = false; - - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, - /* send function */[&payload, &handshakeReceived](const std::string& m) - { - if (handshakeReceived) - { - payload = m; - return pplx::task_from_result(); - } - handshakeReceived = true; - return pplx::task_from_result(); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - - hub_connection->send("method", json::value::array()).get(); - - ASSERT_EQ("{\"arguments\":[],\"target\":\"method\",\"type\":1}\x1e", payload); -} - -TEST(send, does_not_wait_for_server_response) -{ - int call_number = -1; - pplx::task_completion_event waitForSend; - - auto websocket_client = create_test_websocket_client( - /* receive function */ [waitForSend, call_number]() mutable - { - std::string responses[] - { - "{ }\x1e", - "{}" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number == 1) - { - pplx::task(waitForSend).get(); - } - - return pplx::task_from_result(responses[call_number]); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - - // wont block waiting for server response - hub_connection->send("method", json::value::array()).get(); - waitForSend.set(); -} - -TEST(invoke, creates_correct_payload) -{ - std::string payload; - bool handshakeReceived = false; - - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, - /* send function */[&payload, &handshakeReceived](const std::string& m) - { - if (handshakeReceived) - { - payload = m; - return pplx::task_from_exception(std::runtime_error("error")); - } - handshakeReceived = true; - return pplx::task_from_result(); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - - try - { - hub_connection->invoke("method", json::value::array()).get(); - } - catch (...) - { - // the invoke is not setup to succeed because it's not needed in this test - } - - ASSERT_EQ("{\"arguments\":[],\"invocationId\":\"0\",\"target\":\"method\",\"type\":1}\x1e", payload); -} - -TEST(invoke, callback_not_called_if_send_throws) -{ - bool handshakeReceived = false; - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, - /* send function */[handshakeReceived](const std::string&) mutable - { - if (handshakeReceived) - { - return pplx::task_from_exception(std::runtime_error("error")); - } - handshakeReceived = true; - return pplx::task_from_result(); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start().get(); - - try - { - hub_connection->invoke("method", json::value::array()).get(); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const std::runtime_error& e) - { - ASSERT_STREQ("error", e.what()); - } - - // stop completes all outstanding callbacks so if we did not remove a callback when `invoke_void` failed an - // unobserved exception exception would be thrown. Note that this would happen on a different thread and would - // crash the process - hub_connection->stop().get(); -} - -TEST(invoke, invoke_returns_value_returned_from_the_server) -{ - auto callback_registered_event = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, callback_registered_event]() - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\", \"result\": \"abc\" }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number > 0) - { - callback_registered_event->wait(); - } - - return pplx::task_from_result(responses[call_number]); - }); - - auto hub_connection = create_hub_connection(websocket_client); - auto result = hub_connection->start() - .then([hub_connection, callback_registered_event]() - { - auto t = hub_connection->invoke("method", json::value::array()); - callback_registered_event->set(); - return t; - }).get(); - - ASSERT_EQ(_XPLATSTR("\"abc\""), result.serialize()); -} - -TEST(invoke, invoke_propagates_errors_from_server_as_hub_exceptions) -{ - auto callback_registered_event = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, callback_registered_event]() - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number > 0) - { - callback_registered_event->wait(); - } - - return pplx::task_from_result(responses[call_number]); - }); - - auto hub_connection = create_hub_connection(websocket_client); - try - { - hub_connection->start() - .then([hub_connection, callback_registered_event]() - { - auto t = hub_connection->invoke("method", json::value::array()); - callback_registered_event->set(); - return t; - }).get(); - - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const hub_exception& e) - { - ASSERT_STREQ("\"Ooops\"", e.what()); - } -} - -TEST(invoke, unblocks_task_when_server_completes_call) -{ - auto callback_registered_event = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, callback_registered_event]() - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\" }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number > 0) - { - callback_registered_event->wait(); - } - - return pplx::task_from_result(responses[call_number]); - }); - - auto hub_connection = create_hub_connection(websocket_client); - hub_connection->start() - .then([hub_connection, callback_registered_event]() - { - auto t = hub_connection->invoke("method", json::value::array()); - callback_registered_event->set(); - return t; - }).get(); - - // should not block - ASSERT_TRUE(true); -} - -TEST(receive, logs_if_callback_for_given_id_not_found) -{ - auto message_received_event = std::make_shared(); - auto handshake_sent = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, message_received_event, handshake_sent]() - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\" }\x1e", - "{}" - }; - - handshake_sent->wait(1000); - - call_number = std::min(call_number + 1, 2); - - if (call_number > 1) - { - message_received_event->set(); - } - - return pplx::task_from_result(responses[call_number]); - }, - [handshake_sent](const std::string&) - { - handshake_sent->set(); - return pplx::task_from_result(); - }); - - std::shared_ptr writer(std::make_shared()); - auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::info); - hub_connection->start().get(); - - ASSERT_FALSE(message_received_event->wait(5000)); - - auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); - ASSERT_TRUE(log_entries.size() > 1); - - auto entry = remove_date_from_log_entry(log_entries[2]); - ASSERT_EQ("[info ] no callback found for id: 0\n", entry) << dump_vector(log_entries); -} - -TEST(invoke_void, invoke_creates_runtime_error) -{ - auto callback_registered_event = std::make_shared(); - - int call_number = -1; - auto websocket_client = create_test_websocket_client( - /* receive function */ [call_number, callback_registered_event]() - mutable { - std::string responses[] - { - "{ }\x1e", - "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" - }; - - call_number = std::min(call_number + 1, 1); - - if (call_number > 0) - { - callback_registered_event->wait(); - } - - return pplx::task_from_result(responses[call_number]); - }); - - auto hub_connection = create_hub_connection(websocket_client); - try - { - hub_connection->start() - .then([hub_connection, callback_registered_event]() - { - auto t = hub_connection->invoke("method", json::value::array()); - callback_registered_event->set(); - return t; - }).get(); - - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const hub_exception & e) - { - ASSERT_STREQ("\"Ooops\"", e.what()); - ASSERT_FALSE(callback_registered_event->wait(0)); - } -} - -TEST(connection_id, can_get_connection_id) -{ - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); - auto hub_connection = create_hub_connection(websocket_client); - - ASSERT_EQ("", hub_connection->get_connection_id()); - - hub_connection->start().get(); - auto connection_id = hub_connection->get_connection_id(); - hub_connection->stop().get(); - - ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); - ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", hub_connection->get_connection_id()); -} - -TEST(on, event_name_must_not_be_empty_string) -{ - auto hub_connection = create_hub_connection(); - try - { - hub_connection->on("", [](const json::value&) {}); - - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const std::invalid_argument& e) - { - ASSERT_STREQ("event_name cannot be empty", e.what()); - } -} - -TEST(on, cannot_register_multiple_handlers_for_event) -{ - auto hub_connection = create_hub_connection(); - hub_connection->on("ping", [](const json::value&) {}); - - try - { - hub_connection->on("ping", [](const json::value&) {}); - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("an action for this event has already been registered. event name: ping", e.what()); - } -} - -TEST(on, cannot_register_handler_if_connection_not_in_disconnected_state) -{ - try - { - auto websocket_client = create_test_websocket_client( - /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); - auto hub_connection = create_hub_connection(websocket_client); - - hub_connection->start().get(); - - hub_connection->on("myfunc", [](const web::json::value&) {}); - - ASSERT_TRUE(false); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("can't register a handler if the connection is in a disconnected state", e.what()); - } -} - -TEST(invoke, invoke_throws_when_the_underlying_connection_is_not_valid) -{ - auto hub_connection = create_hub_connection(); - - try - { - hub_connection->invoke("method", json::value::array()).get(); - ASSERT_TRUE(true); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); - } -} - -TEST(invoke, send_throws_when_the_underlying_connection_is_not_valid) -{ - auto hub_connection = create_hub_connection(); - - try - { - hub_connection->invoke("method", json::value::array()).get(); - ASSERT_TRUE(true); // exception expected but not thrown - } - catch (const signalr_exception& e) - { - ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); - } -} +//#include "test_utils.h" +//#include "test_transport_factory.h" +//#include "test_web_request_factory.h" +//#include "hub_connection_impl.h" +//#include "trace_log_writer.h" +//#include "memory_log_writer.h" +//#include "signalrclient/hub_exception.h" +//#include "signalrclient/signalr_exception.h" +// +//using namespace signalr; +// +//std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), +// std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) +//{ +// return hub_connection_impl::create(create_uri(), trace_level, log_writer, +// create_test_web_request_factory(), std::make_unique(websocket_client)); +//} +// +//TEST(url, negotiate_appended_to_url) +//{ +// std::string base_urls[] = { "http://fakeuri", "http://fakeuri/" }; +// +// for (const auto& base_url : base_urls) +// { +// std::string requested_url; +// auto web_request_factory = std::make_unique([&requested_url](const std::string& url) +// { +// requested_url = url; +// return std::unique_ptr(new web_request_stub((unsigned short)404, "Bad request", "")); +// }); +// +// auto hub_connection = hub_connection_impl::create(base_url, trace_level::none, +// std::make_shared(), std::move(web_request_factory), +// std::make_unique(create_test_websocket_client())); +// +// try +// { +// hub_connection->start().get(); +// } +// catch (const std::exception&) {} +// +// ASSERT_EQ("http://fakeuri/negotiate", requested_url); +// } +//} +// +//TEST(start, start_starts_connection) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// hub_connection->start().get(); +// +// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +//} +// +//TEST(start, start_sends_handshake) +//{ +// auto message = std::make_shared(); +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, +// /* send function */ [message](const std::string& msg) { *message = msg; return pplx::task_from_result(); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// hub_connection->start().get(); +// +// ASSERT_EQ("{\"protocol\":\"json\",\"version\":1}\x1e", *message); +// +// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +//} +// +//TEST(start, start_waits_for_handshake_response) +//{ +// pplx::task_completion_event tce; +// pplx::task_completion_event tceWaitForSend; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [tce, tceWaitForSend]() +// { +// tceWaitForSend.set(); +// pplx::task(tce).get(); +// return pplx::task_from_result(std::string("{ }\x1e")); +// }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// auto startTask = hub_connection->start(); +// pplx::task(tceWaitForSend).get(); +// ASSERT_FALSE(startTask.is_done()); +// tce.set(); +// startTask.get(); +// +// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +//} +// +//TEST(start, start_fails_for_handshake_response_with_error) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{\"error\":\"bad things\"}\x1e")); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// try +// { +// hub_connection->start().get(); +// ASSERT_TRUE(false); +// } +// catch (std::exception ex) +// { +// ASSERT_STREQ("Received an error during handshake: bad things", ex.what()); +// } +// +// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +//} +// +//TEST(start, start_fails_if_stop_called_before_handshake_response) +//{ +// pplx::task_completion_event tce; +// pplx::task_completion_event tceWaitForSend; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [tce]() { return pplx::task(tce); }, +// /* send function */ [tceWaitForSend](const std::string &) +// { +// tceWaitForSend.set(); +// return pplx::task_from_result(); +// }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// auto startTask = hub_connection->start(); +// pplx::task(tceWaitForSend).get(); +// hub_connection->stop(); +// +// try +// { +// startTask.get(); +// ASSERT_TRUE(false); +// } +// catch (std::exception ex) +// { +// ASSERT_STREQ("connection closed while handshake was in progress.", ex.what()); +// } +// +// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +//} +// +//TEST(stop, stop_stops_connection) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// hub_connection->start().get(); +// hub_connection->stop().get(); +// +// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +//} +// +//TEST(stop, disconnected_callback_called_when_hub_connection_stops) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// auto disconnected_invoked = false; +// hub_connection->set_disconnected([&disconnected_invoked]() { disconnected_invoked = true; }); +// +// hub_connection->start().get(); +// hub_connection->stop().get(); +// +// ASSERT_TRUE(disconnected_invoked); +//} +// +//TEST(stop, connection_stopped_when_going_out_of_scope) +//{ +// std::shared_ptr writer(std::make_shared()); +// +// { +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); +// auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::state_changes); +// +// hub_connection->start().get(); +// } +// +// auto memory_writer = std::dynamic_pointer_cast(writer); +// +// // The underlying connection_impl will be destroyed when the last reference to shared_ptr holding is released. This can happen +// // on a different thread in which case the dtor will be invoked on a different thread so we need to wait for this +// // to happen and if it does not the test will fail. There is nothing we can block on. +// for (int wait_time_ms = 5; wait_time_ms < 100 && memory_writer->get_log_entries().size() < 4; wait_time_ms <<= 1) +// { +// std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); +// } +// +// auto log_entries = memory_writer->get_log_entries(); +// ASSERT_EQ(4U, log_entries.size()) << dump_vector(log_entries); +// ASSERT_EQ("[state change] disconnected -> connecting\n", remove_date_from_log_entry(log_entries[0])); +// ASSERT_EQ("[state change] connecting -> connected\n", remove_date_from_log_entry(log_entries[1])); +// ASSERT_EQ("[state change] connected -> disconnecting\n", remove_date_from_log_entry(log_entries[2])); +// ASSERT_EQ("[state change] disconnecting -> disconnected\n", remove_date_from_log_entry(log_entries[3])); +//} +// +//TEST(stop, stop_cancels_pending_callbacks) +//{ +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number]() +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{}" +// }; +// +// if (call_number < 1) +// { +// call_number++; +// } +// +// return pplx::task_from_result(responses[call_number]); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// auto t = hub_connection->invoke("method", json::value::array()); +// hub_connection->stop(); +// +// try +// { +// t.get(); +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("\"connection was stopped before invocation result was received\"", e.what()); +// } +//} +// +//TEST(stop, pending_callbacks_finished_if_hub_connections_goes_out_of_scope) +//{ +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number]() +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{}" +// }; +// +// if (call_number < 1) +// { +// call_number++; +// } +// +// return pplx::task_from_result(responses[call_number]); +// }); +// +// pplx::task t; +// +// { +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// t = hub_connection->invoke("method", json::value::array()); +// } +// +// try +// { +// t.get(); +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("\"connection went out of scope before invocation result was received\"", e.what()); +// } +//} +// +//TEST(hub_invocation, hub_connection_invokes_users_code_on_hub_invocations) +//{ +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number]() +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 1, \"target\": \"BROADcast\", \"arguments\": [ \"message\", 1 ] }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// return pplx::task_from_result(responses[call_number]); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// +// auto payload = std::make_shared(); +// auto on_broadcast_event = std::make_shared(); +// hub_connection->on("broadCAST", [on_broadcast_event, payload](const json::value& message) +// { +// *payload = utility::conversions::to_utf8string(message.serialize()); +// on_broadcast_event->set(); +// }); +// +// hub_connection->start().get(); +// ASSERT_FALSE(on_broadcast_event->wait(5000)); +// +// ASSERT_EQ("[\"message\",1]", *payload); +//} +// +//TEST(send, creates_correct_payload) +//{ +// std::string payload; +// bool handshakeReceived = false; +// +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, +// /* send function */[&payload, &handshakeReceived](const std::string& m) +// { +// if (handshakeReceived) +// { +// payload = m; +// return pplx::task_from_result(); +// } +// handshakeReceived = true; +// return pplx::task_from_result(); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// +// hub_connection->send("method", json::value::array()).get(); +// +// ASSERT_EQ("{\"arguments\":[],\"target\":\"method\",\"type\":1}\x1e", payload); +//} +// +//TEST(send, does_not_wait_for_server_response) +//{ +// int call_number = -1; +// pplx::task_completion_event waitForSend; +// +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [waitForSend, call_number]() mutable +// { +// std::string responses[] +// { +// "{ }\x1e", +// "{}" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number == 1) +// { +// pplx::task(waitForSend).get(); +// } +// +// return pplx::task_from_result(responses[call_number]); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// +// // wont block waiting for server response +// hub_connection->send("method", json::value::array()).get(); +// waitForSend.set(); +//} +// +//TEST(invoke, creates_correct_payload) +//{ +// std::string payload; +// bool handshakeReceived = false; +// +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, +// /* send function */[&payload, &handshakeReceived](const std::string& m) +// { +// if (handshakeReceived) +// { +// payload = m; +// return pplx::task_from_exception(std::runtime_error("error")); +// } +// handshakeReceived = true; +// return pplx::task_from_result(); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// +// try +// { +// hub_connection->invoke("method", json::value::array()).get(); +// } +// catch (...) +// { +// // the invoke is not setup to succeed because it's not needed in this test +// } +// +// ASSERT_EQ("{\"arguments\":[],\"invocationId\":\"0\",\"target\":\"method\",\"type\":1}\x1e", payload); +//} +// +//TEST(invoke, callback_not_called_if_send_throws) +//{ +// bool handshakeReceived = false; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, +// /* send function */[handshakeReceived](const std::string&) mutable +// { +// if (handshakeReceived) +// { +// return pplx::task_from_exception(std::runtime_error("error")); +// } +// handshakeReceived = true; +// return pplx::task_from_result(); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start().get(); +// +// try +// { +// hub_connection->invoke("method", json::value::array()).get(); +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const std::runtime_error& e) +// { +// ASSERT_STREQ("error", e.what()); +// } +// +// // stop completes all outstanding callbacks so if we did not remove a callback when `invoke_void` failed an +// // unobserved exception exception would be thrown. Note that this would happen on a different thread and would +// // crash the process +// hub_connection->stop().get(); +//} +// +//TEST(invoke, invoke_returns_value_returned_from_the_server) +//{ +// auto callback_registered_event = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, callback_registered_event]() +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\", \"result\": \"abc\" }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number > 0) +// { +// callback_registered_event->wait(); +// } +// +// return pplx::task_from_result(responses[call_number]); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// auto result = hub_connection->start() +// .then([hub_connection, callback_registered_event]() +// { +// auto t = hub_connection->invoke("method", json::value::array()); +// callback_registered_event->set(); +// return t; +// }).get(); +// +// ASSERT_EQ(_XPLATSTR("\"abc\""), result.serialize()); +//} +// +//TEST(invoke, invoke_propagates_errors_from_server_as_hub_exceptions) +//{ +// auto callback_registered_event = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, callback_registered_event]() +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number > 0) +// { +// callback_registered_event->wait(); +// } +// +// return pplx::task_from_result(responses[call_number]); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// try +// { +// hub_connection->start() +// .then([hub_connection, callback_registered_event]() +// { +// auto t = hub_connection->invoke("method", json::value::array()); +// callback_registered_event->set(); +// return t; +// }).get(); +// +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const hub_exception& e) +// { +// ASSERT_STREQ("\"Ooops\"", e.what()); +// } +//} +// +//TEST(invoke, unblocks_task_when_server_completes_call) +//{ +// auto callback_registered_event = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, callback_registered_event]() +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\" }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number > 0) +// { +// callback_registered_event->wait(); +// } +// +// return pplx::task_from_result(responses[call_number]); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// hub_connection->start() +// .then([hub_connection, callback_registered_event]() +// { +// auto t = hub_connection->invoke("method", json::value::array()); +// callback_registered_event->set(); +// return t; +// }).get(); +// +// // should not block +// ASSERT_TRUE(true); +//} +// +//TEST(receive, logs_if_callback_for_given_id_not_found) +//{ +// auto message_received_event = std::make_shared(); +// auto handshake_sent = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, message_received_event, handshake_sent]() +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\" }\x1e", +// "{}" +// }; +// +// handshake_sent->wait(1000); +// +// call_number = std::min(call_number + 1, 2); +// +// if (call_number > 1) +// { +// message_received_event->set(); +// } +// +// return pplx::task_from_result(responses[call_number]); +// }, +// [handshake_sent](const std::string&) +// { +// handshake_sent->set(); +// return pplx::task_from_result(); +// }); +// +// std::shared_ptr writer(std::make_shared()); +// auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::info); +// hub_connection->start().get(); +// +// ASSERT_FALSE(message_received_event->wait(5000)); +// +// auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); +// ASSERT_TRUE(log_entries.size() > 1); +// +// auto entry = remove_date_from_log_entry(log_entries[2]); +// ASSERT_EQ("[info ] no callback found for id: 0\n", entry) << dump_vector(log_entries); +//} +// +//TEST(invoke_void, invoke_creates_runtime_error) +//{ +// auto callback_registered_event = std::make_shared(); +// +// int call_number = -1; +// auto websocket_client = create_test_websocket_client( +// /* receive function */ [call_number, callback_registered_event]() +// mutable { +// std::string responses[] +// { +// "{ }\x1e", +// "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" +// }; +// +// call_number = std::min(call_number + 1, 1); +// +// if (call_number > 0) +// { +// callback_registered_event->wait(); +// } +// +// return pplx::task_from_result(responses[call_number]); +// }); +// +// auto hub_connection = create_hub_connection(websocket_client); +// try +// { +// hub_connection->start() +// .then([hub_connection, callback_registered_event]() +// { +// auto t = hub_connection->invoke("method", json::value::array()); +// callback_registered_event->set(); +// return t; +// }).get(); +// +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const hub_exception & e) +// { +// ASSERT_STREQ("\"Ooops\"", e.what()); +// ASSERT_FALSE(callback_registered_event->wait(0)); +// } +//} +// +//TEST(connection_id, can_get_connection_id) +//{ +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// ASSERT_EQ("", hub_connection->get_connection_id()); +// +// hub_connection->start().get(); +// auto connection_id = hub_connection->get_connection_id(); +// hub_connection->stop().get(); +// +// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); +// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", hub_connection->get_connection_id()); +//} +// +//TEST(on, event_name_must_not_be_empty_string) +//{ +// auto hub_connection = create_hub_connection(); +// try +// { +// hub_connection->on("", [](const json::value&) {}); +// +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const std::invalid_argument& e) +// { +// ASSERT_STREQ("event_name cannot be empty", e.what()); +// } +//} +// +//TEST(on, cannot_register_multiple_handlers_for_event) +//{ +// auto hub_connection = create_hub_connection(); +// hub_connection->on("ping", [](const json::value&) {}); +// +// try +// { +// hub_connection->on("ping", [](const json::value&) {}); +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("an action for this event has already been registered. event name: ping", e.what()); +// } +//} +// +//TEST(on, cannot_register_handler_if_connection_not_in_disconnected_state) +//{ +// try +// { +// auto websocket_client = create_test_websocket_client( +// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); +// auto hub_connection = create_hub_connection(websocket_client); +// +// hub_connection->start().get(); +// +// hub_connection->on("myfunc", [](const web::json::value&) {}); +// +// ASSERT_TRUE(false); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("can't register a handler if the connection is in a disconnected state", e.what()); +// } +//} +// +//TEST(invoke, invoke_throws_when_the_underlying_connection_is_not_valid) +//{ +// auto hub_connection = create_hub_connection(); +// +// try +// { +// hub_connection->invoke("method", json::value::array()).get(); +// ASSERT_TRUE(true); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); +// } +//} +// +//TEST(invoke, send_throws_when_the_underlying_connection_is_not_valid) +//{ +// auto hub_connection = create_hub_connection(); +// +// try +// { +// hub_connection->invoke("method", json::value::array()).get(); +// ASSERT_TRUE(true); // exception expected but not thrown +// } +// catch (const signalr_exception& e) +// { +// ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); +// } +//} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp new file mode 100644 index 000000000000..d24b7724a62c --- /dev/null +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "test_websocket_client.h" +#include "pplx/pplxtasks.h" + +test_websocket_client::test_websocket_client() + : m_connect_function([](const std::string&, std::function callback) { callback(nullptr); }), + m_send_function([](const std::string msg, std::function callback) { callback(nullptr); }), + m_receive_function([](std::function callback) { callback("", nullptr); }), + m_close_function([](std::function callback) { callback(nullptr); }) +{ } + +void test_websocket_client::start(std::string url, transfer_format, std::function callback) +{ + pplx::create_task([url, callback, this]() + { + m_connect_function(url, callback); + }); +} + +void test_websocket_client::stop(std::function callback) +{ + pplx::create_task([callback, this]() + { + m_close_function(callback); + }); +} + +void test_websocket_client::send(std::string payload, std::function callback) +{ + pplx::create_task([payload, callback, this]() + { + m_send_function(payload, callback); + }); +} + +void test_websocket_client::receive(std::function callback) +{ + pplx::create_task([callback, this]() + { + m_receive_function(callback); + }); +} + +void test_websocket_client::set_connect_function(std::function)> connect_function) +{ + m_connect_function = connect_function; +} + +void test_websocket_client::set_send_function(std::function)> send_function) +{ + m_send_function = send_function; +} + +void test_websocket_client::set_receive_function(std::function)> receive_function) +{ + m_receive_function = receive_function; +} + +void test_websocket_client::set_close_function(std::function)> close_function) +{ + m_close_function = close_function; +} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp new file mode 100644 index 000000000000..831fb72aa0b0 --- /dev/null +++ b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp @@ -0,0 +1,645 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "test_utils.h" +#include "trace_log_writer.h" +#include "test_websocket_client.h" +#include "websocket_transport.h" +#include "memory_log_writer.h" + +using namespace signalr; + +TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop) +{ + auto connect_called = false; + auto receive_called = std::make_shared(); + auto client = std::make_shared(); + + client->set_connect_function([&connect_called](const std::string&, std::function callback) + { + connect_called = true; + callback(nullptr); + }); + + client->set_receive_function([receive_called](std::function callback) + { + receive_called->set(); + callback("", nullptr); + }); + + std::shared_ptr writer(std::make_shared()); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::info)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool start_finished; + ws_transport->start("ws://fakeuri.org/connect?param=42", transfer_format::text, [&cv, &start_finished](std::exception_ptr exception) + { + start_finished = true; + cv.notify_one(); + }); + + ASSERT_FALSE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_finished]() { return start_finished; }) == false); + + ASSERT_TRUE(connect_called); + ASSERT_FALSE(receive_called->wait(5000)); + + auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); + ASSERT_FALSE(log_entries.empty()); + + auto entry = remove_date_from_log_entry(log_entries[0]); + ASSERT_EQ("[info ] [websocket transport] connecting to: ws://fakeuri.org/connect?param=42\n", entry); +} + +TEST(websocket_transport_connect, connect_propagates_exceptions) +{ + auto client = std::make_shared(); + client->set_connect_function([](const std::string&, std::function callback) + { + callback(std::make_exception_ptr(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")))); + }); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + try + { + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + std::exception_ptr start_exception; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &start_exception](std::exception_ptr exception) + { + start_exception = exception; + cv.notify_one(); + }); + + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_exception]() { return start_exception != nullptr; })); + std::rethrow_exception(start_exception); + } + catch (const std::exception &e) + { + ASSERT_EQ(_XPLATSTR("connecting failed"), utility::conversions::to_string_t(e.what())); + } +} + +TEST(websocket_transport_connect, connect_logs_exceptions) +{ + auto client = std::make_shared(); + client->set_connect_function([](const std::string&, std::function callback) + { + callback(std::make_exception_ptr(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")))); + }); + + std::shared_ptr writer(std::make_shared()); + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::errors)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + std::exception_ptr start_exception; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &start_exception](std::exception_ptr exception) + { + start_exception = exception; + cv.notify_one(); + }); + + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_exception]() { return start_exception != nullptr; })); + + auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); + + ASSERT_FALSE(log_entries.empty()); + + auto entry = remove_date_from_log_entry(log_entries[0]); + + ASSERT_EQ( + "[error ] [websocket transport] exception when connecting to the server: connecting failed\n", + entry); +} + +TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_transport) +{ + auto client = std::make_shared(); + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool start_completed; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &start_completed](std::exception_ptr) + { + start_completed = true; + cv.notify_one(); + }); + + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_completed]() { return start_completed; })); + + try + { + std::exception_ptr start_exception; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &start_exception](std::exception_ptr exception) + { + start_exception = exception; + cv.notify_one(); + }); + + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_exception]() { return start_exception != nullptr; })); + std::rethrow_exception(start_exception); + } + catch (const std::exception &e) + { + ASSERT_EQ(_XPLATSTR("transport already connected"), utility::conversions::to_string_t(e.what())); + } +} + +TEST(websocket_transport_connect, can_connect_after_disconnecting) +{ + auto client = std::make_shared(); + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + // shouldn't throw or crash +} + +TEST(websocket_transport_send, send_creates_and_sends_websocket_messages) +{ + bool send_called = false; + + auto client = std::make_shared(); + + client->set_send_function([&send_called](const std::string&, std::function callback) + { + send_called = true; + callback(nullptr); + }); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + ws_transport->send("ABC", [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + ASSERT_TRUE(send_called); +} + +TEST(websocket_transport_disconnect, disconnect_closes_websocket) +{ + bool close_called = false; + + auto client = std::make_shared(); + + client->set_close_function([&close_called](std::function callback) + { + close_called = true; + callback(nullptr); + }); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + ASSERT_TRUE(close_called); +} + +TEST(websocket_transport_disconnect, disconnect_does_not_throw) +{ + auto client = std::make_shared(); + + bool close_called = false; + client->set_close_function([&close_called](std::function callback) + { + callback(std::make_exception_ptr(std::exception())); + close_called = true; + }); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + ASSERT_TRUE(close_called); +} + +TEST(websocket_transport_disconnect, disconnect_logs_exceptions) +{ + auto client = std::make_shared(); + client->set_close_function([](std::function callback) + { + callback(std::make_exception_ptr(web::websockets::client::websocket_exception(_XPLATSTR("connection closing failed")))); + }); + + std::shared_ptr writer(std::make_shared()); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::errors)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); + + ASSERT_FALSE(log_entries.empty()); + + // disconnect cancels the receive loop by setting the cancellation token source to cancelled which results in writing + // to the log. Exceptions from close are also logged but this happens on a different thread. As a result the order + // of messages in the log is not deterministic and therefore we just use the "contains" idiom to find the message. + ASSERT_NE(std::find_if(log_entries.begin(), log_entries.end(), [](const std::string& entry) + { + return remove_date_from_log_entry(entry) == + "[error ] [websocket transport] exception when closing websocket: connection closing failed\n"; + }), + log_entries.end()); +} + +TEST(websocket_transport_disconnect, DISABLED_receive_not_called_after_disconnect) +{ + auto client = std::make_shared(); + + pplx::task_completion_event receive_task_tce; + pplx::task_completion_event receive_task_started_tce; + + // receive_task_tce is captured by reference since we assign it a new value after the first disconnect. This is + // safe here because we are blocking on disconnect and therefore we won't get into a state where we would be using + // an invalid reference because the tce went out of scope and was destroyed. + client->set_close_function([&receive_task_tce](std::function callback) + { + // unblock receive + receive_task_tce.set(std::string("")); + callback(nullptr); + }); + + int num_called = 0; + client->set_receive_function([&receive_task_tce, &receive_task_started_tce, &num_called](std::function callback) + { + num_called++; + receive_task_started_tce.set(); + pplx::create_task(receive_task_tce) + .then([callback](pplx::task prev) + { + prev.get(); + callback("", nullptr); + }); + }); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + pplx::create_task(receive_task_started_tce).get(); + + ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + receive_task_tce = pplx::task_completion_event(); + receive_task_started_tce = pplx::task_completion_event(); + + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + pplx::create_task(receive_task_started_tce).get(); + + ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + ASSERT_EQ(2, num_called); +} + +TEST(websocket_transport_disconnect, disconnect_is_no_op_if_transport_not_started) +{ + auto client = std::make_shared(); + + auto close_called = false; + + client->set_close_function([&close_called](std::function callback) + { + close_called = true; + callback(nullptr); + }); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + ASSERT_FALSE(close_called); +} + +TEST(websocket_transport_disconnect, exceptions_from_outstanding_receive_task_observed_after_websocket_transport_disconnected) +{ + auto client = std::make_shared(); + + auto receive_event = std::make_shared(); + client->set_receive_function([receive_event](std::function callback) + { + pplx::create_task([receive_event, callback]() + { + receive_event->wait(); + callback("", std::make_exception_ptr(std::runtime_error("exception from receive"))); + }); + }); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + callback_completed = false; + + // at this point the cancellation token that closes the receive loop is set to cancelled so + // we can unblock the the receive task which throws an exception that should be observed otwherwise the test will crash + receive_event->set(); +} + +template +void receive_loop_logs_exception_runner(const T& e, const std::string& expected_message, trace_level trace_level); + +TEST(websocket_transport_receive_loop, receive_loop_logs_websocket_exceptions) +{ + receive_loop_logs_exception_runner( + web::websockets::client::websocket_exception(_XPLATSTR("receive failed")), + "[error ] [websocket transport] error receiving response from websocket: receive failed\n", + trace_level::errors); +} + +TEST(websocket_transport_receive_loop, receive_loop_logs_if_receive_task_canceled) +{ + receive_loop_logs_exception_runner( + pplx::task_canceled("canceled"), + "[info ] [websocket transport] receive task canceled.\n", + trace_level::info); +} + +TEST(websocket_transport_receive_loop, receive_loop_logs_std_exception) +{ + receive_loop_logs_exception_runner( + std::runtime_error("exception"), + "[error ] [websocket transport] error receiving response from websocket: exception\n", + trace_level::errors); +} + +template +void receive_loop_logs_exception_runner(const T& e, const std::string& expected_message, trace_level trace_level) +{ + event receive_event; + auto client = std::make_shared(); + + client->set_receive_function([&receive_event, &e](std::function callback) + { + receive_event.set(); + callback("", std::make_exception_ptr(e)); + }); + + std::shared_ptr writer(std::make_shared()); + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + receive_event.wait(); + + // this is race'y but there is nothing we can block on + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); + + ASSERT_NE(std::find_if(log_entries.begin(), log_entries.end(), + [&expected_message](std::string entry) { return remove_date_from_log_entry(entry) == expected_message; }), + log_entries.end()); +} + +TEST(websocket_transport_receive_loop, process_response_callback_called_when_message_received) +{ + auto client = std::make_shared(); + client->set_receive_function([](std::function callback) + { + callback("msg", nullptr); + }); + + auto process_response_event = std::make_shared(); + auto msg = std::make_shared(); + + auto process_response = [msg, process_response_event](const std::string& message) + { + *msg = message; + process_response_event->set(); + }; + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + process_response_event->wait(1000); + + ASSERT_EQ(std::string("msg"), *msg); +} + +TEST(websocket_transport_receive_loop, error_callback_called_when_exception_thrown) +{ + auto client = std::make_shared(); + client->set_receive_function([](std::function callback) + { + callback("", std::make_exception_ptr(std::runtime_error("error"))); + }); + + auto close_invoked = std::make_shared(false); + client->set_close_function([close_invoked](std::function callback) + { + *close_invoked = true; + callback(nullptr); + }); + + auto error_event = std::make_shared(); + auto exception_msg = std::make_shared(); + + auto error_callback = [exception_msg, error_event](const std::exception& e) + { + *exception_msg = e.what(); + error_event->set(); + }; + + auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + + std::mutex mtx; + std::unique_lock lock(mtx); + std::condition_variable cv; + bool callback_completed; + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + { + callback_completed = true; + cv.notify_one(); + }); + ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + + error_event->wait(1000); + + ASSERT_STREQ("error", exception_msg->c_str()); + ASSERT_TRUE(*close_invoked); +} + +TEST(websocket_transport_get_transport_type, get_transport_type_returns_websockets) +{ + auto ws_transport = websocket_transport::create( + [](){ return std::make_shared(); }, + logger(std::make_shared(), trace_level::none)); + + ASSERT_EQ(transport_type::websockets, ws_transport->get_transport_type()); +} From 933c4c1be08f6f2977ab154e1a0e3810ba29fff5 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Fri, 15 Mar 2019 13:49:55 -0700 Subject: [PATCH 09/19] all tests --- .../cpp/src/signalrclient/negotiate.cpp | 6 + .../src/signalrclient/websocket_transport.cpp | 36 +- .../connection_impl_tests.cpp | 12 +- .../hub_connection_impl_tests.cpp | 1481 +++++++++-------- .../websocket_transport_tests.cpp | 402 ++--- 5 files changed, 996 insertions(+), 941 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp b/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp index 3aebb458267e..fde3034ea93e 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp @@ -29,6 +29,12 @@ namespace signalr return; } + if (http_response.status_code != 200) + { + tce.set_exception(signalr_exception("negotiate failed with status code " + std::to_string(http_response.status_code))); + return; + } + try { auto negotiation_response_json = web::json::value::parse(utility::conversions::to_string_t(http_response.content)); diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp index 28cc59508295..ea487e616e10 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp @@ -80,9 +80,25 @@ namespace signalr std::string("[websocket transport] error receiving response from websocket: ") .append(e.what())); - websocket_client->stop([](std::exception_ptr exception) + std::promise prom; + websocket_client->stop([&prom](std::exception_ptr exception) { + if (exception != nullptr) + { + prom.set_exception(exception); + } + else + { + prom.set_value(); + } }); + try + { + prom.get_future().get(); + } + // We prefer the outer exception bubbling up to the user + // REVIEW: log here? + catch (...) { } auto transport = weak_transport.lock(); if (transport) @@ -98,9 +114,25 @@ namespace signalr trace_level::errors, std::string("[websocket transport] unknown error occurred when receiving response from websocket")); - websocket_client->stop([](std::exception_ptr) + std::promise prom; + websocket_client->stop([&prom](std::exception_ptr exception) { + if (exception != nullptr) + { + prom.set_exception(exception); + } + else + { + prom.set_value(); + } }); + try + { + prom.get_future().get(); + } + // We prefer the outer exception bubbling up to the user + // REVIEW: log here? + catch (...) {} auto transport = weak_transport.lock(); if (transport) diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp index 636d719f7faa..c50a3fb30f85 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp @@ -209,7 +209,7 @@ TEST(connection_impl_start, start_logs_exceptions) ASSERT_FALSE(log_entries.empty()); auto entry = remove_date_from_log_entry(log_entries[0]); - ASSERT_EQ("[error ] connection could not be started due to: web exception - 404 Bad request\n", entry); + ASSERT_EQ("[error ] connection could not be started due to: negotiate failed with status code 404\n", entry); } @@ -231,7 +231,7 @@ TEST(connection_impl_start, start_propagates_exceptions_from_negotiate) } catch (const std::exception &e) { - ASSERT_EQ(_XPLATSTR("web exception - 404 Bad request"), utility::conversions::to_string_t(e.what())); + ASSERT_STREQ("negotiate failed with status code 404", e.what()); } } @@ -325,9 +325,9 @@ TEST(connection_impl_start, start_fails_if_negotiate_request_fails) connection->start().get(); ASSERT_TRUE(false); // exception not thrown } - catch (const web_exception &e) + catch (const std::exception &e) { - ASSERT_STREQ("web exception - 400 Bad Request", e.what()); + ASSERT_STREQ("negotiate failed with status code 400", e.what()); } } @@ -544,12 +544,12 @@ TEST(connection_impl_start, negotiate_follows_redirect) ASSERT_EQ("ws://redirected/?id=f7707523-307d-4cba-9abf-3eef701241e8", connectUrl); } -TEST(connection_impl_start, negotiate_redirect_uses_accessToken) +TEST(connection_impl_start, DISABLED_negotiate_redirect_uses_accessToken) { std::shared_ptr writer(std::make_shared()); std::string accessToken; - auto http_client = std::make_unique([&accessToken](const std::string& url, http_request) + auto http_client = std::make_unique([&accessToken](const std::string& url, http_request request) { std::string response_body = ""; if (url.find("/negotiate") != std::string::npos) diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp index 58a0199c96a3..8739dc38f14c 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp @@ -1,738 +1,745 @@ -//// Copyright (c) .NET Foundation. All rights reserved. -//// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + #include "stdafx.h" -//#include "test_utils.h" -//#include "test_transport_factory.h" -//#include "test_web_request_factory.h" -//#include "hub_connection_impl.h" -//#include "trace_log_writer.h" -//#include "memory_log_writer.h" -//#include "signalrclient/hub_exception.h" -//#include "signalrclient/signalr_exception.h" -// -//using namespace signalr; -// -//std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), -// std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) -//{ -// return hub_connection_impl::create(create_uri(), trace_level, log_writer, -// create_test_web_request_factory(), std::make_unique(websocket_client)); -//} -// -//TEST(url, negotiate_appended_to_url) -//{ -// std::string base_urls[] = { "http://fakeuri", "http://fakeuri/" }; -// -// for (const auto& base_url : base_urls) -// { -// std::string requested_url; -// auto web_request_factory = std::make_unique([&requested_url](const std::string& url) -// { -// requested_url = url; -// return std::unique_ptr(new web_request_stub((unsigned short)404, "Bad request", "")); -// }); -// -// auto hub_connection = hub_connection_impl::create(base_url, trace_level::none, -// std::make_shared(), std::move(web_request_factory), -// std::make_unique(create_test_websocket_client())); -// -// try -// { -// hub_connection->start().get(); -// } -// catch (const std::exception&) {} -// -// ASSERT_EQ("http://fakeuri/negotiate", requested_url); -// } -//} -// -//TEST(start, start_starts_connection) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// hub_connection->start().get(); -// -// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -//} -// -//TEST(start, start_sends_handshake) -//{ -// auto message = std::make_shared(); -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, -// /* send function */ [message](const std::string& msg) { *message = msg; return pplx::task_from_result(); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// hub_connection->start().get(); -// -// ASSERT_EQ("{\"protocol\":\"json\",\"version\":1}\x1e", *message); -// -// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -//} -// -//TEST(start, start_waits_for_handshake_response) -//{ -// pplx::task_completion_event tce; -// pplx::task_completion_event tceWaitForSend; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [tce, tceWaitForSend]() -// { -// tceWaitForSend.set(); -// pplx::task(tce).get(); -// return pplx::task_from_result(std::string("{ }\x1e")); -// }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// auto startTask = hub_connection->start(); -// pplx::task(tceWaitForSend).get(); -// ASSERT_FALSE(startTask.is_done()); -// tce.set(); -// startTask.get(); -// -// ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); -//} -// -//TEST(start, start_fails_for_handshake_response_with_error) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{\"error\":\"bad things\"}\x1e")); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// try -// { -// hub_connection->start().get(); -// ASSERT_TRUE(false); -// } -// catch (std::exception ex) -// { -// ASSERT_STREQ("Received an error during handshake: bad things", ex.what()); -// } -// -// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -//} -// -//TEST(start, start_fails_if_stop_called_before_handshake_response) -//{ -// pplx::task_completion_event tce; -// pplx::task_completion_event tceWaitForSend; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [tce]() { return pplx::task(tce); }, -// /* send function */ [tceWaitForSend](const std::string &) -// { -// tceWaitForSend.set(); -// return pplx::task_from_result(); -// }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// auto startTask = hub_connection->start(); -// pplx::task(tceWaitForSend).get(); -// hub_connection->stop(); -// -// try -// { -// startTask.get(); -// ASSERT_TRUE(false); -// } -// catch (std::exception ex) -// { -// ASSERT_STREQ("connection closed while handshake was in progress.", ex.what()); -// } -// -// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -//} -// -//TEST(stop, stop_stops_connection) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// hub_connection->start().get(); -// hub_connection->stop().get(); -// -// ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); -//} -// -//TEST(stop, disconnected_callback_called_when_hub_connection_stops) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// auto disconnected_invoked = false; -// hub_connection->set_disconnected([&disconnected_invoked]() { disconnected_invoked = true; }); -// -// hub_connection->start().get(); -// hub_connection->stop().get(); -// -// ASSERT_TRUE(disconnected_invoked); -//} -// -//TEST(stop, connection_stopped_when_going_out_of_scope) -//{ -// std::shared_ptr writer(std::make_shared()); -// -// { -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); -// auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::state_changes); -// -// hub_connection->start().get(); -// } -// -// auto memory_writer = std::dynamic_pointer_cast(writer); -// -// // The underlying connection_impl will be destroyed when the last reference to shared_ptr holding is released. This can happen -// // on a different thread in which case the dtor will be invoked on a different thread so we need to wait for this -// // to happen and if it does not the test will fail. There is nothing we can block on. -// for (int wait_time_ms = 5; wait_time_ms < 100 && memory_writer->get_log_entries().size() < 4; wait_time_ms <<= 1) -// { -// std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); -// } -// -// auto log_entries = memory_writer->get_log_entries(); -// ASSERT_EQ(4U, log_entries.size()) << dump_vector(log_entries); -// ASSERT_EQ("[state change] disconnected -> connecting\n", remove_date_from_log_entry(log_entries[0])); -// ASSERT_EQ("[state change] connecting -> connected\n", remove_date_from_log_entry(log_entries[1])); -// ASSERT_EQ("[state change] connected -> disconnecting\n", remove_date_from_log_entry(log_entries[2])); -// ASSERT_EQ("[state change] disconnecting -> disconnected\n", remove_date_from_log_entry(log_entries[3])); -//} -// -//TEST(stop, stop_cancels_pending_callbacks) -//{ -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number]() -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{}" -// }; -// -// if (call_number < 1) -// { -// call_number++; -// } -// -// return pplx::task_from_result(responses[call_number]); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// auto t = hub_connection->invoke("method", json::value::array()); -// hub_connection->stop(); -// -// try -// { -// t.get(); -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("\"connection was stopped before invocation result was received\"", e.what()); -// } -//} -// -//TEST(stop, pending_callbacks_finished_if_hub_connections_goes_out_of_scope) -//{ -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number]() -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{}" -// }; -// -// if (call_number < 1) -// { -// call_number++; -// } -// -// return pplx::task_from_result(responses[call_number]); -// }); -// -// pplx::task t; -// -// { -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// t = hub_connection->invoke("method", json::value::array()); -// } -// -// try -// { -// t.get(); -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("\"connection went out of scope before invocation result was received\"", e.what()); -// } -//} -// -//TEST(hub_invocation, hub_connection_invokes_users_code_on_hub_invocations) -//{ -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number]() -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 1, \"target\": \"BROADcast\", \"arguments\": [ \"message\", 1 ] }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// return pplx::task_from_result(responses[call_number]); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// -// auto payload = std::make_shared(); -// auto on_broadcast_event = std::make_shared(); -// hub_connection->on("broadCAST", [on_broadcast_event, payload](const json::value& message) -// { -// *payload = utility::conversions::to_utf8string(message.serialize()); -// on_broadcast_event->set(); -// }); -// -// hub_connection->start().get(); -// ASSERT_FALSE(on_broadcast_event->wait(5000)); -// -// ASSERT_EQ("[\"message\",1]", *payload); -//} -// -//TEST(send, creates_correct_payload) -//{ -// std::string payload; -// bool handshakeReceived = false; -// -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, -// /* send function */[&payload, &handshakeReceived](const std::string& m) -// { -// if (handshakeReceived) -// { -// payload = m; -// return pplx::task_from_result(); -// } -// handshakeReceived = true; -// return pplx::task_from_result(); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// -// hub_connection->send("method", json::value::array()).get(); -// -// ASSERT_EQ("{\"arguments\":[],\"target\":\"method\",\"type\":1}\x1e", payload); -//} -// -//TEST(send, does_not_wait_for_server_response) -//{ -// int call_number = -1; -// pplx::task_completion_event waitForSend; -// -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [waitForSend, call_number]() mutable -// { -// std::string responses[] -// { -// "{ }\x1e", -// "{}" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number == 1) -// { -// pplx::task(waitForSend).get(); -// } -// -// return pplx::task_from_result(responses[call_number]); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// -// // wont block waiting for server response -// hub_connection->send("method", json::value::array()).get(); -// waitForSend.set(); -//} -// -//TEST(invoke, creates_correct_payload) -//{ -// std::string payload; -// bool handshakeReceived = false; -// -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, -// /* send function */[&payload, &handshakeReceived](const std::string& m) -// { -// if (handshakeReceived) -// { -// payload = m; -// return pplx::task_from_exception(std::runtime_error("error")); -// } -// handshakeReceived = true; -// return pplx::task_from_result(); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// -// try -// { -// hub_connection->invoke("method", json::value::array()).get(); -// } -// catch (...) -// { -// // the invoke is not setup to succeed because it's not needed in this test -// } -// -// ASSERT_EQ("{\"arguments\":[],\"invocationId\":\"0\",\"target\":\"method\",\"type\":1}\x1e", payload); -//} -// -//TEST(invoke, callback_not_called_if_send_throws) -//{ -// bool handshakeReceived = false; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }, -// /* send function */[handshakeReceived](const std::string&) mutable -// { -// if (handshakeReceived) -// { -// return pplx::task_from_exception(std::runtime_error("error")); -// } -// handshakeReceived = true; -// return pplx::task_from_result(); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start().get(); -// -// try -// { -// hub_connection->invoke("method", json::value::array()).get(); -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const std::runtime_error& e) -// { -// ASSERT_STREQ("error", e.what()); -// } -// -// // stop completes all outstanding callbacks so if we did not remove a callback when `invoke_void` failed an -// // unobserved exception exception would be thrown. Note that this would happen on a different thread and would -// // crash the process -// hub_connection->stop().get(); -//} -// -//TEST(invoke, invoke_returns_value_returned_from_the_server) -//{ -// auto callback_registered_event = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, callback_registered_event]() -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\", \"result\": \"abc\" }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number > 0) -// { -// callback_registered_event->wait(); -// } -// -// return pplx::task_from_result(responses[call_number]); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// auto result = hub_connection->start() -// .then([hub_connection, callback_registered_event]() -// { -// auto t = hub_connection->invoke("method", json::value::array()); -// callback_registered_event->set(); -// return t; -// }).get(); -// -// ASSERT_EQ(_XPLATSTR("\"abc\""), result.serialize()); -//} -// -//TEST(invoke, invoke_propagates_errors_from_server_as_hub_exceptions) -//{ -// auto callback_registered_event = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, callback_registered_event]() -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number > 0) -// { -// callback_registered_event->wait(); -// } -// -// return pplx::task_from_result(responses[call_number]); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// try -// { -// hub_connection->start() -// .then([hub_connection, callback_registered_event]() -// { -// auto t = hub_connection->invoke("method", json::value::array()); -// callback_registered_event->set(); -// return t; -// }).get(); -// -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const hub_exception& e) -// { -// ASSERT_STREQ("\"Ooops\"", e.what()); -// } -//} -// -//TEST(invoke, unblocks_task_when_server_completes_call) -//{ -// auto callback_registered_event = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, callback_registered_event]() -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\" }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number > 0) -// { -// callback_registered_event->wait(); -// } -// -// return pplx::task_from_result(responses[call_number]); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// hub_connection->start() -// .then([hub_connection, callback_registered_event]() -// { -// auto t = hub_connection->invoke("method", json::value::array()); -// callback_registered_event->set(); -// return t; -// }).get(); -// -// // should not block -// ASSERT_TRUE(true); -//} -// -//TEST(receive, logs_if_callback_for_given_id_not_found) -//{ -// auto message_received_event = std::make_shared(); -// auto handshake_sent = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, message_received_event, handshake_sent]() -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\" }\x1e", -// "{}" -// }; -// -// handshake_sent->wait(1000); -// -// call_number = std::min(call_number + 1, 2); -// -// if (call_number > 1) -// { -// message_received_event->set(); -// } -// -// return pplx::task_from_result(responses[call_number]); -// }, -// [handshake_sent](const std::string&) -// { -// handshake_sent->set(); -// return pplx::task_from_result(); -// }); -// -// std::shared_ptr writer(std::make_shared()); -// auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::info); -// hub_connection->start().get(); -// -// ASSERT_FALSE(message_received_event->wait(5000)); -// -// auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); -// ASSERT_TRUE(log_entries.size() > 1); -// -// auto entry = remove_date_from_log_entry(log_entries[2]); -// ASSERT_EQ("[info ] no callback found for id: 0\n", entry) << dump_vector(log_entries); -//} -// -//TEST(invoke_void, invoke_creates_runtime_error) -//{ -// auto callback_registered_event = std::make_shared(); -// -// int call_number = -1; -// auto websocket_client = create_test_websocket_client( -// /* receive function */ [call_number, callback_registered_event]() -// mutable { -// std::string responses[] -// { -// "{ }\x1e", -// "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" -// }; -// -// call_number = std::min(call_number + 1, 1); -// -// if (call_number > 0) -// { -// callback_registered_event->wait(); -// } -// -// return pplx::task_from_result(responses[call_number]); -// }); -// -// auto hub_connection = create_hub_connection(websocket_client); -// try -// { -// hub_connection->start() -// .then([hub_connection, callback_registered_event]() -// { -// auto t = hub_connection->invoke("method", json::value::array()); -// callback_registered_event->set(); -// return t; -// }).get(); -// -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const hub_exception & e) -// { -// ASSERT_STREQ("\"Ooops\"", e.what()); -// ASSERT_FALSE(callback_registered_event->wait(0)); -// } -//} -// -//TEST(connection_id, can_get_connection_id) -//{ -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// ASSERT_EQ("", hub_connection->get_connection_id()); -// -// hub_connection->start().get(); -// auto connection_id = hub_connection->get_connection_id(); -// hub_connection->stop().get(); -// -// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); -// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", hub_connection->get_connection_id()); -//} -// -//TEST(on, event_name_must_not_be_empty_string) -//{ -// auto hub_connection = create_hub_connection(); -// try -// { -// hub_connection->on("", [](const json::value&) {}); -// -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const std::invalid_argument& e) -// { -// ASSERT_STREQ("event_name cannot be empty", e.what()); -// } -//} -// -//TEST(on, cannot_register_multiple_handlers_for_event) -//{ -// auto hub_connection = create_hub_connection(); -// hub_connection->on("ping", [](const json::value&) {}); -// -// try -// { -// hub_connection->on("ping", [](const json::value&) {}); -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("an action for this event has already been registered. event name: ping", e.what()); -// } -//} -// -//TEST(on, cannot_register_handler_if_connection_not_in_disconnected_state) -//{ -// try -// { -// auto websocket_client = create_test_websocket_client( -// /* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); }); -// auto hub_connection = create_hub_connection(websocket_client); -// -// hub_connection->start().get(); -// -// hub_connection->on("myfunc", [](const web::json::value&) {}); -// -// ASSERT_TRUE(false); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("can't register a handler if the connection is in a disconnected state", e.what()); -// } -//} -// -//TEST(invoke, invoke_throws_when_the_underlying_connection_is_not_valid) -//{ -// auto hub_connection = create_hub_connection(); -// -// try -// { -// hub_connection->invoke("method", json::value::array()).get(); -// ASSERT_TRUE(true); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); -// } -//} -// -//TEST(invoke, send_throws_when_the_underlying_connection_is_not_valid) -//{ -// auto hub_connection = create_hub_connection(); -// -// try -// { -// hub_connection->invoke("method", json::value::array()).get(); -// ASSERT_TRUE(true); // exception expected but not thrown -// } -// catch (const signalr_exception& e) -// { -// ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); -// } -//} +#include "test_utils.h" +#include "test_transport_factory.h" +#include "test_http_client.h" +#include "hub_connection_impl.h" +#include "trace_log_writer.h" +#include "memory_log_writer.h" +#include "signalrclient/hub_exception.h" +#include "signalrclient/signalr_exception.h" + +using namespace signalr; + +std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), + std::shared_ptr log_writer = std::make_shared(), trace_level trace_level = trace_level::all) +{ + return hub_connection_impl::create(create_uri(), trace_level, log_writer, + create_test_http_client(), std::make_unique(websocket_client)); +} + +TEST(url, negotiate_appended_to_url) +{ + std::string base_urls[] = { "http://fakeuri", "http://fakeuri/" }; + + for (const auto& base_url : base_urls) + { + std::string requested_url; + auto http_client = std::make_unique([&requested_url](const std::string& url, http_request) + { + requested_url = url; + return http_response{ 404, "" }; + }); + + auto hub_connection = hub_connection_impl::create(base_url, trace_level::none, + std::make_shared(), std::move(http_client), + std::make_unique(create_test_websocket_client())); + + try + { + hub_connection->start().get(); + } + catch (const std::exception&) {} + + ASSERT_EQ("http://fakeuri/negotiate", requested_url); + } +} + +TEST(start, start_starts_connection) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + hub_connection->start().get(); + + ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +} + +TEST(start, start_sends_handshake) +{ + auto message = std::make_shared(); + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, + /* send function */ [message](const std::string& msg, std::function callback) { *message = msg; callback(nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + hub_connection->start().get(); + + ASSERT_EQ("{\"protocol\":\"json\",\"version\":1}\x1e", *message); + + ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +} + +TEST(start, start_waits_for_handshake_response) +{ + pplx::task_completion_event tce; + pplx::task_completion_event tceWaitForSend; + auto websocket_client = create_test_websocket_client( + /* receive function */ [tce, tceWaitForSend](std::function callback) + { + tceWaitForSend.set(); + pplx::task(tce).get(); + callback("{ }\x1e", nullptr); + }); + auto hub_connection = create_hub_connection(websocket_client); + + auto startTask = hub_connection->start(); + pplx::task(tceWaitForSend).get(); + ASSERT_FALSE(startTask.is_done()); + tce.set(); + startTask.get(); + + ASSERT_EQ(connection_state::connected, hub_connection->get_connection_state()); +} + +TEST(start, start_fails_for_handshake_response_with_error) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{\"error\":\"bad things\"}\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + try + { + hub_connection->start().get(); + ASSERT_TRUE(false); + } + catch (std::exception ex) + { + ASSERT_STREQ("Received an error during handshake: bad things", ex.what()); + } + + ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +} + +TEST(start, start_fails_if_stop_called_before_handshake_response) +{ + pplx::task_completion_event tce; + pplx::task_completion_event tceWaitForSend; + auto websocket_client = create_test_websocket_client( + /* receive function */ [tce](std::function callback) + { + auto str = pplx::task(tce).get(); + callback(str, nullptr); + }, + /* send function */ [tceWaitForSend](const std::string &, std::function callback) + { + tceWaitForSend.set(); + callback(nullptr); + }); + auto hub_connection = create_hub_connection(websocket_client); + + auto startTask = hub_connection->start(); + pplx::task(tceWaitForSend).get(); + hub_connection->stop(); + + try + { + startTask.get(); + ASSERT_TRUE(false); + } + catch (std::exception ex) + { + ASSERT_STREQ("connection closed while handshake was in progress.", ex.what()); + } + + ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +} + +TEST(stop, stop_stops_connection) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + hub_connection->start().get(); + hub_connection->stop().get(); + + ASSERT_EQ(connection_state::disconnected, hub_connection->get_connection_state()); +} + +TEST(stop, disconnected_callback_called_when_hub_connection_stops) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + auto disconnected_invoked = false; + hub_connection->set_disconnected([&disconnected_invoked]() { disconnected_invoked = true; }); + + hub_connection->start().get(); + hub_connection->stop().get(); + + ASSERT_TRUE(disconnected_invoked); +} + +TEST(stop, connection_stopped_when_going_out_of_scope) +{ + std::shared_ptr writer(std::make_shared()); + + { + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::state_changes); + + hub_connection->start().get(); + } + + auto memory_writer = std::dynamic_pointer_cast(writer); + + // The underlying connection_impl will be destroyed when the last reference to shared_ptr holding is released. This can happen + // on a different thread in which case the dtor will be invoked on a different thread so we need to wait for this + // to happen and if it does not the test will fail. There is nothing we can block on. + for (int wait_time_ms = 5; wait_time_ms < 100 && memory_writer->get_log_entries().size() < 4; wait_time_ms <<= 1) + { + std::this_thread::sleep_for(std::chrono::milliseconds(wait_time_ms)); + } + + auto log_entries = memory_writer->get_log_entries(); + ASSERT_EQ(4U, log_entries.size()) << dump_vector(log_entries); + ASSERT_EQ("[state change] disconnected -> connecting\n", remove_date_from_log_entry(log_entries[0])); + ASSERT_EQ("[state change] connecting -> connected\n", remove_date_from_log_entry(log_entries[1])); + ASSERT_EQ("[state change] connected -> disconnecting\n", remove_date_from_log_entry(log_entries[2])); + ASSERT_EQ("[state change] disconnecting -> disconnected\n", remove_date_from_log_entry(log_entries[3])); +} + +TEST(stop, stop_cancels_pending_callbacks) +{ + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{}" + }; + + if (call_number < 1) + { + call_number++; + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + hub_connection->start().get(); + auto t = hub_connection->invoke("method", json::value::array()); + hub_connection->stop(); + + try + { + t.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("\"connection was stopped before invocation result was received\"", e.what()); + } +} + +TEST(stop, pending_callbacks_finished_if_hub_connections_goes_out_of_scope) +{ + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{}" + }; + + if (call_number < 1) + { + call_number++; + } + + callback(responses[call_number], nullptr); + }); + + pplx::task t; + + { + auto hub_connection = create_hub_connection(websocket_client); + hub_connection->start().get(); + t = hub_connection->invoke("method", json::value::array()); + } + + try + { + t.get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("\"connection went out of scope before invocation result was received\"", e.what()); + } +} + +TEST(hub_invocation, hub_connection_invokes_users_code_on_hub_invocations) +{ + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 1, \"target\": \"BROADcast\", \"arguments\": [ \"message\", 1 ] }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + + auto payload = std::make_shared(); + auto on_broadcast_event = std::make_shared(); + hub_connection->on("broadCAST", [on_broadcast_event, payload](const json::value& message) + { + *payload = utility::conversions::to_utf8string(message.serialize()); + on_broadcast_event->set(); + }); + + hub_connection->start().get(); + ASSERT_FALSE(on_broadcast_event->wait(5000)); + + ASSERT_EQ("[\"message\",1]", *payload); +} + +TEST(send, creates_correct_payload) +{ + std::string payload; + bool handshakeReceived = false; + + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, + /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) + { + if (handshakeReceived) + { + payload = m; + callback(nullptr); + return; + } + handshakeReceived = true; + callback(nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + hub_connection->start().get(); + + hub_connection->send("method", json::value::array()).get(); + + ASSERT_EQ("{\"arguments\":[],\"target\":\"method\",\"type\":1}\x1e", payload); +} + +TEST(send, does_not_wait_for_server_response) +{ + int call_number = -1; + pplx::task_completion_event waitForSend; + + auto websocket_client = create_test_websocket_client( + /* receive function */ [waitForSend, call_number](std::function callback) mutable + { + std::string responses[] + { + "{ }\x1e", + "{}" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number == 1) + { + pplx::task(waitForSend).get(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + hub_connection->start().get(); + + // wont block waiting for server response + hub_connection->send("method", json::value::array()).get(); + waitForSend.set(); +} + +TEST(invoke, creates_correct_payload) +{ + std::string payload; + bool handshakeReceived = false; + + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, + /* send function */[&payload, &handshakeReceived](const std::string& m, std::function callback) + { + if (handshakeReceived) + { + payload = m; + callback(std::make_exception_ptr(std::runtime_error("error"))); + return; + } + handshakeReceived = true; + callback(nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + hub_connection->start().get(); + + try + { + hub_connection->invoke("method", json::value::array()).get(); + } + catch (...) + { + // the invoke is not setup to succeed because it's not needed in this test + } + + ASSERT_EQ("{\"arguments\":[],\"invocationId\":\"0\",\"target\":\"method\",\"type\":1}\x1e", payload); +} + +TEST(invoke, callback_not_called_if_send_throws) +{ + bool handshakeReceived = false; + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }, + /* send function */[handshakeReceived](const std::string&, std::function callback) mutable + { + if (handshakeReceived) + { + callback(std::make_exception_ptr(std::runtime_error("error"))); + return; + } + handshakeReceived = true; + callback(nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + hub_connection->start().get(); + + try + { + hub_connection->invoke("method", json::value::array()).get(); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const std::runtime_error& e) + { + ASSERT_STREQ("error", e.what()); + } + + // stop completes all outstanding callbacks so if we did not remove a callback when `invoke_void` failed an + // unobserved exception exception would be thrown. Note that this would happen on a different thread and would + // crash the process + hub_connection->stop().get(); +} + +TEST(invoke, invoke_returns_value_returned_from_the_server) +{ + auto callback_registered_event = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, callback_registered_event](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\", \"result\": \"abc\" }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number > 0) + { + callback_registered_event->wait(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + auto result = hub_connection->start() + .then([hub_connection, callback_registered_event]() + { + auto t = hub_connection->invoke("method", json::value::array()); + callback_registered_event->set(); + return t; + }).get(); + + ASSERT_EQ(_XPLATSTR("\"abc\""), result.serialize()); +} + +TEST(invoke, invoke_propagates_errors_from_server_as_hub_exceptions) +{ + auto callback_registered_event = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, callback_registered_event](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number > 0) + { + callback_registered_event->wait(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + try + { + hub_connection->start() + .then([hub_connection, callback_registered_event]() + { + auto t = hub_connection->invoke("method", json::value::array()); + callback_registered_event->set(); + return t; + }).get(); + + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const hub_exception& e) + { + ASSERT_STREQ("\"Ooops\"", e.what()); + } +} + +TEST(invoke, unblocks_task_when_server_completes_call) +{ + auto callback_registered_event = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, callback_registered_event](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\" }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number > 0) + { + callback_registered_event->wait(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + hub_connection->start() + .then([hub_connection, callback_registered_event]() + { + auto t = hub_connection->invoke("method", json::value::array()); + callback_registered_event->set(); + return t; + }).get(); + + // should not block + ASSERT_TRUE(true); +} + +TEST(receive, logs_if_callback_for_given_id_not_found) +{ + auto message_received_event = std::make_shared(); + auto handshake_sent = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, message_received_event, handshake_sent](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\" }\x1e", + "{}" + }; + + handshake_sent->wait(1000); + + call_number = std::min(call_number + 1, 2); + + if (call_number > 1) + { + message_received_event->set(); + } + + callback(responses[call_number], nullptr); + }, + [handshake_sent](const std::string&, std::function callback) + { + handshake_sent->set(); + callback(nullptr); + }); + + std::shared_ptr writer(std::make_shared()); + auto hub_connection = create_hub_connection(websocket_client, writer, trace_level::info); + hub_connection->start().get(); + + ASSERT_FALSE(message_received_event->wait(5000)); + + auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); + ASSERT_TRUE(log_entries.size() > 1); + + auto entry = remove_date_from_log_entry(log_entries[2]); + ASSERT_EQ("[info ] no callback found for id: 0\n", entry) << dump_vector(log_entries); +} + +TEST(invoke_void, invoke_creates_runtime_error) +{ + auto callback_registered_event = std::make_shared(); + + int call_number = -1; + auto websocket_client = create_test_websocket_client( + /* receive function */ [call_number, callback_registered_event](std::function callback) + mutable { + std::string responses[] + { + "{ }\x1e", + "{ \"type\": 3, \"invocationId\": \"0\", \"error\": \"Ooops\" }\x1e" + }; + + call_number = std::min(call_number + 1, 1); + + if (call_number > 0) + { + callback_registered_event->wait(); + } + + callback(responses[call_number], nullptr); + }); + + auto hub_connection = create_hub_connection(websocket_client); + try + { + hub_connection->start() + .then([hub_connection, callback_registered_event]() + { + auto t = hub_connection->invoke("method", json::value::array()); + callback_registered_event->set(); + return t; + }).get(); + + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const hub_exception & e) + { + ASSERT_STREQ("\"Ooops\"", e.what()); + ASSERT_FALSE(callback_registered_event->wait(0)); + } +} + +TEST(connection_id, can_get_connection_id) +{ + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + ASSERT_EQ("", hub_connection->get_connection_id()); + + hub_connection->start().get(); + auto connection_id = hub_connection->get_connection_id(); + hub_connection->stop().get(); + + ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", connection_id); + ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", hub_connection->get_connection_id()); +} + +TEST(on, event_name_must_not_be_empty_string) +{ + auto hub_connection = create_hub_connection(); + try + { + hub_connection->on("", [](const json::value&) {}); + + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const std::invalid_argument& e) + { + ASSERT_STREQ("event_name cannot be empty", e.what()); + } +} + +TEST(on, cannot_register_multiple_handlers_for_event) +{ + auto hub_connection = create_hub_connection(); + hub_connection->on("ping", [](const json::value&) {}); + + try + { + hub_connection->on("ping", [](const json::value&) {}); + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("an action for this event has already been registered. event name: ping", e.what()); + } +} + +TEST(on, cannot_register_handler_if_connection_not_in_disconnected_state) +{ + try + { + auto websocket_client = create_test_websocket_client( + /* receive function */ [](std::function callback) { callback("{ }\x1e", nullptr); }); + auto hub_connection = create_hub_connection(websocket_client); + + hub_connection->start().get(); + + hub_connection->on("myfunc", [](const web::json::value&) {}); + + ASSERT_TRUE(false); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("can't register a handler if the connection is in a disconnected state", e.what()); + } +} + +TEST(invoke, invoke_throws_when_the_underlying_connection_is_not_valid) +{ + auto hub_connection = create_hub_connection(); + + try + { + hub_connection->invoke("method", json::value::array()).get(); + ASSERT_TRUE(true); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); + } +} + +TEST(invoke, send_throws_when_the_underlying_connection_is_not_valid) +{ + auto hub_connection = create_hub_connection(); + + try + { + hub_connection->invoke("method", json::value::array()).get(); + ASSERT_TRUE(true); // exception expected but not thrown + } + catch (const signalr_exception& e) + { + ASSERT_STREQ("cannot send data when the connection is not in the connected state. current connection state: disconnected", e.what()); + } +} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp index 831fb72aa0b0..b75bcd1d64ce 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp @@ -7,9 +7,85 @@ #include "test_websocket_client.h" #include "websocket_transport.h" #include "memory_log_writer.h" +#include using namespace signalr; +template +class manual_reset_event +{ +public: + void set(T value) + { + m_promise.set_value(value); + } + + void set_exception(std::exception exception) + { + m_promise.set_exception(std::make_exception_ptr(exception)); + } + + void set_exception(std::exception_ptr exception) + { + m_promise.set_exception(exception); + } + + T get() + { + // TODO: timeout + try + { + auto ret = m_promise.get_future().get(); + m_promise = std::promise(); + return ret; + } + catch (...) + { + m_promise = std::promise(); + std::rethrow_exception(std::current_exception()); + } + } +private: + std::promise m_promise; +}; + +template <> +class manual_reset_event +{ +public: + void set() + { + m_promise.set_value(); + } + + void set_exception(std::exception exception) + { + m_promise.set_exception(std::make_exception_ptr(exception)); + } + + void set_exception(std::exception_ptr exception) + { + m_promise.set_exception(exception); + } + + void get() + { + try + { + m_promise.get_future().get(); + } + catch (...) + { + m_promise = std::promise(); + std::rethrow_exception(std::current_exception()); + } + + m_promise = std::promise(); + } +private: + std::promise m_promise; +}; + TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop) { auto connect_called = false; @@ -32,17 +108,13 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::info)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool start_finished; - ws_transport->start("ws://fakeuri.org/connect?param=42", transfer_format::text, [&cv, &start_finished](std::exception_ptr exception) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org/connect?param=42", transfer_format::text, [&mre](std::exception_ptr) { - start_finished = true; - cv.notify_one(); + mre.set(); }); - ASSERT_FALSE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_finished]() { return start_finished; }) == false); + mre.get(); ASSERT_TRUE(connect_called); ASSERT_FALSE(receive_called->wait(5000)); @@ -66,18 +138,13 @@ TEST(websocket_transport_connect, connect_propagates_exceptions) try { - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - std::exception_ptr start_exception; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &start_exception](std::exception_ptr exception) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - start_exception = exception; - cv.notify_one(); + mre.set_exception(exception); }); - - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_exception]() { return start_exception != nullptr; })); - std::rethrow_exception(start_exception); + mre.get(); + ASSERT_TRUE(false); } catch (const std::exception &e) { @@ -96,17 +163,16 @@ TEST(websocket_transport_connect, connect_logs_exceptions) std::shared_ptr writer(std::make_shared()); auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::errors)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - std::exception_ptr start_exception; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &start_exception](std::exception_ptr exception) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - start_exception = exception; - cv.notify_one(); + mre.set_exception(exception); }); - - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_exception]() { return start_exception != nullptr; })); + try + { + mre.get(); + } + catch (...) {} auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); @@ -124,29 +190,21 @@ TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_trans auto client = std::make_shared(); auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool start_completed; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &start_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) { - start_completed = true; - cv.notify_one(); + mre.set(); }); - - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_completed]() { return start_completed; })); + mre.get(); try { - std::exception_ptr start_exception; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &start_exception](std::exception_ptr exception) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception) { - start_exception = exception; - cv.notify_one(); + mre.set_exception(exception); }); - - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [start_exception]() { return start_exception != nullptr; })); - std::rethrow_exception(start_exception); + mre.get(); + ASSERT_TRUE(false); } catch (const std::exception &e) { @@ -159,36 +217,24 @@ TEST(websocket_transport_connect, can_connect_after_disconnecting) auto client = std::make_shared(); auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); + mre.get(); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; - - ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); + mre.get(); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; - - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - // shouldn't throw or crash + mre.get(); } TEST(websocket_transport_send, send_creates_and_sends_websocket_messages) @@ -205,25 +251,18 @@ TEST(websocket_transport_send, send_creates_and_sends_websocket_messages) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); + mre.get(); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; - - ws_transport->send("ABC", [&cv, &callback_completed](std::exception_ptr) + ws_transport->send("ABC", [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + mre.get(); ASSERT_TRUE(send_called); } @@ -242,59 +281,59 @@ TEST(websocket_transport_disconnect, disconnect_closes_websocket) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; + mre.get(); - ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + mre.get(); ASSERT_TRUE(close_called); } -TEST(websocket_transport_disconnect, disconnect_does_not_throw) +TEST(websocket_transport_stop, propogates_exception_from_close) { auto client = std::make_shared(); bool close_called = false; client->set_close_function([&close_called](std::function callback) { - callback(std::make_exception_ptr(std::exception())); close_called = true; + callback(std::make_exception_ptr(std::exception())); }); auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; + mre.get(); - ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr exception) { - callback_completed = true; - cv.notify_one(); + if (exception) + { + mre.set_exception(exception); + } + else + { + mre.set(); + } }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + try + { + mre.get(); + ASSERT_TRUE(false); + } + catch (...) { } ASSERT_TRUE(close_called); } @@ -311,24 +350,18 @@ TEST(websocket_transport_disconnect, disconnect_logs_exceptions) auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level::errors)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; + mre.get(); - ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + mre.get(); auto log_entries = std::dynamic_pointer_cast(writer)->get_log_entries(); @@ -377,47 +410,37 @@ TEST(websocket_transport_disconnect, DISABLED_receive_not_called_after_disconnec auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; + mre.get(); pplx::create_task(receive_task_started_tce).get(); - ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; + mre.get(); receive_task_tce = pplx::task_completion_event(); receive_task_started_tce = pplx::task_completion_event(); - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; + mre.get(); pplx::create_task(receive_task_started_tce).get(); - ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + mre.get(); ASSERT_EQ(2, num_called); } @@ -436,16 +459,12 @@ TEST(websocket_transport_disconnect, disconnect_is_no_op_if_transport_not_starte auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->stop([&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + mre.get(); ASSERT_FALSE(close_called); } @@ -466,25 +485,18 @@ TEST(websocket_transport_disconnect, exceptions_from_outstanding_receive_task_ob auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; + mre.get(); - ws_transport->stop([&cv, &callback_completed](std::exception_ptr) + ws_transport->stop([&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); - callback_completed = false; + mre.get(); // at this point the cancellation token that closes the receive loop is set to cancelled so // we can unblock the the receive task which throws an exception that should be observed otwherwise the test will crash @@ -506,8 +518,8 @@ TEST(websocket_transport_receive_loop, receive_loop_logs_if_receive_task_cancele { receive_loop_logs_exception_runner( pplx::task_canceled("canceled"), - "[info ] [websocket transport] receive task canceled.\n", - trace_level::info); + "[error ] [websocket transport] error receiving response from websocket: canceled\n", + trace_level::errors); } TEST(websocket_transport_receive_loop, receive_loop_logs_std_exception) @@ -526,24 +538,20 @@ void receive_loop_logs_exception_runner(const T& e, const std::string& expected_ client->set_receive_function([&receive_event, &e](std::function callback) { - receive_event.set(); callback("", std::make_exception_ptr(e)); + receive_event.set(); }); std::shared_ptr writer(std::make_shared()); auto ws_transport = websocket_transport::create([&](){ return client; }, logger(writer, trace_level)); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://url", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + mre.get(); receive_event.wait(); @@ -554,7 +562,7 @@ void receive_loop_logs_exception_runner(const T& e, const std::string& expected_ ASSERT_NE(std::find_if(log_entries.begin(), log_entries.end(), [&expected_message](std::string entry) { return remove_date_from_log_entry(entry) == expected_message; }), - log_entries.end()); + log_entries.end()) << dump_vector(log_entries); } TEST(websocket_transport_receive_loop, process_response_callback_called_when_message_received) @@ -568,24 +576,22 @@ TEST(websocket_transport_receive_loop, process_response_callback_called_when_mes auto process_response_event = std::make_shared(); auto msg = std::make_shared(); - auto process_response = [msg, process_response_event](const std::string& message) + auto process_response = [msg, process_response_event](const std::string& message, std::exception_ptr exception) { + ASSERT_FALSE(exception); *msg = message; process_response_event->set(); }; auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + ws_transport->on_receive(process_response); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + mre.get(); process_response_event->wait(1000); @@ -610,24 +616,28 @@ TEST(websocket_transport_receive_loop, error_callback_called_when_exception_thro auto error_event = std::make_shared(); auto exception_msg = std::make_shared(); - auto error_callback = [exception_msg, error_event](const std::exception& e) + auto error_callback = [exception_msg, error_event](std::exception_ptr exception) { - *exception_msg = e.what(); + try + { + std::rethrow_exception(exception); + } + catch (const std::exception& e) + { + *exception_msg = e.what(); + } error_event->set(); }; auto ws_transport = websocket_transport::create([&](){ return client; }, logger(std::make_shared(), trace_level::none)); + ws_transport->on_close(error_callback); - std::mutex mtx; - std::unique_lock lock(mtx); - std::condition_variable cv; - bool callback_completed; - ws_transport->start("ws://fakeuri.org", transfer_format::text, [&cv, &callback_completed](std::exception_ptr) + auto mre = manual_reset_event(); + ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr) { - callback_completed = true; - cv.notify_one(); + mre.set(); }); - ASSERT_TRUE(cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(5), [callback_completed]() { return callback_completed; })); + mre.get(); error_event->wait(1000); From a61168e7bc1bf83e42c650a63f2410aa63e93c8f Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Tue, 19 Mar 2019 09:13:00 -0700 Subject: [PATCH 10/19] enum class --- src/SignalR/clients/cpp/src/signalrclient/http_client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/http_client.h b/src/SignalR/clients/cpp/src/signalrclient/http_client.h index 0f2e617b4bb5..233ef4544a1c 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/http_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/http_client.h @@ -10,7 +10,7 @@ namespace signalr { - enum http_method + enum class http_method { GET, POST From ca8bcd06567875fdcd1c747176e963be91db643a Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Tue, 19 Mar 2019 14:49:24 -0700 Subject: [PATCH 11/19] clean --- .../src/signalrclient/default_http_client.cpp | 7 +- .../cpp/src/signalrclient/negotiate.cpp | 1 - .../cpp/test/signalrclienttests/test_utils.h | 76 +++++++++++++++++++ .../websocket_transport_tests.cpp | 75 ------------------ 4 files changed, 82 insertions(+), 77 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp index 5653cde732f8..eeaf627869e3 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp @@ -17,6 +17,11 @@ namespace signalr { method = U("POST"); } + else + { + callback(http_response(), std::make_exception_ptr(std::runtime_error("unknown http method"))); + return; + } web::http::http_request http_request; http_request.set_method(method); @@ -61,7 +66,7 @@ namespace signalr } catch (...) { - callback(signalr::http_response(), std::current_exception()); + callback(http_response(), std::current_exception()); } }); } diff --git a/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp b/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp index fde3034ea93e..5a3a07ea9faa 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp @@ -3,7 +3,6 @@ #include "stdafx.h" #include "negotiate.h" -#include "http_sender.h" #include "url_builder.h" #include "signalrclient/signalr_exception.h" diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h index 604f71dc8a50..b17abad24eb3 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h @@ -7,6 +7,7 @@ #include "websocket_client.h" #include "web_request_factory.h" #include "http_client.h" +#include std::string remove_date_from_log_entry(const std::string &log_entry); @@ -22,3 +23,78 @@ std::string create_uri(); std::string create_uri(const std::string& query_string); std::vector filter_vector(const std::vector& source, const std::string& string); std::string dump_vector(const std::vector& source); + +template +class manual_reset_event +{ +public: + void set(T value) + { + m_promise.set_value(value); + } + + void set_exception(std::exception exception) + { + m_promise.set_exception(std::make_exception_ptr(exception)); + } + + void set_exception(std::exception_ptr exception) + { + m_promise.set_exception(exception); + } + + T get() + { + // TODO: timeout + try + { + auto ret = m_promise.get_future().get(); + m_promise = std::promise(); + return ret; + } + catch (...) + { + m_promise = std::promise(); + std::rethrow_exception(std::current_exception()); + } + } +private: + std::promise m_promise; +}; + +template <> +class manual_reset_event +{ +public: + void set() + { + m_promise.set_value(); + } + + void set_exception(std::exception exception) + { + m_promise.set_exception(std::make_exception_ptr(exception)); + } + + void set_exception(std::exception_ptr exception) + { + m_promise.set_exception(exception); + } + + void get() + { + try + { + m_promise.get_future().get(); + } + catch (...) + { + m_promise = std::promise(); + std::rethrow_exception(std::current_exception()); + } + + m_promise = std::promise(); + } +private: + std::promise m_promise; +}; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp index b75bcd1d64ce..ea65ad37b1eb 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp @@ -11,81 +11,6 @@ using namespace signalr; -template -class manual_reset_event -{ -public: - void set(T value) - { - m_promise.set_value(value); - } - - void set_exception(std::exception exception) - { - m_promise.set_exception(std::make_exception_ptr(exception)); - } - - void set_exception(std::exception_ptr exception) - { - m_promise.set_exception(exception); - } - - T get() - { - // TODO: timeout - try - { - auto ret = m_promise.get_future().get(); - m_promise = std::promise(); - return ret; - } - catch (...) - { - m_promise = std::promise(); - std::rethrow_exception(std::current_exception()); - } - } -private: - std::promise m_promise; -}; - -template <> -class manual_reset_event -{ -public: - void set() - { - m_promise.set_value(); - } - - void set_exception(std::exception exception) - { - m_promise.set_exception(std::make_exception_ptr(exception)); - } - - void set_exception(std::exception_ptr exception) - { - m_promise.set_exception(exception); - } - - void get() - { - try - { - m_promise.get_future().get(); - } - catch (...) - { - m_promise = std::promise(); - std::rethrow_exception(std::current_exception()); - } - - m_promise = std::promise(); - } -private: - std::promise m_promise; -}; - TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop) { auto connect_called = false; From 065094eb46eb2ed7b25dd89f136619f193347a7e Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Tue, 19 Mar 2019 15:15:25 -0700 Subject: [PATCH 12/19] headers --- src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp | 8 +++++++- .../cpp/test/signalrclienttests/connection_impl_tests.cpp | 8 ++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp b/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp index 5a3a07ea9faa..a38d1e31e3a9 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/negotiate.cpp @@ -11,7 +11,7 @@ namespace signalr namespace negotiate { pplx::task negotiate(http_client& client, const std::string& base_url, - const signalr_client_config&) + const signalr_client_config& config) { auto negotiate_url = url_builder::build_negotiate(base_url); @@ -20,6 +20,12 @@ namespace signalr // TODO: signalr_client_config http_request request; request.method = http_method::POST; + + for (auto& header : config.get_http_headers()) + { + request.headers.insert(std::make_pair(utility::conversions::to_utf8string(header.first), utility::conversions::to_utf8string(header.second))); + } + client.send(negotiate_url, request, [tce](http_response http_response, std::exception_ptr exception) { if (exception != nullptr) diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp index c50a3fb30f85..9da56d17d477 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp @@ -544,7 +544,7 @@ TEST(connection_impl_start, negotiate_follows_redirect) ASSERT_EQ("ws://redirected/?id=f7707523-307d-4cba-9abf-3eef701241e8", connectUrl); } -TEST(connection_impl_start, DISABLED_negotiate_redirect_uses_accessToken) +TEST(connection_impl_start, negotiate_redirect_uses_accessToken) { std::shared_ptr writer(std::make_shared()); std::string accessToken; @@ -565,11 +565,7 @@ TEST(connection_impl_start, DISABLED_negotiate_redirect_uses_accessToken) } } - /*auto request = new web_request_stub((unsigned short)200, "OK", response_body); - request->on_get_response = [&accessToken](web_request_stub& stub) - { - accessToken = utility::conversions::to_utf8string(stub.m_signalr_client_config.get_http_headers()[_XPLATSTR("Authorization")]); - };*/ + accessToken = request.headers["Authorization"]; return http_response{ 200, response_body }; }); From 9d41aab8dfe5bfcee21f6daa009a9b8f268326cc Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Tue, 19 Mar 2019 15:26:41 -0700 Subject: [PATCH 13/19] compilation failures on mac --- .../cpp/src/signalrclient/default_http_client.cpp | 2 +- .../clients/cpp/src/signalrclient/http_client.h | 14 ++++++++++++++ .../test/signalrclienttests/test_http_client.cpp | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp index eeaf627869e3..6a56e5c0d723 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.cpp @@ -61,7 +61,7 @@ namespace signalr signalr::http_response response; response.content = response_body; response.status_code = status_code; - callback(response, nullptr); + callback(std::move(response), nullptr); }); } catch (...) diff --git a/src/SignalR/clients/cpp/src/signalrclient/http_client.h b/src/SignalR/clients/cpp/src/signalrclient/http_client.h index 233ef4544a1c..3b3b9b390afd 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/http_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/http_client.h @@ -28,6 +28,18 @@ namespace signalr class http_response { public: + http_response() {} + http_response(http_response&& rhs) noexcept : status_code(rhs.status_code), content(std::move(rhs.content)) {} + http_response(int code, const std::string& content) : status_code(code), content(content) {} + + http_response& operator=(http_response&& rhs) + { + status_code = rhs.status_code; + content = std::move(rhs.content); + + return *this; + } + int status_code = 0; std::string content; }; @@ -36,5 +48,7 @@ namespace signalr { public: virtual void send(std::string url, http_request request, std::function callback) = 0; + + virtual ~http_client() {} }; } diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.cpp index 4f71fd74607b..9fd9c11f0e01 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.cpp @@ -22,5 +22,5 @@ void test_http_client::send(std::string url, http_request request, std::function exception = std::current_exception(); } - callback(response, exception); + callback(std::move(response), exception); } From a24c117f4070df6cfcb21fcbba3d4d8e30eabef2 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Tue, 19 Mar 2019 15:49:44 -0700 Subject: [PATCH 14/19] more cleanup --- .../Build/VS/signalrclienttests.vcxproj | 2 +- .../VS/signalrclienttests.vcxproj.filters | 6 +- .../signalrclienttests/negotiate_tests.cpp | 66 +++++++++++++++++++ .../request_sender_tests.cpp | 53 --------------- 4 files changed, 70 insertions(+), 57 deletions(-) create mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/negotiate_tests.cpp delete mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj b/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj index 745f9c6226bd..e88d39671070 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj +++ b/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj @@ -61,7 +61,7 @@ - + Create diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj.filters b/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj.filters index b96cd7a1664d..f8329224c0b3 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj.filters +++ b/src/SignalR/clients/cpp/test/signalrclienttests/Build/VS/signalrclienttests.vcxproj.filters @@ -56,9 +56,6 @@ Source Files - - Source Files - Source Files @@ -104,5 +101,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/negotiate_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/negotiate_tests.cpp new file mode 100644 index 000000000000..bc69936b33b2 --- /dev/null +++ b/src/SignalR/clients/cpp/test/signalrclienttests/negotiate_tests.cpp @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "negotiate.h" +#include "test_http_client.h" + +using namespace signalr; + +TEST(negotiate, request_created_with_correct_url) +{ + std::string requested_url; + auto http_client = test_http_client([&requested_url](const std::string& url, http_request request) + { + std::string response_body( + "{ \"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " + "\"availableTransports\" : [] }"); + + requested_url = url; + return http_response(200, response_body); + }); + + negotiate::negotiate(http_client, "http://fake/signalr").get(); + + ASSERT_EQ("http://fake/signalr/negotiate", requested_url); +} + +TEST(negotiate, negotiation_request_sent_and_response_serialized) +{ + auto request_factory = test_http_client([](const std::string&, http_request request) + { + std::string response_body( + "{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " + "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] }," + "{ \"transport\": \"ServerSentEvents\", \"transferFormats\": [ \"Text\" ] } ] }"); + + return http_response(200, response_body); + }); + + auto response = negotiate::negotiate(request_factory, "http://fake/signalr").get(); + + ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", response.connectionId); + ASSERT_EQ(2u, response.availableTransports.size()); + ASSERT_EQ(2u, response.availableTransports[0].transfer_formats.size()); + ASSERT_EQ("Text", response.availableTransports[0].transfer_formats[0]); + ASSERT_EQ("Binary", response.availableTransports[0].transfer_formats[1]); + ASSERT_EQ(1u, response.availableTransports[1].transfer_formats.size()); + ASSERT_EQ("Text", response.availableTransports[1].transfer_formats[0]); +} + +TEST(negotiate, negotiation_response_with_redirect) +{ + auto request_factory = test_http_client([](const std::string&, http_request request) + { + std::string response_body( + "{\"url\" : \"http://redirect\", " + "\"accessToken\" : \"secret\" }"); + + return http_response(200, response_body); + }); + + auto response = negotiate::negotiate(request_factory, "http://fake/signalr").get(); + + ASSERT_EQ("http://redirect", response.url); + ASSERT_EQ("secret", response.accessToken); +} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp deleted file mode 100644 index a97789663812..000000000000 --- a/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp +++ /dev/null @@ -1,53 +0,0 @@ -//// Copyright (c) .NET Foundation. All rights reserved. -//// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// -#include "stdafx.h" -//#include "cpprest/details/basic_types.h" -//#include "signalrclient/web_exception.h" -//#include "request_sender.h" -//#include "web_request_stub.h" -//#include "test_web_request_factory.h" -//#include "signalrclient/signalr_exception.h" -// -//using namespace signalr; -// -//TEST(request_sender_negotiate, request_created_with_correct_url) -//{ -// std::string requested_url; -// auto request_factory = test_web_request_factory([&requested_url](const std::string& url) -> std::unique_ptr -// { -// std::string response_body( -// "{ \"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " -// "\"availableTransports\" : [] }"); -// -// requested_url = url; -// return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); -// }); -// -// request_sender::negotiate(request_factory, "http://fake/signalr").get(); -// -// ASSERT_EQ("http://fake/signalr/negotiate", requested_url); -//} -// -//TEST(request_sender_negotiate, negotiation_request_sent_and_response_serialized) -//{ -// auto request_factory = test_web_request_factory([](const std::string&) -> std::unique_ptr -// { -// std::string response_body( -// "{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", " -// "\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] }," -// "{ \"transport\": \"ServerSentEvents\", \"transferFormats\": [ \"Text\" ] } ] }"); -// -// return std::unique_ptr(new web_request_stub((unsigned short)200, "OK", response_body)); -// }); -// -// auto response = request_sender::negotiate(request_factory, "http://fake/signalr").get(); -// -// ASSERT_EQ("f7707523-307d-4cba-9abf-3eef701241e8", response.connectionId); -// ASSERT_EQ(2u, response.availableTransports.size()); -// ASSERT_EQ(2u, response.availableTransports[0].transfer_formats.size()); -// ASSERT_EQ("Text", response.availableTransports[0].transfer_formats[0]); -// ASSERT_EQ("Binary", response.availableTransports[0].transfer_formats[1]); -// ASSERT_EQ(1u, response.availableTransports[1].transfer_formats.size()); -// ASSERT_EQ("Text", response.availableTransports[1].transfer_formats[0]); -//} From df96942a7d201b5c5a2053803c91e523ace9429b Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Wed, 20 Mar 2019 09:49:18 -0700 Subject: [PATCH 15/19] fb --- .../cpp/samples/HubConnectionSample/HubConnectionSample.cpp | 2 +- .../clients/cpp/src/signalrclient/connection_impl.cpp | 5 +---- .../test/signalrclienttests/websocket_transport_tests.cpp | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp index 9c72b12cc092..d1e8896986a4 100644 --- a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp +++ b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp @@ -23,7 +23,7 @@ void send_message(signalr::hub_connection& connection, const std::string& messag args[0] = web::json::value(utility::conversions::to_string_t(message)); // if you get an internal compiler error uncomment the lambda below or install VS Update 4 - connection.invoke("Send", args/*, [](const web::json::value&){}*/) + connection.invoke("Send", args) .then([](pplx::task invoke_task) // fire and forget but we need to observe exceptions { try diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 6008983473d2..3ac69d7382da 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -242,6 +242,7 @@ namespace signalr { try { + // Rethrowing the exception so we can log it std::rethrow_exception(exception); } catch (const std::exception & e) @@ -265,10 +266,6 @@ namespace signalr } else { - // When a connection is stopped we don't wait for its transport to stop. As a result if the same connection - // is immediately re-started the old transport can still invoke this callback. To prevent this we capture - // the disconnect_cts by value which allows distinguishing if the message is for the running connection - // or for the one that was already stopped. If this is the latter we just ignore it. if (disconnect_cts.get_token().is_canceled()) { logger.log(trace_level::info, diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp index ea65ad37b1eb..03114e44b18a 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp @@ -303,7 +303,7 @@ TEST(websocket_transport_disconnect, disconnect_logs_exceptions) log_entries.end()); } -TEST(websocket_transport_disconnect, DISABLED_receive_not_called_after_disconnect) +TEST(websocket_transport_disconnect, receive_not_called_after_disconnect) { auto client = std::make_shared(); From a48049c01c8478b9c934d00eec4b23aaab8901ee Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Wed, 20 Mar 2019 11:08:21 -0700 Subject: [PATCH 16/19] moving files --- .../signalrclient/http_client.h | 0 .../signalrclient/transfer_format.h | 0 .../signalrclient/websocket_client.h | 0 .../Build/VS/signalrclient.vcxproj | 6 +-- .../Build/VS/signalrclient.vcxproj.filters | 14 +++---- .../cpp/src/signalrclient/connection_impl.h | 2 +- .../src/signalrclient/default_http_client.h | 2 +- .../signalrclient/default_websocket_client.h | 2 +- .../clients/cpp/src/signalrclient/negotiate.h | 2 +- .../clients/cpp/src/signalrclient/transport.h | 2 +- .../Build/VS/signalrclientdll.vcxproj | 6 +-- .../Build/VS/signalrclientdll.vcxproj.filters | 14 +++---- .../signalrclienttests/test_http_client.h | 2 +- .../test_transport_factory.h | 2 +- .../cpp/test/signalrclienttests/test_utils.h | 4 +- .../test_websocket_client.h | 40 +++++++++++++++++++ 16 files changed, 69 insertions(+), 29 deletions(-) rename src/SignalR/clients/cpp/{src => include}/signalrclient/http_client.h (100%) rename src/SignalR/clients/cpp/{src => include}/signalrclient/transfer_format.h (100%) rename src/SignalR/clients/cpp/{src => include}/signalrclient/websocket_client.h (100%) create mode 100644 src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.h diff --git a/src/SignalR/clients/cpp/src/signalrclient/http_client.h b/src/SignalR/clients/cpp/include/signalrclient/http_client.h similarity index 100% rename from src/SignalR/clients/cpp/src/signalrclient/http_client.h rename to src/SignalR/clients/cpp/include/signalrclient/http_client.h diff --git a/src/SignalR/clients/cpp/src/signalrclient/transfer_format.h b/src/SignalR/clients/cpp/include/signalrclient/transfer_format.h similarity index 100% rename from src/SignalR/clients/cpp/src/signalrclient/transfer_format.h rename to src/SignalR/clients/cpp/include/signalrclient/transfer_format.h diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_client.h b/src/SignalR/clients/cpp/include/signalrclient/websocket_client.h similarity index 100% rename from src/SignalR/clients/cpp/src/signalrclient/websocket_client.h rename to src/SignalR/clients/cpp/include/signalrclient/websocket_client.h diff --git a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj index b39a81768a69..88168fdf9844 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj +++ b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj @@ -41,13 +41,16 @@ + + + @@ -56,7 +59,6 @@ - @@ -65,11 +67,9 @@ - - diff --git a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters index 6615d38f11d3..aceb1a98079d 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters +++ b/src/SignalR/clients/cpp/src/signalrclient/Build/VS/signalrclient.vcxproj.filters @@ -54,9 +54,6 @@ Header Files - - Header Files - Header Files @@ -108,16 +105,19 @@ Header Files - - Header Files - Header Files Header Files - + + Header Files + + + Header Files + + Header Files diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h index 83aacb662cc4..1972c4ebc118 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h @@ -6,7 +6,7 @@ #include #include #include "cpprest/http_client.h" -#include "http_client.h" +#include "signalrclient/http_client.h" #include "signalrclient/trace_level.h" #include "signalrclient/connection_state.h" #include "signalrclient/signalr_client_config.h" diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_http_client.h b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.h index 685213389357..6cffb8d37a11 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_http_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/default_http_client.h @@ -3,7 +3,7 @@ #pragma once -#include "http_client.h" +#include "signalrclient/http_client.h" #include "cpprest/http_client.h" namespace signalr diff --git a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h index 8394882dc7c9..7da7760a1f5c 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h +++ b/src/SignalR/clients/cpp/src/signalrclient/default_websocket_client.h @@ -5,7 +5,7 @@ #include "cpprest/ws_client.h" #include "signalrclient/signalr_client_config.h" -#include "websocket_client.h" +#include "signalrclient/websocket_client.h" namespace signalr { diff --git a/src/SignalR/clients/cpp/src/signalrclient/negotiate.h b/src/SignalR/clients/cpp/src/signalrclient/negotiate.h index 9e5c8d04fd16..a627e533d78c 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/negotiate.h +++ b/src/SignalR/clients/cpp/src/signalrclient/negotiate.h @@ -7,7 +7,7 @@ #include "signalrclient/transport_type.h" #include "web_request_factory.h" #include "negotiation_response.h" -#include "http_client.h" +#include "signalrclient/http_client.h" namespace signalr { diff --git a/src/SignalR/clients/cpp/src/signalrclient/transport.h b/src/SignalR/clients/cpp/src/signalrclient/transport.h index b6daa1703ded..4bb3a5643df4 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/transport.h +++ b/src/SignalR/clients/cpp/src/signalrclient/transport.h @@ -5,7 +5,7 @@ #include "pplx/pplxtasks.h" #include "signalrclient/transport_type.h" -#include "transfer_format.h" +#include "signalrclient/transfer_format.h" #include "logger.h" namespace signalr diff --git a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj index 9219bdff9934..8639ac5c6d29 100644 --- a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj +++ b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj @@ -44,11 +44,14 @@ + + + @@ -56,7 +59,6 @@ - @@ -65,11 +67,9 @@ - - diff --git a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters index c573648c8125..16b4115e9b8d 100644 --- a/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters +++ b/src/SignalR/clients/cpp/src/signalrclientdll/Build/VS/signalrclientdll.vcxproj.filters @@ -57,9 +57,6 @@ Header Files - - Header Files - Header Files @@ -102,16 +99,19 @@ Header Files - - Header Files - Header Files Header Files - + + Header Files + + + Header Files + + Header Files diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.h index ea8f8b55e20a..74f0d4d8bb93 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.h +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_http_client.h @@ -3,7 +3,7 @@ #pragma once -#include "http_client.h" +#include "signalrclient/http_client.h" using namespace signalr; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.h index 2f7f6c9a81ad..6da269c091dd 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.h +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.h @@ -4,7 +4,7 @@ #pragma once #include "transport_factory.h" -#include "websocket_client.h" +#include "signalrclient/websocket_client.h" using namespace signalr; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h index b17abad24eb3..eb7d98c25625 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h @@ -4,9 +4,9 @@ #pragma once #include "cpprest/details/basic_types.h" -#include "websocket_client.h" +#include "signalrclient/websocket_client.h" #include "web_request_factory.h" -#include "http_client.h" +#include "signalrclient/http_client.h" #include std::string remove_date_from_log_entry(const std::string &log_entry); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.h new file mode 100644 index 000000000000..85ec4e460014 --- /dev/null +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.h @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#pragma once + +#include +#include "signalrclient/websocket_client.h" + +using namespace signalr; + +class test_websocket_client : public websocket_client +{ +public: + test_websocket_client(); + + void start(std::string url, transfer_format format, std::function callback) override; + + void stop(std::function callback) override; + + void send(std::string payload, std::function callback) override; + + void receive(std::function callback) override; + + void set_connect_function(std::function)> connect_function); + + void set_send_function(std::function)> send_function); + + void set_receive_function(std::function)> receive_function); + + void set_close_function(std::function)> close_function); + +private: + std::function)> m_connect_function; + + std::function)> m_send_function; + + std::function)> m_receive_function; + + std::function)> m_close_function; +}; From db41d8a28ad8ebd9512e5edf04fcddddfd44be6f Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 25 Mar 2019 09:31:00 -0700 Subject: [PATCH 17/19] fb --- .../BindTests/AddressRegistrationTests.cs | 2 + .../src/signalrclient/websocket_transport.cpp | 77 +++++++------------ 2 files changed, 28 insertions(+), 51 deletions(-) diff --git a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs index 88c37750f604..34bae68a5755 100644 --- a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs +++ b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs @@ -100,6 +100,7 @@ public async Task RegisterIPEndPoint_IPv6StaticPort_Success() [ConditionalTheory] [MemberData(nameof(IPEndPointRegistrationDataDynamicPort))] [IPv6SupportedCondition] + [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2074", "Helix:Queue:All")] public async Task RegisterIPEndPoint_DynamicPort_Success(IPEndPoint endPoint, string testUrl) { await RegisterIPEndPoint_Success(endPoint, testUrl); @@ -447,6 +448,7 @@ public Task DefaultsServerAddress_BindsToIPv4WithHttps() [ConditionalFact] [IPv6SupportedCondition] + [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1756", "Helix:Queue:All")] public Task DefaultsServerAddress_BindsToIPv6WithHttps() { if (!CanBindToEndpoint(IPAddress.Loopback, 5000) || !CanBindToEndpoint(IPAddress.IPv6Loopback, 5000) diff --git a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp index ea487e616e10..85e4f556eecd 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/websocket_transport.cpp @@ -73,72 +73,47 @@ namespace signalr } catch (const std::exception & e) { - cts.cancel(); - logger.log( trace_level::errors, std::string("[websocket transport] error receiving response from websocket: ") .append(e.what())); - - std::promise prom; - websocket_client->stop([&prom](std::exception_ptr exception) - { - if (exception != nullptr) - { - prom.set_exception(exception); - } - else - { - prom.set_value(); - } - }); - try - { - prom.get_future().get(); - } - // We prefer the outer exception bubbling up to the user - // REVIEW: log here? - catch (...) { } - - auto transport = weak_transport.lock(); - if (transport) - { - transport->m_close_callback(exception); - } } catch (...) { - cts.cancel(); - logger.log( trace_level::errors, std::string("[websocket transport] unknown error occurred when receiving response from websocket")); - std::promise prom; - websocket_client->stop([&prom](std::exception_ptr exception) - { - if (exception != nullptr) - { - prom.set_exception(exception); - } - else - { - prom.set_value(); - } - }); - try + exception = std::make_exception_ptr(signalr_exception("unknown error")); + } + + cts.cancel(); + + std::promise promise; + websocket_client->stop([&promise](std::exception_ptr exception) + { + if (exception != nullptr) { - prom.get_future().get(); + promise.set_exception(exception); } - // We prefer the outer exception bubbling up to the user - // REVIEW: log here? - catch (...) {} - - auto transport = weak_transport.lock(); - if (transport) + else { - transport->m_close_callback(std::make_exception_ptr(signalr_exception("unknown error"))); + promise.set_value(); } + }); + + try + { + promise.get_future().get(); + } + // We prefer the outer exception bubbling up to the user + // REVIEW: log here? + catch (...) {} + + auto transport = weak_transport.lock(); + if (transport) + { + transport->m_close_callback(exception); } return; From 0cffafa7c4e2d95c6da0f1919caa1504080aa2a4 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 25 Mar 2019 09:46:56 -0700 Subject: [PATCH 18/19] wrong queue --- .../Kestrel/test/BindTests/AddressRegistrationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs index 34bae68a5755..50fbbfb73dc9 100644 --- a/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs +++ b/src/Servers/Kestrel/test/BindTests/AddressRegistrationTests.cs @@ -100,7 +100,7 @@ public async Task RegisterIPEndPoint_IPv6StaticPort_Success() [ConditionalTheory] [MemberData(nameof(IPEndPointRegistrationDataDynamicPort))] [IPv6SupportedCondition] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2074", "Helix:Queue:All")] + [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/2074", FlakyOn.AzP.macOS)] public async Task RegisterIPEndPoint_DynamicPort_Success(IPEndPoint endPoint, string testUrl) { await RegisterIPEndPoint_Success(endPoint, testUrl); @@ -448,7 +448,7 @@ public Task DefaultsServerAddress_BindsToIPv4WithHttps() [ConditionalFact] [IPv6SupportedCondition] - [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1756", "Helix:Queue:All")] + [Flaky("https://github.com/aspnet/AspNetCore-Internal/issues/1756", FlakyOn.AzP.macOS)] public Task DefaultsServerAddress_BindsToIPv6WithHttps() { if (!CanBindToEndpoint(IPAddress.Loopback, 5000) || !CanBindToEndpoint(IPAddress.IPv6Loopback, 5000) From e091fdee351ee24495e12e8619da29db115bd515 Mon Sep 17 00:00:00 2001 From: Brennan Conroy Date: Mon, 25 Mar 2019 09:48:37 -0700 Subject: [PATCH 19/19] fix rebase --- .../cpp/test/signalrclienttests/test_transport_factory.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.h index 6da269c091dd..f4fdc4761920 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.h +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_transport_factory.h @@ -14,9 +14,7 @@ class test_transport_factory : public transport_factory test_transport_factory(const std::shared_ptr& websocket_client); std::shared_ptr create_transport(transport_type transport_type, const logger& logger, - const signalr_client_config& signalr_client_config, - std::function process_message_callback, - std::function error_callback) override; + const signalr_client_config& signalr_client_config) override; private: std::shared_ptr m_websocket_client;