Add unit tests for environment with fake HTTP server.#139
Add unit tests for environment with fake HTTP server.#139davidbtucker merged 7 commits intomasterfrom
Conversation
| http::client::options options; | ||
| http::client client(options.timeout(2)); | ||
| http::client::request request(kGceMetadataServerAddress + path); | ||
| http::client::request request(config_.GceMetadataServerAddress() + path); |
There was a problem hiding this comment.
I'd rather not make this a configuration parameter. There is no reason for anything but our tests to override this. You could instead make this an instance field of the Environment object, and either add a private constructor that injects it, or a private test-only setter.
There was a problem hiding this comment.
+1 to not making this a config parameter.
| TEST_F(EnvironmentTest, GetMetadataStringWithFakeServer) { | ||
| // Start a server in a separate thread, and allow it to choose an | ||
| // available port. | ||
| FakeHandler handler(std::map<std::string, std::string>({{"/a/b/c", "hello"}})); |
There was a problem hiding this comment.
It might be nicer to follow the style of Python testing and add a SetResponse(path, response) method (maybe even SetResponse(path, code, response))... Then you can fold the handler and the options object into the FakeServer implementation, rather than expose them to clients.
There was a problem hiding this comment.
Done (did not include code yet).
| FakeServer::options options(handler); | ||
| FakeServer server(options.address("127.0.0.1").port("")); | ||
| server.listen(); | ||
| std::thread t1([&server] { server.run(); }); |
There was a problem hiding this comment.
Your fake server is always single-threaded, so you can fold the thread into the server object, regardless (calling join() in the destructor).
| "{\"client_email\":\"user@example.com\",\"private_key\":\"some_key\"}"); | ||
| std::string cfg; | ||
| Configuration config(std::istringstream( | ||
| "CredentialsFile: '" + credentials_file.FullPath().native() + "'\n" |
There was a problem hiding this comment.
Why is this necessary for testing a fake server?
There was a problem hiding this comment.
Aha, it's not! Removed.
| std::string cfg; | ||
| Configuration config(std::istringstream( | ||
| "CredentialsFile: '" + credentials_file.FullPath().native() + "'\n" | ||
| "GceMetadataServerAddress: 'http://invalidaddress'\n" |
There was a problem hiding this comment.
This is fragile, and breaks in environments where an ISP intercepts invalid lookups:
environment_unittest.cc:171: Failure
Expected equality of these values:
""
environment.GetMetadataString("/a/b/c")
Which is: "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><head><meta http-equiv=\"refresh\" content=\"0;url=http://searchassist.verizon.com/main?ParticipantID=euekiz39ksg8nwp7iqj2fp5wzfwi5q76&FailedURI=http%3A%2F%2Finvalidaddress%2Fa%2Fb%2Fc&FailureMode=1&Implementation=&AddInType=4&Version=pywr1.0&ClientLocation=us\"/><script type=\"text/javascript\">url=\"http://searchassist.verizon.com/main?ParticipantID=euekiz39ksg8nwp7iqj2fp5wzfwi5q76&FailedURI=http%3A%2F%2Finvalidaddress%2Fa%2Fb%2Fc&FailureMode=1&Implementation=&AddInType=4&Version=pywr1.0&ClientLocation=us\";if(top.location!=location){var w=window,d=document,e=d.documentElement,b=d.body,x=w.innerWidth||e.clientWidth||b.clientWidth,y=w.innerHeight||e.clientHeight||b.clientHeight;url+=\"&w=\"+x+\"&h=\"+y;}window.location.replace(url);</script></head><body></body></html>"
We should instead make the fake server throw boost::system::system_error on simulated resolution failures.
There was a problem hiding this comment.
I removed this test altogether, as the other test already exercises this catch clause.
| http::client::options options; | ||
| http::client client(options.timeout(2)); | ||
| http::client::request request(kGceMetadataServerAddress + path); | ||
| http::client::request request(config_.GceMetadataServerAddress() + path); |
| TEST_F(EnvironmentTest, GetMetadataStringWithFakeServer) { | ||
| // Start a server in a separate thread, and allow it to choose an | ||
| // available port. | ||
| FakeHandler handler(std::map<std::string, std::string>({{"/a/b/c", "hello"}})); |
There was a problem hiding this comment.
Done (did not include code yet).
| FakeServer::options options(handler); | ||
| FakeServer server(options.address("127.0.0.1").port("")); | ||
| server.listen(); | ||
| std::thread t1([&server] { server.run(); }); |
| "{\"client_email\":\"user@example.com\",\"private_key\":\"some_key\"}"); | ||
| std::string cfg; | ||
| Configuration config(std::istringstream( | ||
| "CredentialsFile: '" + credentials_file.FullPath().native() + "'\n" |
There was a problem hiding this comment.
Aha, it's not! Removed.
| std::string cfg; | ||
| Configuration config(std::istringstream( | ||
| "CredentialsFile: '" + credentials_file.FullPath().native() + "'\n" | ||
| "GceMetadataServerAddress: 'http://invalidaddress'\n" |
There was a problem hiding this comment.
I removed this test altogether, as the other test already exercises this catch clause.
igorpeshansky
left a comment
There was a problem hiding this comment.
Some naming/location nits.
| } else { | ||
| // Note: We have to set headers; otherwise, an exception is thrown. | ||
| connection->set_status(FakeServer::connection::not_found); | ||
| connection->set_headers(std::map<std::string, std::string>({})); |
There was a problem hiding this comment.
It's unfortunate that this bit is necessary, but we can deal with this later.
| Environment::Environment(const Configuration& config) | ||
| : config_(config), application_default_credentials_read_(false) {} | ||
| : config_(config), | ||
| gce_metadata_server_address_(kGceMetadataServerAddress), |
There was a problem hiding this comment.
Let's just call it metadata_server_url_.
|
|
||
| const Configuration& config_; | ||
|
|
||
| std::string gce_metadata_server_address_; |
|
|
||
| const Configuration& config_; | ||
|
|
||
| std::string gce_metadata_server_address_; |
There was a problem hiding this comment.
This is technically cached data as well. Let's put this just before application_default_credentials_read_.
| void ReadApplicationDefaultCredentials() const; | ||
|
|
||
| // Used to override server address in tests. | ||
| void SetGceMetadataServerAddress(const std::string& address); |
There was a problem hiding this comment.
SetMetadataServerUrlForTest, and you can get rid of the comment.
| class FakeServerThread { | ||
| public: | ||
| FakeServerThread() | ||
| : server_(Server::options(handler_).address("127.0.0.1").port("")) { |
There was a problem hiding this comment.
The "empty port" behavior seems to be undocumented. Sigh.
| handler_.path_responses[path] = response; | ||
| } | ||
|
|
||
| std::string HostPort() { |
There was a problem hiding this comment.
Just curious, why HostPort and not GetUrl?
There was a problem hiding this comment.
Changed to GetUrl.
| connection->set_headers(std::map<std::string, std::string>()); | ||
| } | ||
| } | ||
| std::map<std::string, std::string> path_responses; |
There was a problem hiding this comment.
Should this map live in the FakeServer class instead?
There was a problem hiding this comment.
I think I'd have to pass in a FakeServer reference to the Handler, which seems more complicated.
There was a problem hiding this comment.
You could also just pass a const reference to the map... But it's also fine the way it is.
|
|
||
| Handler handler_; | ||
| Server server_; | ||
| std::thread thread_; |
|
|
||
| // Starts a server in a separate thread, allowing it to choose an | ||
| // available port. | ||
| class FakeServerThread { |
There was a problem hiding this comment.
Can we pull this out to a separate header/.cc file now?
davidbtucker
left a comment
There was a problem hiding this comment.
PTAL, thanks!
| Environment::Environment(const Configuration& config) | ||
| : config_(config), application_default_credentials_read_(false) {} | ||
| : config_(config), | ||
| gce_metadata_server_address_(kGceMetadataServerAddress), |
| void ReadApplicationDefaultCredentials() const; | ||
|
|
||
| // Used to override server address in tests. | ||
| void SetGceMetadataServerAddress(const std::string& address); |
|
|
||
| const Configuration& config_; | ||
|
|
||
| std::string gce_metadata_server_address_; |
|
|
||
| const Configuration& config_; | ||
|
|
||
| std::string gce_metadata_server_address_; |
| environment.ReadApplicationDefaultCredentials(); | ||
| } | ||
|
|
||
| static void SetGceMetadataServerAddress(Environment* environment, |
|
|
||
| Handler handler_; | ||
| Server server_; | ||
| std::thread thread_; |
|
|
||
| // Starts a server in a separate thread, allowing it to choose an | ||
| // available port. | ||
| class FakeServerThread { |
|
|
||
| // Starts a server in a separate thread, allowing it to choose an | ||
| // available port. | ||
| class FakeServerThread { |
| connection->set_headers(std::map<std::string, std::string>()); | ||
| } | ||
| } | ||
| std::map<std::string, std::string> path_responses; |
There was a problem hiding this comment.
I think I'd have to pass in a FakeServer reference to the Handler, which seems more complicated.
| void ReadApplicationDefaultCredentials() const; | ||
|
|
||
| // Used to override server address in tests. | ||
| void SetGceMetadataServerAddress(const std::string& address); |
| public: | ||
| FakeServerThread() | ||
| : server_(Server::options(handler_).address("127.0.0.1").port("")) { | ||
| server_.listen(); |
There was a problem hiding this comment.
Cool, can you please add a comment saying exactly that?
| connection->set_headers(std::map<std::string, std::string>()); | ||
| } | ||
| } | ||
| std::map<std::string, std::string> path_responses; |
There was a problem hiding this comment.
You could also just pass a const reference to the map... But it's also fine the way it is.
|
|
||
| void ReadApplicationDefaultCredentials() const; | ||
|
|
||
| void SetMetadataServerUrlForTest(const std::string& url) { |
There was a problem hiding this comment.
You could add a comment saying that the url must end in a '/'...
| EXPECT_EQ("some_key", environment.CredentialsPrivateKey()); | ||
| } | ||
|
|
||
| TEST_F(EnvironmentTest, GetMetadataStringWithFakeServer) { |
There was a problem hiding this comment.
Missed this one before... I'd just call it GetMetadataString — FakeServer is an implementation detail.
| namespace google { | ||
| namespace testing { | ||
|
|
||
| FakeServer::FakeServer() |
There was a problem hiding this comment.
How about a comment along the lines of "an empty port selects a random available port (undocumented)"?
There was a problem hiding this comment.
This isn't a class comment — let's move it to where the empty port is passed in.
| } | ||
|
|
||
| std::string FakeServer::GetUrl() { | ||
| return std::string("http://") + server_.address() + ":" + server_.port() + "/"; |
There was a problem hiding this comment.
[Optional]
network::uri_builder builder;
builder.scheme("http").host(server_.address()).port(server_.port()).path("/");
return builder.uri().to_string();| handler_.path_responses[path] = response; | ||
| } | ||
|
|
||
| void FakeServer::Handler::operator() (Server::request const &request, |
There was a problem hiding this comment.
No space after operator().
|
|
||
| // Handler that maps paths to response strings. | ||
| struct Handler { | ||
| void operator() (Server::request const &request, |
There was a problem hiding this comment.
No space after operator().
igorpeshansky
left a comment
There was a problem hiding this comment.
One remaining comment.
| namespace google { | ||
| namespace testing { | ||
|
|
||
| FakeServer::FakeServer() |
There was a problem hiding this comment.
This isn't a class comment — let's move it to where the empty port is passed in.
| void ReadApplicationDefaultCredentials() const; | ||
|
|
||
| // The url must end in a '/'. | ||
| void SetMetadataServerUrlForTest(const std::string& url) { |
There was a problem hiding this comment.
Why is this only "ForTest"? It seems like a reasonable enough setter function to remove that suffix.
There was a problem hiding this comment.
Just to make it clear that the method only exists because we need to override the URL in test code. If we ever need to use it in production code, we can rename the method.
No description provided.