diff --git a/src/environment.cc b/src/environment.cc index bc9ac79b..c3d2e843 100644 --- a/src/environment.cc +++ b/src/environment.cc @@ -78,12 +78,14 @@ constexpr const char kGceInstanceResourceType[] = "gce_instance"; } Environment::Environment(const Configuration& config) - : config_(config), application_default_credentials_read_(false) {} + : config_(config), + metadata_server_url_(kGceMetadataServerAddress), + application_default_credentials_read_(false) {} std::string Environment::GetMetadataString(const std::string& path) const { http::client::options options; http::client client(options.timeout(2)); - http::client::request request(kGceMetadataServerAddress + path); + http::client::request request(metadata_server_url_ + path); request << boost::network::header("Metadata-Flavor", "Google"); try { http::client::response response = client.get(request); @@ -98,7 +100,7 @@ std::string Environment::GetMetadataString(const std::string& path) const { } } catch (const boost::system::system_error& e) { LOG(ERROR) << "Exception: " << e.what() - << ": '" << kGceMetadataServerAddress << path << "'"; + << ": '" << metadata_server_url_ << path << "'"; return ""; } } diff --git a/src/environment.h b/src/environment.h index aa356b61..2ca4c079 100644 --- a/src/environment.h +++ b/src/environment.h @@ -48,6 +48,11 @@ class Environment { void ReadApplicationDefaultCredentials() const; + // The url must end in a '/'. + void SetMetadataServerUrlForTest(const std::string& url) { + metadata_server_url_ = url; + } + const Configuration& config_; // Cached data. @@ -60,6 +65,7 @@ class Environment { mutable std::string kubernetes_cluster_location_; mutable std::string client_email_; mutable std::string private_key_; + mutable std::string metadata_server_url_; mutable bool application_default_credentials_read_; }; diff --git a/test/Makefile b/test/Makefile index 5129a492..3ad7a3bc 100644 --- a/test/Makefile +++ b/test/Makefile @@ -121,7 +121,7 @@ base64_unittest: base64_unittest.o $(SRC_DIR)/base64.o $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ configuration_unittest: configuration_unittest.o $(SRC_DIR)/configuration.o $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ -environment_unittest: environment_unittest.o $(SRC_DIR)/environment.o $(SRC_DIR)/configuration.o $(SRC_DIR)/format.o $(SRC_DIR)/json.o $(SRC_DIR)/logging.o $(SRC_DIR)/time.o +environment_unittest: environment_unittest.o fake_http_server.o $(SRC_DIR)/environment.o $(SRC_DIR)/configuration.o $(SRC_DIR)/format.o $(SRC_DIR)/json.o $(SRC_DIR)/logging.o $(SRC_DIR)/time.o $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ format_unittest: format_unittest.o $(SRC_DIR)/format.o $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@ diff --git a/test/environment_unittest.cc b/test/environment_unittest.cc index 09ab5655..a76e530b 100644 --- a/test/environment_unittest.cc +++ b/test/environment_unittest.cc @@ -1,4 +1,5 @@ #include "../src/environment.h" +#include "fake_http_server.h" #include "gtest/gtest.h" #include @@ -12,9 +13,15 @@ class EnvironmentTest : public ::testing::Test { static void ReadApplicationDefaultCredentials(const Environment& environment) { environment.ReadApplicationDefaultCredentials(); } + + static void SetMetadataServerUrlForTest(Environment* environment, + const std::string& url) { + environment->SetMetadataServerUrlForTest(url); + } }; namespace { + // A file with a given name in a temporary (unique) directory. boost::filesystem::path TempPath(const std::string& filename) { boost::filesystem::path path = boost::filesystem::temp_directory_path(); @@ -42,6 +49,7 @@ class TemporaryFile { private: boost::filesystem::path path_; }; + } // namespace TEST(TemporaryFile, Basic) { @@ -70,7 +78,6 @@ TEST_F(EnvironmentTest, ReadApplicationDefaultCredentialsSucceeds) { TemporaryFile credentials_file( std::string(test_info_->name()) + "_creds.json", "{\"client_email\":\"user@example.com\",\"private_key\":\"some_key\"}"); - std::string cfg; Configuration config(std::istringstream( "CredentialsFile: '" + credentials_file.FullPath().native() + "'\n" )); @@ -98,4 +105,17 @@ TEST_F(EnvironmentTest, ReadApplicationDefaultCredentialsCaches) { ); EXPECT_EQ("some_key", environment.CredentialsPrivateKey()); } + +TEST_F(EnvironmentTest, GetMetadataString) { + testing::FakeServer server; + server.SetResponse("/a/b/c", "hello"); + + Configuration config; + Environment environment(config); + SetMetadataServerUrlForTest(&environment, server.GetUrl()); + + EXPECT_EQ("hello", environment.GetMetadataString("a/b/c")); + EXPECT_EQ("", environment.GetMetadataString("unknown/path")); +} + } // namespace google diff --git a/test/fake_http_server.cc b/test/fake_http_server.cc new file mode 100644 index 00000000..d5980dec --- /dev/null +++ b/test/fake_http_server.cc @@ -0,0 +1,47 @@ +#include "fake_http_server.h" + +namespace google { +namespace testing { + +FakeServer::FakeServer() + // Note: An empty port selects a random available port (this behavior + // is not documented). + : server_(Server::options(handler_).address("127.0.0.1").port("")) { + server_.listen(); + server_thread_ = std::thread([this] { server_.run(); }); +} + +FakeServer::~FakeServer() { + server_.stop(); + server_thread_.join(); +} + +std::string FakeServer::GetUrl() { + network::uri_builder builder; + builder.scheme("http").host(server_.address()).port(server_.port()).path("/"); + return builder.uri().string(); +} + +void FakeServer::SetResponse(const std::string& path, + const std::string& response) { + handler_.path_responses[path] = response; +} + +void FakeServer::Handler::operator()(Server::request const &request, + Server::connection_ptr connection) { + auto it = path_responses.find(request.destination); + if (it != path_responses.end()) { + connection->set_status(Server::connection::ok); + connection->set_headers(std::map({ + {"Content-Type", "text/plain"}, + })); + connection->write(it->second); + } else { + // Note: We have to set headers; otherwise, an exception is thrown. + connection->set_status(Server::connection::not_found); + connection->set_headers(std::map()); + } +} + +} // testing +} // google diff --git a/test/fake_http_server.h b/test/fake_http_server.h new file mode 100644 index 00000000..7eb5881a --- /dev/null +++ b/test/fake_http_server.h @@ -0,0 +1,38 @@ +#ifndef FAKE_HTTP_SERVER_H_ +#define FAKE_HTTP_SERVER_H_ + +#include + +namespace google { +namespace testing { + +// Starts a server in a separate thread, allowing it to choose an +// available port. +class FakeServer { + public: + FakeServer(); + ~FakeServer(); + + std::string GetUrl(); + void SetResponse(const std::string& path, const std::string& response); + + private: + struct Handler; + typedef boost::network::http::server Server; + + // Handler that maps paths to response strings. + struct Handler { + void operator()(Server::request const &request, + Server::connection_ptr connection); + std::map path_responses; + }; + + Handler handler_; + Server server_; + std::thread server_thread_; +}; + +} // testing +} // google + +#endif // FAKE_HTTP_SERVER_H_