summaryrefslogtreecommitdiffstats
path: root/remoting/test
diff options
context:
space:
mode:
authortonychun <tonychun@google.com>2015-07-10 11:45:30 -0700
committerCommit bot <commit-bot@chromium.org>2015-07-10 18:47:27 +0000
commite3fe98489be2fe1f9e504fa5c6ac1f2e205fd561 (patch)
tree5a6148898eb7ab4069a91cf2ff795610019fb968 /remoting/test
parent740b0fa6a5b957093f117a9076475df8ae5dbfa6 (diff)
downloadchromium_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.cc103
-rw-r--r--remoting/test/host_info.cc74
-rw-r--r--remoting/test/host_info.h39
-rw-r--r--remoting/test/host_list_fetcher.cc115
-rw-r--r--remoting/test/host_list_fetcher.h74
-rw-r--r--remoting/test/host_list_fetcher_unittest.cc444
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