/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) * * Distributed under the Boost Software License, Version 1.0. (See * accompanying file LICENSE.txt) */ #include #define BOOST_TEST_MODULE conn_exec_error #include #include "common.hpp" #include #include namespace net = boost::asio; namespace redis = boost::redis; namespace resp3 = redis::resp3; using error_code = boost::system::error_code; using boost::redis::connection; using boost::redis::request; using boost::redis::response; using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::error; using boost::redis::operation; using namespace std::chrono_literals; namespace { BOOST_AUTO_TEST_CASE(no_ignore_error) { request req; // HELLO expects a number, by feeding a string we should get a simple error. req.push("HELLO", "not-a-number"); net::io_context ioc; auto conn = std::make_shared(ioc); bool exec_finished = false; conn->async_exec(req, ignore, [&](error_code ec, std::size_t) { exec_finished = true; BOOST_TEST(ec == error::resp3_simple_error); conn->cancel(operation::run); conn->cancel(operation::reconnection); }); run(conn); ioc.run_for(test_timeout); BOOST_TEST(exec_finished); } BOOST_AUTO_TEST_CASE(has_diagnostic) { request req; // HELLO expects a number, by feeding a string we should get a simple error. req.push("HELLO", "not-a-number"); // The second command should be also executed. Notice PING does not // require resp3. req.push("PING", "Barra do Una"); net::io_context ioc; auto conn = std::make_shared(ioc); response resp; bool exec_finished = false; conn->async_exec(req, resp, [&](error_code ec, std::size_t) { exec_finished = true; BOOST_TEST(ec == error_code()); // HELLO BOOST_TEST(std::get<0>(resp).has_error()); BOOST_TEST(std::get<0>(resp).error().data_type == resp3::type::simple_error); auto const diag = std::get<0>(resp).error().diagnostic; BOOST_TEST(!std::empty(diag)); std::cout << "has_diagnostic: " << diag << std::endl; // PING BOOST_TEST(std::get<1>(resp).has_value()); BOOST_TEST(std::get<1>(resp).value() == "Barra do Una"); conn->cancel(operation::run); conn->cancel(operation::reconnection); }); run(conn); ioc.run_for(test_timeout); BOOST_TEST(exec_finished); } BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) { request req1; req1.push("HELLO", "3"); req1.push("PING", "req1-msg1"); req1.push("PING", "req1-msg2", "extra arg"); // Error. req1.push("PING", "req1-msg3"); // Should run ok. response resp1; request req2; req2.push("PING", "req2-msg1"); response resp2; net::io_context ioc; auto conn = std::make_shared(ioc); bool c2_called = false, c1_called = false; auto c2 = [&](error_code ec, std::size_t) { c2_called = true; BOOST_TEST(ec == error_code()); BOOST_TEST(std::get<0>(resp2).has_value()); BOOST_TEST(std::get<0>(resp2).value() == "req2-msg1"); conn->cancel(operation::run); conn->cancel(operation::reconnection); }; auto c1 = [&](error_code ec, std::size_t) { c1_called = true; BOOST_TEST(ec == error_code()); BOOST_TEST(std::get<2>(resp1).has_error()); BOOST_TEST(std::get<2>(resp1).error().data_type == resp3::type::simple_error); auto const diag = std::get<2>(resp1).error().diagnostic; BOOST_TEST(!std::empty(diag)); std::cout << "resp3_error_in_cmd_pipeline: " << diag << std::endl; BOOST_TEST(std::get<3>(resp1).has_value()); BOOST_TEST(std::get<3>(resp1).value() == "req1-msg3"); conn->async_exec(req2, resp2, c2); }; conn->async_exec(req1, resp1, c1); run(conn); ioc.run_for(test_timeout); BOOST_TEST(c1_called); BOOST_TEST(c2_called); } BOOST_AUTO_TEST_CASE(error_in_transaction) { request req; req.push("HELLO", 3); req.push("MULTI"); req.push("PING"); req.push("PING", "msg2", "error"); // Error. req.push("PING"); req.push("EXEC"); req.push("PING"); response< ignore_t, // hello ignore_t, // multi ignore_t, // ping ignore_t, // ping ignore_t, // ping response, // exec std::string // ping > resp; net::io_context ioc; auto conn = std::make_shared(ioc); bool finished = false; conn->async_exec(req, resp, [&](error_code ec, std::size_t) { finished = true; BOOST_TEST(ec == error_code()); BOOST_TEST(std::get<0>(resp).has_value()); BOOST_TEST(std::get<1>(resp).has_value()); BOOST_TEST(std::get<2>(resp).has_value()); BOOST_TEST(std::get<3>(resp).has_value()); BOOST_TEST(std::get<4>(resp).has_value()); BOOST_TEST(std::get<5>(resp).has_value()); // Test errors in the pipeline commands. BOOST_TEST(std::get<0>(std::get<5>(resp).value()).has_value()); BOOST_TEST(std::get<0>(std::get<5>(resp).value()).value() == "PONG"); // The ping in the transaction that should be an error. BOOST_TEST(std::get<1>(std::get<5>(resp).value()).has_error()); BOOST_TEST( std::get<1>(std::get<5>(resp).value()).error().data_type == resp3::type::simple_error); auto const diag = std::get<1>(std::get<5>(resp).value()).error().diagnostic; BOOST_TEST(!std::empty(diag)); // The ping thereafter in the transaction should not be an error. BOOST_TEST(std::get<2>(std::get<5>(resp).value()).has_value()); BOOST_TEST(std::get<2>(std::get<5>(resp).value()).value() == "PONG"); // The command right after the pipeline should be successful. BOOST_TEST(std::get<6>(resp).has_value()); BOOST_TEST(std::get<6>(resp).value() == "PONG"); conn->cancel(operation::run); conn->cancel(operation::reconnection); }); run(conn); ioc.run_for(test_timeout); BOOST_TEST(finished); } // This test is important because a SUBSCRIBE command has no response // on success, but does on error. for example when using a wrong // syntax, the server will send a simple error response the client is // not expecting. // // Sending the subscribe after the ping command below is just a // convenience to avoid have it merged in a pipeline making things // even more complex. For example, without a ping, we might get the // sequence HELLO + SUBSCRIBE + PING where the hello and ping are // automatically sent by the implementation. In this case, if the // subscribe syntax is wrong, redis will send a response, which does // not exist on success. That response will be interpreted as the // response to the PING command that comes thereafter and won't be // forwarded to the receive_op, resulting in a difficult to handle // error. BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) { request req1; req1.push("PING"); request req2; req2.push("SUBSCRIBE"); // Wrong command syntax. net::io_context ioc; auto conn = std::make_shared(ioc); bool c1_called = false, c2_called = false, c3_called = false; auto c2 = [&](error_code ec, std::size_t) { c2_called = true; std::cout << "async_exec: subscribe" << std::endl; BOOST_TEST(ec == error_code()); }; auto c1 = [&](error_code ec, std::size_t) { c1_called = true; std::cout << "async_exec: hello" << std::endl; BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c2); }; conn->async_exec(req1, ignore, c1); generic_response gresp; conn->set_receive_response(gresp); auto c3 = [&](error_code ec, std::size_t) { c3_called = true; std::cout << "async_receive" << std::endl; BOOST_TEST(!ec); BOOST_TEST(gresp.has_error()); BOOST_CHECK_EQUAL(gresp.error().data_type, resp3::type::simple_error); BOOST_TEST(!std::empty(gresp.error().diagnostic)); std::cout << gresp.error().diagnostic << std::endl; conn->cancel(operation::run); conn->cancel(operation::reconnection); }; conn->async_receive(c3); run(conn); ioc.run_for(test_timeout); BOOST_TEST(c1_called); BOOST_TEST(c2_called); BOOST_TEST(c3_called); } } // namespace