diff --git a/include/boost/redis/impl/request.ipp b/include/boost/redis/impl/request.ipp index 0e15b19a..f0a08e0c 100644 --- a/include/boost/redis/impl/request.ipp +++ b/include/boost/redis/impl/request.ipp @@ -18,6 +18,8 @@ auto has_response(std::string_view cmd) -> bool return true; if (cmd == "UNSUBSCRIBE") return true; + if (cmd == "PUNSUBSCRIBE") + return true; return false; } diff --git a/test/common.cpp b/test/common.cpp index 7095a75b..f0533bf3 100644 --- a/test/common.cpp +++ b/test/common.cpp @@ -68,3 +68,19 @@ void run_coroutine_test(net::awaitable op, std::chrono::steady_clock::dura throw std::runtime_error("Coroutine test did not finish"); } #endif // BOOST_ASIO_HAS_CO_AWAIT + +// Finds a value in the output of the CLIENT INFO command +// format: key1=value1 key2=value2 +// TODO: duplicated +std::string_view find_client_info(std::string_view client_info, std::string_view key) +{ + std::string prefix{key}; + prefix += '='; + + auto const pos = client_info.find(prefix); + if (pos == std::string_view::npos) + return {}; + auto const pos_begin = pos + prefix.size(); + auto const pos_end = client_info.find(' ', pos_begin); + return client_info.substr(pos_begin, pos_end - pos_begin); +} diff --git a/test/common.hpp b/test/common.hpp index 4c260692..af190580 100644 --- a/test/common.hpp +++ b/test/common.hpp @@ -11,6 +11,7 @@ #include #include +#include // The timeout for tests involving communication to a real server. // Some tests use a longer timeout by multiplying this value by some @@ -35,3 +36,7 @@ void run( boost::redis::config cfg = make_test_config(), boost::system::error_code ec = boost::asio::error::operation_aborted, boost::redis::operation op = boost::redis::operation::receive); + +// Finds a value in the output of the CLIENT INFO command +// format: key1=value1 key2=value2 +std::string_view find_client_info(std::string_view client_info, std::string_view key); diff --git a/test/test_conn_push.cpp b/test/test_conn_push.cpp index 1049c615..c3f1c339 100644 --- a/test/test_conn_push.cpp +++ b/test/test_conn_push.cpp @@ -6,10 +6,14 @@ #include #include +#include +#include #include #include +#include + #define BOOST_TEST_MODULE conn_push #include @@ -327,4 +331,71 @@ BOOST_AUTO_TEST_CASE(many_subscribers) BOOST_TEST(finished); } +BOOST_AUTO_TEST_CASE(test_unsubscribe) +{ + net::io_context ioc; + connection conn{ioc}; + + // Subscribe to 3 channels and 2 patterns. Use CLIENT INFO to verify this took effect + request req_subscribe; + req_subscribe.push("SUBSCRIBE", "ch1", "ch2", "ch3"); + req_subscribe.push("PSUBSCRIBE", "ch1*", "ch2*"); + req_subscribe.push("CLIENT", "INFO"); + + // Then, unsubscribe from some of them, and verify again + request req_unsubscribe; + req_unsubscribe.push("UNSUBSCRIBE", "ch1"); + req_unsubscribe.push("PUNSUBSCRIBE", "ch2*"); + req_unsubscribe.push("CLIENT", "INFO"); + + // Finally, ping to verify that the connection is still usable + request req_ping; + req_ping.push("PING", "test_unsubscribe"); + + response resp_subscribe, resp_unsubscribe, resp_ping; + + bool subscribe_finished = false, unsubscribe_finished = false, ping_finished = false, + run_finished = false; + + auto on_ping = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); + ping_finished = true; + BOOST_TEST(std::get<0>(resp_ping).has_value()); + BOOST_TEST(std::get<0>(resp_ping).value() == "test_unsubscribe"); + conn.cancel(); + }; + + auto on_unsubscribe = [&](error_code ec, std::size_t) { + unsubscribe_finished = true; + BOOST_TEST(ec == error_code()); + BOOST_TEST(std::get<0>(resp_unsubscribe).has_value()); + BOOST_TEST(find_client_info(std::get<0>(resp_unsubscribe).value(), "sub") == "2"); + BOOST_TEST(find_client_info(std::get<0>(resp_unsubscribe).value(), "psub") == "1"); + conn.async_exec(req_ping, resp_ping, on_ping); + }; + + auto on_subscribe = [&](error_code ec, std::size_t) { + subscribe_finished = true; + BOOST_TEST(ec == error_code()); + BOOST_TEST(std::get<0>(resp_subscribe).has_value()); + BOOST_TEST(find_client_info(std::get<0>(resp_subscribe).value(), "sub") == "3"); + BOOST_TEST(find_client_info(std::get<0>(resp_subscribe).value(), "psub") == "2"); + conn.async_exec(req_unsubscribe, resp_unsubscribe, on_unsubscribe); + }; + + conn.async_exec(req_subscribe, resp_subscribe, on_subscribe); + + conn.async_run(make_test_config(), [&run_finished](error_code ec) { + BOOST_TEST(ec == net::error::operation_aborted); + run_finished = true; + }); + + ioc.run_for(test_timeout); + + BOOST_TEST(subscribe_finished); + BOOST_TEST(unsubscribe_finished); + BOOST_TEST(ping_finished); + BOOST_TEST(run_finished); +} + } // namespace diff --git a/test/test_conn_setup.cpp b/test/test_conn_setup.cpp index 3c66cfeb..52d7b017 100644 --- a/test/test_conn_setup.cpp +++ b/test/test_conn_setup.cpp @@ -29,21 +29,6 @@ using boost::system::error_code; namespace { -// Finds a value in the output of the CLIENT INFO command -// format: key1=value1 key2=value2 -std::string_view find_client_info(std::string_view client_info, std::string_view key) -{ - std::string prefix{key}; - prefix += '='; - - auto const pos = client_info.find(prefix); - if (pos == std::string_view::npos) - return {}; - auto const pos_begin = pos + prefix.size(); - auto const pos_end = client_info.find(' ', pos_begin); - return client_info.substr(pos_begin, pos_end - pos_begin); -} - // Creates a user with a known password. Harmless if the user already exists void setup_password() {