diff options
author | tonychun <tonychun@google.com> | 2015-07-10 11:45:30 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-07-10 18:47:27 +0000 |
commit | e3fe98489be2fe1f9e504fa5c6ac1f2e205fd561 (patch) | |
tree | 5a6148898eb7ab4069a91cf2ff795610019fb968 /remoting/test | |
parent | 740b0fa6a5b957093f117a9076475df8ae5dbfa6 (diff) | |
download | chromium_src-e3fe98489be2fe1f9e504fa5c6ac1f2e205fd561.zip chromium_src-e3fe98489be2fe1f9e504fa5c6ac1f2e205fd561.tar.gz chromium_src-e3fe98489be2fe1f9e504fa5c6ac1f2e205fd561.tar.bz2 |
Adding the ChromotingHostListFetcher and its respective unittests.
The ChromotingHostListFetcher requests a host list from the Directory service (currently a REST call). The rest call is parsed and creates a ChromotingHostInfo struct instance, which encapsulates important host information. Once the host list of ChromotingHostInfos is created, it is returned to the callback.
BUG=
Review URL: https://codereview.chromium.org/1212333011
Cr-Commit-Position: refs/heads/master@{#338328}
Diffstat (limited to 'remoting/test')
-rw-r--r-- | remoting/test/chromoting_test_driver.cc | 103 | ||||
-rw-r--r-- | remoting/test/host_info.cc | 74 | ||||
-rw-r--r-- | remoting/test/host_info.h | 39 | ||||
-rw-r--r-- | remoting/test/host_list_fetcher.cc | 115 | ||||
-rw-r--r-- | remoting/test/host_list_fetcher.h | 74 | ||||
-rw-r--r-- | remoting/test/host_list_fetcher_unittest.cc | 444 |
6 files changed, 822 insertions, 27 deletions
diff --git a/remoting/test/chromoting_test_driver.cc b/remoting/test/chromoting_test_driver.cc index 5641f1c..bae3ca7 100644 --- a/remoting/test/chromoting_test_driver.cc +++ b/remoting/test/chromoting_test_driver.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include <string> +#include <vector> #include "base/bind.h" #include "base/command_line.h" @@ -16,6 +17,8 @@ #include "google_apis/google_api_keys.h" #include "net/base/escape.h" #include "remoting/test/access_token_fetcher.h" +#include "remoting/test/host_info.h" +#include "remoting/test/host_list_fetcher.h" #include "remoting/test/refresh_token_store.h" #include "testing/gtest/include/gtest/gtest.h" @@ -59,17 +62,23 @@ void PrintUsage() { printf("************************************\n"); printf("\nUsage:\n"); - printf(" chromoting_test_driver --username=<example@gmail.com> [options]\n"); + printf(" chromoting_test_driver --username=<example@gmail.com> [options]" + " --hostname=<example hostname>\n"); printf("\nRequired Parameters:\n"); printf(" %s: Specifies which account to use when running tests\n", switches::kUserNameSwitchName); + printf(" %s: Specifies which host to connect to when running tests\n", + switches::kHostNameSwitchName); printf("\nOptional Parameters:\n"); printf(" %s: Exchanged for a refresh and access token for authentication\n", switches::kAuthCodeSwitchName); - printf(" %s: Path to a JSON file containing username/refresh_token KVPs\n", - switches::kRefreshTokenPathSwitchName); printf(" %s: Displays additional usage information\n", switches::kHelpSwitchName); + printf(" %s: Path to a JSON file containing username/refresh_token KVPs\n", + switches::kRefreshTokenPathSwitchName); + printf(" %s: Specifies the optional logging level of the tool (0-3)." + " [default: off]\n", + switches::kLoggingLevelSwitchName); } void PrintAuthCodeInfo() { @@ -88,24 +97,25 @@ void PrintAuthCodeInfo() { printf("\n has been revoked or expired.\n"); printf(" Passing in the same auth code twice will result in an error\n"); - printf( - "\nFollow these steps to produce an auth code:\n" - " - Open the Authorization URL link shown below in your browser\n" - " - Approve the requested permissions for the tool\n" - " - Copy the 'code' value in the redirected URL\n" - " - Run the tool and pass in copied auth code as a parameter\n"); + printf("\nFollow these steps to produce an auth code:\n" + " - Open the Authorization URL link shown below in your browser\n" + " - Approve the requested permissions for the tool\n" + " - Copy the 'code' value in the redirected URL\n" + " - Run the tool and pass in copied auth code as a parameter\n"); printf("\nAuthorization URL:\n"); printf("%s\n", GetAuthorizationCodeUri().c_str()); printf("\nRedirected URL Example:\n"); - printf( - "https://chromoting-oauth.talkgadget.google.com/talkgadget/oauth/" - "chrome-remote-desktop/dev?code=4/AKtf...\n"); + printf("https://chromoting-oauth.talkgadget.google.com/talkgadget/oauth/" + "chrome-remote-desktop/dev?code=4/AKtf...\n"); printf("\nTool usage example with the newly created auth code:\n"); - printf("chromoting_test_driver --%s=example@gmail.com --%s=4/AKtf...\n\n", - switches::kUserNameSwitchName, switches::kAuthCodeSwitchName); + printf("chromoting_test_driver --%s=example@gmail.com --%s=example_host_name" + " --%s=4/AKtf...\n\n", + switches::kUserNameSwitchName, + switches::kHostNameSwitchName, + switches::kAuthCodeSwitchName); } void PrintJsonFileInfo() { @@ -122,21 +132,40 @@ void PrintJsonFileInfo() { printf("}\n\n"); printf("\nTool usage example:\n"); - printf("chromoting_test_driver --%s=%s --%s=./example_file.json\n\n", + printf("chromoting_test_driver --%s=%s --%s=example_host_name" + " --%s=./example_file.json\n\n", switches::kUserNameSwitchName, "username1@fauxdomain.com", - switches::kRefreshTokenPathSwitchName); + switches::kHostNameSwitchName, switches::kRefreshTokenPathSwitchName); } -} // namespace +} // namespace + +void OnHostlistRetrieved( + base::Closure done_closure, + std::vector<remoting::test::HostInfo>* hostlist, + const std::vector<remoting::test::HostInfo>& retrieved_hostlist) { + + VLOG(1) << "OnHostlistRetrieved() Called"; + + DCHECK(hostlist); + + *hostlist = retrieved_hostlist; + + VLOG(1) << "There are " << hostlist->size() << " hosts in the hostlist"; + + done_closure.Run(); +} void OnAccessTokenRetrieved( base::Closure done_closure, - const std::string& access_token, - const std::string& refresh_token) { + std::string* access_token, + const std::string& retrieved_access_token, + const std::string& retrieved_refresh_token) { - DVLOG(1) << "OnAccessTokenRetrieved() Called"; + VLOG(1) << "OnAccessTokenRetrieved() Called"; + VLOG(1) << "Access Token: " << retrieved_access_token; - DVLOG(1) << "Access Token: " << access_token; + *access_token = retrieved_access_token; done_closure.Run(); } @@ -183,7 +212,7 @@ int main(int argc, char* argv[]) { LOG(ERROR) << "No username passed in, can't authenticate or run tests!"; return -1; } - DVLOG(1) << "Running chromoting tests as: " << username; + VLOG(1) << "Running chromoting tests as: " << username; // Check to see if the user passed in a one time use auth_code for // refreshing their credentials. @@ -194,7 +223,7 @@ int main(int argc, char* argv[]) { command_line->GetSwitchValuePath(switches::kRefreshTokenPathSwitchName); // The hostname determines which host to initiate a session with from the list - // returned from the Directory service. + // returned from the directory service. std::string hostname = command_line->GetSwitchValueASCII(switches::kHostNameSwitchName); @@ -202,7 +231,7 @@ int main(int argc, char* argv[]) { LOG(ERROR) << "No hostname passed in, connect to host requires hostname!"; return -1; } - DVLOG(1) << "Chromoting tests will connect to: " << hostname; + VLOG(1) << "Chromoting tests will connect to: " << hostname; // TODO(TonyChun): Move this logic into a shared environment class. scoped_ptr<remoting::test::RefreshTokenStore> refresh_token_store = @@ -221,10 +250,17 @@ int main(int argc, char* argv[]) { // Uses the refresh token to get the access token from GAIA. remoting::test::AccessTokenFetcher access_token_fetcher; - base::RunLoop run_loop; + // A RunLoop that yields to the thread's MessageLoop. + scoped_ptr<base::RunLoop> run_loop; + // RunLoop to handle callback from GAIA. + run_loop.reset(new base::RunLoop()); + + std::string access_token; remoting::test::AccessTokenCallback access_token_callback = - base::Bind(&OnAccessTokenRetrieved, run_loop.QuitClosure()); + base::Bind(&OnAccessTokenRetrieved, + run_loop->QuitClosure(), + &access_token); if (!auth_code.empty()) { access_token_fetcher.GetAccessTokenFromAuthCode(auth_code, @@ -235,7 +271,20 @@ int main(int argc, char* argv[]) { access_token_callback); } - run_loop.Run(); + run_loop->Run(); + + // RunLoop to handle callback from directory service. + run_loop.reset(new base::RunLoop()); + + std::vector<remoting::test::HostInfo> hostlist; + remoting::test::HostListFetcher::HostlistCallback hostlist_request_callback = + base::Bind(&OnHostlistRetrieved, run_loop->QuitClosure(), &hostlist); + + // Uses the access token to get the hostlist from the directory service. + remoting::test::HostListFetcher hostlist_fetcher; + hostlist_fetcher.RetrieveHostlist(access_token, hostlist_request_callback); + + run_loop->Run(); return 0; } diff --git a/remoting/test/host_info.cc b/remoting/test/host_info.cc new file mode 100644 index 0000000..341829f --- /dev/null +++ b/remoting/test/host_info.cc @@ -0,0 +1,74 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/test/host_info.h" + +#include "base/logging.h" + +namespace remoting { +namespace test { + +HostInfo::HostInfo() { +} + +HostInfo::~HostInfo() { +} + +bool HostInfo::ParseHostInfo(const base::DictionaryValue& host_info) { + const base::ListValue* list_value = nullptr; + + // Add TokenUrlPatterns to HostInfo. + if (host_info.GetList("tokenUrlPatterns", &list_value)) { + if (!list_value->empty()) { + for (base::Value* item : *list_value) { + std::string token_url_pattern; + if (!item->GetAsString(&token_url_pattern)) { + return false; + } + token_url_patterns.push_back(token_url_pattern); + } + } + } + + std::string response_status; + host_info.GetString("status", &response_status); + if (response_status == "ONLINE") { + status = kHostStatusOnline; + } else if (response_status == "OFFLINE") { + status = kHostStatusOffline; + } else { + LOG(ERROR) << "Response Status is " << response_status; + return false; + } + + if (!host_info.GetString("hostId", &host_id)) { + LOG(ERROR) << "hostId was not found in host_info"; + return false; + } + + if (!host_info.GetString("hostName", &host_name)) { + LOG(ERROR) << "hostName was not found in host_info"; + return false; + } + + if (!host_info.GetString("publicKey", &public_key)) { + LOG(ERROR) << "publicKey was not found for " << host_name; + return false; + } + + // If the host entry was created but the host was never online, then the jid + // is never set. + if (!host_info.GetString("jabberId", &host_jid) && + status == kHostStatusOnline) { + LOG(ERROR) << host_name << " is online but is missing a jabberId"; + return false; + } + + host_info.GetString("hostOfflineReason", &offline_reason); + + return true; +} + +} // namespace test +} // namespace remoting diff --git a/remoting/test/host_info.h b/remoting/test/host_info.h new file mode 100644 index 0000000..06348fb --- /dev/null +++ b/remoting/test/host_info.h @@ -0,0 +1,39 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_TEST_HOST_INFO_H_ +#define REMOTING_TEST_HOST_INFO_H_ + +#include <string> +#include <vector> + +#include "base/values.h" + +namespace remoting { +namespace test { + +enum HostStatus { + kHostStatusOnline, + kHostStatusOffline +}; + +struct HostInfo { + HostInfo(); + ~HostInfo(); + + bool ParseHostInfo(const base::DictionaryValue& host_info); + + std::string host_id; + std::string host_jid; + std::string host_name; + HostStatus status = kHostStatusOffline; + std::string offline_reason; + std::string public_key; + std::vector<std::string> token_url_patterns; +}; + +} // namespace test +} // namespace remoting + +#endif // REMOTING_TEST_HOST_INFO_H_ diff --git a/remoting/test/host_list_fetcher.cc b/remoting/test/host_list_fetcher.cc new file mode 100644 index 0000000..bac2db2 --- /dev/null +++ b/remoting/test/host_list_fetcher.cc @@ -0,0 +1,115 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/test/host_list_fetcher.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/thread_task_runner_handle.h" +#include "base/values.h" +#include "net/http/http_status_code.h" +#include "net/url_request/url_fetcher.h" +#include "remoting/base/url_request_context_getter.h" + +namespace remoting { +namespace test { + +HostListFetcher::HostListFetcher() { +} + +HostListFetcher::~HostListFetcher() { +} + +void HostListFetcher::RetrieveHostlist( + const std::string& access_token, + const HostlistCallback& callback) { + + VLOG(2) << "HostListFetcher::RetrieveHostlist() called"; + + DCHECK(!access_token.empty()); + DCHECK(!callback.is_null()); + DCHECK(hostlist_callback_.is_null()); + + hostlist_callback_ = callback; + + request_context_getter_ = new remoting::URLRequestContextGetter( + base::ThreadTaskRunnerHandle::Get(), // network_runner + base::ThreadTaskRunnerHandle::Get()); // file_runner + + request_ = net::URLFetcher::Create( + GURL(kHostListProdRequestUrl), net::URLFetcher::GET, this); + request_->SetRequestContext(request_context_getter_.get()); + request_->AddExtraRequestHeader("Authorization: OAuth " + access_token); + request_->Start(); +} + +bool HostListFetcher::ProcessResponse( + std::vector<HostInfo>* hostlist) { + int response_code = request_->GetResponseCode(); + if (response_code != net::HTTP_OK) { + LOG(ERROR) << "Hostlist request failed with error code: " << response_code; + return false; + } + + std::string response_string; + if (!request_->GetResponseAsString(&response_string)) { + LOG(ERROR) << "Failed to retrieve Hostlist response data"; + return false; + } + + scoped_ptr<base::Value> response_value( + base::JSONReader::Read(response_string)); + if (!response_value || + !response_value->IsType(base::Value::TYPE_DICTIONARY)) { + LOG(ERROR) << "Failed to parse response string to JSON"; + return false; + } + + const base::DictionaryValue* response; + if (!response_value->GetAsDictionary(&response)) { + LOG(ERROR) << "Failed to convert parsed JSON to a dictionary object"; + return false; + } + + const base::DictionaryValue* data = nullptr; + if (!response->GetDictionary("data", &data)) { + LOG(ERROR) << "Hostlist response data is empty"; + return false; + } + + const base::ListValue* hosts = nullptr; + if (!data->GetList("items", &hosts)) { + LOG(ERROR) << "Failed to find hosts in Hostlist response data"; + return false; + } + + // Any host_info with malformed data will not be added to the hostlist. + base::DictionaryValue* host_dict; + for (base::Value* host_info : *hosts) { + HostInfo host; + if (host_info->GetAsDictionary(&host_dict) && + host.ParseHostInfo(*host_dict)) { + hostlist->push_back(host); + } + } + return true; +} + +void HostListFetcher::OnURLFetchComplete( + const net::URLFetcher* source) { + DCHECK(source); + VLOG(2) << "URL Fetch Completed for: " << source->GetOriginalURL(); + + std::vector<HostInfo> hostlist; + + if (!ProcessResponse(&hostlist)) { + hostlist.clear(); + } + base::ResetAndReturn(&hostlist_callback_).Run(hostlist); +} + +} // namespace test +} // namespace remoting diff --git a/remoting/test/host_list_fetcher.h b/remoting/test/host_list_fetcher.h new file mode 100644 index 0000000..af014d2 --- /dev/null +++ b/remoting/test/host_list_fetcher.h @@ -0,0 +1,74 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef REMOTING_TEST_HOST_LIST_FETCHER_H_ +#define REMOTING_TEST_HOST_LIST_FETCHER_H_ + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "remoting/test/host_info.h" + +namespace net { +class UrlFetcher; +} +namespace remoting { +class URLRequestContextGetter; +} + +namespace remoting { +namespace test { + +// Used by the HostlistFetcher to make HTTP requests and also by the +// unittests for this class to set fake response data for these URLs. +const char kHostListProdRequestUrl[] = "https://www.googleapis.com/" + "chromoting/v1/@me/hosts"; + +// Requests a host list from the directory service for an access token. +// Destroying the RemoteHostInfoFetcher while a request is outstanding +// will cancel the request. It is safe to delete the fetcher from within a +// completion callback. Must be used from a thread running a message loop. +// The public method is virtual to allow for mocking and fakes. +class HostListFetcher : public net::URLFetcherDelegate { + public: + HostListFetcher(); + ~HostListFetcher() override; + + // Supplied by the client for each hostlist request and returns a valid, + // initialized Hostlist object on success. + typedef base::Callback<void(const std::vector<HostInfo>& hostlist)> + HostlistCallback; + + // Makes a service call to retrieve a hostlist. The + // callback will be called once the HTTP request has completed. + virtual void RetrieveHostlist(const std::string& access_token, + const HostlistCallback& callback); + + private: + // Processes the response from the directory service. + bool ProcessResponse(std::vector<HostInfo>* hostlist); + + // net::URLFetcherDelegate interface. + void OnURLFetchComplete(const net::URLFetcher* source) override; + + // Holds the URLFetcher for the Host List request. + scoped_ptr<net::URLFetcher> request_; + + // Provides application-specific context for the network request. + scoped_refptr<remoting::URLRequestContextGetter> request_context_getter_; + + // Caller-supplied callback used to return hostlist on success. + HostlistCallback hostlist_callback_; + + DISALLOW_COPY_AND_ASSIGN(HostListFetcher); +}; + +} // namespace test +} // namespace remoting + +#endif // REMOTING_TEST_HOST_LIST_FETCHER_H_ diff --git a/remoting/test/host_list_fetcher_unittest.cc b/remoting/test/host_list_fetcher_unittest.cc new file mode 100644 index 0000000..a0db3817 --- /dev/null +++ b/remoting/test/host_list_fetcher_unittest.cc @@ -0,0 +1,444 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "remoting/test/host_list_fetcher.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "remoting/test/host_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Used as a HostListCallback for testing. +void OnHostlistRetrieved( + base::Closure done_closure, + std::vector<remoting::test::HostInfo>* hostlist, + const std::vector<remoting::test::HostInfo>& retrieved_hostlist) { + *hostlist = retrieved_hostlist; + + done_closure.Run(); +} + +const char kAccessTokenValue[] = "test_access_token_value"; +const char kHostListReadyResponse[] = +"{" +" \"data\":{" +" \"kind\":\"chromoting#hostList\"," +" \"items\":[" +" {" +" \"tokenUrlPatterns\":[" +" \"tokenUrlPattern_1A\"," +" \"tokenUrlPattern_1B\"," +" \"tokenUrlPattern_1C\"" +" ]," +" \"kind\":\"chromoting#host\"," +" \"hostId\":\"test_host_id_1\"," +" \"hostName\":\"test_host_name_1\"," +" \"publicKey\":\"test_public_key_1\"," +" \"jabberId\":\"test_jabber_id_1\"," +" \"createdTime\":\"test_created_time_1\"," +" \"updatedTime\":\"test_updated_time_1\"," +" \"status\":\"ONLINE\"," +" \"hostOfflineReason\":\"\"," +" \"hostVersion\":\"test_host_version_1\"" +" }," +" {" +" \"kind\":\"chromoting#host\"," +" \"hostId\":\"test_host_id_2\"," +" \"hostName\":\"test_host_name_2\"," +" \"publicKey\":\"test_public_key_2\"," +" \"jabberId\":\"test_jabber_id_2\"," +" \"createdTime\":\"test_created_time_2\"," +" \"updatedTime\":\"test_updated_time_2\"," +" \"status\":\"OFFLINE\"," +" \"hostOfflineReason\":\"test_host_offline_reason_2\"," +" \"hostVersion\":\"test_host_version_2\"" +" }" +" ]" +" }" +"}"; +const char kHostListMissingParametersResponse[] = +"{" +" \"data\":{" +" \"kind\":\"chromoting#hostList\"," +" \"items\":[" +" {" +" \"tokenUrlPatterns\":[" +" \"tokenUrlPattern_1A\"," +" \"tokenUrlPattern_1B\"," +" \"tokenUrlPattern_1C\"" +" ]," +" \"kind\":\"chromoting#host\"," +" \"hostId\":\"test_host_id_1\"," +" \"hostName\":\"test_host_name_1\"," +" \"publicKey\":\"test_public_key_1\"," +" \"createdTime\":\"test_created_time_1\"," +" \"updatedTime\":\"test_updated_time_1\"," +" \"status\":\"OFFLINE\"," +" \"hostOfflineReason\":\"\"," +" \"hostVersion\":\"test_host_version_1\"" +" }," +" {" +" \"kind\":\"chromoting#host\"," +" \"hostName\":\"test_host_name_2\"," +" \"publicKey\":\"test_public_key_2\"," +" \"jabberId\":\"test_jabber_id_2\"," +" \"createdTime\":\"test_created_time_2\"," +" \"updatedTime\":\"test_updated_time_2\"," +" \"status\":\"ONLINE\"," +" \"hostOfflineReason\":\"\"," +" \"hostVersion\":\"test_host_version_2\"" +" }," +" {" +" \"kind\":\"chromoting#host\"," +" \"hostId\":\"test_host_id_3\"," +" \"publicKey\":\"test_public_key_3\"," +" \"jabberId\":\"test_jabber_id_3\"," +" \"createdTime\":\"test_created_time_3\"," +" \"updatedTime\":\"test_updated_time_3\"," +" \"status\":\"ONLINE\"," +" \"hostOfflineReason\":\"\"," +" \"hostVersion\":\"test_host_version_3\"" +" }," +" {" +" \"kind\":\"chromoting#host\"," +" \"hostId\":\"test_host_id_4\"," +" \"hostName\":\"test_host_name_4\"," +" \"jabberId\":\"test_jabber_id_4\"," +" \"createdTime\":\"test_created_time_4\"," +" \"updatedTime\":\"test_updated_time_4\"," +" \"status\":\"ONLINE\"," +" \"hostOfflineReason\":\"\"," +" \"hostVersion\":\"test_host_version_4\"" +" }," +" {" +" \"kind\":\"chromoting#host\"," +" \"hostId\":\"test_host_id_5\"," +" \"hostName\":\"test_host_name_5\"," +" \"publicKey\":\"test_public_key_5\"," +" \"jabberId\":\"test_jabber_id_5\"," +" \"createdTime\":\"test_created_time_5\"," +" \"updatedTime\":\"test_updated_time_5\"," +" \"status\":\"OFFLINE\"," +" \"hostVersion\":\"test_host_version_5\"" +" }" +" ]" +" }" +"}"; +const char kHostListEmptyTokenUrlPatternsResponse[] = +"{" +" \"data\":{" +" \"kind\":\"chromoting#hostList\"," +" \"items\":[" +" {" +" \"tokenUrlPatterns\":[" +" ]," +" \"kind\":\"chromoting#host\"," +" \"hostId\":\"test_host_id_1\"," +" \"hostName\":\"test_host_name_1\"," +" \"publicKey\":\"test_public_key_1\"," +" \"jabberId\":\"test_jabber_id_1\"," +" \"createdTime\":\"test_created_time_1\"," +" \"updatedTime\":\"test_updated_time_1\"," +" \"status\":\"ONLINE\"," +" \"hostOfflineReason\":\"\"," +" \"hostVersion\":\"test_host_version_1\"" +" }" +" ]" +" }" +"}"; +const char kHostListEmptyItemsResponse[] = +"{" +" \"data\":{" +" \"kind\":\"chromoting#hostList\"," +" \"items\":[" +" ]" +" }" +"}"; +const char kHostListEmptyResponse[] = "{}"; + +const unsigned int kExpectedEmptyPatternsHostListSize = 1; +const unsigned int kExpectedHostListSize = 2; +const unsigned int kExpectedPatternsSize = 3; + +} // namespace + +namespace remoting { +namespace test { + +// Provides base functionality for the HostListFetcher Tests below. +// The FakeURLFetcherFactory allows us to override the response data and payload +// for specified URLs. We use this to stub out network calls made by the +// HostListFetcher. This fixture also creates an IO MessageLoop +// for use by the HostListFetcher. +class HostListFetcherTest : public ::testing::Test { + public: + HostListFetcherTest() : url_fetcher_factory_(nullptr) {} + ~HostListFetcherTest() override {} + + protected: + // testing::Test interface. + void SetUp() override; + + // Sets the HTTP status and data returned for a specified URL. + void SetFakeResponse(const GURL& url, + const std::string& data, + net::HttpStatusCode code, + net::URLRequestStatus::Status status); + + private: + net::FakeURLFetcherFactory url_fetcher_factory_; + scoped_ptr<base::MessageLoopForIO> message_loop_; + + DISALLOW_COPY_AND_ASSIGN(HostListFetcherTest); +}; + +void HostListFetcherTest::SetUp() { + DCHECK(!message_loop_); + message_loop_.reset(new base::MessageLoopForIO); + + SetFakeResponse(GURL(kHostListProdRequestUrl), + kHostListEmptyResponse, net::HTTP_NOT_FOUND, + net::URLRequestStatus::FAILED); +} + +void HostListFetcherTest::SetFakeResponse( + const GURL& url, + const std::string& data, + net::HttpStatusCode code, + net::URLRequestStatus::Status status) { + url_fetcher_factory_.SetFakeResponse(url, data, code, status); +} + +TEST_F(HostListFetcherTest, RetrieveHostListFromProd) { + SetFakeResponse(GURL(kHostListProdRequestUrl), + kHostListReadyResponse, net::HTTP_OK, + net::URLRequestStatus::SUCCESS); + + std::vector<HostInfo> hostlist; + + base::RunLoop run_loop; + HostListFetcher::HostlistCallback host_list_callback = + base::Bind(&OnHostlistRetrieved, run_loop.QuitClosure(), &hostlist); + + HostListFetcher host_list_fetcher; + host_list_fetcher.RetrieveHostlist(kAccessTokenValue, host_list_callback); + + run_loop.Run(); + + EXPECT_EQ(hostlist.size(), kExpectedHostListSize); + + HostInfo online_host_info = hostlist.at(0); + EXPECT_EQ(online_host_info.token_url_patterns.size(), kExpectedPatternsSize); + EXPECT_FALSE(online_host_info.host_id.empty()); + EXPECT_FALSE(online_host_info.host_jid.empty()); + EXPECT_FALSE(online_host_info.host_name.empty()); + EXPECT_EQ(online_host_info.status, HostStatus::kHostStatusOnline); + EXPECT_TRUE(online_host_info.offline_reason.empty()); + EXPECT_FALSE(online_host_info.public_key.empty()); + + HostInfo offline_host_info = hostlist.at(1); + EXPECT_TRUE(offline_host_info.token_url_patterns.empty()); + EXPECT_FALSE(offline_host_info.host_id.empty()); + EXPECT_FALSE(offline_host_info.host_jid.empty()); + EXPECT_FALSE(offline_host_info.host_name.empty()); + EXPECT_EQ(offline_host_info.status, HostStatus::kHostStatusOffline); + EXPECT_FALSE(offline_host_info.offline_reason.empty()); + EXPECT_FALSE(offline_host_info.public_key.empty()); +} + +TEST_F(HostListFetcherTest, RetrieveHostListWithEmptyPatterns) { + SetFakeResponse(GURL(kHostListProdRequestUrl), + kHostListEmptyTokenUrlPatternsResponse, net::HTTP_OK, + net::URLRequestStatus::SUCCESS); + + std::vector<HostInfo> hostlist; + + base::RunLoop run_loop; + HostListFetcher::HostlistCallback host_list_callback = + base::Bind(&OnHostlistRetrieved, run_loop.QuitClosure(), &hostlist); + + HostListFetcher host_list_fetcher; + host_list_fetcher.RetrieveHostlist(kAccessTokenValue, host_list_callback); + + run_loop.Run(); + + EXPECT_EQ(hostlist.size(), kExpectedEmptyPatternsHostListSize); + + // While this is unlikely to happen, empty token url patterns are handled. + HostInfo online_host_info = hostlist.at(0); + EXPECT_TRUE(online_host_info.token_url_patterns.empty()); + EXPECT_FALSE(online_host_info.host_id.empty()); + EXPECT_FALSE(online_host_info.host_jid.empty()); + EXPECT_FALSE(online_host_info.host_name.empty()); + EXPECT_EQ(online_host_info.status, HostStatus::kHostStatusOnline); + EXPECT_TRUE(online_host_info.offline_reason.empty()); + EXPECT_FALSE(online_host_info.public_key.empty()); +} + +TEST_F(HostListFetcherTest, + RetrieveHostListMissingParametersResponse) { + SetFakeResponse(GURL(kHostListProdRequestUrl), + kHostListMissingParametersResponse, net::HTTP_OK, + net::URLRequestStatus::SUCCESS); + + std::vector<HostInfo> hostlist; + + base::RunLoop run_loop; + HostListFetcher::HostlistCallback host_list_callback = + base::Bind(&OnHostlistRetrieved, run_loop.QuitClosure(), &hostlist); + + HostListFetcher host_list_fetcher; + host_list_fetcher.RetrieveHostlist(kAccessTokenValue, host_list_callback); + run_loop.Run(); + + EXPECT_EQ(hostlist.size(), kExpectedHostListSize); + + HostInfo no_jid_host_info = hostlist.at(0); + EXPECT_EQ(no_jid_host_info.token_url_patterns.size(), kExpectedPatternsSize); + EXPECT_FALSE(no_jid_host_info.host_id.empty()); + EXPECT_TRUE(no_jid_host_info.host_jid.empty()); + EXPECT_FALSE(no_jid_host_info.host_name.empty()); + EXPECT_EQ(no_jid_host_info.status, HostStatus::kHostStatusOffline); + EXPECT_TRUE(no_jid_host_info.offline_reason.empty()); + EXPECT_FALSE(no_jid_host_info.public_key.empty()); + + HostInfo no_offline_reason_host_info = hostlist.at(1); + EXPECT_TRUE(no_offline_reason_host_info.token_url_patterns.empty()); + EXPECT_FALSE(no_offline_reason_host_info.host_id.empty()); + EXPECT_FALSE(no_offline_reason_host_info.host_jid.empty()); + EXPECT_FALSE(no_offline_reason_host_info.host_name.empty()); + EXPECT_EQ(no_offline_reason_host_info.status, HostStatus::kHostStatusOffline); + EXPECT_TRUE(no_offline_reason_host_info.offline_reason.empty()); + EXPECT_FALSE(no_offline_reason_host_info.public_key.empty()); +} + + +TEST_F(HostListFetcherTest, RetrieveHostListNetworkError) { + base::RunLoop run_loop; + + std::vector<HostInfo> hostlist; + + HostListFetcher::HostlistCallback host_list_callback = + base::Bind(&OnHostlistRetrieved, run_loop.QuitClosure(), &hostlist); + + HostListFetcher host_list_fetcher; + host_list_fetcher.RetrieveHostlist(kAccessTokenValue, host_list_callback); + run_loop.Run(); + + // If there was a network error retrieving the host list, then the host list + // should be empty. + EXPECT_TRUE(hostlist.empty()); +} + +TEST_F(HostListFetcherTest, RetrieveHostListEmptyItemsResponse) { + SetFakeResponse(GURL(kHostListProdRequestUrl), + kHostListEmptyItemsResponse, net::HTTP_OK, + net::URLRequestStatus::SUCCESS); + + base::RunLoop run_loop; + + std::vector<HostInfo> hostlist; + + HostListFetcher::HostlistCallback host_list_callback = + base::Bind(&OnHostlistRetrieved, run_loop.QuitClosure(), &hostlist); + + HostListFetcher host_list_fetcher; + host_list_fetcher.RetrieveHostlist(kAccessTokenValue, host_list_callback); + run_loop.Run(); + + // If we received an empty items response, then host list should be empty. + EXPECT_TRUE(hostlist.empty()); +} + +TEST_F(HostListFetcherTest, RetrieveHostListEmptyResponse) { + SetFakeResponse(GURL(kHostListProdRequestUrl), + kHostListEmptyResponse, net::HTTP_OK, + net::URLRequestStatus::SUCCESS); + + base::RunLoop run_loop; + + std::vector<HostInfo> hostlist; + + HostListFetcher::HostlistCallback host_list_callback = + base::Bind(&OnHostlistRetrieved, run_loop.QuitClosure(), &hostlist); + + HostListFetcher host_list_fetcher; + host_list_fetcher.RetrieveHostlist(kAccessTokenValue, host_list_callback); + run_loop.Run(); + + // If we received an empty response, then host list should be empty. + EXPECT_TRUE(hostlist.empty()); +} + +TEST_F(HostListFetcherTest, MultipleRetrieveHostListRequests) { + // First, we will retrieve a valid response from the directory service. + SetFakeResponse(GURL(kHostListProdRequestUrl), + kHostListReadyResponse, net::HTTP_OK, + net::URLRequestStatus::SUCCESS); + + std::vector<HostInfo> ready_hostlist; + + base::RunLoop ready_run_loop; + HostListFetcher::HostlistCallback ready_host_list_callback = + base::Bind(&OnHostlistRetrieved, + ready_run_loop.QuitClosure(), + &ready_hostlist); + + HostListFetcher host_list_fetcher; + host_list_fetcher.RetrieveHostlist(kAccessTokenValue, + ready_host_list_callback); + + ready_run_loop.Run(); + + EXPECT_EQ(ready_hostlist.size(), kExpectedHostListSize); + + HostInfo online_host_info = ready_hostlist.at(0); + EXPECT_EQ(online_host_info.token_url_patterns.size(), kExpectedPatternsSize); + EXPECT_FALSE(online_host_info.host_id.empty()); + EXPECT_FALSE(online_host_info.host_jid.empty()); + EXPECT_FALSE(online_host_info.host_name.empty()); + EXPECT_EQ(online_host_info.status, HostStatus::kHostStatusOnline); + EXPECT_TRUE(online_host_info.offline_reason.empty()); + EXPECT_FALSE(online_host_info.public_key.empty()); + + HostInfo offline_host_info = ready_hostlist.at(1); + EXPECT_TRUE(offline_host_info.token_url_patterns.empty()); + EXPECT_FALSE(offline_host_info.host_id.empty()); + EXPECT_FALSE(offline_host_info.host_jid.empty()); + EXPECT_FALSE(offline_host_info.host_name.empty()); + EXPECT_EQ(offline_host_info.status, HostStatus::kHostStatusOffline); + EXPECT_FALSE(offline_host_info.offline_reason.empty()); + EXPECT_FALSE(offline_host_info.public_key.empty()); + + // Next, we will retrieve an empty items response from the directory service. + SetFakeResponse(GURL(kHostListProdRequestUrl), + kHostListEmptyItemsResponse, net::HTTP_OK, + net::URLRequestStatus::SUCCESS); + + std::vector<HostInfo> empty_items_hostlist; + + base::RunLoop empty_items_run_loop; + + HostListFetcher::HostlistCallback empty_host_list_callback = + base::Bind(&OnHostlistRetrieved, + empty_items_run_loop.QuitClosure(), + &empty_items_hostlist); + + // Re-use the same host_list_fetcher. + host_list_fetcher.RetrieveHostlist(kAccessTokenValue, + empty_host_list_callback); + + empty_items_run_loop.Run(); + + // If we received an empty items response, then host list should be empty. + EXPECT_TRUE(empty_items_hostlist.empty()); +} + +} // namespace test +} // namespace remoting |