Home | History | Annotate | Download | only in core
      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.
      4 
      5 #include "components/dom_distiller/core/dom_distiller_service.h"
      6 
      7 #include "base/bind.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"
     24 
     25 using leveldb_proto::test::FakeDB;
     26 using testing::Invoke;
     27 using testing::Return;
     28 using testing::_;
     29 
     30 namespace dom_distiller {
     31 namespace test {
     32 
     33 namespace {
     34 
     35 class FakeViewRequestDelegate : public ViewRequestDelegate {
     36  public:
     37   virtual ~FakeViewRequestDelegate() {}
     38   MOCK_METHOD1(OnArticleReady, void(const DistilledArticleProto* proto));
     39   MOCK_METHOD1(OnArticleUpdated,
     40                void(ArticleDistillationUpdate article_update));
     41 };
     42 
     43 class MockDistillerObserver : public DomDistillerObserver {
     44  public:
     45   MOCK_METHOD1(ArticleEntriesUpdated, void(const std::vector<ArticleUpdate>&));
     46   virtual ~MockDistillerObserver() {}
     47 };
     48 
     49 class MockArticleAvailableCallback {
     50  public:
     51   MOCK_METHOD1(DistillationCompleted, void(bool));
     52 };
     53 
     54 DomDistillerService::ArticleAvailableCallback ArticleCallback(
     55     MockArticleAvailableCallback* callback) {
     56   return base::Bind(&MockArticleAvailableCallback::DistillationCompleted,
     57                     base::Unretained(callback));
     58 }
     59 
     60 void RunDistillerCallback(FakeDistiller* distiller,
     61                           scoped_ptr<DistilledArticleProto> proto) {
     62   distiller->RunDistillerCallback(proto.Pass());
     63   base::RunLoop().RunUntilIdle();
     64 }
     65 
     66 scoped_ptr<DistilledArticleProto> CreateArticleWithURL(const std::string& url) {
     67   scoped_ptr<DistilledArticleProto> proto(new DistilledArticleProto);
     68   DistilledPageProto* page = proto->add_pages();
     69   page->set_url(url);
     70   return proto.Pass();
     71 }
     72 
     73 scoped_ptr<DistilledArticleProto> CreateDefaultArticle() {
     74   return CreateArticleWithURL("http://www.example.com/default_article_page1")
     75       .Pass();
     76 }
     77 
     78 }  // namespace
     79 
     80 class DomDistillerServiceTest : public testing::Test {
     81  public:
     82   virtual void SetUp() {
     83     main_loop_.reset(new base::MessageLoop());
     84     FakeDB<ArticleEntry>* fake_db = new FakeDB<ArticleEntry>(&db_model_);
     85     FakeDB<ArticleEntry>::EntryMap store_model;
     86     store_ =
     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);
     97   }
     98 
     99   virtual void TearDown() {
    100     base::RunLoop().RunUntilIdle();
    101     store_ = NULL;
    102     distiller_factory_ = NULL;
    103     service_.reset();
    104   }
    105 
    106  protected:
    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_;
    114 };
    115 
    116 TEST_F(DomDistillerServiceTest, TestViewEntry) {
    117   FakeDistiller* distiller = new FakeDistiller(false);
    118   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    119       .WillOnce(Return(distiller));
    120 
    121   GURL url("http://www.example.com/p1");
    122   std::string entry_id("id0");
    123   ArticleEntry entry;
    124   entry.set_entry_id(entry_id);
    125   entry.add_pages()->set_url(url.spec());
    126 
    127   store_->AddEntry(entry);
    128 
    129   FakeViewRequestDelegate viewer_delegate;
    130   scoped_ptr<ViewerHandle> handle = service_->ViewEntry(
    131       &viewer_delegate, service_->CreateDefaultDistillerPage(gfx::Size()),
    132       entry_id);
    133 
    134   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    135 
    136   scoped_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
    137   EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
    138 
    139   RunDistillerCallback(distiller, proto.Pass());
    140 }
    141 
    142 TEST_F(DomDistillerServiceTest, TestViewUrl) {
    143   FakeDistiller* distiller = new FakeDistiller(false);
    144   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    145       .WillOnce(Return(distiller));
    146 
    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);
    151 
    152   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    153   EXPECT_EQ(url, distiller->GetUrl());
    154 
    155   scoped_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
    156   EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
    157 
    158   RunDistillerCallback(distiller, proto.Pass());
    159 }
    160 
    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));
    167 
    168   FakeViewRequestDelegate viewer_delegate;
    169   FakeViewRequestDelegate viewer_delegate2;
    170 
    171   GURL url("http://www.example.com/p1");
    172   GURL url2("http://www.example.com/a/p1");
    173 
    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()),
    178       url2);
    179 
    180   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    181   EXPECT_EQ(url, distiller->GetUrl());
    182 
    183   scoped_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
    184   EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
    185 
    186   RunDistillerCallback(distiller, proto.Pass());
    187 
    188   ASSERT_FALSE(distiller2->GetArticleCallback().is_null());
    189   EXPECT_EQ(url2, distiller2->GetUrl());
    190 
    191   scoped_ptr<DistilledArticleProto> proto2 = CreateDefaultArticle();
    192   EXPECT_CALL(viewer_delegate2, OnArticleReady(proto2.get()));
    193 
    194   RunDistillerCallback(distiller2, proto2.Pass());
    195 }
    196 
    197 TEST_F(DomDistillerServiceTest, TestViewUrlCancelled) {
    198   FakeDistiller* distiller = new FakeDistiller(false);
    199   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    200       .WillOnce(Return(distiller));
    201 
    202   bool distiller_destroyed = false;
    203   EXPECT_CALL(*distiller, Die())
    204       .WillOnce(testing::Assign(&distiller_destroyed, true));
    205 
    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);
    210 
    211   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    212   EXPECT_EQ(url, distiller->GetUrl());
    213 
    214   EXPECT_CALL(viewer_delegate, OnArticleReady(_)).Times(0);
    215 
    216   EXPECT_FALSE(distiller_destroyed);
    217 
    218   handle.reset();
    219   base::RunLoop().RunUntilIdle();
    220   EXPECT_TRUE(distiller_destroyed);
    221 }
    222 
    223 TEST_F(DomDistillerServiceTest, TestViewUrlDoesNotAddEntry) {
    224   FakeDistiller* distiller = new FakeDistiller(false);
    225   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    226       .WillOnce(Return(distiller));
    227 
    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);
    232 
    233   scoped_ptr<DistilledArticleProto> proto = CreateArticleWithURL(url.spec());
    234   EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
    235 
    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());
    240 }
    241 
    242 TEST_F(DomDistillerServiceTest, TestAddAndRemoveEntry) {
    243   FakeDistiller* distiller = new FakeDistiller(false);
    244   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    245       .WillOnce(Return(distiller));
    246 
    247   GURL url("http://www.example.com/p1");
    248 
    249   MockArticleAvailableCallback article_cb;
    250   EXPECT_CALL(article_cb, DistillationCompleted(true));
    251 
    252   std::string entry_id =
    253       service_->AddToList(url,
    254           service_->CreateDefaultDistillerPage(gfx::Size()).Pass(),
    255           ArticleCallback(&article_cb));
    256 
    257   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    258   EXPECT_EQ(url, distiller->GetUrl());
    259 
    260   scoped_ptr<DistilledArticleProto> proto = CreateArticleWithURL(url.spec());
    261   RunDistillerCallback(distiller, proto.Pass());
    262 
    263   ArticleEntry entry;
    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());
    270 }
    271 
    272 TEST_F(DomDistillerServiceTest, TestCancellation) {
    273   FakeDistiller* distiller = new FakeDistiller(false);
    274   MockDistillerObserver observer;
    275   service_->AddObserver(&observer);
    276 
    277   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    278       .WillOnce(Return(distiller));
    279 
    280   MockArticleAvailableCallback article_cb;
    281   EXPECT_CALL(article_cb, DistillationCompleted(false));
    282 
    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));
    288 
    289   // Remove entry will cause the |article_cb| to be called with false value.
    290   service_->RemoveEntry(entry_id);
    291   base::RunLoop().RunUntilIdle();
    292 }
    293 
    294 TEST_F(DomDistillerServiceTest, TestMultipleObservers) {
    295   FakeDistiller* distiller = new FakeDistiller(false);
    296   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    297       .WillOnce(Return(distiller));
    298 
    299   const int kObserverCount = 5;
    300   MockDistillerObserver observers[kObserverCount];
    301   for (int i = 0; i < kObserverCount; ++i) {
    302     service_->AddObserver(&observers[i]);
    303   }
    304 
    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(),
    309       article_cb);
    310 
    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);
    317 
    318   for (int i = 0; i < kObserverCount; ++i) {
    319     EXPECT_CALL(observers[i], ArticleEntriesUpdated(
    320                                   util::HasExpectedUpdates(expected_updates)));
    321   }
    322 
    323   scoped_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
    324   RunDistillerCallback(distiller, proto.Pass());
    325 
    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)));
    333   }
    334 
    335   service_->RemoveEntry(entry_id);
    336   base::RunLoop().RunUntilIdle();
    337 }
    338 
    339 TEST_F(DomDistillerServiceTest, TestMultipleCallbacks) {
    340   FakeDistiller* distiller = new FakeDistiller(false);
    341   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    342       .WillOnce(Return(distiller));
    343 
    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));
    353 
    354   for (int i = 1; i < kClientsCount; ++i) {
    355     EXPECT_EQ(entry_id,
    356               service_->AddToList(
    357                   url,
    358                   service_->CreateDefaultDistillerPage(gfx::Size()).Pass(),
    359                   ArticleCallback(&article_cb[i])));
    360     EXPECT_CALL(article_cb[i], DistillationCompleted(true));
    361   }
    362 
    363   scoped_ptr<DistilledArticleProto> proto = CreateArticleWithURL(url.spec());
    364   RunDistillerCallback(distiller, proto.Pass());
    365 
    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));
    369     EXPECT_EQ(entry_id,
    370               service_->AddToList(
    371                   url,
    372                   service_->CreateDefaultDistillerPage(gfx::Size()).Pass(),
    373                   ArticleCallback(&article_cb[i])));
    374   }
    375 
    376   base::RunLoop().RunUntilIdle();
    377 }
    378 
    379 TEST_F(DomDistillerServiceTest, TestMultipleCallbacksOnRemove) {
    380   FakeDistiller* distiller = new FakeDistiller(false);
    381   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    382       .WillOnce(Return(distiller));
    383 
    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]));
    393 
    394   EXPECT_CALL(article_cb[0], DistillationCompleted(false));
    395   for (int i = 1; i < kClientsCount; ++i) {
    396     EXPECT_EQ(entry_id,
    397               service_->AddToList(
    398                   url, service_->CreateDefaultDistillerPage(gfx::Size()).Pass(),
    399                   ArticleCallback(&article_cb[i])));
    400     EXPECT_CALL(article_cb[i], DistillationCompleted(false));
    401   }
    402 
    403   service_->RemoveEntry(entry_id);
    404   base::RunLoop().RunUntilIdle();
    405 }
    406 
    407 TEST_F(DomDistillerServiceTest, TestMultiplePageArticle) {
    408   FakeDistiller* distiller = new FakeDistiller(false);
    409   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    410       .WillOnce(Return(distiller));
    411 
    412   const int kPageCount = 8;
    413 
    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));
    418   }
    419 
    420   MockArticleAvailableCallback article_cb;
    421   EXPECT_CALL(article_cb, DistillationCompleted(true));
    422 
    423   std::string entry_id = service_->AddToList(
    424       pages_url[0], service_->CreateDefaultDistillerPage(gfx::Size()).Pass(),
    425       ArticleCallback(&article_cb));
    426 
    427   ArticleEntry entry;
    428   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    429   EXPECT_EQ(pages_url[0], distiller->GetUrl());
    430 
    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());
    437   }
    438 
    439   RunDistillerCallback(distiller, proto.Pass());
    440   EXPECT_TRUE(store_->GetEntryByUrl(pages_url[0], &entry));
    441 
    442   EXPECT_EQ(kPageCount, entry.pages_size());
    443   // An article should have just one entry.
    444   EXPECT_EQ(1u, store_->GetEntries().size());
    445 
    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());
    449   }
    450 
    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));
    454   }
    455 
    456   service_->RemoveEntry(entry_id);
    457   base::RunLoop().RunUntilIdle();
    458   EXPECT_EQ(0u, store_->GetEntries().size());
    459 }
    460 
    461 TEST_F(DomDistillerServiceTest, TestHasEntry) {
    462   FakeDistiller* distiller = new FakeDistiller(false);
    463   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    464       .WillOnce(Return(distiller));
    465 
    466   GURL url("http://www.example.com/p1");
    467 
    468   MockArticleAvailableCallback article_cb;
    469   EXPECT_CALL(article_cb, DistillationCompleted(true));
    470 
    471   std::string entry_id = service_->AddToList(
    472       url,
    473       service_->CreateDefaultDistillerPage(gfx::Size()).Pass(),
    474       ArticleCallback(&article_cb));
    475 
    476   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    477   EXPECT_EQ(url, distiller->GetUrl());
    478 
    479   scoped_ptr<DistilledArticleProto> proto = CreateArticleWithURL(url.spec());
    480   RunDistillerCallback(distiller, proto.Pass());
    481 
    482   // Check that HasEntry returns true for the article just added.
    483   EXPECT_TRUE(service_->HasEntry(entry_id));
    484 
    485   // Remove article and check that there is no longer an entry for the given
    486   // entry id.
    487   service_->RemoveEntry(entry_id);
    488   base::RunLoop().RunUntilIdle();
    489   EXPECT_EQ(0u, store_->GetEntries().size());
    490   EXPECT_FALSE(service_->HasEntry(entry_id));
    491 }
    492 
    493 TEST_F(DomDistillerServiceTest, TestGetUrlForOnePageEntry) {
    494   FakeDistiller* distiller = new FakeDistiller(false);
    495   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    496       .WillOnce(Return(distiller));
    497 
    498   GURL url("http://www.example.com/p1");
    499 
    500   MockArticleAvailableCallback article_cb;
    501   EXPECT_CALL(article_cb, DistillationCompleted(true));
    502 
    503   std::string entry_id = service_->AddToList(
    504       url,
    505       service_->CreateDefaultDistillerPage(gfx::Size()).Pass(),
    506       ArticleCallback(&article_cb));
    507 
    508   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    509   EXPECT_EQ(url, distiller->GetUrl());
    510 
    511   scoped_ptr<DistilledArticleProto> proto = CreateArticleWithURL(url.spec());
    512   RunDistillerCallback(distiller, proto.Pass());
    513 
    514   // Check if retrieved URL is same as given URL.
    515   GURL retrieved_url(service_->GetUrlForEntry(entry_id));
    516   EXPECT_EQ(url, retrieved_url);
    517 
    518   // Remove article and check that there is no longer an entry for the given
    519   // entry id.
    520   service_->RemoveEntry(entry_id);
    521   base::RunLoop().RunUntilIdle();
    522   EXPECT_EQ(0u, store_->GetEntries().size());
    523   EXPECT_EQ("", service_->GetUrlForEntry(entry_id));
    524 }
    525 
    526 TEST_F(DomDistillerServiceTest, TestGetUrlForMultiPageEntry) {
    527   FakeDistiller* distiller = new FakeDistiller(false);
    528   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    529       .WillOnce(Return(distiller));
    530 
    531   const int kPageCount = 8;
    532 
    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));
    537   }
    538 
    539   MockArticleAvailableCallback article_cb;
    540   EXPECT_CALL(article_cb, DistillationCompleted(true));
    541 
    542   std::string entry_id = service_->AddToList(
    543       pages_url[0],
    544       service_->CreateDefaultDistillerPage(gfx::Size()).Pass(),
    545       ArticleCallback(&article_cb));
    546 
    547   ArticleEntry entry;
    548   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    549   EXPECT_EQ(pages_url[0], distiller->GetUrl());
    550 
    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());
    557   }
    558 
    559   RunDistillerCallback(distiller, proto.Pass());
    560   EXPECT_TRUE(store_->GetEntryByUrl(pages_url[0], &entry));
    561 
    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);
    565 
    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));
    571 }
    572 
    573 }  // namespace test
    574 }  // namespace dom_distiller
    575