1 // Copyright 2013 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.
5 #include "components/dom_distiller/core/dom_distiller_service.h"
8 #include "base/callback.h"
9 #include "base/containers/hash_tables.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/run_loop.h"
12 #include "components/dom_distiller/core/article_entry.h"
13 #include "components/dom_distiller/core/dom_distiller_model.h"
14 #include "components/dom_distiller/core/dom_distiller_store.h"
15 #include "components/dom_distiller/core/dom_distiller_test_util.h"
16 #include "components/dom_distiller/core/fake_db.h"
17 #include "components/dom_distiller/core/fake_distiller.h"
18 #include "components/dom_distiller/core/task_tracker.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
22 using testing::Invoke;
23 using testing::Return;
26 namespace dom_distiller {
31 class FakeViewRequestDelegate : public ViewRequestDelegate {
33 virtual ~FakeViewRequestDelegate() {}
34 MOCK_METHOD1(OnArticleReady, void(DistilledPageProto* proto));
37 class MockDistillerObserver : public DomDistillerObserver {
39 MOCK_METHOD1(ArticleEntriesUpdated, void(const std::vector<ArticleUpdate>&));
40 virtual ~MockDistillerObserver() {}
43 class MockArticleAvailableCallback {
45 MOCK_METHOD1(DistillationCompleted, void(bool));
48 DomDistillerService::ArticleAvailableCallback ArticleCallback(
49 MockArticleAvailableCallback* callback) {
50 return base::Bind(&MockArticleAvailableCallback::DistillationCompleted,
51 base::Unretained(callback));
54 void RunDistillerCallback(FakeDistiller* distiller,
55 scoped_ptr<DistilledPageProto> proto) {
56 distiller->RunDistillerCallback(proto.Pass());
57 base::RunLoop().RunUntilIdle();
62 class DomDistillerServiceTest : public testing::Test {
64 virtual void SetUp() {
65 main_loop_.reset(new base::MessageLoop());
66 FakeDB* fake_db = new FakeDB(&db_model_);
67 FakeDB::EntryMap store_model;
68 store_ = test::util::CreateStoreWithFakeDB(fake_db, store_model);
69 distiller_factory_ = new MockDistillerFactory();
70 service_.reset(new DomDistillerService(
71 scoped_ptr<DomDistillerStoreInterface>(store_),
72 scoped_ptr<DistillerFactory>(distiller_factory_)));
73 fake_db->InitCallback(true);
74 fake_db->LoadCallback(true);
77 virtual void TearDown() {
78 base::RunLoop().RunUntilIdle();
80 distiller_factory_ = NULL;
85 // store is owned by service_.
86 DomDistillerStoreInterface* store_;
87 MockDistillerFactory* distiller_factory_;
88 scoped_ptr<DomDistillerService> service_;
89 scoped_ptr<base::MessageLoop> main_loop_;
90 FakeDB::EntryMap db_model_;
93 TEST_F(DomDistillerServiceTest, TestViewEntry) {
94 FakeDistiller* distiller = new FakeDistiller(false);
95 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
96 .WillOnce(Return(distiller));
98 GURL url("http://www.example.com/p1");
99 std::string entry_id("id0");
101 entry.set_entry_id(entry_id);
102 entry.add_pages()->set_url(url.spec());
104 store_->AddEntry(entry);
106 FakeViewRequestDelegate viewer_delegate;
107 scoped_ptr<ViewerHandle> handle =
108 service_->ViewEntry(&viewer_delegate, entry_id);
110 ASSERT_FALSE(distiller->GetCallback().is_null());
112 scoped_ptr<DistilledPageProto> proto(new DistilledPageProto);
113 EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
115 RunDistillerCallback(distiller, proto.Pass());
118 TEST_F(DomDistillerServiceTest, TestViewUrl) {
119 FakeDistiller* distiller = new FakeDistiller(false);
120 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
121 .WillOnce(Return(distiller));
123 FakeViewRequestDelegate viewer_delegate;
124 GURL url("http://www.example.com/p1");
125 scoped_ptr<ViewerHandle> handle = service_->ViewUrl(&viewer_delegate, url);
127 ASSERT_FALSE(distiller->GetCallback().is_null());
128 EXPECT_EQ(url, distiller->GetUrl());
130 scoped_ptr<DistilledPageProto> proto(new DistilledPageProto);
131 EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
133 RunDistillerCallback(distiller, proto.Pass());
136 TEST_F(DomDistillerServiceTest, TestMultipleViewUrl) {
137 FakeDistiller* distiller = new FakeDistiller(false);
138 FakeDistiller* distiller2 = new FakeDistiller(false);
139 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
140 .WillOnce(Return(distiller))
141 .WillOnce(Return(distiller2));
143 FakeViewRequestDelegate viewer_delegate;
144 FakeViewRequestDelegate viewer_delegate2;
146 GURL url("http://www.example.com/p1");
147 GURL url2("http://www.example.com/a/p1");
149 scoped_ptr<ViewerHandle> handle = service_->ViewUrl(&viewer_delegate, url);
150 scoped_ptr<ViewerHandle> handle2 = service_->ViewUrl(&viewer_delegate2, url2);
152 ASSERT_FALSE(distiller->GetCallback().is_null());
153 EXPECT_EQ(url, distiller->GetUrl());
155 scoped_ptr<DistilledPageProto> proto(new DistilledPageProto);
156 EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
158 RunDistillerCallback(distiller, proto.Pass());
160 ASSERT_FALSE(distiller2->GetCallback().is_null());
161 EXPECT_EQ(url2, distiller2->GetUrl());
163 scoped_ptr<DistilledPageProto> proto2(new DistilledPageProto);
164 EXPECT_CALL(viewer_delegate2, OnArticleReady(proto2.get()));
166 RunDistillerCallback(distiller2, proto2.Pass());
169 TEST_F(DomDistillerServiceTest, TestViewUrlCancelled) {
170 FakeDistiller* distiller = new FakeDistiller(false);
171 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
172 .WillOnce(Return(distiller));
174 bool distiller_destroyed = false;
175 EXPECT_CALL(*distiller, Die())
176 .WillOnce(testing::Assign(&distiller_destroyed, true));
178 FakeViewRequestDelegate viewer_delegate;
179 GURL url("http://www.example.com/p1");
180 scoped_ptr<ViewerHandle> handle = service_->ViewUrl(&viewer_delegate, url);
182 ASSERT_FALSE(distiller->GetCallback().is_null());
183 EXPECT_EQ(url, distiller->GetUrl());
185 EXPECT_CALL(viewer_delegate, OnArticleReady(_)).Times(0);
187 EXPECT_FALSE(distiller_destroyed);
190 base::RunLoop().RunUntilIdle();
191 EXPECT_TRUE(distiller_destroyed);
194 TEST_F(DomDistillerServiceTest, TestAddAndRemoveEntry) {
195 FakeDistiller* distiller = new FakeDistiller(false);
196 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
197 .WillOnce(Return(distiller));
199 GURL url("http://www.example.com/p1");
201 MockArticleAvailableCallback article_cb;
202 EXPECT_CALL(article_cb, DistillationCompleted(true));
204 std::string entry_id = service_->AddToList(url, ArticleCallback(&article_cb));
207 EXPECT_TRUE(store_->GetEntryByUrl(url, &entry));
208 EXPECT_EQ(entry.entry_id(), entry_id);
210 ASSERT_FALSE(distiller->GetCallback().is_null());
211 EXPECT_EQ(url, distiller->GetUrl());
213 scoped_ptr<DistilledPageProto> proto(new DistilledPageProto);
214 RunDistillerCallback(distiller, proto.Pass());
216 EXPECT_TRUE(store_->GetEntryByUrl(url, &entry));
217 EXPECT_EQ(1u, store_->GetEntries().size());
218 service_->RemoveEntry(entry_id);
219 base::RunLoop().RunUntilIdle();
220 EXPECT_EQ(0u, store_->GetEntries().size());
223 TEST_F(DomDistillerServiceTest, TestCancellation) {
224 FakeDistiller* distiller = new FakeDistiller(false);
225 MockDistillerObserver observer;
226 service_->AddObserver(&observer);
228 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
229 .WillOnce(Return(distiller));
230 EXPECT_CALL(observer, ArticleEntriesUpdated(_));
232 MockArticleAvailableCallback article_cb;
233 EXPECT_CALL(article_cb, DistillationCompleted(false));
235 GURL url("http://www.example.com/p1");
236 std::string entry_id = service_->AddToList(url, ArticleCallback(&article_cb));
238 // Just remove the entry, there should only be a REMOVE update and no UPDATE
240 std::vector<DomDistillerObserver::ArticleUpdate> expected_updates;
241 DomDistillerObserver::ArticleUpdate update;
242 update.entry_id = entry_id;
243 update.update_type = DomDistillerObserver::ArticleUpdate::REMOVE;
244 expected_updates.push_back(update);
247 ArticleEntriesUpdated(util::HasExpectedUpdates(expected_updates)));
249 // Remove entry will post a cancellation task.
250 service_->RemoveEntry(entry_id);
251 base::RunLoop().RunUntilIdle();
254 TEST_F(DomDistillerServiceTest, TestMultipleObservers) {
255 FakeDistiller* distiller = new FakeDistiller(false);
256 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
257 .WillOnce(Return(distiller));
259 const int kObserverCount = 5;
260 MockDistillerObserver observers[kObserverCount];
261 for (int i = 0; i < kObserverCount; ++i) {
262 service_->AddObserver(&observers[i]);
263 EXPECT_CALL(observers[i], ArticleEntriesUpdated(_));
266 DomDistillerService::ArticleAvailableCallback article_cb;
267 GURL url("http://www.example.com/p1");
268 std::string entry_id = service_->AddToList(url, article_cb);
270 // Distillation should notify all observers that article is updated.
271 std::vector<DomDistillerObserver::ArticleUpdate> expected_updates;
272 DomDistillerObserver::ArticleUpdate update;
273 update.entry_id = entry_id;
274 update.update_type = DomDistillerObserver::ArticleUpdate::UPDATE;
275 expected_updates.push_back(update);
277 for (int i = 0; i < kObserverCount; ++i) {
280 ArticleEntriesUpdated(util::HasExpectedUpdates(expected_updates)));
283 scoped_ptr<DistilledPageProto> proto(new DistilledPageProto);
284 RunDistillerCallback(distiller, proto.Pass());
286 // Remove should notify all observers that article is removed.
287 update.update_type = DomDistillerObserver::ArticleUpdate::REMOVE;
288 expected_updates.clear();
289 expected_updates.push_back(update);
290 for (int i = 0; i < kObserverCount; ++i) {
293 ArticleEntriesUpdated(util::HasExpectedUpdates(expected_updates)));
296 service_->RemoveEntry(entry_id);
297 base::RunLoop().RunUntilIdle();
300 TEST_F(DomDistillerServiceTest, TestMultipleCallbacks) {
301 FakeDistiller* distiller = new FakeDistiller(false);
302 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
303 .WillOnce(Return(distiller));
305 const int kClientsCount = 5;
306 MockArticleAvailableCallback article_cb[kClientsCount];
307 // Adding a URL and then distilling calls all clients.
308 GURL url("http://www.example.com/p1");
309 const std::string entry_id =
310 service_->AddToList(url, ArticleCallback(&article_cb[0]));
311 EXPECT_CALL(article_cb[0], DistillationCompleted(true));
313 for (int i = 1; i < kClientsCount; ++i) {
315 service_->AddToList(url, ArticleCallback(&article_cb[i])));
316 EXPECT_CALL(article_cb[i], DistillationCompleted(true));
319 scoped_ptr<DistilledPageProto> proto(new DistilledPageProto);
320 RunDistillerCallback(distiller, proto.Pass());
322 // Add the same url again, all callbacks should be called with true.
323 for (int i = 0; i < kClientsCount; ++i) {
324 EXPECT_CALL(article_cb[i], DistillationCompleted(true));
326 service_->AddToList(url, ArticleCallback(&article_cb[i])));
329 base::RunLoop().RunUntilIdle();
332 TEST_F(DomDistillerServiceTest, TestMultipleCallbacksOnRemove) {
333 FakeDistiller* distiller = new FakeDistiller(false);
334 EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
335 .WillOnce(Return(distiller));
337 const int kClientsCount = 5;
338 MockArticleAvailableCallback article_cb[kClientsCount];
339 // Adding a URL and remove the entry before distillation. Callback should be
340 // called with false.
341 GURL url("http://www.example.com/p1");
342 const std::string entry_id =
343 service_->AddToList(url, ArticleCallback(&article_cb[0]));
345 EXPECT_CALL(article_cb[0], DistillationCompleted(false));
346 for (int i = 1; i < kClientsCount; ++i) {
348 service_->AddToList(url, ArticleCallback(&article_cb[i])));
349 EXPECT_CALL(article_cb[i], DistillationCompleted(false));
352 service_->RemoveEntry(entry_id);
353 base::RunLoop().RunUntilIdle();
357 } // namespace dom_distiller