1080 lines
36 KiB
C++
1080 lines
36 KiB
C++
/*
|
|
Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <thread>
|
|
|
|
#ifdef RAPIDJSON_NO_SIZETYPEDEFINE
|
|
// if we build within the server, it will set RAPIDJSON_NO_SIZETYPEDEFINE
|
|
// globally and require to include my_rapidjson_size_t.h
|
|
#include "my_rapidjson_size_t.h"
|
|
#endif
|
|
|
|
#include <rapidjson/document.h>
|
|
#include "dim.h"
|
|
#include "gmock/gmock.h"
|
|
#include "mock_server_rest_client.h"
|
|
#include "mysql/harness/logging/registry.h"
|
|
#include "mysql_session.h"
|
|
#include "rest_api_testutils.h"
|
|
#include "router_component_test.h"
|
|
#include "tcp_port_pool.h"
|
|
|
|
#include "mysqlrouter/rest_client.h"
|
|
|
|
Path g_origin_path;
|
|
|
|
static constexpr const char kMockServerConnectionsRestUri[] =
|
|
"/api/v1/mock_server/connections/";
|
|
static constexpr const char kMockServerInvalidRestUri[] =
|
|
"/api/v1/mock_server/global/";
|
|
|
|
// AddressSanitizer gets confused by the default, MemoryPoolAllocator
|
|
// Solaris sparc also gets crashes
|
|
using JsonDocument =
|
|
rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator>;
|
|
using JsonValue =
|
|
rapidjson::GenericValue<rapidjson::UTF8<>, rapidjson::CrtAllocator>;
|
|
|
|
class RestMockServerTest : public RouterComponentTest {
|
|
protected:
|
|
TcpPortPool port_pool_;
|
|
};
|
|
|
|
/**
|
|
* base class.
|
|
*
|
|
* starts mock-server with named script and waits for it to startup
|
|
*/
|
|
class RestMockServerScriptTest : public RestMockServerTest {
|
|
protected:
|
|
RestMockServerScriptTest(const std::string &stmt_file)
|
|
: server_port_{port_pool_.get_next_available()},
|
|
http_port_{port_pool_.get_next_available()},
|
|
json_stmts_{get_data_dir().join(stmt_file).str()},
|
|
server_mock_{launch_mysql_server_mock(
|
|
json_stmts_, server_port_, EXIT_SUCCESS, false, http_port_)} {
|
|
SCOPED_TRACE("// start mock-server with http-port");
|
|
|
|
const std::string http_hostname{"127.0.0.1"};
|
|
|
|
check_port_ready(server_mock_, server_port_);
|
|
}
|
|
|
|
const uint16_t server_port_;
|
|
const uint16_t http_port_;
|
|
const std::string json_stmts_;
|
|
|
|
ProcessWrapper &server_mock_;
|
|
};
|
|
|
|
class RestMockServerScriptsWorkTest
|
|
: public RestMockServerTest,
|
|
public ::testing::WithParamInterface<std::string> {};
|
|
|
|
class RestMockServerScriptsThrowsTest
|
|
: public RestMockServerTest,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<const char *, const char *>> {};
|
|
|
|
class RestMockServerConnectThrowsTest
|
|
: public RestMockServerTest,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<const char *, const char *>> {};
|
|
|
|
class RestMockServerRestServerMockTest : public RestMockServerScriptTest {
|
|
public:
|
|
RestMockServerRestServerMockTest()
|
|
: RestMockServerScriptTest("rest_server_mock.js") {}
|
|
};
|
|
|
|
/**
|
|
* test mock-server loaded the REST bridge.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL12118
|
|
* - TS_1-6
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, get_globals_empty) {
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint");
|
|
ASSERT_TRUE(wait_for_rest_endpoint_ready(http_uri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req = rest_client.request_sync(HttpMethod::Get, http_uri);
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(req.get_response_code(), 200u);
|
|
EXPECT_THAT(req.get_input_headers().get("Content-Type"),
|
|
::testing::StrEq("application/json"));
|
|
|
|
auto resp_body = req.get_input_buffer();
|
|
EXPECT_GT(resp_body.length(), 0u);
|
|
auto resp_body_content = resp_body.pop_front(resp_body.length());
|
|
|
|
// parse json
|
|
|
|
std::string json_payload(resp_body_content.begin(), resp_body_content.end());
|
|
|
|
JsonDocument json_doc;
|
|
json_doc.Parse(json_payload.c_str());
|
|
|
|
EXPECT_TRUE(!json_doc.HasParseError()) << json_payload;
|
|
}
|
|
|
|
template <typename T>
|
|
std::string unit();
|
|
|
|
template <>
|
|
std::string unit<std::chrono::seconds>() {
|
|
return "s";
|
|
}
|
|
|
|
template <>
|
|
std::string unit<std::chrono::milliseconds>() {
|
|
return "ms";
|
|
}
|
|
|
|
template <>
|
|
std::string unit<std::chrono::microseconds>() {
|
|
return "us";
|
|
}
|
|
|
|
template <>
|
|
std::string unit<std::chrono::nanoseconds>() {
|
|
return "ns";
|
|
}
|
|
|
|
// must be defined in the same namespace as the type we want to convert
|
|
namespace std {
|
|
|
|
// add to-stream method for all durations for pretty printing
|
|
template <class Rep, class Per>
|
|
void PrintTo(const chrono::duration<Rep, Per> &span, std::ostream *os) {
|
|
*os << span.count() << unit<typename std::decay<decltype(span)>::type>();
|
|
}
|
|
|
|
} // namespace std
|
|
|
|
/**
|
|
* test handshake's exec_time can be set via globals.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, handshake_exec_time_via_global) {
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
// handshake exec_time to test
|
|
const auto kDelay = std::chrono::milliseconds{100};
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint");
|
|
ASSERT_TRUE(wait_for_rest_endpoint_ready(http_uri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req = rest_client.request_sync(
|
|
HttpMethod::Put, http_uri,
|
|
"{\"connect_exec_time\": " + std::to_string(kDelay.count()) + "}");
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(req.get_response_code(), 204u);
|
|
|
|
auto resp_body = req.get_input_buffer();
|
|
EXPECT_EQ(resp_body.length(), 0u);
|
|
|
|
SCOPED_TRACE("// slow connect");
|
|
auto start_tp = std::chrono::steady_clock::now();
|
|
{
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
ASSERT_NO_THROW(client.connect("127.0.0.1", server_port_, "username",
|
|
"password", "", ""))
|
|
<< server_mock_.get_full_output();
|
|
}
|
|
|
|
// this test is very vague on how to write a stable test:
|
|
//
|
|
// on a slow box creating the TCP connection itself may be slow
|
|
// which may make the test positive even though exec_time was not honoured.
|
|
//
|
|
// On the other side we can't compare the timespan against
|
|
// a non-delayed connect as the external connect time depends
|
|
// on what else happens on the system while the tests are running
|
|
EXPECT_GT(std::chrono::steady_clock::now() - start_tp, kDelay);
|
|
}
|
|
|
|
/**
|
|
* test mock-server's REST bridge denies unknown URLs.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL12118
|
|
* - TS_1-7
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, unknown_url_fails) {
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerInvalidRestUri;
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for HTTP server listening");
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock_, http_port_));
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req = rest_client.request_sync(HttpMethod::Get, http_uri);
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl;
|
|
|
|
EXPECT_EQ(req.get_response_code(), 404u);
|
|
EXPECT_THAT(req.get_input_headers().get("Content-Type"),
|
|
::testing::StrEq("text/html"));
|
|
|
|
auto resp_body = req.get_input_buffer();
|
|
EXPECT_GT(resp_body.length(), 0u);
|
|
auto resp_body_content = resp_body.pop_front(resp_body.length());
|
|
}
|
|
|
|
/**
|
|
* test storing globals in mock_server via REST bridge.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, put_globals_no_json) {
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint");
|
|
ASSERT_TRUE(wait_for_rest_endpoint_ready(http_uri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req = rest_client.request_sync(HttpMethod::Put, http_uri);
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(req.get_response_code(), 415u);
|
|
|
|
auto resp_body = req.get_input_buffer();
|
|
EXPECT_EQ(resp_body.length(), 0u);
|
|
}
|
|
|
|
/**
|
|
* ensure PUT against / fails.
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL12118
|
|
* - TS_1-10
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, put_root_fails) {
|
|
const std::string http_hostname{"127.0.0.1"};
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint");
|
|
ASSERT_TRUE(
|
|
wait_for_rest_endpoint_ready(kMockServerGlobalsRestUri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req = rest_client.request_sync(HttpMethod::Put, "/", "{}");
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(req.get_response_code(), 404u);
|
|
|
|
auto resp_body = req.get_input_buffer();
|
|
EXPECT_NE(resp_body.length(), 0u);
|
|
}
|
|
|
|
class RestMockServerRequireTest : public RestMockServerScriptTest {
|
|
protected:
|
|
RestMockServerRequireTest()
|
|
: RestMockServerScriptTest("js_test_require.js") {}
|
|
};
|
|
|
|
class RestMockServerRequirePTest : public RestMockServerRequireTest,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<const char *, const char *>> {
|
|
};
|
|
|
|
/**
|
|
* ensure require() honours load-order.
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL11861
|
|
* - TS_1-8
|
|
*/
|
|
TEST_P(RestMockServerRequirePTest, require) {
|
|
// mysql query
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
ASSERT_NO_THROW(
|
|
client.connect("127.0.0.1", server_port_, "username", "password", "", ""))
|
|
<< server_mock_.get_full_output();
|
|
|
|
EXPECT_NO_THROW({
|
|
std::unique_ptr<mysqlrouter::MySQLSession::ResultRow> result{
|
|
client.query_one(std::get<0>(GetParam()))};
|
|
ASSERT_NE(nullptr, result.get());
|
|
ASSERT_EQ(1u, result->size());
|
|
EXPECT_EQ(std::string((*result)[0]), std::get<1>(GetParam()));
|
|
});
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
js_require_paths, RestMockServerRequirePTest,
|
|
::testing::Values(std::make_tuple("direct", "direct"),
|
|
std::make_tuple("dir-with-indexjs", "dir-with-index.js"),
|
|
std::make_tuple("dir-with-packagejson",
|
|
"dir-with-package.json")));
|
|
|
|
/**
|
|
* ensure require() only loads and evalutes modules once.
|
|
*
|
|
* js_test_require.js requires the same module twice which exposes
|
|
* a counter function.
|
|
*
|
|
* calling the counter via the first module, and via the 2nd module
|
|
* should both increment the same counter if a module is only
|
|
* loaded once.
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL11861
|
|
* - TS_1-7
|
|
*/
|
|
TEST_F(RestMockServerRequireTest, no_reload) {
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
|
|
// mysql query
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
ASSERT_NO_THROW(
|
|
client.connect("127.0.0.1", server_port_, "username", "password", "", ""))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// via first module");
|
|
EXPECT_NO_THROW({
|
|
std::unique_ptr<mysqlrouter::MySQLSession::ResultRow> result{
|
|
client.query_one("no-reload-0")};
|
|
ASSERT_NE(nullptr, result.get());
|
|
ASSERT_EQ(1u, result->size());
|
|
EXPECT_EQ(std::string((*result)[0]), "0");
|
|
});
|
|
|
|
SCOPED_TRACE("// via 2nd module");
|
|
EXPECT_NO_THROW({
|
|
std::unique_ptr<mysqlrouter::MySQLSession::ResultRow> result{
|
|
client.query_one("no-reload-1")};
|
|
ASSERT_NE(nullptr, result.get());
|
|
ASSERT_EQ(1u, result->size());
|
|
EXPECT_EQ(std::string((*result)[0]), "1");
|
|
});
|
|
}
|
|
|
|
class RestMockServerNestingTest : public RestMockServerScriptTest {
|
|
protected:
|
|
RestMockServerNestingTest()
|
|
: RestMockServerScriptTest("js_test_nesting.js") {}
|
|
};
|
|
|
|
/**
|
|
* ensure require() can be deeply nested.
|
|
*
|
|
*
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL11861
|
|
* - TS_1-10
|
|
*/
|
|
TEST_F(RestMockServerNestingTest, nesting) {
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
|
|
// mysql query
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
ASSERT_THROW_LIKE(
|
|
client.connect("127.0.0.1", server_port_, "username", "password", "", ""),
|
|
mysqlrouter::MySQLSession::Error,
|
|
"test-require-nesting-5.js:5: SyntaxError: parse error");
|
|
}
|
|
|
|
class RestMockServerMethodsTest
|
|
: public RestMockServerRestServerMockTest,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<unsigned int, std::string, unsigned int>> {};
|
|
|
|
/**
|
|
* ensure OPTIONS, HEAD and others work.
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL12118
|
|
* - TS_1-11
|
|
*/
|
|
TEST_P(RestMockServerMethodsTest, methods_avail) {
|
|
const std::string http_hostname{"127.0.0.1"};
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
const std::string uri = std::get<1>(GetParam());
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint: " + uri);
|
|
ASSERT_TRUE(wait_for_rest_endpoint_ready(uri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req = rest_client.request_sync(std::get<0>(GetParam()), uri);
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(std::get<2>(GetParam()), req.get_response_code());
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
api__v1__mock_server__globals, RestMockServerMethodsTest,
|
|
::testing::Values(
|
|
std::make_tuple(HttpMethod::Get, kMockServerGlobalsRestUri,
|
|
HttpStatusCode::Ok),
|
|
std::make_tuple(HttpMethod::Put, kMockServerGlobalsRestUri,
|
|
HttpStatusCode::UnsupportedMediaType),
|
|
std::make_tuple(HttpMethod::Delete, kMockServerGlobalsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Trace, kMockServerGlobalsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Options, kMockServerGlobalsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Connect, kMockServerGlobalsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Head, kMockServerGlobalsRestUri,
|
|
HttpStatusCode::MethodNotAllowed)));
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
api__v1__mock_server__connections, RestMockServerMethodsTest,
|
|
::testing::Values(
|
|
std::make_tuple(HttpMethod::Get, kMockServerConnectionsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Put, kMockServerConnectionsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Delete, kMockServerConnectionsRestUri,
|
|
HttpStatusCode::Ok),
|
|
std::make_tuple(HttpMethod::Trace, kMockServerConnectionsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Options, kMockServerConnectionsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Connect, kMockServerConnectionsRestUri,
|
|
HttpStatusCode::MethodNotAllowed),
|
|
std::make_tuple(HttpMethod::Head, kMockServerConnectionsRestUri,
|
|
HttpStatusCode::MethodNotAllowed)));
|
|
|
|
/**
|
|
* test storing globals in mock_server via REST bridge.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, put_globals_ok) {
|
|
SCOPED_TRACE("// start mock-server with http-port");
|
|
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint");
|
|
ASSERT_TRUE(wait_for_rest_endpoint_ready(http_uri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req = rest_client.request_sync(HttpMethod::Put, http_uri, "{}");
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(req.get_response_code(), 204u);
|
|
|
|
auto resp_body = req.get_input_buffer();
|
|
EXPECT_EQ(resp_body.length(), 0u);
|
|
}
|
|
|
|
class RestMockServerRequestTest
|
|
: public RestMockServerRestServerMockTest,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<int, const char *, const char *, unsigned int>> {};
|
|
|
|
/**
|
|
* ensure valid and invalid JSON results in the correct behaviour.
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL12118
|
|
* - TS_1-8
|
|
*/
|
|
TEST_P(RestMockServerRequestTest, request) {
|
|
SCOPED_TRACE("// start mock-server with http-port");
|
|
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint");
|
|
ASSERT_TRUE(wait_for_rest_endpoint_ready(http_uri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req =
|
|
rest_client.request_sync(std::get<0>(GetParam()), std::get<1>(GetParam()),
|
|
std::get<2>(GetParam()));
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(req.get_response_code(), std::get<3>(GetParam()));
|
|
|
|
// if we executed a PUT against globals which was not meant to succeed,
|
|
// check the globals are unchanged.
|
|
if (std::get<0>(GetParam()) == HttpMethod::Put &&
|
|
std::get<1>(GetParam()) == std::string(kMockServerGlobalsRestUri) &&
|
|
std::get<3>(GetParam()) != HttpStatusCode::NoContent) {
|
|
auto get_req = rest_client.request_sync(HttpMethod::Get, http_uri);
|
|
SCOPED_TRACE("// checking GET response");
|
|
ASSERT_TRUE(get_req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << get_req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(get_req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << get_req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(get_req.get_response_code(), 200u);
|
|
EXPECT_THAT(get_req.get_input_headers().get("Content-Type"),
|
|
::testing::StrEq("application/json"));
|
|
|
|
auto get_resp_body = get_req.get_input_buffer();
|
|
EXPECT_GT(get_resp_body.length(), 0u);
|
|
auto get_resp_body_content =
|
|
get_resp_body.pop_front(get_resp_body.length());
|
|
|
|
// parse json
|
|
|
|
std::string json_payload(get_resp_body_content.begin(),
|
|
get_resp_body_content.end());
|
|
|
|
JsonDocument json_doc;
|
|
json_doc.Parse(json_payload.c_str());
|
|
|
|
EXPECT_TRUE(!json_doc.HasParseError());
|
|
EXPECT_THAT(json_payload, ::testing::StrEq("{}"));
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
api__v1__mock_server__globals, RestMockServerRequestTest,
|
|
::testing::Values(
|
|
// parse error
|
|
std::make_tuple(HttpMethod::Put, kMockServerGlobalsRestUri, "[",
|
|
HttpStatusCode::UnprocessableEntity),
|
|
|
|
// not an object
|
|
std::make_tuple(HttpMethod::Put, kMockServerGlobalsRestUri, "[]",
|
|
HttpStatusCode::UnprocessableEntity),
|
|
|
|
// parse-error
|
|
std::make_tuple(HttpMethod::Put, kMockServerGlobalsRestUri, "{1}",
|
|
HttpStatusCode::UnprocessableEntity),
|
|
|
|
// not-an-object
|
|
std::make_tuple(HttpMethod::Put, kMockServerGlobalsRestUri, "1",
|
|
HttpStatusCode::UnprocessableEntity)));
|
|
|
|
/**
|
|
* test storing globals in mock_server via REST bridge.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL12118
|
|
* - TS_1-6
|
|
* - TS_1-9
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, put_globals_and_read_back) {
|
|
SCOPED_TRACE("// start mock-server with http-port");
|
|
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint");
|
|
ASSERT_TRUE(wait_for_rest_endpoint_ready(http_uri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
auto put_req = rest_client.request_sync(HttpMethod::Put, http_uri,
|
|
"{\"key\": [ [1, 2, 3 ] ]}");
|
|
|
|
SCOPED_TRACE("// checking PUT response");
|
|
ASSERT_TRUE(put_req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << put_req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(put_req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << put_req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(put_req.get_response_code(), 204u);
|
|
|
|
auto put_resp_body = put_req.get_input_buffer();
|
|
EXPECT_EQ(put_resp_body.length(), 0u);
|
|
|
|
// GET request
|
|
|
|
auto get_req = rest_client.request_sync(HttpMethod::Get, http_uri);
|
|
SCOPED_TRACE("// checking GET response");
|
|
ASSERT_TRUE(get_req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << get_req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(get_req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << get_req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(get_req.get_response_code(), 200u);
|
|
EXPECT_THAT(get_req.get_input_headers().get("Content-Type"),
|
|
::testing::StrEq("application/json"));
|
|
|
|
auto get_resp_body = get_req.get_input_buffer();
|
|
EXPECT_GT(get_resp_body.length(), 0u);
|
|
auto get_resp_body_content = get_resp_body.pop_front(get_resp_body.length());
|
|
|
|
// parse json
|
|
|
|
std::string json_payload(get_resp_body_content.begin(),
|
|
get_resp_body_content.end());
|
|
|
|
JsonDocument json_doc;
|
|
json_doc.Parse(json_payload.c_str());
|
|
|
|
EXPECT_TRUE(!json_doc.HasParseError());
|
|
EXPECT_THAT(json_payload, ::testing::StrEq("{\"key\":[[1,2,3]]}"));
|
|
}
|
|
|
|
/**
|
|
* test DELETE connections.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*
|
|
* verifies:
|
|
*
|
|
* - WL12118
|
|
* - TS_1-2
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, delete_all_connections) {
|
|
SCOPED_TRACE("// start mock-server with http-port");
|
|
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerConnectionsRestUri;
|
|
|
|
IOContext io_ctx;
|
|
RestClient rest_client(io_ctx, http_hostname, http_port_);
|
|
|
|
SCOPED_TRACE("// wait for REST endpoint");
|
|
ASSERT_TRUE(wait_for_rest_endpoint_ready(http_uri, http_port_))
|
|
<< server_mock_.get_full_output();
|
|
|
|
// mysql query
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
ASSERT_NO_THROW(
|
|
client.connect("127.0.0.1", server_port_, "username", "password", "", ""))
|
|
<< server_mock_.get_full_output();
|
|
|
|
SCOPED_TRACE("// check connection works");
|
|
std::unique_ptr<mysqlrouter::MySQLSession::ResultRow> result{
|
|
client.query_one("select @@port")};
|
|
ASSERT_NE(nullptr, result.get());
|
|
ASSERT_EQ(1u, result->size());
|
|
EXPECT_EQ(std::to_string(server_port_), std::string((*result)[0]));
|
|
|
|
SCOPED_TRACE("// make a http connections");
|
|
auto req = rest_client.request_sync(HttpMethod::Delete, http_uri, "{}");
|
|
|
|
SCOPED_TRACE("// checking HTTP response");
|
|
ASSERT_TRUE(req) << "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_)
|
|
<< " failed (early): " << req.error_msg() << std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
ASSERT_GT(req.get_response_code(), 0u)
|
|
<< "HTTP Request to " << http_hostname << ":"
|
|
<< std::to_string(http_port_) << " failed: " << req.error_msg()
|
|
<< std::endl
|
|
<< server_mock_.get_full_output() << std::endl;
|
|
|
|
EXPECT_EQ(req.get_response_code(), 200u);
|
|
|
|
auto resp_body = req.get_input_buffer();
|
|
EXPECT_EQ(resp_body.length(), 0u);
|
|
|
|
SCOPED_TRACE("// check connection is killed");
|
|
EXPECT_THROW_LIKE(client.query_one("select @@port"),
|
|
mysqlrouter::MySQLSession::Error,
|
|
"Lost connection to MySQL server during query");
|
|
}
|
|
|
|
/**
|
|
* ensure @@port reported by mock is real port.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*/
|
|
TEST_F(RestMockServerRestServerMockTest, select_port) {
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
// mysql query
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
ASSERT_NO_THROW(
|
|
client.connect("127.0.0.1", server_port_, "username", "password", "", ""))
|
|
<< server_mock_.get_full_output();
|
|
|
|
EXPECT_NO_THROW({
|
|
std::unique_ptr<mysqlrouter::MySQLSession::ResultRow> result{
|
|
client.query_one("select @@port")};
|
|
ASSERT_NE(nullptr, result.get());
|
|
ASSERT_EQ(1u, result->size());
|
|
EXPECT_EQ(std::to_string(server_port_), std::string((*result)[0]));
|
|
});
|
|
}
|
|
|
|
// make pretty param-names
|
|
static std::string sanitize_param_name(const std::string &name) {
|
|
std::string p{name};
|
|
for (auto &c : p) {
|
|
if (!isalpha(c) && !isdigit(c)) c = '_';
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* ensure connect returns error.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*/
|
|
TEST_P(RestMockServerConnectThrowsTest, js_test_stmts_is_string) {
|
|
SCOPED_TRACE("// start mock-server with http-port");
|
|
|
|
const auto server_port = port_pool_.get_next_available();
|
|
const auto http_port = port_pool_.get_next_available();
|
|
const std::string json_stmts =
|
|
get_data_dir().join(std::get<0>(GetParam())).str();
|
|
auto &server_mock = launch_mysql_server_mock(json_stmts, server_port,
|
|
EXIT_SUCCESS, false, http_port);
|
|
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
|
|
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
ASSERT_THROW_LIKE(
|
|
client.connect("127.0.0.1", server_port, "username", "password", "", ""),
|
|
mysqlrouter::MySQLSession::Error, std::get<1>(GetParam()));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
ScriptsFails, RestMockServerConnectThrowsTest,
|
|
::testing::Values(
|
|
std::make_tuple("js_test_parse_error.js",
|
|
"parse error"), // WL11861 TS-1_2
|
|
std::make_tuple("js_test_stmts_is_string.js",
|
|
"expected 'stmts' to be"), // WL11861 TS-1_4
|
|
std::make_tuple("js_test_empty_file.js",
|
|
"expected statement handler to return an object, got "
|
|
"primitive, undefined"), // WL11861 TS-1_4
|
|
std::make_tuple("js_test_handshake_greeting_exec_time_is_empty.js",
|
|
"exec_time must be a number, if set. Is object"),
|
|
std::make_tuple(
|
|
"js_test_handshake_is_string.js",
|
|
"handshake must be an object, if set. Is primitive, string")),
|
|
[](const ::testing::TestParamInfo<std::tuple<const char *, const char *>>
|
|
&info) -> std::string {
|
|
return sanitize_param_name(std::get<0>(info.param));
|
|
});
|
|
|
|
/**
|
|
* ensure int fields in 'columns' can't be negative.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
* - run a query which triggers the server-side exception
|
|
*/
|
|
TEST_P(RestMockServerScriptsThrowsTest, scripts_throws) {
|
|
SCOPED_TRACE("// start mock-server with http-port");
|
|
|
|
const unsigned server_port = port_pool_.get_next_available();
|
|
const unsigned http_port = port_pool_.get_next_available();
|
|
const std::string json_stmts =
|
|
get_data_dir().join(std::get<0>(GetParam())).str();
|
|
auto &server_mock = launch_mysql_server_mock(json_stmts, server_port,
|
|
EXIT_SUCCESS, false, http_port);
|
|
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
|
|
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
ASSERT_NO_THROW(
|
|
client.connect("127.0.0.1", server_port, "username", "password", "", ""));
|
|
|
|
SCOPED_TRACE("// select @@port");
|
|
ASSERT_THROW_LIKE(client.query_one("select @@port"),
|
|
mysqlrouter::MySQLSession::Error, std::get<1>(GetParam()));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
ScriptsFails, RestMockServerScriptsThrowsTest,
|
|
::testing::Values(
|
|
std::make_tuple("js_test_stmts_result_has_negative_int.js",
|
|
"value out-of-range for field \"decimals\""),
|
|
std::make_tuple(
|
|
"js_test_stmts_result_has_infinity.js",
|
|
"value out-of-range for field \"decimals\""), // WL11861 TS-1_11
|
|
std::make_tuple("js_test_stmts_result_has_repeat.js",
|
|
"repeat is not supported"), // WL11861 TS-1_5
|
|
std::make_tuple("js_test_stmts_is_empty.js",
|
|
"executing statement failed: Unsupported command in "
|
|
"handle_statement()")),
|
|
[](const ::testing::TestParamInfo<std::tuple<const char *, const char *>>
|
|
&info) -> std::string {
|
|
return sanitize_param_name(std::get<0>(info.param));
|
|
});
|
|
|
|
/**
|
|
* ensure script works.
|
|
*
|
|
* - start the mock-server
|
|
* - make a client connect to the mock-server
|
|
*/
|
|
TEST_P(RestMockServerScriptsWorkTest, scripts_work) {
|
|
SCOPED_TRACE("// start mock-server with http-port");
|
|
|
|
const auto server_port = port_pool_.get_next_available();
|
|
const auto http_port = port_pool_.get_next_available();
|
|
const std::string json_stmts = get_data_dir().join(GetParam()).str();
|
|
auto &server_mock = launch_mysql_server_mock(json_stmts, server_port,
|
|
EXIT_SUCCESS, false, http_port);
|
|
|
|
std::string http_hostname = "127.0.0.1";
|
|
std::string http_uri = kMockServerGlobalsRestUri;
|
|
|
|
ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
|
|
|
|
mysqlrouter::MySQLSession client;
|
|
|
|
SCOPED_TRACE("// connecting via mysql protocol");
|
|
ASSERT_NO_THROW(
|
|
client.connect("127.0.0.1", server_port, "username", "password", "", ""));
|
|
|
|
SCOPED_TRACE("// select @@port");
|
|
ASSERT_NO_THROW(client.execute("select @@port"));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
ScriptsWork, RestMockServerScriptsWorkTest,
|
|
::testing::Values("simple-client.js", "js_test_handshake_is_empty.js",
|
|
"js_test_handshake_greeting_is_empty.js",
|
|
"js_test_handshake_greeting_exec_time_is_number.js",
|
|
"js_test_stmts_is_array.js",
|
|
"js_test_stmts_is_coroutine.js",
|
|
"js_test_stmts_is_function.js"),
|
|
[](const ::testing::TestParamInfo<std::string> &info) -> std::string {
|
|
return sanitize_param_name(info.param);
|
|
});
|
|
|
|
static void init_DIM() {
|
|
mysql_harness::DIM &dim = mysql_harness::DIM::instance();
|
|
|
|
// logging facility
|
|
dim.set_LoggingRegistry(
|
|
[]() {
|
|
static mysql_harness::logging::Registry registry;
|
|
return ®istry;
|
|
},
|
|
[](mysql_harness::logging::Registry *) {} // don't delete our static!
|
|
);
|
|
mysql_harness::logging::Registry ®istry = dim.get_LoggingRegistry();
|
|
|
|
mysql_harness::logging::create_module_loggers(
|
|
registry, mysql_harness::logging::LogLevel::kWarning,
|
|
{mysql_harness::logging::kMainLogger, "sql"},
|
|
mysql_harness::logging::kMainLogger);
|
|
mysql_harness::logging::create_main_log_handler(registry, "", "", true);
|
|
|
|
registry.set_ready();
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
init_windows_sockets();
|
|
init_DIM();
|
|
ProcessManager::set_origin(Path(argv[0]).dirname());
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|