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/task_tracker.h"
20 #include "testing/gmock/include/gmock/gmock.h"
21 #include "testing/gtest/include/gtest/gtest.h"
23 using testing::Invoke
;
24 using testing::Return
;
27 namespace dom_distiller
{
32 class FakeViewRequestDelegate
: public ViewRequestDelegate
{
34 virtual ~FakeViewRequestDelegate() {}
35 MOCK_METHOD1(OnArticleReady
, void(const DistilledArticleProto
* proto
));
38 class MockDistillerObserver
: public DomDistillerObserver
{
40 MOCK_METHOD1(ArticleEntriesUpdated
, void(const std::vector
<ArticleUpdate
>&));
41 virtual ~MockDistillerObserver() {}
44 class MockArticleAvailableCallback
{
46 MOCK_METHOD1(DistillationCompleted
, void(bool));
49 DomDistillerService::ArticleAvailableCallback
ArticleCallback(
50 MockArticleAvailableCallback
* callback
) {
51 return base::Bind(&MockArticleAvailableCallback::DistillationCompleted
,
52 base::Unretained(callback
));
55 void RunDistillerCallback(FakeDistiller
* distiller
,
56 scoped_ptr
<DistilledArticleProto
> proto
) {
57 distiller
->RunDistillerCallback(proto
.Pass());
58 base::RunLoop().RunUntilIdle();
61 scoped_ptr
<DistilledArticleProto
> CreateArticleWithURL(const std::string
& url
) {
62 scoped_ptr
<DistilledArticleProto
> proto(new DistilledArticleProto
);
63 DistilledPageProto
* page
= proto
->add_pages();
68 scoped_ptr
<DistilledArticleProto
> CreateDefaultArticle() {
69 return CreateArticleWithURL("http://www.example.com/default_article_page1")
75 class DomDistillerServiceTest
: public testing::Test
{
77 virtual void SetUp() {
78 main_loop_
.reset(new base::MessageLoop());
79 FakeDB
* fake_db
= new FakeDB(&db_model_
);
80 FakeDB::EntryMap store_model
;
81 store_
= test::util::CreateStoreWithFakeDB(fake_db
, store_model
);
82 distiller_factory_
= new MockDistillerFactory();
83 service_
.reset(new DomDistillerService(
84 scoped_ptr
<DomDistillerStoreInterface
>(store_
),
85 scoped_ptr
<DistillerFactory
>(distiller_factory_
)));
86 fake_db
->InitCallback(true);
87 fake_db
->LoadCallback(true);
90 virtual void TearDown() {
91 base::RunLoop().RunUntilIdle();
93 distiller_factory_
= NULL
;
98 // store is owned by service_.
99 DomDistillerStoreInterface
* store_
;
100 MockDistillerFactory
* distiller_factory_
;
101 scoped_ptr
<DomDistillerService
> service_
;
102 scoped_ptr
<base::MessageLoop
> main_loop_
;
103 FakeDB::EntryMap db_model_
;
106 TEST_F(DomDistillerServiceTest
, TestViewEntry
) {
107 FakeDistiller
* distiller
= new FakeDistiller(false);
108 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
109 .WillOnce(Return(distiller
));
111 GURL
url("http://www.example.com/p1");
112 std::string
entry_id("id0");
114 entry
.set_entry_id(entry_id
);
115 entry
.add_pages()->set_url(url
.spec());
117 store_
->AddEntry(entry
);
119 FakeViewRequestDelegate viewer_delegate
;
120 scoped_ptr
<ViewerHandle
> handle
=
121 service_
->ViewEntry(&viewer_delegate
, entry_id
);
123 ASSERT_FALSE(distiller
->GetCallback().is_null());
125 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
126 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
128 RunDistillerCallback(distiller
, proto
.Pass());
131 TEST_F(DomDistillerServiceTest
, TestViewUrl
) {
132 FakeDistiller
* distiller
= new FakeDistiller(false);
133 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
134 .WillOnce(Return(distiller
));
136 FakeViewRequestDelegate viewer_delegate
;
137 GURL
url("http://www.example.com/p1");
138 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(&viewer_delegate
, url
);
140 ASSERT_FALSE(distiller
->GetCallback().is_null());
141 EXPECT_EQ(url
, distiller
->GetUrl());
143 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
144 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
146 RunDistillerCallback(distiller
, proto
.Pass());
149 TEST_F(DomDistillerServiceTest
, TestMultipleViewUrl
) {
150 FakeDistiller
* distiller
= new FakeDistiller(false);
151 FakeDistiller
* distiller2
= new FakeDistiller(false);
152 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
153 .WillOnce(Return(distiller
))
154 .WillOnce(Return(distiller2
));
156 FakeViewRequestDelegate viewer_delegate
;
157 FakeViewRequestDelegate viewer_delegate2
;
159 GURL
url("http://www.example.com/p1");
160 GURL
url2("http://www.example.com/a/p1");
162 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(&viewer_delegate
, url
);
163 scoped_ptr
<ViewerHandle
> handle2
= service_
->ViewUrl(&viewer_delegate2
, url2
);
165 ASSERT_FALSE(distiller
->GetCallback().is_null());
166 EXPECT_EQ(url
, distiller
->GetUrl());
168 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
169 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
171 RunDistillerCallback(distiller
, proto
.Pass());
173 ASSERT_FALSE(distiller2
->GetCallback().is_null());
174 EXPECT_EQ(url2
, distiller2
->GetUrl());
176 scoped_ptr
<DistilledArticleProto
> proto2
= CreateDefaultArticle();
177 EXPECT_CALL(viewer_delegate2
, OnArticleReady(proto2
.get()));
179 RunDistillerCallback(distiller2
, proto2
.Pass());
182 TEST_F(DomDistillerServiceTest
, TestViewUrlCancelled
) {
183 FakeDistiller
* distiller
= new FakeDistiller(false);
184 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
185 .WillOnce(Return(distiller
));
187 bool distiller_destroyed
= false;
188 EXPECT_CALL(*distiller
, Die())
189 .WillOnce(testing::Assign(&distiller_destroyed
, true));
191 FakeViewRequestDelegate viewer_delegate
;
192 GURL
url("http://www.example.com/p1");
193 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(&viewer_delegate
, url
);
195 ASSERT_FALSE(distiller
->GetCallback().is_null());
196 EXPECT_EQ(url
, distiller
->GetUrl());
198 EXPECT_CALL(viewer_delegate
, OnArticleReady(_
)).Times(0);
200 EXPECT_FALSE(distiller_destroyed
);
203 base::RunLoop().RunUntilIdle();
204 EXPECT_TRUE(distiller_destroyed
);
207 TEST_F(DomDistillerServiceTest
, TestViewUrlDoesNotAddEntry
) {
208 FakeDistiller
* distiller
= new FakeDistiller(false);
209 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
210 .WillOnce(Return(distiller
));
212 FakeViewRequestDelegate viewer_delegate
;
213 GURL
url("http://www.example.com/p1");
214 scoped_ptr
<ViewerHandle
> handle
= service_
->ViewUrl(&viewer_delegate
, url
);
216 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
217 EXPECT_CALL(viewer_delegate
, OnArticleReady(proto
.get()));
219 RunDistillerCallback(distiller
, proto
.Pass());
220 base::RunLoop().RunUntilIdle();
221 // The entry should not be added to the store.
222 EXPECT_EQ(0u, store_
->GetEntries().size());
225 TEST_F(DomDistillerServiceTest
, TestAddAndRemoveEntry
) {
226 FakeDistiller
* distiller
= new FakeDistiller(false);
227 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
228 .WillOnce(Return(distiller
));
230 GURL
url("http://www.example.com/p1");
232 MockArticleAvailableCallback article_cb
;
233 EXPECT_CALL(article_cb
, DistillationCompleted(true));
235 std::string entry_id
= service_
->AddToList(url
, ArticleCallback(&article_cb
));
237 ASSERT_FALSE(distiller
->GetCallback().is_null());
238 EXPECT_EQ(url
, distiller
->GetUrl());
240 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
241 RunDistillerCallback(distiller
, proto
.Pass());
244 EXPECT_TRUE(store_
->GetEntryByUrl(url
, &entry
));
245 EXPECT_EQ(entry
.entry_id(), entry_id
);
246 EXPECT_EQ(1u, store_
->GetEntries().size());
247 service_
->RemoveEntry(entry_id
);
248 base::RunLoop().RunUntilIdle();
249 EXPECT_EQ(0u, store_
->GetEntries().size());
252 TEST_F(DomDistillerServiceTest
, TestCancellation
) {
253 FakeDistiller
* distiller
= new FakeDistiller(false);
254 MockDistillerObserver observer
;
255 service_
->AddObserver(&observer
);
257 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
258 .WillOnce(Return(distiller
));
260 MockArticleAvailableCallback article_cb
;
261 EXPECT_CALL(article_cb
, DistillationCompleted(false));
263 GURL
url("http://www.example.com/p1");
264 std::string entry_id
= service_
->AddToList(url
, ArticleCallback(&article_cb
));
266 // Remove entry will cause the |article_cb| to be called with false value.
267 service_
->RemoveEntry(entry_id
);
268 base::RunLoop().RunUntilIdle();
271 TEST_F(DomDistillerServiceTest
, TestMultipleObservers
) {
272 FakeDistiller
* distiller
= new FakeDistiller(false);
273 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
274 .WillOnce(Return(distiller
));
276 const int kObserverCount
= 5;
277 MockDistillerObserver observers
[kObserverCount
];
278 for (int i
= 0; i
< kObserverCount
; ++i
) {
279 service_
->AddObserver(&observers
[i
]);
282 DomDistillerService::ArticleAvailableCallback article_cb
;
283 GURL
url("http://www.example.com/p1");
284 std::string entry_id
= service_
->AddToList(url
, article_cb
);
286 // Distillation should notify all observers that article is added.
287 std::vector
<DomDistillerObserver::ArticleUpdate
> expected_updates
;
288 DomDistillerObserver::ArticleUpdate update
;
289 update
.entry_id
= entry_id
;
290 update
.update_type
= DomDistillerObserver::ArticleUpdate::ADD
;
291 expected_updates
.push_back(update
);
293 for (int i
= 0; i
< kObserverCount
; ++i
) {
296 ArticleEntriesUpdated(util::HasExpectedUpdates(expected_updates
)));
299 scoped_ptr
<DistilledArticleProto
> proto
= CreateDefaultArticle();
300 RunDistillerCallback(distiller
, proto
.Pass());
302 // Remove should notify all observers that article is removed.
303 update
.update_type
= DomDistillerObserver::ArticleUpdate::REMOVE
;
304 expected_updates
.clear();
305 expected_updates
.push_back(update
);
306 for (int i
= 0; i
< kObserverCount
; ++i
) {
309 ArticleEntriesUpdated(util::HasExpectedUpdates(expected_updates
)));
312 service_
->RemoveEntry(entry_id
);
313 base::RunLoop().RunUntilIdle();
316 TEST_F(DomDistillerServiceTest
, TestMultipleCallbacks
) {
317 FakeDistiller
* distiller
= new FakeDistiller(false);
318 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
319 .WillOnce(Return(distiller
));
321 const int kClientsCount
= 5;
322 MockArticleAvailableCallback article_cb
[kClientsCount
];
323 // Adding a URL and then distilling calls all clients.
324 GURL
url("http://www.example.com/p1");
325 const std::string entry_id
=
326 service_
->AddToList(url
, ArticleCallback(&article_cb
[0]));
327 EXPECT_CALL(article_cb
[0], DistillationCompleted(true));
329 for (int i
= 1; i
< kClientsCount
; ++i
) {
331 service_
->AddToList(url
, ArticleCallback(&article_cb
[i
])));
332 EXPECT_CALL(article_cb
[i
], DistillationCompleted(true));
335 scoped_ptr
<DistilledArticleProto
> proto
= CreateArticleWithURL(url
.spec());
336 RunDistillerCallback(distiller
, proto
.Pass());
338 // Add the same url again, all callbacks should be called with true.
339 for (int i
= 0; i
< kClientsCount
; ++i
) {
340 EXPECT_CALL(article_cb
[i
], DistillationCompleted(true));
342 service_
->AddToList(url
, ArticleCallback(&article_cb
[i
])));
345 base::RunLoop().RunUntilIdle();
348 TEST_F(DomDistillerServiceTest
, TestMultipleCallbacksOnRemove
) {
349 FakeDistiller
* distiller
= new FakeDistiller(false);
350 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
351 .WillOnce(Return(distiller
));
353 const int kClientsCount
= 5;
354 MockArticleAvailableCallback article_cb
[kClientsCount
];
355 // Adding a URL and remove the entry before distillation. Callback should be
356 // called with false.
357 GURL
url("http://www.example.com/p1");
358 const std::string entry_id
=
359 service_
->AddToList(url
, ArticleCallback(&article_cb
[0]));
361 EXPECT_CALL(article_cb
[0], DistillationCompleted(false));
362 for (int i
= 1; i
< kClientsCount
; ++i
) {
364 service_
->AddToList(url
, ArticleCallback(&article_cb
[i
])));
365 EXPECT_CALL(article_cb
[i
], DistillationCompleted(false));
368 service_
->RemoveEntry(entry_id
);
369 base::RunLoop().RunUntilIdle();
372 TEST_F(DomDistillerServiceTest
, TestMultiplePageArticle
) {
373 FakeDistiller
* distiller
= new FakeDistiller(false);
374 EXPECT_CALL(*distiller_factory_
, CreateDistillerImpl())
375 .WillOnce(Return(distiller
));
377 const int kPageCount
= 8;
379 std::string
base_url("http://www.example.com/p");
380 GURL pages_url
[kPageCount
];
381 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
382 pages_url
[page_num
] = GURL(base_url
+ base::IntToString(page_num
));
385 MockArticleAvailableCallback article_cb
;
386 EXPECT_CALL(article_cb
, DistillationCompleted(true));
388 std::string entry_id
=
389 service_
->AddToList(pages_url
[0], ArticleCallback(&article_cb
));
392 ASSERT_FALSE(distiller
->GetCallback().is_null());
393 EXPECT_EQ(pages_url
[0], distiller
->GetUrl());
395 // Create the article with pages to pass to the distiller.
396 scoped_ptr
<DistilledArticleProto
> proto
=
397 CreateArticleWithURL(pages_url
[0].spec());
398 for (int page_num
= 1; page_num
< kPageCount
; ++page_num
) {
399 DistilledPageProto
* distilled_page
= proto
->add_pages();
400 distilled_page
->set_url(pages_url
[page_num
].spec());
403 RunDistillerCallback(distiller
, proto
.Pass());
404 EXPECT_TRUE(store_
->GetEntryByUrl(pages_url
[0], &entry
));
406 EXPECT_EQ(kPageCount
, entry
.pages_size());
407 // An article should have just one entry.
408 EXPECT_EQ(1u, store_
->GetEntries().size());
410 // All pages should have correct urls.
411 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
412 EXPECT_EQ(pages_url
[page_num
].spec(), entry
.pages(page_num
).url());
415 // Should be able to query article using any of the pages url.
416 for (int page_num
= 0; page_num
< kPageCount
; ++page_num
) {
417 EXPECT_TRUE(store_
->GetEntryByUrl(pages_url
[page_num
], &entry
));
420 service_
->RemoveEntry(entry_id
);
421 base::RunLoop().RunUntilIdle();
422 EXPECT_EQ(0u, store_
->GetEntries().size());
426 } // namespace dom_distiller