activity_database_unittest.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <string>
6
7#include "base/command_line.h"
8#include "base/file_util.h"
9#include "base/files/file_path.h"
10#include "base/files/scoped_temp_dir.h"
11#include "base/run_loop.h"
12#include "base/test/simple_test_clock.h"
13#include "base/time/time.h"
14#include "chrome/browser/extensions/activity_log/activity_action_constants.h"
15#include "chrome/browser/extensions/activity_log/activity_database.h"
16#include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h"
17#include "chrome/browser/extensions/extension_service.h"
18#include "chrome/browser/extensions/test_extension_system.h"
19#include "chrome/common/chrome_constants.h"
20#include "chrome/common/chrome_switches.h"
21#include "chrome/common/extensions/dom_action_types.h"
22#include "chrome/test/base/chrome_render_view_host_test_harness.h"
23#include "chrome/test/base/testing_profile.h"
24#include "content/public/browser/web_contents.h"
25#include "content/public/test/test_browser_thread.h"
26#include "sql/statement.h"
27#include "testing/gtest/include/gtest/gtest.h"
28
29#if defined(OS_CHROMEOS)
30#include "chrome/browser/chromeos/cros/network_library.h"
31#include "chrome/browser/chromeos/login/mock_user_manager.h"
32#include "chrome/browser/chromeos/login/user_manager.h"
33#include "chrome/browser/chromeos/settings/cros_settings.h"
34#include "chrome/browser/chromeos/settings/device_settings_service.h"
35#include "chromeos/chromeos_switches.h"
36#endif
37
38using content::BrowserThread;
39
40namespace constants = activity_log_constants;
41
42namespace extensions {
43
44// A dummy implementation of ActivityDatabase::Delegate, sufficient for
45// the unit tests.
46class ActivityDatabaseTestPolicy : public ActivityDatabase::Delegate {
47 public:
48  ActivityDatabaseTestPolicy() {};
49
50  static const char kTableName[];
51  static const char* kTableContentFields[];
52  static const char* kTableFieldTypes[];
53
54  virtual void Record(ActivityDatabase* db, scoped_refptr<Action> action);
55
56 protected:
57  virtual bool InitDatabase(sql::Connection* db) OVERRIDE;
58  virtual bool FlushDatabase(sql::Connection*) OVERRIDE;
59  virtual void OnDatabaseFailure() OVERRIDE {}
60  virtual void OnDatabaseClose() OVERRIDE { delete this; }
61
62  std::vector<scoped_refptr<Action> > queue_;
63};
64
65const char ActivityDatabaseTestPolicy::kTableName[] = "actions";
66const char* ActivityDatabaseTestPolicy::kTableContentFields[] = {
67    "extension_id", "time", "action_type", "api_name"};
68const char* ActivityDatabaseTestPolicy::kTableFieldTypes[] = {
69    "LONGVARCHAR NOT NULL", "INTEGER", "INTEGER", "LONGVARCHAR"};
70
71bool ActivityDatabaseTestPolicy::InitDatabase(sql::Connection* db) {
72  return ActivityDatabase::InitializeTable(db,
73                                           kTableName,
74                                           kTableContentFields,
75                                           kTableFieldTypes,
76                                           arraysize(kTableContentFields));
77}
78
79bool ActivityDatabaseTestPolicy::FlushDatabase(sql::Connection* db) {
80  std::string sql_str =
81      "INSERT INTO " + std::string(kTableName) +
82      " (extension_id, time, action_type, api_name) VALUES (?,?,?,?)";
83
84  std::vector<scoped_refptr<Action> >::size_type i;
85  for (i = 0; i < queue_.size(); i++) {
86    const Action& action = *queue_[i];
87    sql::Statement statement(db->GetCachedStatement(
88        sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
89    statement.BindString(0, action.extension_id());
90    statement.BindInt64(1, action.time().ToInternalValue());
91    statement.BindInt(2, static_cast<int>(action.action_type()));
92    statement.BindString(3, action.api_name());
93    if (!statement.Run()) {
94      LOG(ERROR) << "Activity log database I/O failed: " << sql_str;
95      return false;
96    }
97  }
98
99  queue_.clear();
100  return true;
101}
102
103void ActivityDatabaseTestPolicy::Record(ActivityDatabase* db,
104                                        scoped_refptr<Action> action) {
105  queue_.push_back(action);
106  db->NotifyAction();
107}
108
109class ActivityDatabaseTest : public ChromeRenderViewHostTestHarness {
110 protected:
111  virtual void SetUp() OVERRIDE {
112    ChromeRenderViewHostTestHarness::SetUp();
113#if defined OS_CHROMEOS
114    test_user_manager_.reset(new chromeos::ScopedTestUserManager());
115#endif
116    CommandLine command_line(CommandLine::NO_PROGRAM);
117    CommandLine::ForCurrentProcess()->AppendSwitch(
118        switches::kEnableExtensionActivityLogTesting);
119  }
120
121  virtual void TearDown() OVERRIDE {
122#if defined OS_CHROMEOS
123    test_user_manager_.reset();
124#endif
125    ChromeRenderViewHostTestHarness::TearDown();
126  }
127
128  // Creates a test database and initializes the table schema.
129  ActivityDatabase* OpenDatabase(const base::FilePath& db_file) {
130    db_delegate_ = new ActivityDatabaseTestPolicy();
131    ActivityDatabase* activity_db = new ActivityDatabase(db_delegate_);
132    activity_db->Init(db_file);
133    CHECK(activity_db->is_db_valid());
134    return activity_db;
135  }
136
137  scoped_refptr<Action> CreateAction(const base::Time& time,
138                                     const std::string& api_name) const {
139    scoped_refptr<Action> action(
140        new Action("punky", time, Action::ACTION_API_CALL, api_name));
141    return action;
142  }
143
144  void Record(ActivityDatabase* db, scoped_refptr<Action> action) {
145    db_delegate_->Record(db, action);
146  }
147
148  int CountActions(sql::Connection* db, const std::string& api_name_pattern) {
149    if (!db->DoesTableExist(ActivityDatabaseTestPolicy::kTableName))
150      return -1;
151    std::string sql_str = "SELECT COUNT(*) FROM " +
152                          std::string(ActivityDatabaseTestPolicy::kTableName) +
153                          " WHERE api_name LIKE ?";
154    sql::Statement statement(db->GetUniqueStatement(sql_str.c_str()));
155    statement.BindString(0, api_name_pattern);
156    if (!statement.Step())
157      return -1;
158    return statement.ColumnInt(0);
159  }
160
161 private:
162#if defined OS_CHROMEOS
163  chromeos::ScopedStubNetworkLibraryEnabler stub_network_library_enabler_;
164  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
165  chromeos::ScopedTestCrosSettings test_cros_settings_;
166  scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_;
167#endif
168
169  ActivityDatabaseTestPolicy* db_delegate_;
170};
171
172// Check that the database is initialized properly.
173TEST_F(ActivityDatabaseTest, Init) {
174  base::ScopedTempDir temp_dir;
175  base::FilePath db_file;
176  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
177  db_file = temp_dir.path().AppendASCII("ActivityInit.db");
178  base::DeleteFile(db_file, false);
179
180  ActivityDatabase* activity_db = OpenDatabase(db_file);
181  activity_db->Close();
182
183  sql::Connection db;
184  ASSERT_TRUE(db.Open(db_file));
185  ASSERT_TRUE(db.DoesTableExist(ActivityDatabaseTestPolicy::kTableName));
186  db.Close();
187}
188
189// Check that actions are recorded in the db.
190TEST_F(ActivityDatabaseTest, RecordAction) {
191  base::ScopedTempDir temp_dir;
192  base::FilePath db_file;
193  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
194  db_file = temp_dir.path().AppendASCII("ActivityRecord.db");
195  base::DeleteFile(db_file, false);
196
197  ActivityDatabase* activity_db = OpenDatabase(db_file);
198  activity_db->SetBatchModeForTesting(false);
199  scoped_refptr<Action> action = CreateAction(base::Time::Now(), "brewster");
200  Record(activity_db, action);
201  activity_db->Close();
202
203  sql::Connection db;
204  ASSERT_TRUE(db.Open(db_file));
205
206  ASSERT_EQ(1, CountActions(&db, "brewster"));
207}
208
209TEST_F(ActivityDatabaseTest, BatchModeOff) {
210  base::ScopedTempDir temp_dir;
211  base::FilePath db_file;
212  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
213  db_file = temp_dir.path().AppendASCII("ActivityRecord.db");
214  base::DeleteFile(db_file, false);
215
216  // Record some actions
217  ActivityDatabase* activity_db = OpenDatabase(db_file);
218  activity_db->SetBatchModeForTesting(false);
219
220  scoped_refptr<Action> action = CreateAction(base::Time::Now(), "brewster");
221  Record(activity_db, action);
222  ASSERT_EQ(1, CountActions(&activity_db->db_, "brewster"));
223
224  activity_db->Close();
225}
226
227TEST_F(ActivityDatabaseTest, BatchModeOn) {
228  base::ScopedTempDir temp_dir;
229  base::FilePath db_file;
230  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
231  db_file = temp_dir.path().AppendASCII("ActivityRecord.db");
232  base::DeleteFile(db_file, false);
233
234  // Record some actions
235  ActivityDatabase* activity_db = OpenDatabase(db_file);
236  activity_db->SetBatchModeForTesting(true);
237  scoped_refptr<Action> action = CreateAction(base::Time::Now(), "brewster");
238  Record(activity_db, action);
239  ASSERT_EQ(0, CountActions(&activity_db->db_, "brewster"));
240
241  // Artificially trigger and then stop the timer.
242  activity_db->SetTimerForTesting(0);
243  base::MessageLoop::current()->RunUntilIdle();
244  ASSERT_EQ(1, CountActions(&activity_db->db_, "brewster"));
245
246  activity_db->Close();
247}
248
249// Check that nothing explodes if the DB isn't initialized.
250TEST_F(ActivityDatabaseTest, InitFailure) {
251  base::ScopedTempDir temp_dir;
252  base::FilePath db_file;
253  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
254  db_file = temp_dir.path().AppendASCII("ActivityRecord.db");
255  base::DeleteFile(db_file, false);
256
257  ActivityDatabaseTestPolicy* delegate = new ActivityDatabaseTestPolicy();
258  ActivityDatabase* activity_db = new ActivityDatabase(delegate);
259  scoped_refptr<Action> action = new Action(
260      "punky", base::Time::Now(), Action::ACTION_API_CALL, "brewster");
261  action->mutable_args()->AppendString("woof");
262  delegate->Record(activity_db, action);
263  activity_db->Close();
264}
265
266}  // namespace extensions
267