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 "base/strings/string_number_conversions.h"
13 #include "components/dom_distiller/core/article_entry.h"
14 #include "components/dom_distiller/core/dom_distiller_model.h"
15 #include "components/dom_distiller/core/dom_distiller_store.h"
16 #include "components/dom_distiller/core/dom_distiller_test_util.h"
17 #include "components/dom_distiller/core/fake_db.h"
18 #include "components/dom_distiller/core/fake_distiller.h"
19 #include "components/dom_distiller/core/fake_distiller_page.h"
20 #include "components/dom_distiller/core/task_tracker.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
24 using testing::Invoke
;
25 using testing::Return
;
28 namespace dom_distiller
{
33 class FakeViewRequestDelegate
: public ViewRequestDelegate
{
35 virtual ~FakeViewRequestDelegate() {}
36 MOCK_METHOD1(OnArticleReady
, void(const DistilledArticleProto
* proto
));
37 MOCK_METHOD1(OnArticleUpdated
,
38 void(ArticleDistillationUpdate article_update
));
41 class MockDistillerObserver
: public DomDistillerObserver
{
43 MOCK_METHOD1(ArticleEntriesUpdated
, void(const std::vector
<ArticleUpdate
>&));
44 virtual ~MockDistillerObserver() {}
47 class MockArticleAvailableCallback
{
49 MOCK_METHOD1(DistillationCompleted
, void(bool));
52 DomDistillerService::ArticleAvailableCallback
ArticleCallback(
53 MockArticleAvailableCallback
* callback
) {
54 return base::Bind(&MockArticleAvailableCallback::DistillationCompleted
,
55 base::Unretained(callback
));
58 void RunDistillerCallback(FakeDistiller
* distiller
,
59 scoped_ptr
<DistilledArticleProto
> proto
) {
60 distiller
->RunDistillerCallback(proto
.Pass());
61 base::RunLoop().RunUntilIdle();
64 scoped_ptr
<DistilledArticleProto
> CreateArticleWithURL(const std::string
& url
) {
65 scoped_ptr
<DistilledArticleProto
> proto(new DistilledArticleProto
);
66 DistilledPageProto
* page
= proto
->add_pages();
71 scoped_ptr
<DistilledArticleProto
> CreateDefaultArticle() {
72 return CreateArticleWithURL("http://www.example.com/default_article_page1")
78 class DomDistillerServiceTest
: public testing::Test
{
80 virtual void SetUp() {
81 main_loop_
.reset(new base::MessageLoop());
82 FakeDB
* fake_db
= new FakeDB(&db_model_
);
83 FakeDB::EntryMap store_model
;
84 store_
= test::util::CreateStoreWithFakeDB(fake_db
, store_model
);
85 distiller_factory_
= new MockDistillerFactory();
86 distiller_page_factory_
= new MockDistillerPageFactory();
87 service_
.reset(new DomDistillerService(
88 scoped_ptr
<DomDistillerStoreInterface
>(store_
),
89 scoped_ptr
<DistillerFactory
>(distiller_factory_
),
90 scoped_ptr
<DistillerPageFactory
>(distiller_page_factory_
)));
91 fake_db
->InitCallback(true);
92 fake_db
->LoadCallback(true);
95 virtual void TearDown() {
96 base::RunLoop().RunUntilIdle();
98 distiller_factory_
= NULL
;
103 // store is owned by service_.
104 DomDistillerStoreInterface
* store_
;
105 MockDistillerFactory
* distiller_factory_
;
106 MockDistillerPageFactory
* distiller_page_factory_
;
107 scoped_ptr
<DomDistillerService
> service_
;
108 scoped_ptr
<base::MessageLoop
> main_loop_
;
109 FakeDB::EntryMap db_model_
;
112 TEST_F(DomDistillerServiceTest
, TestViewEntry
) {
113 FakeDistiller
* distiller
= new FakeDistiller(false);
114 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
115 .WillOnce(Return(distiller
));
117 GURL
url("http://www.example.com/p1");
118 std::string
entry_id("id0");
120 entry
.set_entry_id(entry_id
);
121 entry
.add_pages()->set_url(url
.spec());
123 store_
->AddEntry(entry
);
125 FakeViewRequestDelegate viewer_delegate
;
126 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewEntry(
127 &viewer_delegate
, service_
->CreateDefaultDistillerPage(), entry_id
);
129 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
131 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
132 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
134 RunDistillerCallback(distiller
, proto
.Pass());
137 TEST_F(DomDistillerServiceTest
, TestViewUrl
) {
138 FakeDistiller
* distiller
= new FakeDistiller(false);
139 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
140 .WillOnce(Return(distiller
));
142 FakeViewRequestDelegate viewer_delegate
;
143 GURL
url("http://www.example.com/p1");
144 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(
145 &viewer_delegate
, service_
->CreateDefaultDistillerPage(), url
);
147 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
148 EXPECT_EQ(url
, distiller
->GetUrl());
150 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
151 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
153 RunDistillerCallback(distiller
, proto
.Pass());
156 TEST_F(DomDistillerServiceTest
, TestMultipleViewUrl
) {
157 FakeDistiller
* distiller
= new FakeDistiller(false);
158 FakeDistiller
* distiller2
= new FakeDistiller(false);
159 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
160 .WillOnce(Return(distiller
))
161 .WillOnce(Return(distiller2
));
163 FakeViewRequestDelegate viewer_delegate
;
164 FakeViewRequestDelegate viewer_delegate2
;
166 GURL
url("http://www.example.com/p1");
167 GURL
url2("http://www.example.com/a/p1");
169 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(
170 &viewer_delegate
, service_
->CreateDefaultDistillerPage(), url
);
171 scoped_ptr
<ViewerHandle
> handle2
= service_
->ViewUrl(
172 &viewer_delegate2
, service_
->CreateDefaultDistillerPage(), url2
);
174 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
175 EXPECT_EQ(url
, distiller
->GetUrl());
177 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
178 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
180 RunDistillerCallback(distiller
, proto
.Pass());
182 ASSERT_FALSE(distiller2
->GetArticleCallback().is_null());
183 EXPECT_EQ(url2
, distiller2
->GetUrl());
185 scoped_ptr
<DistilledArticleProto
> proto2
= CreateDefaultArticle();
186 EXPECT_CALL(viewer_delegate2
, OnArticleReady(proto2
.get()));
188 RunDistillerCallback(distiller2
, proto2
.Pass());
191 TEST_F(DomDistillerServiceTest
, TestViewUrlCancelled
) {
192 FakeDistiller
* distiller
= new FakeDistiller(false);
193 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
194 .WillOnce(Return(distiller
));
196 bool distiller_destroyed
= false;
197 EXPECT_CALL(*distiller
, Die())
198 .WillOnce(testing::Assign(&distiller_destroyed
, true));
200 FakeViewRequestDelegate viewer_delegate
;
201 GURL
url("http://www.example.com/p1");
202 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(
203 &viewer_delegate
, service_
->CreateDefaultDistillerPage(), url
);
205 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
206 EXPECT_EQ(url
, distiller
->GetUrl());
208 EXPECT_CALL(viewer_delegate
, OnArticleReady(_
)).Times(0);
210 EXPECT_FALSE(distiller_destroyed
);
213 base::RunLoop().RunUntilIdle();
214 EXPECT_TRUE(distiller_destroyed
);
217 TEST_F(DomDistillerServiceTest
, TestViewUrlDoesNotAddEntry
) {
218 FakeDistiller
* distiller
= new FakeDistiller(false);
219 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
220 .WillOnce(Return(distiller
));
222 FakeViewRequestDelegate viewer_delegate
;
223 GURL
url("http://www.example.com/p1");
224 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(
225 &viewer_delegate
, service_
->CreateDefaultDistillerPage(), url
);
227 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
228 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
230 RunDistillerCallback(distiller
, proto
.Pass());
231 base::RunLoop().RunUntilIdle();
232 // The entry should not be added to the store.
233 EXPECT_EQ(0u, store_
->GetEntries().size());
236 TEST_F(DomDistillerServiceTest
, TestAddAndRemoveEntry
) {
237 FakeDistiller
* distiller
= new FakeDistiller(false);
238 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
239 .WillOnce(Return(distiller
));
241 GURL
url("http://www.example.com/p1");
243 MockArticleAvailableCallback article_cb
;
244 EXPECT_CALL(article_cb
, DistillationCompleted(true));
246 std::string entry_id
=
247 service_
->AddToList(url
,
248 service_
->CreateDefaultDistillerPage().Pass(),
249 ArticleCallback(&article_cb
));
251 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
252 EXPECT_EQ(url
, distiller
->GetUrl());
254 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
255 RunDistillerCallback(distiller
, proto
.Pass());
258 EXPECT_TRUE(store_
->GetEntryByUrl(url
, &entry
));
259 EXPECT_EQ(entry
.entry_id(), entry_id
);
260 EXPECT_EQ(1u, store_
->GetEntries().size());
261 service_
->RemoveEntry(entry_id
);
262 base::RunLoop().RunUntilIdle();
263 EXPECT_EQ(0u, store_
->GetEntries().size());
266 TEST_F(DomDistillerServiceTest
, TestCancellation
) {
267 FakeDistiller
* distiller
= new FakeDistiller(false);
268 MockDistillerObserver observer
;
269 service_
->AddObserver(&observer
);
271 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
272 .WillOnce(Return(distiller
));
274 MockArticleAvailableCallback article_cb
;
275 EXPECT_CALL(article_cb
, DistillationCompleted(false));
277 GURL
url("http://www.example.com/p1");
278 std::string entry_id
=
279 service_
->AddToList(url
,
280 service_
->CreateDefaultDistillerPage().Pass(),
281 ArticleCallback(&article_cb
));
283 // Remove entry will cause the |article_cb| to be called with false value.
284 service_
->RemoveEntry(entry_id
);
285 base::RunLoop().RunUntilIdle();
288 TEST_F(DomDistillerServiceTest
, TestMultipleObservers
) {
289 FakeDistiller
* distiller
= new FakeDistiller(false);
290 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
291 .WillOnce(Return(distiller
));
293 const int kObserverCount
= 5;
294 MockDistillerObserver observers
[kObserverCount
];
295 for (int i
= 0; i
< kObserverCount
; ++i
) {
296 service_
->AddObserver(&observers
[i
]);
299 DomDistillerService::ArticleAvailableCallback article_cb
;
300 GURL
url("http://www.example.com/p1");
301 std::string entry_id
= service_
->AddToList(
302 url
, service_
->CreateDefaultDistillerPage().Pass(), article_cb
);
304 // Distillation should notify all observers that article is added.
305 std::vector
<DomDistillerObserver::ArticleUpdate
> expected_updates
;
306 DomDistillerObserver::ArticleUpdate update
;
307 update
.entry_id
= entry_id
;
308 update
.update_type
= DomDistillerObserver::ArticleUpdate::ADD
;
309 expected_updates
.push_back(update
);
311 for (int i
= 0; i
< kObserverCount
; ++i
) {
314 ArticleEntriesUpdated(util::HasExpectedUpdates(expected_updates
)));
317 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
318 RunDistillerCallback(distiller
, proto
.Pass());
320 // Remove should notify all observers that article is removed.
321 update
.update_type
= DomDistillerObserver::ArticleUpdate::REMOVE
;
322 expected_updates
.clear();
323 expected_updates
.push_back(update
);
324 for (int i
= 0; i
< kObserverCount
; ++i
) {
327 ArticleEntriesUpdated(util::HasExpectedUpdates(expected_updates
)));
330 service_
->RemoveEntry(entry_id
);
331 base::RunLoop().RunUntilIdle();
334 TEST_F(DomDistillerServiceTest
, TestMultipleCallbacks
) {
335 FakeDistiller
* distiller
= new FakeDistiller(false);
336 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
337 .WillOnce(Return(distiller
));
339 const int kClientsCount
= 5;
340 MockArticleAvailableCallback article_cb
[kClientsCount
];
341 // Adding a URL and then distilling calls all clients.
342 GURL
url("http://www.example.com/p1");
343 const std::string entry_id
=
344 service_
->AddToList(url
,
345 service_
->CreateDefaultDistillerPage().Pass(),
346 ArticleCallback(&article_cb
[0]));
347 EXPECT_CALL(article_cb
[0], DistillationCompleted(true));
349 for (int i
= 1; i
< kClientsCount
; ++i
) {
351 service_
->AddToList(url
,
352 service_
->CreateDefaultDistillerPage().Pass(),
353 ArticleCallback(&article_cb
[i
])));
354 EXPECT_CALL(article_cb
[i
], DistillationCompleted(true));
357 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
358 RunDistillerCallback(distiller
, proto
.Pass());
360 // Add the same url again, all callbacks should be called with true.
361 for (int i
= 0; i
< kClientsCount
; ++i
) {
362 EXPECT_CALL(article_cb
[i
], DistillationCompleted(true));
364 service_
->AddToList(url
,
365 service_
->CreateDefaultDistillerPage().Pass(),
366 ArticleCallback(&article_cb
[i
])));
369 base::RunLoop().RunUntilIdle();
372 TEST_F(DomDistillerServiceTest
, TestMultipleCallbacksOnRemove
) {
373 FakeDistiller
* distiller
= new FakeDistiller(false);
374 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
375 .WillOnce(Return(distiller
));
377 const int kClientsCount
= 5;
378 MockArticleAvailableCallback article_cb
[kClientsCount
];
379 // Adding a URL and remove the entry before distillation. Callback should be
380 // called with false.
381 GURL
url("http://www.example.com/p1");
382 const std::string entry_id
=
383 service_
->AddToList(url
,
384 service_
->CreateDefaultDistillerPage().Pass(),
385 ArticleCallback(&article_cb
[0]));
387 EXPECT_CALL(article_cb
[0], DistillationCompleted(false));
388 for (int i
= 1; i
< kClientsCount
; ++i
) {
390 service_
->AddToList(url
,
391 service_
->CreateDefaultDistillerPage().Pass(),
392 ArticleCallback(&article_cb
[i
])));
393 EXPECT_CALL(article_cb
[i
], DistillationCompleted(false));
396 service_
->RemoveEntry(entry_id
);
397 base::RunLoop().RunUntilIdle();
400 TEST_F(DomDistillerServiceTest
, TestMultiplePageArticle
) {
401 FakeDistiller
* distiller
= new FakeDistiller(false);
402 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
403 .WillOnce(Return(distiller
));
405 const int kPageCount
= 8;
407 std::string
base_url("http://www.example.com/p");
408 GURL pages_url
[kPageCount
];
409 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
410 pages_url
[page_num
] = GURL(base_url
+ base::IntToString(page_num
));
413 MockArticleAvailableCallback article_cb
;
414 EXPECT_CALL(article_cb
, DistillationCompleted(true));
416 std::string entry_id
=
417 service_
->AddToList(pages_url
[0],
418 service_
->CreateDefaultDistillerPage().Pass(),
419 ArticleCallback(&article_cb
));
422 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
423 EXPECT_EQ(pages_url
[0], distiller
->GetUrl());
425 // Create the article with pages to pass to the distiller.
426 scoped_ptr
<DistilledArticleProto
> proto
=
427 CreateArticleWithURL(pages_url
[0].spec());
428 for (int page_num
= 1; page_num
< kPageCount
; ++page_num
) {
429 DistilledPageProto
* distilled_page
= proto
->add_pages();
430 distilled_page
->set_url(pages_url
[page_num
].spec());
433 RunDistillerCallback(distiller
, proto
.Pass());
434 EXPECT_TRUE(store_
->GetEntryByUrl(pages_url
[0], &entry
));
436 EXPECT_EQ(kPageCount
, entry
.pages_size());
437 // An article should have just one entry.
438 EXPECT_EQ(1u, store_
->GetEntries().size());
440 // All pages should have correct urls.
441 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
442 EXPECT_EQ(pages_url
[page_num
].spec(), entry
.pages(page_num
).url());
445 // Should be able to query article using any of the pages url.
446 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
447 EXPECT_TRUE(store_
->GetEntryByUrl(pages_url
[page_num
], &entry
));
450 service_
->RemoveEntry(entry_id
);
451 base::RunLoop().RunUntilIdle();
452 EXPECT_EQ(0u, store_
->GetEntries().size());
456 } // namespace dom_distiller