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/distilled_page_prefs.h"
15 #include "components/dom_distiller/core/dom_distiller_model.h"
16 #include "components/dom_distiller/core/dom_distiller_store.h"
17 #include "components/dom_distiller/core/dom_distiller_test_util.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 "components/leveldb_proto/testing/fake_db.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23 #include "testing/gtest/include/gtest/gtest.h"
25 using leveldb_proto::test::FakeDB
;
26 using testing::Invoke
;
27 using testing::Return
;
30 namespace dom_distiller
{
35 class FakeViewRequestDelegate
: public ViewRequestDelegate
{
37 virtual ~FakeViewRequestDelegate() {}
38 MOCK_METHOD1(OnArticleReady
, void(const DistilledArticleProto
* proto
));
39 MOCK_METHOD1(OnArticleUpdated
,
40 void(ArticleDistillationUpdate article_update
));
43 class MockDistillerObserver
: public DomDistillerObserver
{
45 MOCK_METHOD1(ArticleEntriesUpdated
, void(const std::vector
<ArticleUpdate
>&));
46 virtual ~MockDistillerObserver() {}
49 class MockArticleAvailableCallback
{
51 MOCK_METHOD1(DistillationCompleted
, void(bool));
54 DomDistillerService::ArticleAvailableCallback
ArticleCallback(
55 MockArticleAvailableCallback
* callback
) {
56 return base::Bind(&MockArticleAvailableCallback::DistillationCompleted
,
57 base::Unretained(callback
));
60 void RunDistillerCallback(FakeDistiller
* distiller
,
61 scoped_ptr
<DistilledArticleProto
> proto
) {
62 distiller
->RunDistillerCallback(proto
.Pass());
63 base::RunLoop().RunUntilIdle();
66 scoped_ptr
<DistilledArticleProto
> CreateArticleWithURL(const std::string
& url
) {
67 scoped_ptr
<DistilledArticleProto
> proto(new DistilledArticleProto
);
68 DistilledPageProto
* page
= proto
->add_pages();
73 scoped_ptr
<DistilledArticleProto
> CreateDefaultArticle() {
74 return CreateArticleWithURL("http://www.example.com/default_article_page1")
80 class DomDistillerServiceTest
: public testing::Test
{
82 void SetUp() override
{
83 main_loop_
.reset(new base::MessageLoop());
84 FakeDB
<ArticleEntry
>* fake_db
= new FakeDB
<ArticleEntry
>(&db_model_
);
85 FakeDB
<ArticleEntry
>::EntryMap store_model
;
87 test::util::CreateStoreWithFakeDB(fake_db
, store_model
);
88 distiller_factory_
= new MockDistillerFactory();
89 distiller_page_factory_
= new MockDistillerPageFactory();
90 service_
.reset(new DomDistillerService(
91 scoped_ptr
<DomDistillerStoreInterface
>(store_
),
92 scoped_ptr
<DistillerFactory
>(distiller_factory_
),
93 scoped_ptr
<DistillerPageFactory
>(distiller_page_factory_
),
94 scoped_ptr
<DistilledPagePrefs
>()));
95 fake_db
->InitCallback(true);
96 fake_db
->LoadCallback(true);
99 void TearDown() override
{
100 base::RunLoop().RunUntilIdle();
102 distiller_factory_
= NULL
;
107 // store is owned by service_.
108 DomDistillerStoreInterface
* store_
;
109 MockDistillerFactory
* distiller_factory_
;
110 MockDistillerPageFactory
* distiller_page_factory_
;
111 scoped_ptr
<DomDistillerService
> service_
;
112 scoped_ptr
<base::MessageLoop
> main_loop_
;
113 FakeDB
<ArticleEntry
>::EntryMap db_model_
;
116 TEST_F(DomDistillerServiceTest
, TestViewEntry
) {
117 FakeDistiller
* distiller
= new FakeDistiller(false);
118 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
119 .WillOnce(Return(distiller
));
121 GURL
url("http://www.example.com/p1");
122 std::string
entry_id("id0");
124 entry
.set_entry_id(entry_id
);
125 entry
.add_pages()->set_url(url
.spec());
127 store_
->AddEntry(entry
);
129 FakeViewRequestDelegate viewer_delegate
;
130 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewEntry(
131 &viewer_delegate
, service_
->CreateDefaultDistillerPage(gfx::Size()),
134 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
136 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
137 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
139 RunDistillerCallback(distiller
, proto
.Pass());
142 TEST_F(DomDistillerServiceTest
, TestViewUrl
) {
143 FakeDistiller
* distiller
= new FakeDistiller(false);
144 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
145 .WillOnce(Return(distiller
));
147 FakeViewRequestDelegate viewer_delegate
;
148 GURL
url("http://www.example.com/p1");
149 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(
150 &viewer_delegate
, service_
->CreateDefaultDistillerPage(gfx::Size()), url
);
152 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
153 EXPECT_EQ(url
, distiller
->GetUrl());
155 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
156 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
158 RunDistillerCallback(distiller
, proto
.Pass());
161 TEST_F(DomDistillerServiceTest
, TestMultipleViewUrl
) {
162 FakeDistiller
* distiller
= new FakeDistiller(false);
163 FakeDistiller
* distiller2
= new FakeDistiller(false);
164 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
165 .WillOnce(Return(distiller
))
166 .WillOnce(Return(distiller2
));
168 FakeViewRequestDelegate viewer_delegate
;
169 FakeViewRequestDelegate viewer_delegate2
;
171 GURL
url("http://www.example.com/p1");
172 GURL
url2("http://www.example.com/a/p1");
174 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(
175 &viewer_delegate
, service_
->CreateDefaultDistillerPage(gfx::Size()), url
);
176 scoped_ptr
<ViewerHandle
> handle2
= service_
->ViewUrl(
177 &viewer_delegate2
, service_
->CreateDefaultDistillerPage(gfx::Size()),
180 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
181 EXPECT_EQ(url
, distiller
->GetUrl());
183 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
184 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
186 RunDistillerCallback(distiller
, proto
.Pass());
188 ASSERT_FALSE(distiller2
->GetArticleCallback().is_null());
189 EXPECT_EQ(url2
, distiller2
->GetUrl());
191 scoped_ptr
<DistilledArticleProto
> proto2
= CreateDefaultArticle();
192 EXPECT_CALL(viewer_delegate2
, OnArticleReady(proto2
.get()));
194 RunDistillerCallback(distiller2
, proto2
.Pass());
197 TEST_F(DomDistillerServiceTest
, TestViewUrlCancelled
) {
198 FakeDistiller
* distiller
= new FakeDistiller(false);
199 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
200 .WillOnce(Return(distiller
));
202 bool distiller_destroyed
= false;
203 EXPECT_CALL(*distiller
, Die())
204 .WillOnce(testing::Assign(&distiller_destroyed
, true));
206 FakeViewRequestDelegate viewer_delegate
;
207 GURL
url("http://www.example.com/p1");
208 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(
209 &viewer_delegate
, service_
->CreateDefaultDistillerPage(gfx::Size()), url
);
211 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
212 EXPECT_EQ(url
, distiller
->GetUrl());
214 EXPECT_CALL(viewer_delegate
, OnArticleReady(_
)).Times(0);
216 EXPECT_FALSE(distiller_destroyed
);
219 base::RunLoop().RunUntilIdle();
220 EXPECT_TRUE(distiller_destroyed
);
223 TEST_F(DomDistillerServiceTest
, TestViewUrlDoesNotAddEntry
) {
224 FakeDistiller
* distiller
= new FakeDistiller(false);
225 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
226 .WillOnce(Return(distiller
));
228 FakeViewRequestDelegate viewer_delegate
;
229 GURL
url("http://www.example.com/p1");
230 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(
231 &viewer_delegate
, service_
->CreateDefaultDistillerPage(gfx::Size()), url
);
233 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
234 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
236 RunDistillerCallback(distiller
, proto
.Pass());
237 base::RunLoop().RunUntilIdle();
238 // The entry should not be added to the store.
239 EXPECT_EQ(0u, store_
->GetEntries().size());
242 TEST_F(DomDistillerServiceTest
, TestAddAndRemoveEntry
) {
243 FakeDistiller
* distiller
= new FakeDistiller(false);
244 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
245 .WillOnce(Return(distiller
));
247 GURL
url("http://www.example.com/p1");
249 MockArticleAvailableCallback article_cb
;
250 EXPECT_CALL(article_cb
, DistillationCompleted(true));
252 std::string entry_id
=
253 service_
->AddToList(url
,
254 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
255 ArticleCallback(&article_cb
));
257 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
258 EXPECT_EQ(url
, distiller
->GetUrl());
260 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
261 RunDistillerCallback(distiller
, proto
.Pass());
264 EXPECT_TRUE(store_
->GetEntryByUrl(url
, &entry
));
265 EXPECT_EQ(entry
.entry_id(), entry_id
);
266 EXPECT_EQ(1u, store_
->GetEntries().size());
267 service_
->RemoveEntry(entry_id
);
268 base::RunLoop().RunUntilIdle();
269 EXPECT_EQ(0u, store_
->GetEntries().size());
272 TEST_F(DomDistillerServiceTest
, TestCancellation
) {
273 FakeDistiller
* distiller
= new FakeDistiller(false);
274 MockDistillerObserver observer
;
275 service_
->AddObserver(&observer
);
277 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
278 .WillOnce(Return(distiller
));
280 MockArticleAvailableCallback article_cb
;
281 EXPECT_CALL(article_cb
, DistillationCompleted(false));
283 GURL
url("http://www.example.com/p1");
284 std::string entry_id
=
285 service_
->AddToList(url
,
286 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
287 ArticleCallback(&article_cb
));
289 // Remove entry will cause the |article_cb| to be called with false value.
290 service_
->RemoveEntry(entry_id
);
291 base::RunLoop().RunUntilIdle();
294 TEST_F(DomDistillerServiceTest
, TestMultipleObservers
) {
295 FakeDistiller
* distiller
= new FakeDistiller(false);
296 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
297 .WillOnce(Return(distiller
));
299 const int kObserverCount
= 5;
300 MockDistillerObserver observers
[kObserverCount
];
301 for (int i
= 0; i
< kObserverCount
; ++i
) {
302 service_
->AddObserver(&observers
[i
]);
305 DomDistillerService::ArticleAvailableCallback article_cb
;
306 GURL
url("http://www.example.com/p1");
307 std::string entry_id
= service_
->AddToList(
308 url
, service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
311 // Distillation should notify all observers that article is added.
312 std::vector
<DomDistillerObserver::ArticleUpdate
> expected_updates
;
313 DomDistillerObserver::ArticleUpdate update
;
314 update
.entry_id
= entry_id
;
315 update
.update_type
= DomDistillerObserver::ArticleUpdate::ADD
;
316 expected_updates
.push_back(update
);
318 for (int i
= 0; i
< kObserverCount
; ++i
) {
319 EXPECT_CALL(observers
[i
], ArticleEntriesUpdated(
320 util::HasExpectedUpdates(expected_updates
)));
323 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
324 RunDistillerCallback(distiller
, proto
.Pass());
326 // Remove should notify all observers that article is removed.
327 update
.update_type
= DomDistillerObserver::ArticleUpdate::REMOVE
;
328 expected_updates
.clear();
329 expected_updates
.push_back(update
);
330 for (int i
= 0; i
< kObserverCount
; ++i
) {
331 EXPECT_CALL(observers
[i
], ArticleEntriesUpdated(
332 util::HasExpectedUpdates(expected_updates
)));
335 service_
->RemoveEntry(entry_id
);
336 base::RunLoop().RunUntilIdle();
339 TEST_F(DomDistillerServiceTest
, TestMultipleCallbacks
) {
340 FakeDistiller
* distiller
= new FakeDistiller(false);
341 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
342 .WillOnce(Return(distiller
));
344 const int kClientsCount
= 5;
345 MockArticleAvailableCallback article_cb
[kClientsCount
];
346 // Adding a URL and then distilling calls all clients.
347 GURL
url("http://www.example.com/p1");
348 const std::string entry_id
=
349 service_
->AddToList(url
,
350 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
351 ArticleCallback(&article_cb
[0]));
352 EXPECT_CALL(article_cb
[0], DistillationCompleted(true));
354 for (int i
= 1; i
< kClientsCount
; ++i
) {
358 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
359 ArticleCallback(&article_cb
[i
])));
360 EXPECT_CALL(article_cb
[i
], DistillationCompleted(true));
363 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
364 RunDistillerCallback(distiller
, proto
.Pass());
366 // Add the same url again, all callbacks should be called with true.
367 for (int i
= 0; i
< kClientsCount
; ++i
) {
368 EXPECT_CALL(article_cb
[i
], DistillationCompleted(true));
372 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
373 ArticleCallback(&article_cb
[i
])));
376 base::RunLoop().RunUntilIdle();
379 TEST_F(DomDistillerServiceTest
, TestMultipleCallbacksOnRemove
) {
380 FakeDistiller
* distiller
= new FakeDistiller(false);
381 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
382 .WillOnce(Return(distiller
));
384 const int kClientsCount
= 5;
385 MockArticleAvailableCallback article_cb
[kClientsCount
];
386 // Adding a URL and remove the entry before distillation. Callback should be
387 // called with false.
388 GURL
url("http://www.example.com/p1");
389 const std::string entry_id
=
390 service_
->AddToList(url
,
391 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
392 ArticleCallback(&article_cb
[0]));
394 EXPECT_CALL(article_cb
[0], DistillationCompleted(false));
395 for (int i
= 1; i
< kClientsCount
; ++i
) {
398 url
, service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
399 ArticleCallback(&article_cb
[i
])));
400 EXPECT_CALL(article_cb
[i
], DistillationCompleted(false));
403 service_
->RemoveEntry(entry_id
);
404 base::RunLoop().RunUntilIdle();
407 TEST_F(DomDistillerServiceTest
, TestMultiplePageArticle
) {
408 FakeDistiller
* distiller
= new FakeDistiller(false);
409 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
410 .WillOnce(Return(distiller
));
412 const int kPageCount
= 8;
414 std::string
base_url("http://www.example.com/p");
415 GURL pages_url
[kPageCount
];
416 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
417 pages_url
[page_num
] = GURL(base_url
+ base::IntToString(page_num
));
420 MockArticleAvailableCallback article_cb
;
421 EXPECT_CALL(article_cb
, DistillationCompleted(true));
423 std::string entry_id
= service_
->AddToList(
424 pages_url
[0], service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
425 ArticleCallback(&article_cb
));
428 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
429 EXPECT_EQ(pages_url
[0], distiller
->GetUrl());
431 // Create the article with pages to pass to the distiller.
432 scoped_ptr
<DistilledArticleProto
> proto
=
433 CreateArticleWithURL(pages_url
[0].spec());
434 for (int page_num
= 1; page_num
< kPageCount
; ++page_num
) {
435 DistilledPageProto
* distilled_page
= proto
->add_pages();
436 distilled_page
->set_url(pages_url
[page_num
].spec());
439 RunDistillerCallback(distiller
, proto
.Pass());
440 EXPECT_TRUE(store_
->GetEntryByUrl(pages_url
[0], &entry
));
442 EXPECT_EQ(kPageCount
, entry
.pages_size());
443 // An article should have just one entry.
444 EXPECT_EQ(1u, store_
->GetEntries().size());
446 // All pages should have correct urls.
447 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
448 EXPECT_EQ(pages_url
[page_num
].spec(), entry
.pages(page_num
).url());
451 // Should be able to query article using any of the pages url.
452 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
453 EXPECT_TRUE(store_
->GetEntryByUrl(pages_url
[page_num
], &entry
));
456 service_
->RemoveEntry(entry_id
);
457 base::RunLoop().RunUntilIdle();
458 EXPECT_EQ(0u, store_
->GetEntries().size());
461 TEST_F(DomDistillerServiceTest
, TestHasEntry
) {
462 FakeDistiller
* distiller
= new FakeDistiller(false);
463 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
464 .WillOnce(Return(distiller
));
466 GURL
url("http://www.example.com/p1");
468 MockArticleAvailableCallback article_cb
;
469 EXPECT_CALL(article_cb
, DistillationCompleted(true));
471 std::string entry_id
= service_
->AddToList(
473 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
474 ArticleCallback(&article_cb
));
476 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
477 EXPECT_EQ(url
, distiller
->GetUrl());
479 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
480 RunDistillerCallback(distiller
, proto
.Pass());
482 // Check that HasEntry returns true for the article just added.
483 EXPECT_TRUE(service_
->HasEntry(entry_id
));
485 // Remove article and check that there is no longer an entry for the given
487 service_
->RemoveEntry(entry_id
);
488 base::RunLoop().RunUntilIdle();
489 EXPECT_EQ(0u, store_
->GetEntries().size());
490 EXPECT_FALSE(service_
->HasEntry(entry_id
));
493 TEST_F(DomDistillerServiceTest
, TestGetUrlForOnePageEntry
) {
494 FakeDistiller
* distiller
= new FakeDistiller(false);
495 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
496 .WillOnce(Return(distiller
));
498 GURL
url("http://www.example.com/p1");
500 MockArticleAvailableCallback article_cb
;
501 EXPECT_CALL(article_cb
, DistillationCompleted(true));
503 std::string entry_id
= service_
->AddToList(
505 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
506 ArticleCallback(&article_cb
));
508 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
509 EXPECT_EQ(url
, distiller
->GetUrl());
511 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
512 RunDistillerCallback(distiller
, proto
.Pass());
514 // Check if retrieved URL is same as given URL.
515 GURL
retrieved_url(service_
->GetUrlForEntry(entry_id
));
516 EXPECT_EQ(url
, retrieved_url
);
518 // Remove article and check that there is no longer an entry for the given
520 service_
->RemoveEntry(entry_id
);
521 base::RunLoop().RunUntilIdle();
522 EXPECT_EQ(0u, store_
->GetEntries().size());
523 EXPECT_EQ("", service_
->GetUrlForEntry(entry_id
));
526 TEST_F(DomDistillerServiceTest
, TestGetUrlForMultiPageEntry
) {
527 FakeDistiller
* distiller
= new FakeDistiller(false);
528 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
529 .WillOnce(Return(distiller
));
531 const int kPageCount
= 8;
533 std::string
base_url("http://www.example.com/p");
534 GURL pages_url
[kPageCount
];
535 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
536 pages_url
[page_num
] = GURL(base_url
+ base::IntToString(page_num
));
539 MockArticleAvailableCallback article_cb
;
540 EXPECT_CALL(article_cb
, DistillationCompleted(true));
542 std::string entry_id
= service_
->AddToList(
544 service_
->CreateDefaultDistillerPage(gfx::Size()).Pass(),
545 ArticleCallback(&article_cb
));
548 ASSERT_FALSE(distiller
->GetArticleCallback().is_null());
549 EXPECT_EQ(pages_url
[0], distiller
->GetUrl());
551 // Create the article with pages to pass to the distiller.
552 scoped_ptr
<DistilledArticleProto
> proto
=
553 CreateArticleWithURL(pages_url
[0].spec());
554 for (int page_num
= 1; page_num
< kPageCount
; ++page_num
) {
555 DistilledPageProto
* distilled_page
= proto
->add_pages();
556 distilled_page
->set_url(pages_url
[page_num
].spec());
559 RunDistillerCallback(distiller
, proto
.Pass());
560 EXPECT_TRUE(store_
->GetEntryByUrl(pages_url
[0], &entry
));
562 // Check if retrieved URL is same as given URL for the first page.
563 GURL
retrieved_url(service_
->GetUrlForEntry(entry_id
));
564 EXPECT_EQ(pages_url
[0], retrieved_url
);
566 // Remove the article and check that no URL can be retrieved for the entry.
567 service_
->RemoveEntry(entry_id
);
568 base::RunLoop().RunUntilIdle();
569 EXPECT_EQ(0u, store_
->GetEntries().size());
570 EXPECT_EQ("", service_
->GetUrlForEntry(entry_id
));
574 } // namespace dom_distiller