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/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_distiller.h"
     18 #include "components/dom_distiller/core/fake_distiller_page.h"
     19 #include "components/dom_distiller/core/task_tracker.h"
     20 #include "components/leveldb_proto/testing/fake_db.h"
     21 #include "testing/gmock/include/gmock/gmock.h"
     22 #include "testing/gtest/include/gtest/gtest.h"
     23 
     24 using leveldb_proto::test::FakeDB;
     25 using testing::Invoke;
     26 using testing::Return;
     27 using testing::_;
     28 
     29 namespace dom_distiller {
     30 namespace test {
     31 
     32 namespace {
     33 
     34 class FakeViewRequestDelegate : public ViewRequestDelegate {
     35  public:
     36   virtual ~FakeViewRequestDelegate() {}
     37   MOCK_METHOD1(OnArticleReady, void(const DistilledArticleProto* proto));
     38   MOCK_METHOD1(OnArticleUpdated,
     39                void(ArticleDistillationUpdate article_update));
     40 };
     41 
     42 class MockDistillerObserver : public DomDistillerObserver {
     43  public:
     44   MOCK_METHOD1(ArticleEntriesUpdated, void(const std::vector<ArticleUpdate>&));
     45   virtual ~MockDistillerObserver() {}
     46 };
     47 
     48 class MockArticleAvailableCallback {
     49  public:
     50   MOCK_METHOD1(DistillationCompleted, void(bool));
     51 };
     52 
     53 DomDistillerService::ArticleAvailableCallback ArticleCallback(
     54     MockArticleAvailableCallback* callback) {
     55   return base::Bind(&MockArticleAvailableCallback::DistillationCompleted,
     56                     base::Unretained(callback));
     57 }
     58 
     59 void RunDistillerCallback(FakeDistiller* distiller,
     60                           scoped_ptr<DistilledArticleProto> proto) {
     61   distiller->RunDistillerCallback(proto.Pass());
     62   base::RunLoop().RunUntilIdle();
     63 }
     64 
     65 scoped_ptr<DistilledArticleProto> CreateArticleWithURL(const std::string& url) {
     66   scoped_ptr<DistilledArticleProto> proto(new DistilledArticleProto);
     67   DistilledPageProto* page = proto->add_pages();
     68   page->set_url(url);
     69   return proto.Pass();
     70 }
     71 
     72 scoped_ptr<DistilledArticleProto> CreateDefaultArticle() {
     73   return CreateArticleWithURL("http://www.example.com/default_article_page1")
     74       .Pass();
     75 }
     76 
     77 }  // namespace
     78 
     79 class DomDistillerServiceTest : public testing::Test {
     80  public:
     81   virtual void SetUp() {
     82     main_loop_.reset(new base::MessageLoop());
     83     FakeDB<ArticleEntry>* fake_db = new FakeDB<ArticleEntry>(&db_model_);
     84     FakeDB<ArticleEntry>::EntryMap store_model;
     85     store_ =
     86         test::util::CreateStoreWithFakeDB(fake_db, store_model);
     87     distiller_factory_ = new MockDistillerFactory();
     88     distiller_page_factory_ = new MockDistillerPageFactory();
     89     service_.reset(new DomDistillerService(
     90         scoped_ptr<DomDistillerStoreInterface>(store_),
     91         scoped_ptr<DistillerFactory>(distiller_factory_),
     92         scoped_ptr<DistillerPageFactory>(distiller_page_factory_)));
     93     fake_db->InitCallback(true);
     94     fake_db->LoadCallback(true);
     95   }
     96 
     97   virtual void TearDown() {
     98     base::RunLoop().RunUntilIdle();
     99     store_ = NULL;
    100     distiller_factory_ = NULL;
    101     service_.reset();
    102   }
    103 
    104  protected:
    105   // store is owned by service_.
    106   DomDistillerStoreInterface* store_;
    107   MockDistillerFactory* distiller_factory_;
    108   MockDistillerPageFactory* distiller_page_factory_;
    109   scoped_ptr<DomDistillerService> service_;
    110   scoped_ptr<base::MessageLoop> main_loop_;
    111   FakeDB<ArticleEntry>::EntryMap db_model_;
    112 };
    113 
    114 TEST_F(DomDistillerServiceTest, TestViewEntry) {
    115   FakeDistiller* distiller = new FakeDistiller(false);
    116   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    117       .WillOnce(Return(distiller));
    118 
    119   GURL url("http://www.example.com/p1");
    120   std::string entry_id("id0");
    121   ArticleEntry entry;
    122   entry.set_entry_id(entry_id);
    123   entry.add_pages()->set_url(url.spec());
    124 
    125   store_->AddEntry(entry);
    126 
    127   FakeViewRequestDelegate viewer_delegate;
    128   scoped_ptr<ViewerHandle> handle = service_->ViewEntry(
    129       &viewer_delegate, service_->CreateDefaultDistillerPage(), entry_id);
    130 
    131   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    132 
    133   scoped_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
    134   EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
    135 
    136   RunDistillerCallback(distiller, proto.Pass());
    137 }
    138 
    139 TEST_F(DomDistillerServiceTest, TestViewUrl) {
    140   FakeDistiller* distiller = new FakeDistiller(false);
    141   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    142       .WillOnce(Return(distiller));
    143 
    144   FakeViewRequestDelegate viewer_delegate;
    145   GURL url("http://www.example.com/p1");
    146   scoped_ptr<ViewerHandle> handle = service_->ViewUrl(
    147       &viewer_delegate, service_->CreateDefaultDistillerPage(), url);
    148 
    149   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    150   EXPECT_EQ(url, distiller->GetUrl());
    151 
    152   scoped_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
    153   EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
    154 
    155   RunDistillerCallback(distiller, proto.Pass());
    156 }
    157 
    158 TEST_F(DomDistillerServiceTest, TestMultipleViewUrl) {
    159   FakeDistiller* distiller = new FakeDistiller(false);
    160   FakeDistiller* distiller2 = new FakeDistiller(false);
    161   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    162       .WillOnce(Return(distiller))
    163       .WillOnce(Return(distiller2));
    164 
    165   FakeViewRequestDelegate viewer_delegate;
    166   FakeViewRequestDelegate viewer_delegate2;
    167 
    168   GURL url("http://www.example.com/p1");
    169   GURL url2("http://www.example.com/a/p1");
    170 
    171   scoped_ptr<ViewerHandle> handle = service_->ViewUrl(
    172       &viewer_delegate, service_->CreateDefaultDistillerPage(), url);
    173   scoped_ptr<ViewerHandle> handle2 = service_->ViewUrl(
    174       &viewer_delegate2, service_->CreateDefaultDistillerPage(), url2);
    175 
    176   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    177   EXPECT_EQ(url, distiller->GetUrl());
    178 
    179   scoped_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
    180   EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
    181 
    182   RunDistillerCallback(distiller, proto.Pass());
    183 
    184   ASSERT_FALSE(distiller2->GetArticleCallback().is_null());
    185   EXPECT_EQ(url2, distiller2->GetUrl());
    186 
    187   scoped_ptr<DistilledArticleProto> proto2 = CreateDefaultArticle();
    188   EXPECT_CALL(viewer_delegate2, OnArticleReady(proto2.get()));
    189 
    190   RunDistillerCallback(distiller2, proto2.Pass());
    191 }
    192 
    193 TEST_F(DomDistillerServiceTest, TestViewUrlCancelled) {
    194   FakeDistiller* distiller = new FakeDistiller(false);
    195   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    196       .WillOnce(Return(distiller));
    197 
    198   bool distiller_destroyed = false;
    199   EXPECT_CALL(*distiller, Die())
    200       .WillOnce(testing::Assign(&distiller_destroyed, true));
    201 
    202   FakeViewRequestDelegate viewer_delegate;
    203   GURL url("http://www.example.com/p1");
    204   scoped_ptr<ViewerHandle> handle = service_->ViewUrl(
    205       &viewer_delegate, service_->CreateDefaultDistillerPage(), url);
    206 
    207   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    208   EXPECT_EQ(url, distiller->GetUrl());
    209 
    210   EXPECT_CALL(viewer_delegate, OnArticleReady(_)).Times(0);
    211 
    212   EXPECT_FALSE(distiller_destroyed);
    213 
    214   handle.reset();
    215   base::RunLoop().RunUntilIdle();
    216   EXPECT_TRUE(distiller_destroyed);
    217 }
    218 
    219 TEST_F(DomDistillerServiceTest, TestViewUrlDoesNotAddEntry) {
    220   FakeDistiller* distiller = new FakeDistiller(false);
    221   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    222       .WillOnce(Return(distiller));
    223 
    224   FakeViewRequestDelegate viewer_delegate;
    225   GURL url("http://www.example.com/p1");
    226   scoped_ptr<ViewerHandle> handle = service_->ViewUrl(
    227       &viewer_delegate, service_->CreateDefaultDistillerPage(), url);
    228 
    229   scoped_ptr<DistilledArticleProto> proto = CreateArticleWithURL(url.spec());
    230   EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
    231 
    232   RunDistillerCallback(distiller, proto.Pass());
    233   base::RunLoop().RunUntilIdle();
    234   // The entry should not be added to the store.
    235   EXPECT_EQ(0u, store_->GetEntries().size());
    236 }
    237 
    238 TEST_F(DomDistillerServiceTest, TestAddAndRemoveEntry) {
    239   FakeDistiller* distiller = new FakeDistiller(false);
    240   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    241       .WillOnce(Return(distiller));
    242 
    243   GURL url("http://www.example.com/p1");
    244 
    245   MockArticleAvailableCallback article_cb;
    246   EXPECT_CALL(article_cb, DistillationCompleted(true));
    247 
    248   std::string entry_id =
    249       service_->AddToList(url, service_->CreateDefaultDistillerPage().Pass(),
    250                           ArticleCallback(&article_cb));
    251 
    252   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    253   EXPECT_EQ(url, distiller->GetUrl());
    254 
    255   scoped_ptr<DistilledArticleProto> proto = CreateArticleWithURL(url.spec());
    256   RunDistillerCallback(distiller, proto.Pass());
    257 
    258   ArticleEntry entry;
    259   EXPECT_TRUE(store_->GetEntryByUrl(url, &entry));
    260   EXPECT_EQ(entry.entry_id(), entry_id);
    261   EXPECT_EQ(1u, store_->GetEntries().size());
    262   service_->RemoveEntry(entry_id);
    263   base::RunLoop().RunUntilIdle();
    264   EXPECT_EQ(0u, store_->GetEntries().size());
    265 }
    266 
    267 TEST_F(DomDistillerServiceTest, TestCancellation) {
    268   FakeDistiller* distiller = new FakeDistiller(false);
    269   MockDistillerObserver observer;
    270   service_->AddObserver(&observer);
    271 
    272   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    273       .WillOnce(Return(distiller));
    274 
    275   MockArticleAvailableCallback article_cb;
    276   EXPECT_CALL(article_cb, DistillationCompleted(false));
    277 
    278   GURL url("http://www.example.com/p1");
    279   std::string entry_id =
    280       service_->AddToList(url, service_->CreateDefaultDistillerPage().Pass(),
    281                           ArticleCallback(&article_cb));
    282 
    283   // Remove entry will cause the |article_cb| to be called with false value.
    284   service_->RemoveEntry(entry_id);
    285   base::RunLoop().RunUntilIdle();
    286 }
    287 
    288 TEST_F(DomDistillerServiceTest, TestMultipleObservers) {
    289   FakeDistiller* distiller = new FakeDistiller(false);
    290   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    291       .WillOnce(Return(distiller));
    292 
    293   const int kObserverCount = 5;
    294   MockDistillerObserver observers[kObserverCount];
    295   for (int i = 0; i < kObserverCount; ++i) {
    296     service_->AddObserver(&observers[i]);
    297   }
    298 
    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);
    303 
    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);
    310 
    311   for (int i = 0; i < kObserverCount; ++i) {
    312     EXPECT_CALL(observers[i], ArticleEntriesUpdated(
    313                                   util::HasExpectedUpdates(expected_updates)));
    314   }
    315 
    316   scoped_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
    317   RunDistillerCallback(distiller, proto.Pass());
    318 
    319   // Remove should notify all observers that article is removed.
    320   update.update_type = DomDistillerObserver::ArticleUpdate::REMOVE;
    321   expected_updates.clear();
    322   expected_updates.push_back(update);
    323   for (int i = 0; i < kObserverCount; ++i) {
    324     EXPECT_CALL(observers[i], ArticleEntriesUpdated(
    325                                   util::HasExpectedUpdates(expected_updates)));
    326   }
    327 
    328   service_->RemoveEntry(entry_id);
    329   base::RunLoop().RunUntilIdle();
    330 }
    331 
    332 TEST_F(DomDistillerServiceTest, TestMultipleCallbacks) {
    333   FakeDistiller* distiller = new FakeDistiller(false);
    334   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    335       .WillOnce(Return(distiller));
    336 
    337   const int kClientsCount = 5;
    338   MockArticleAvailableCallback article_cb[kClientsCount];
    339   // Adding a URL and then distilling calls all clients.
    340   GURL url("http://www.example.com/p1");
    341   const std::string entry_id =
    342       service_->AddToList(url, service_->CreateDefaultDistillerPage().Pass(),
    343                           ArticleCallback(&article_cb[0]));
    344   EXPECT_CALL(article_cb[0], DistillationCompleted(true));
    345 
    346   for (int i = 1; i < kClientsCount; ++i) {
    347     EXPECT_EQ(entry_id, service_->AddToList(
    348                             url, service_->CreateDefaultDistillerPage().Pass(),
    349                             ArticleCallback(&article_cb[i])));
    350     EXPECT_CALL(article_cb[i], DistillationCompleted(true));
    351   }
    352 
    353   scoped_ptr<DistilledArticleProto> proto = CreateArticleWithURL(url.spec());
    354   RunDistillerCallback(distiller, proto.Pass());
    355 
    356   // Add the same url again, all callbacks should be called with true.
    357   for (int i = 0; i < kClientsCount; ++i) {
    358     EXPECT_CALL(article_cb[i], DistillationCompleted(true));
    359     EXPECT_EQ(entry_id, service_->AddToList(
    360                             url, service_->CreateDefaultDistillerPage().Pass(),
    361                             ArticleCallback(&article_cb[i])));
    362   }
    363 
    364   base::RunLoop().RunUntilIdle();
    365 }
    366 
    367 TEST_F(DomDistillerServiceTest, TestMultipleCallbacksOnRemove) {
    368   FakeDistiller* distiller = new FakeDistiller(false);
    369   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    370       .WillOnce(Return(distiller));
    371 
    372   const int kClientsCount = 5;
    373   MockArticleAvailableCallback article_cb[kClientsCount];
    374   // Adding a URL and remove the entry before distillation. Callback should be
    375   // called with false.
    376   GURL url("http://www.example.com/p1");
    377   const std::string entry_id =
    378       service_->AddToList(url, service_->CreateDefaultDistillerPage().Pass(),
    379                           ArticleCallback(&article_cb[0]));
    380 
    381   EXPECT_CALL(article_cb[0], DistillationCompleted(false));
    382   for (int i = 1; i < kClientsCount; ++i) {
    383     EXPECT_EQ(entry_id, service_->AddToList(
    384                             url, service_->CreateDefaultDistillerPage().Pass(),
    385                             ArticleCallback(&article_cb[i])));
    386     EXPECT_CALL(article_cb[i], DistillationCompleted(false));
    387   }
    388 
    389   service_->RemoveEntry(entry_id);
    390   base::RunLoop().RunUntilIdle();
    391 }
    392 
    393 TEST_F(DomDistillerServiceTest, TestMultiplePageArticle) {
    394   FakeDistiller* distiller = new FakeDistiller(false);
    395   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
    396       .WillOnce(Return(distiller));
    397 
    398   const int kPageCount = 8;
    399 
    400   std::string base_url("http://www.example.com/p");
    401   GURL pages_url[kPageCount];
    402   for (int page_num = 0; page_num < kPageCount; ++page_num) {
    403     pages_url[page_num] = GURL(base_url + base::IntToString(page_num));
    404   }
    405 
    406   MockArticleAvailableCallback article_cb;
    407   EXPECT_CALL(article_cb, DistillationCompleted(true));
    408 
    409   std::string entry_id = service_->AddToList(
    410       pages_url[0], service_->CreateDefaultDistillerPage().Pass(),
    411       ArticleCallback(&article_cb));
    412 
    413   ArticleEntry entry;
    414   ASSERT_FALSE(distiller->GetArticleCallback().is_null());
    415   EXPECT_EQ(pages_url[0], distiller->GetUrl());
    416 
    417   // Create the article with pages to pass to the distiller.
    418   scoped_ptr<DistilledArticleProto> proto =
    419       CreateArticleWithURL(pages_url[0].spec());
    420   for (int page_num = 1; page_num < kPageCount; ++page_num) {
    421     DistilledPageProto* distilled_page = proto->add_pages();
    422     distilled_page->set_url(pages_url[page_num].spec());
    423   }
    424 
    425   RunDistillerCallback(distiller, proto.Pass());
    426   EXPECT_TRUE(store_->GetEntryByUrl(pages_url[0], &entry));
    427 
    428   EXPECT_EQ(kPageCount, entry.pages_size());
    429   // An article should have just one entry.
    430   EXPECT_EQ(1u, store_->GetEntries().size());
    431 
    432   // All pages should have correct urls.
    433   for (int page_num = 0; page_num < kPageCount; ++page_num) {
    434     EXPECT_EQ(pages_url[page_num].spec(), entry.pages(page_num).url());
    435   }
    436 
    437   // Should be able to query article using any of the pages url.
    438   for (int page_num = 0; page_num < kPageCount; ++page_num) {
    439     EXPECT_TRUE(store_->GetEntryByUrl(pages_url[page_num], &entry));
    440   }
    441 
    442   service_->RemoveEntry(entry_id);
    443   base::RunLoop().RunUntilIdle();
    444   EXPECT_EQ(0u, store_->GetEntries().size());
    445 }
    446 
    447 }  // namespace test
    448 }  // namespace dom_distiller
    449