/* Copyright (c) 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 #include #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 #include #include #include #include #include "config_builder.h" #include "dim.h" #include "mysql/harness/logging/registry.h" #include "mysql/harness/utility/string.h" // ::join #include "mysql_session.h" #include "rest_api_testutils.h" #include "router_component_test.h" #include "tcp_port_pool.h" #include "temp_dir.h" #include "mysqlrouter/rest_client.h" using namespace std::chrono_literals; static const std::string http_auth_realm_name("somerealm"); static const std::string http_auth_backend_name("somebackend"); // init_keyring() creates it static const std::string keyring_username("mysql_router1_user"); static const std::string metadata_cache_section_name("gr_shard_1"); class RestApiTestBase : public RestApiComponentTest { protected: void SetUp() override { RouterComponentTest::SetUp(); default_section_ = get_DEFAULT_defaults(); init_keyring(default_section_, conf_dir_.name()); } std::string passwd_filename_; std::map default_section_; }; static const std::vector kMetadataSwaggerPaths{ {"/metadata/{metadataName}/config", "Get config of the metadata cache of a replicaset of a cluster", "config of metadata cache", "cache not found"}, {"/metadata/{metadataName}/status", "Get status of the metadata cache of a replicaset of a cluster", "status of metadata cache", "cache not found"}, {"/metadata", "Get list of the metadata cache instances", "list of the metadata cache instances", ""}, }; // global variables used by the test size_t g_refresh_failed = 0, g_refresh_succeeded = 0; std::string g_time_last_refresh_succeeded, g_time_last_refresh_failed; #if 0 // precondition to these tests is that we can start a router agianst a metadata-cluster // which has no nodes. But with Bug#28352482 (no empty bootstrap_server_addresses) fixed // we can't bring the metadata into that state anymore. We just won't start. // // An empty dynamic_config file will also not allow to start. // // In case that functionally ever comes back, we'll leave this code around, but disabled. class RestMetadataCacheApiWithoutClusterTest : public RestApiTestBase, public ::testing::WithParamInterface {}; TEST_P(RestMetadataCacheApiWithoutClusterTest, ensure_openapi) { const std::string http_hostname = "127.0.0.1"; const std::string userfile = create_password_file(); const std::string http_uri = GetParam().uri; auto config_sections = get_restapi_config("rest_routing", userfile, GetParam().request_authentication); config_sections.push_back(ConfigBuilder::build_section("rest_api", {})); config_sections.push_back(ConfigBuilder::build_section( "rest_metadata_cache", { {"require_realm", http_auth_realm_name}, })); config_sections.push_back(ConfigBuilder::build_section( "metadata_cache:" + metadata_cache_section_name, { {"user", keyring_username}, {"ttl", "0.2"}, })); std::string conf_file{create_config_file( conf_dir_.name(), mysql_harness::join(config_sections, "\n"), &default_section_)}; ProcessWrapper &http_server{launch_router({"-c", conf_file})}; g_refresh_failed = 0; g_time_last_refresh_failed = ""; fetch_and_validate_schema_and_resource(GetParam(), http_server); // this part is relevant only for Get OK, otherwise let's avoid useless sleep if (GetParam().status_code == HttpMethod::Get && GetParam().methods == HttpStatusCode::Ok) { // sleep a while to make the counters and timestamps change std::this_thread::sleep_for(std::chrono::seconds(1)); // check the resources again, we want to compare them against the previous // ones fetch_and_validate_schema_and_resource(GetParam(), http_server); } } static const RestApiTestParams rest_api_params_without_cluster[]{ // The socpe of WL#12441 was limited and does not include those // {"cluster_list_no_cluster", // std::string(rest_api_basepath) + "/clusters/", // "/clusters", // HttpMethod::Get, // HttpStatusCode::Ok, // kContentTypeJson, // kRestApiUsername, // kRestApiPassword, // /*request_authentication =*/true, // { // {"/items", // [](const JsonValue *value) { // ASSERT_NE(value, nullptr); // ASSERT_TRUE(value->IsArray()); // ASSERT_EQ(value->GetArray().Size(), 0); // }}, // }}, // {"cluster_nodes_no_cluster", // std::string(rest_api_basepath) + "/clusters/71286871562387612/nodes", // "/clusters/{clusterName}/nodes", // HttpMethod::Get, // HttpStatusCode::Ok, // kContentTypeJson, // kRestApiUsername, // kRestApiPassword, // /*request_authentication =*/true, // { // {"/items", // [](const JsonValue *value) { // ASSERT_NE(value, nullptr); // ASSERT_TRUE(value->IsArray()); // ASSERT_EQ(value->GetArray().Size(), 0); // }}, // }}, {"metadata_list_no_cluster", std::string(rest_api_basepath) + "/metadata/", "/metadata", HttpMethod::Get, HttpStatusCode::Ok, kContentTypeJson, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, { {"/items", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsArray()); ASSERT_EQ(value->GetArray().Size(), 1); }}, {"/items/0/name", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_EQ(value->GetString(), metadata_cache_section_name); }}, }, kMetadataSwaggerPaths}, {"metadata_status_no_cluster", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/status", "/metadata/{metadataName}/status", HttpMethod::Get, HttpStatusCode::Ok, kContentTypeJson, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, { {"/refreshFailed", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsInt()); ASSERT_GT(value->GetInt(), 0); // check if it is more than last time we checked ASSERT_GT(value->GetInt(), g_refresh_failed); g_refresh_failed = static_cast(value->GetInt()); }}, {"/refreshSucceeded", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsInt()); ASSERT_EQ(value->GetInt(), 0); }}, {"/timeLastRefreshFailed", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_TRUE(pattern_found(value->GetString(), kTimestampPattern)) << value->GetString(); // check if it is later than last time we checked // timestamp format is YY-MM-DDThh:mm:ss.milisecZ (which we check // above) so lexical string comparison should be fine const std::string currentLastRefreshFailed = value->GetString(); ASSERT_TRUE(currentLastRefreshFailed > g_time_last_refresh_failed); // save the current value g_time_last_refresh_failed = currentLastRefreshFailed; }}, {"/timeLastRefreshSucceeded", [](const JsonValue *value) -> void { ASSERT_EQ(value, nullptr); }}, }, kMetadataSwaggerPaths}, {"metadata_config_no_cluster", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/config", "/metadata/{metadataName}/config", HttpMethod::Get, HttpStatusCode::Ok, kContentTypeJson, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, { {"/clusterName", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_STREQ(value->GetString(), ""); }}, {"/groupReplicationId", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_STREQ(value->GetString(), ""); }}, {"/timeRefreshInMs", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsInt()); ASSERT_EQ(value->GetInt(), 200); }}, }, kMetadataSwaggerPaths}, }; INSTANTIATE_TEST_CASE_P( Spec, RestMetadataCacheApiWithoutClusterTest, ::testing::ValuesIn(rest_api_params_without_cluster), [](const ::testing::TestParamInfo &info) { return info.param.test_name; }); #endif /** * with cluster. */ class RestMetadataCacheApiTest : public RestApiTestBase, public ::testing::WithParamInterface { protected: const uint16_t metadata_server_port_{port_pool_.get_next_available()}; }; TEST_P(RestMetadataCacheApiTest, ensure_openapi) { const std::string http_hostname = "127.0.0.1"; const std::string http_uri = GetParam().uri; auto &md_server = ProcessManager::launch_mysql_server_mock( get_data_dir().join("metadata_1_node_repeat.js").str(), metadata_server_port_, EXIT_SUCCESS, false); const std::string userfile = create_password_file(); auto config_sections = get_restapi_config("rest_routing", userfile, GetParam().request_authentication); config_sections.push_back(ConfigBuilder::build_section("rest_api", {})); config_sections.push_back(ConfigBuilder::build_section( "rest_metadata_cache", { {"require_realm", http_auth_realm_name}, })); config_sections.push_back(ConfigBuilder::build_section( "metadata_cache:" + metadata_cache_section_name, { {"user", keyring_username}, // name of the cluster in the mock's metadata {"metadata_cluster", "test"}, {"ttl", "0.2"}, {"bootstrap_server_addresses", "mysql://127.0.0.1:" + std::to_string(metadata_server_port_)}, })); std::string conf_file{create_config_file( conf_dir_.name(), mysql_harness::join(config_sections, "\n"), &default_section_)}; // delay the wait until we really need it. ASSERT_NO_FATAL_FAILURE( check_port_ready(md_server, metadata_server_port_, 5000ms)); auto &router_proc{launch_router({"-c", conf_file})}; g_refresh_succeeded = 0; g_time_last_refresh_failed = ""; EXPECT_NO_FATAL_FAILURE( fetch_and_validate_schema_and_resource(GetParam(), router_proc)) << router_proc.get_full_output(); // this part is relevant only for Get OK, otherwise let's avoid useless sleep if (GetParam().status_code == HttpMethod::Get && GetParam().methods == HttpStatusCode::Ok) { // sleep a while to make the counters and timestamps change std::this_thread::sleep_for(std::chrono::seconds(1)); // check the resources again, we want to compare them against the previous // ones fetch_and_validate_schema_and_resource(GetParam(), router_proc); } } // **************************************************************************** // Request the resource(s) using supported methods with authentication enabled // and valid credentials // **************************************************************************** static const RestApiTestParams rest_api_valid_methods[]{ // The socpe of WL#12441 was limited and does not include those // {"cluster_list", // std::string(rest_api_basepath) + "/clusters/", // "/clusters", // HttpMethod::Get, // HttpStatusCode::Ok, // kContentTypeJson, // kRestApiUsername, // kRestApiPassword, // /*request_authentication =*/true, // { // {"/items", // [](const JsonValue *value) { // ASSERT_NE(value, nullptr); // ASSERT_TRUE(value->IsArray()); // ASSERT_EQ(value->GetArray().Size(), 0); // }}, // }}, // {"cluster_nodes", // std::string(rest_api_basepath) + "/clusters/71286871562387612/nodes", // "/clusters/{clusterName}/nodes", // HttpMethod::Get, // HttpStatusCode::Ok, // kContentTypeJson, // kRestApiUsername, // kRestApiPassword, // /*request_authentication =*/true, // { // {"/items", // [](const JsonValue *value) { // ASSERT_NE(value, nullptr); // ASSERT_TRUE(value->IsArray()); // ASSERT_EQ(value->GetArray().Size(), 0); // }}, // }}, {"metadata_list", std::string(rest_api_basepath) + "/metadata/", "/metadata", HttpMethod::Get, HttpStatusCode::Ok, kContentTypeJson, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, { {"/items", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsArray()); ASSERT_EQ(value->GetArray().Size(), 1); }}, {"/items/0/name", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_EQ(value->GetString(), metadata_cache_section_name); }}, }, kMetadataSwaggerPaths}, {"metadata_status", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/status", "/metadata/{metadataName}/status", HttpMethod::Get, HttpStatusCode::Ok, kContentTypeJson, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, { {"/refreshFailed", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsInt()); ASSERT_EQ(value->GetInt(), 0); }}, {"/refreshSucceeded", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsInt()); // check if it is more than last time we checked ASSERT_GT(value->GetInt(), g_refresh_succeeded); g_refresh_succeeded = static_cast(value->GetInt()); }}, {"/timeLastRefreshSucceeded", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_TRUE(pattern_found(value->GetString(), kTimestampPattern)) << value->GetString(); // check if it is later than last time we checked // timestamp format is YY-MM-DDThh:mm:ss.milisecZ (which we check // above) so lexical string comparison should be fine const std::string currentLastRefreshSucceeded = value->GetString(); ASSERT_TRUE(currentLastRefreshSucceeded > g_time_last_refresh_succeeded); // save the current value g_time_last_refresh_succeeded = currentLastRefreshSucceeded; }}, {"/timeLastRefreshFailed", [](const JsonValue *value) -> void { ASSERT_EQ(value, nullptr); }}, {"/lastRefreshHostname", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_STRNE(value->GetString(), ""); }}, {"/lastRefreshPort", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsInt()); ASSERT_GT(value->GetInt(), 0); }}, }, kMetadataSwaggerPaths}, {"metadata_config", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/config", "/metadata/{metadataName}/config", HttpMethod::Get, HttpStatusCode::Ok, kContentTypeJson, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, { {"/clusterName", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_STREQ(value->GetString(), "test"); }}, {"/groupReplicationId", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsString()); ASSERT_STREQ(value->GetString(), ""); }}, {"/timeRefreshInMs", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsUint64()); ASSERT_EQ(value->GetUint64(), 200); }}, {"/nodes", [](const JsonValue *value) -> void { ASSERT_NE(value, nullptr); ASSERT_TRUE(value->IsArray()); auto nodes = value->GetArray(); ASSERT_GT(nodes.Size(), 0); for (unsigned i = 0; i < nodes.Size(); ++i) { ASSERT_TRUE(nodes[i].IsObject()); const auto &node = nodes[i].GetObject(); ASSERT_TRUE(node.HasMember("hostname")); ASSERT_TRUE(node["hostname"].IsString()); ASSERT_STRNE(node["hostname"].GetString(), ""); ASSERT_TRUE(node.HasMember("port")); ASSERT_TRUE(node["port"].IsInt()); ASSERT_GT(node["port"].GetInt(), 0); } }}, }, kMetadataSwaggerPaths}, }; INSTANTIATE_TEST_CASE_P( ValidMethods, RestMetadataCacheApiTest, ::testing::ValuesIn(rest_api_valid_methods), [](const ::testing::TestParamInfo &info) { return info.param.test_name; }); // **************************************************************************** // Request non-existing resource(s) using supported methods with authentication // enabled and valid credentials // **************************************************************************** static const RestApiTestParams rest_api_non_existig_resouces[]{ {"metadata_status_non_existing", std::string(rest_api_basepath) + "/metadata/NON_EXISTING/status", "/metadata/{metadataName}/status", HttpMethod::Get, HttpStatusCode::NotFound, kContentTypeJson, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, {}, kMetadataSwaggerPaths}, {"metadata_config_non_existing", std::string(rest_api_basepath) + "/metadata/NON_EXISTING/config", "/metadata/{metadataName}/config", HttpMethod::Get, HttpStatusCode::NotFound, kContentTypeJson, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, {}, kMetadataSwaggerPaths}, {"metadata_unsupported_param", std::string(rest_api_basepath) + "/metadata/?limit=10", "/metadata", HttpMethod::Get, HttpStatusCode::BadRequest, kContentTypeJsonProblem, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, {}, kMetadataSwaggerPaths}, {"metadata_status_unsupported_param", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/status?refreshFailed=0&refreshSucceeded=1", "/metadata/{metadataName}/status", HttpMethod::Get, HttpStatusCode::BadRequest, kContentTypeJsonProblem, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, {}, kMetadataSwaggerPaths}, {"metadata_config_unsupported_param", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/config?refreshFailed=0&refreshSucceeded=1", "/metadata/{metadataName}/config", HttpMethod::Get, HttpStatusCode::BadRequest, kContentTypeJsonProblem, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, {}, kMetadataSwaggerPaths}, }; INSTANTIATE_TEST_CASE_P( NoNexistingResources, RestMetadataCacheApiTest, ::testing::ValuesIn(rest_api_non_existig_resouces), [](const ::testing::TestParamInfo &info) { return info.param.test_name; }); // **************************************************************************** // Request the resource(s) using supported methods with authentication enabled // and invalid credentials // **************************************************************************** static const RestApiTestParams rest_api_valid_methods_invalid_auth_params[]{ // The socpe of WL#12441 was limited and does not include those // {"cluster_list_invalid_auth", // std::string(rest_api_basepath) + "/clusters/", // "/clusters", // HttpMethod::Get, // HttpStatusCode::Unauthorized, // kContentTypeHtmlCharset, // kRestApiUsername, // "invalid password", // /*request_authentication =*/true, // {}}, // {"cluster_nodes_invalid_auth", // std::string(rest_api_basepath) + "/clusters/71286871562387612/nodes", // "/clusters/{clusterName}/nodes", // HttpMethod::Get, // HttpStatusCode::Unauthorized, // kContentTypeHtmlCharset, // kRestApiUsername, // "invalid password", // /*request_authentication =*/true, // {}}, {"metadata_list_invalid_auth", std::string(rest_api_basepath) + "/metadata/", "/metadata", HttpMethod::Get, HttpStatusCode::Unauthorized, kContentTypeHtmlCharset, kRestApiUsername, "invalid password", /*request_authentication =*/true, {}, kMetadataSwaggerPaths}, {"metadata_status_invalid_auth", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/status", "/metadata/{metadataName}/status", HttpMethod::Get, HttpStatusCode::Unauthorized, kContentTypeHtmlCharset, kRestApiUsername, "invalid password", /*request_authentication =*/true, {}, kMetadataSwaggerPaths}, {"metadata_config_invalid_auth", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/config", "/metadata/{metadataName}/config", HttpMethod::Get, HttpStatusCode::Unauthorized, kContentTypeHtmlCharset, kRestApiUsername, "invalid password", /*request_authentication =*/true, {}, kMetadataSwaggerPaths}, }; INSTANTIATE_TEST_CASE_P( ValidMethodsInvalidAuth, RestMetadataCacheApiTest, ::testing::ValuesIn(rest_api_valid_methods_invalid_auth_params), [](const ::testing::TestParamInfo &info) { return info.param.test_name; }); // **************************************************************************** // Request the resource(s) using unsupported methods with authentication enabled // and valid credentials // **************************************************************************** static const RestApiTestParams rest_api_invalid_methods_params[]{ // The socpe of WL#12441 was limited and does not include those // {"cluster_list_invalid_methods", // std::string(rest_api_basepath) + "/clusters/", // "/clusters", // HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch, // HttpStatusCode::MethodNotAllowed, // kContentTypeJsonProblem, // kRestApiUsername, // kRestApiPassword, // /*request_authentication =*/true, // { // {"/status", // [](const JsonValue *value) -> void { // ASSERT_NE(value, nullptr); // ASSERT_TRUE(value->IsInt()); // ASSERT_EQ(value->GetInt(), HttpStatusCode::MethodNotAllowed); // }}, // }}, // {"cluster_nodes_invalid_methods", // std::string(rest_api_basepath) + "/clusters/71286871562387612/nodes", // "/clusters/{clusterName}/nodes", // HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch, // HttpStatusCode::MethodNotAllowed, // kContentTypeJsonProblem, // kRestApiUsername, // kRestApiPassword, // /*request_authentication =*/true, // { // {"/status", // [](const JsonValue *value) -> void { // ASSERT_NE(value, nullptr); // ASSERT_TRUE(value->IsInt()); // ASSERT_EQ(value->GetInt(), HttpStatusCode::MethodNotAllowed); // }}, // }}, {"metadata_list_invalid_methods", std::string(rest_api_basepath) + "/metadata/", "/metadata", HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch | HttpMethod::Head | HttpMethod::Trace | HttpMethod::Options | HttpMethod::Connect, HttpStatusCode::MethodNotAllowed, kContentTypeJsonProblem, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, RestApiComponentTest::kProblemJsonMethodNotAllowed, kMetadataSwaggerPaths}, {"metadata_status_invalid_methods", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/status", "/metadata/{metadataName}/status", HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch | HttpMethod::Head | HttpMethod::Trace | HttpMethod::Options | HttpMethod::Connect, HttpStatusCode::MethodNotAllowed, kContentTypeJsonProblem, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, RestApiComponentTest::kProblemJsonMethodNotAllowed, kMetadataSwaggerPaths}, {"metadata_config_invalid_methods", std::string(rest_api_basepath) + "/metadata/" + metadata_cache_section_name + "/config", "/metadata/{metadataName}/config", HttpMethod::Post | HttpMethod::Delete | HttpMethod::Patch | HttpMethod::Head | HttpMethod::Trace | HttpMethod::Options | HttpMethod::Connect, HttpStatusCode::MethodNotAllowed, kContentTypeJsonProblem, kRestApiUsername, kRestApiPassword, /*request_authentication =*/true, RestApiComponentTest::kProblemJsonMethodNotAllowed, kMetadataSwaggerPaths}, }; INSTANTIATE_TEST_CASE_P( InvalidMethods, RestMetadataCacheApiTest, ::testing::ValuesIn(rest_api_invalid_methods_params), [](const ::testing::TestParamInfo &info) { return info.param.test_name; }); // **************************************************************************** // Configuration errors scenarios // **************************************************************************** /** * @test Try to disable authentication although a REST API endpoint/plugin * defines authentication as a MUST. * */ TEST_F(RestMetadataCacheApiTest, metadata_cache_api_no_auth) { const std::string userfile = create_password_file(); auto config_sections = get_restapi_config("rest_metadata_cache", userfile, /*request_authentication=*/false); // [rest_api] is always required config_sections.push_back(ConfigBuilder::build_section("rest_api", {})); const std::string conf_file{create_config_file( conf_dir_.name(), mysql_harness::join(config_sections, "\n"))}; auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE); const auto wait_for_process_exit_timeout{10000ms}; check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout); const std::string router_output = router.get_full_logfile(); EXPECT_NE( router_output.find("plugin 'rest_metadata_cache' init failed: option " "require_realm in [rest_metadata_cache] is required"), router_output.npos) << router_output; } /** * @test Enable authentication for the plugin in question. Reference a realm * that does not exist in the configuration file. */ TEST_F(RestMetadataCacheApiTest, invalid_realm) { const std::string userfile = create_password_file(); auto config_sections = get_restapi_config("rest_metadata_cache", userfile, /*request_authentication=*/true, "invalidrealm"); // [rest_api] is always required config_sections.push_back(ConfigBuilder::build_section("rest_api", {})); const std::string conf_file{create_config_file( conf_dir_.name(), mysql_harness::join(config_sections, "\n"))}; auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE); const auto wait_for_process_exit_timeout{10000ms}; check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout); const std::string router_output = router.get_full_logfile(); EXPECT_NE(router_output.find( "Configuration error: unknown authentication " "realm for [rest_metadata_cache] '': invalidrealm, known " "realm(s): somerealm"), router_output.npos) << router_output; } /** * @test Start router with the REST routing API plugin [rest_metadata_cache], * [http_plugin] and [metadata_cache] enabled but not the [rest_api] plugin. * */ TEST_F(RestMetadataCacheApiTest, metadata_cache_api_no_rest_api) { const std::string userfile = create_password_file(); auto config_sections = get_restapi_config("rest_metadata_cache", userfile, /*request_authentication=*/false); const std::string conf_file{create_config_file( conf_dir_.name(), mysql_harness::join(config_sections, "\n"))}; auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE); const auto wait_for_process_exit_timeout{10000ms}; check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout); const std::string router_output = router.get_full_output(); EXPECT_NE(router_output.find("Plugin 'rest_metadata_cache' needs plugin " "'rest_api' which is missing in the " "configuration"), router_output.npos) << router_output; } /** * @test Start router with the REST routing API plugin [rest_metadata_cache], * [http_plugin] and [rest_api] enabled but not the [metadata_cache] plugin. * */ // TEST_F(RestMetadataCacheApiTest, metadata_cache_api_no_mdc_secion) { // const std::string userfile = create_password_file(); // auto config_sections = get_restapi_config("rest_metadata_cache", userfile, // /*request_authentication=*/true); // // [rest_api] is always required // config_sections.push_back(ConfigBuilder::build_section("rest_api", {})); // const std::string conf_file{create_config_file( // conf_dir_.name(), mysql_harness::join(config_sections, "\n"))}; // auto router = launch_router({"-c", conf_file}); // const unsigned wait_for_process_exit_timeout{10000}; // check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout); // const std::string router_output = router.get_full_output(); // EXPECT_NE(router_output.find("Plugin 'rest_metadata_cache' needs plugin " // "'metadata_cache' which is missing in the " // "configuration"), // router_output.npos) // << router_output; //} /** * @test Add [rest_metadata_cache] twice to the configuration file. Start * router. Expect router to fail providing an error about the duplicate section. * */ TEST_F(RestMetadataCacheApiTest, rest_metadata_cache_section_twice) { const std::string userfile = create_password_file(); auto config_sections = get_restapi_config("rest_metadata_cache", userfile, /*request_authentication=*/true); // [rest_api] is always required config_sections.push_back(ConfigBuilder::build_section("rest_api", {})); // force [rest_metadata_cache] twice in the config config_sections.push_back( ConfigBuilder::build_section("rest_metadata_cache", {})); const std::string conf_file{create_config_file( conf_dir_.name(), mysql_harness::join(config_sections, "\n"))}; auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE); const auto wait_for_process_exit_timeout{10000ms}; check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout); const std::string router_output = router.get_full_output(); EXPECT_NE( router_output.find( "Configuration error: Section 'rest_metadata_cache' already exists"), router_output.npos) << router_output; } /** * @test Enable [rest_metadata_cache] using a section key such as * [rest_metadata_cache:A]. Start router. Expect router to fail providing an * error about the use of an unsupported section key. * */ TEST_F(RestMetadataCacheApiTest, rest_metadata_cache_section_has_key) { const std::string userfile = create_password_file(); auto config_sections = get_restapi_config("rest_metadata_cache:A", userfile, /*request_authentication=*/true); // [rest_api] is always required config_sections.push_back(ConfigBuilder::build_section("rest_api", {})); const std::string conf_file{create_config_file( conf_dir_.name(), mysql_harness::join(config_sections, "\n"))}; auto &router = launch_router({"-c", conf_file}, EXIT_FAILURE); const auto wait_for_process_exit_timeout{10000ms}; check_exit_code(router, EXIT_FAILURE, wait_for_process_exit_timeout); const std::string router_output = router.get_full_logfile(); EXPECT_NE( router_output.find( "plugin 'rest_metadata_cache' init failed: [rest_metadata_cache] " "section does not expect a key, found 'A'"), router_output.npos) << router_output; } int main(int argc, char *argv[]) { init_windows_sockets(); ProcessManager::set_origin(Path(argv[0]).dirname()); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }