Skip to content

Commit

Permalink
Initial version of Test Accounts service client. This client will
Browse files Browse the repository at this point in the history
allow Chrome Sync tests to utilize the upcoming Test Accounts
service that allows short-term, exclusive access to test accounts
for the purpose of testing against real servers.

BUG=

Review URL: https://chromiumcodereview.appspot.com/14295014

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@198019 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
pvalenzuela@chromium.org committed May 3, 2013
1 parent 583cd38 commit 0f02f8a
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 0 deletions.
41 changes: 41 additions & 0 deletions sync/sync_tests.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -608,5 +608,46 @@
},
],
}],
# TODO(pvalenzuela): Remove these OS restrictions when moving end-to-end
# tests to other platforms.
['OS != "ios" and OS != "win"', {
'targets': [
# Test support files for using the Test Accounts service.
{
'target_name': 'test_support_accounts_client',
'type': 'static_library',
'direct_dependent_settings': {
'include_dirs': [
'..',
],
},
'dependencies': [
'../base/base.gyp:base',
],
'sources': [
'test/accounts_client/test_accounts_client.cc',
'test/accounts_client/test_accounts_client.h',
],
'link_settings': {
'libraries': [ '-lcurl', ],
},
},

# The Sync end-to-end (and associated infrastructure) tests.
{
'target_name': 'sync_endtoend_tests',
'type': '<(gtest_target_type)',
'dependencies': [
'../base/base.gyp:run_all_unittests',
'../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
'test_support_accounts_client',
],
'sources': [
'test/accounts_client/test_accounts_client_unittest.cc',
],
},
]
}],
],
}
120 changes: 120 additions & 0 deletions sync/test/accounts_client/test_accounts_client.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2013 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 "sync/test/accounts_client/test_accounts_client.h"

#include <algorithm>
#include <curl/curl.h>
#include <string>
#include <vector>

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/stringprintf.h"
#include "base/values.h"

using std::string;
using std::vector;

static const int kMaxSessionLifetimeSeconds = 30 * 60;
static const string kClaimPath = "claim";
static const string kReleasePath = "release";

AccountSession::AccountSession() {}
AccountSession::~AccountSession() {}

TestAccountsClient::TestAccountsClient(const string& server,
const string& account_space,
const vector<string>& usernames)
: server_(server), account_space_(account_space), usernames_(usernames) {
}

TestAccountsClient::~TestAccountsClient() {}

AccountSession TestAccountsClient::ClaimAccount() {
string post_fields;
base::StringAppendF(&post_fields, "account_space=%s", account_space_.c_str());
base::StringAppendF(&post_fields, "&max_lifetime_seconds=%d",
kMaxSessionLifetimeSeconds);

// TODO(pvalenzuela): Select N random usernames instead of all usernames.
for (vector<string>::iterator it = usernames_.begin();
it != usernames_.end(); ++it) {
base::StringAppendF(&post_fields, "&username=%s", it->c_str());
}

string response = SendRequest(kClaimPath, post_fields);
scoped_ptr<Value> value(base::JSONReader::Read(response));
base::DictionaryValue* dict_value;
AccountSession session;
if (value != NULL && value->GetAsDictionary(&dict_value) &&
dict_value != NULL) {
dict_value->GetString("username", &session.username);
dict_value->GetString("account_space", &session.account_space);
dict_value->GetString("session_id", &session.session_id);
dict_value->GetString("expiration_time", &session.expiration_time);
} else {
session.error = response;
}
return session;
}

void TestAccountsClient::ReleaseAccount(const AccountSession& session) {
string post_fields;
// The expiration_time field is ignored since it isn't passed as part of the
// release request.
if (session.username.empty() || session.account_space.empty() ||
account_space_.compare(session.account_space) != 0 ||
session.session_id.empty()) {
return;
}

base::StringAppendF(&post_fields, "account_space=%s",
session.account_space.c_str());
base::StringAppendF(&post_fields, "&username=%s", session.username.c_str());
base::StringAppendF(&post_fields, "&session_id=%s",
session.session_id.c_str());

// This operation is best effort, so don't send any errors back to the caller.
SendRequest(kReleasePath, post_fields);
}

namespace {
int CurlWriteFunction(char* data,
size_t size,
size_t nmemb,
string* write_data) {
if (write_data == NULL) {
return 0;
}
write_data->append(data, size * nmemb);
return size * nmemb;
}
} // namespace

string TestAccountsClient::SendRequest(const string& path,
const string& post_fields) {
CURLcode res;
string response_buffer;
char error_buffer[CURL_ERROR_SIZE];
CURL* curl = curl_easy_init();
if (curl) {
string url;
base::SStringPrintf(&url, "%s/%s", server_.c_str(), path.c_str());
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_fields.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteFunction);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
string error(error_buffer);
return error;
}
return response_buffer;
}
return "There was an error establishing the connection.";
}
63 changes: 63 additions & 0 deletions sync/test/accounts_client/test_accounts_client.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2013 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 SYNC_TEST_ACCOUNTS_CLIENT_TEST_ACCOUNTS_CLIENT_H_
#define SYNC_TEST_ACCOUNTS_CLIENT_TEST_ACCOUNTS_CLIENT_H_

#include <curl/curl.h>
#include <string>
#include <vector>

using std::string;
using std::vector;

// The data associated with an account session.
struct AccountSession {
AccountSession();
~AccountSession();

string username;
string account_space;
string session_id;
string expiration_time;

// Only set if there was an error.
string error;
};

// A test-side client for the Test Accounts service. This service provides
// short-term, exclusive access to test accounts for the purpose of testing
// against real Chrome Sync servers.
class TestAccountsClient {
public:
// Creates a client associated with the given |server| URL (e.g.,
// http://service-runs-here.com), |account_space| (for account segregation),
// and |usernames| (the collection of accounts to be chosen from).
TestAccountsClient(const string& server,
const string& account_space,
const vector<string>& usernames);

virtual ~TestAccountsClient();

// Attempts to claim an account via the Test Accounts service. If
// successful, an AccountSession is returned containing the data associated
// with the session. If an error occurred, then the AccountSession will only
// have its error field set.
AccountSession ClaimAccount();

// Attempts to release an account via the Test Accounts service. The value
// of |session| should be one returned from ClaimAccount(). This function
// is best-effort and fails silently.
void ReleaseAccount(const AccountSession& session);

// Sends an HTTP POST request to the Test Accounts service.
virtual string SendRequest(const string& path, const string& post_fields);

private:
const string server_;
const string account_space_;
vector<string> usernames_;
};

#endif // SYNC_TEST_ACCOUNTS_CLIENT_TEST_ACCOUNTS_CLIENT_H_
95 changes: 95 additions & 0 deletions sync/test/accounts_client/test_accounts_client_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2013 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 <vector>

#include "sync/test/accounts_client/test_accounts_client.cc"
#include "sync/test/accounts_client/test_accounts_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using std::string;
using std::vector;
using testing::_;
using testing::HasSubstr;
using testing::Return;

namespace {
static const string kServer = "https://test-account-service";
static const string kUsername = "foobar@baz.com";
static const string kAccountSpace = "test_account_space";
static const string kSessionId = "1234-ABCD";
static const string kExpirationTime = "12:00";
} // namespace

static AccountSession CreateValidAccountSession() {
AccountSession session;
session.username = kUsername;
session.account_space = kAccountSpace;
session.session_id = kSessionId;
session.expiration_time = kExpirationTime;
return session;
}

class NoNetworkTestAccountsClient : public TestAccountsClient {
public:
NoNetworkTestAccountsClient(const string& server,
const string& account_space,
vector<string> usernames)
: TestAccountsClient(server, account_space, usernames) {}
MOCK_METHOD2(SendRequest,
string(const string&, const string&));
};

TEST(TestAccountsClientTest, ClaimAccountError) {
vector<string> usernames;
NoNetworkTestAccountsClient client(kServer, kAccountSpace, usernames);
string error_response = "error!!!";
EXPECT_CALL(client, SendRequest(kClaimPath, _))
.WillOnce(Return(error_response));
AccountSession session = client.ClaimAccount();
EXPECT_EQ(error_response, session.error);
}

TEST(TestAccountsClientTest, ClaimAccountSuccess) {
vector<string> usernames;
usernames.push_back("foo0@gmail.com");
usernames.push_back("foo1@gmail.com");
usernames.push_back("foo2@gmail.com");
NoNetworkTestAccountsClient client(kServer, kAccountSpace, usernames);
string success_response;
base::StringAppendF(&success_response, "{ \"username\":\"%s\",",
kUsername.c_str());
base::StringAppendF(&success_response, "\"account_space\":\"%s\",",
kAccountSpace.c_str());
base::StringAppendF(&success_response, "\"session_id\":\"%s\",",
kSessionId.c_str());
base::StringAppendF(&success_response, "\"expiration_time\":\"%s\"}",
kExpirationTime.c_str());
EXPECT_CALL(client, SendRequest(kClaimPath, _))
.WillOnce(Return(success_response));
AccountSession session = client.ClaimAccount();
EXPECT_EQ(kUsername, session.username);
EXPECT_EQ(kAccountSpace, session.account_space);
EXPECT_EQ(kSessionId, session.session_id);
EXPECT_EQ(kExpirationTime, session.expiration_time);
}

TEST(TestAccountsClientTest, ReleaseAccountEmptySession) {
vector<string> usernames;
NoNetworkTestAccountsClient client(kServer, kAccountSpace, usernames);
AccountSession session;
// No expectation for SendRequest is made because no network call should be
// performed in this scenario.
client.ReleaseAccount(session);
}

TEST(TestAccountsClientTest, ReleaseAccountSuccess) {
vector<string> usernames;
NoNetworkTestAccountsClient client(kServer, kAccountSpace, usernames);
EXPECT_CALL(client, SendRequest(kReleasePath, _))
.WillOnce(Return(""));
AccountSession session = CreateValidAccountSession();
client.ReleaseAccount(session);
}

0 comments on commit 0f02f8a

Please sign in to comment.