| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/download/internal/background_service/model_impl.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/uuid.h" |
| #include "components/download/internal/background_service/entry.h" |
| #include "components/download/internal/background_service/stats.h" |
| #include "components/download/internal/background_service/test/entry_utils.h" |
| #include "components/download/internal/background_service/test/mock_model_client.h" |
| #include "components/download/internal/background_service/test/test_store.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::InSequence; |
| using ::testing::Return; |
| using ::testing::_; |
| |
| namespace download { |
| |
| namespace { |
| |
| class DownloadServiceModelImplTest : public testing::Test { |
| public: |
| DownloadServiceModelImplTest() : store_(nullptr) {} |
| |
| DownloadServiceModelImplTest(const DownloadServiceModelImplTest&) = delete; |
| DownloadServiceModelImplTest& operator=(const DownloadServiceModelImplTest&) = |
| delete; |
| |
| ~DownloadServiceModelImplTest() override = default; |
| |
| void SetUp() override { |
| auto store = std::make_unique<test::TestStore>(); |
| store_ = store.get(); |
| model_ = std::make_unique<ModelImpl>(std::move(store)); |
| } |
| void TearDown() override { store_ = nullptr; } |
| |
| protected: |
| test::MockModelClient client_; |
| raw_ptr<test::TestStore> store_; |
| std::unique_ptr<ModelImpl> model_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(DownloadServiceModelImplTest, SuccessfulLifecycle) { |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| |
| model_->Initialize(&client_); |
| EXPECT_TRUE(store_->init_called()); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>()); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, SuccessfulInitWithEntries) { |
| Entry entry1 = test::BuildBasicEntry(); |
| Entry entry2 = test::BuildBasicEntry(); |
| std::vector<Entry> entries = {entry1, entry2}; |
| |
| base::HistogramTester histogram_tester; |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| |
| model_->Initialize(&client_); |
| EXPECT_TRUE(store_->init_called()); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>(entries)); |
| |
| EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid))); |
| EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid))); |
| |
| // Verify histograms. |
| histogram_tester.ExpectBucketCount("Download.Service.Db.Records", 2, 1); |
| histogram_tester.ExpectBucketCount("Download.Service.Db.Records.New", 2, 1); |
| histogram_tester.ExpectBucketCount("Download.Service.Db.Records.Available", 0, |
| 1); |
| histogram_tester.ExpectBucketCount("Download.Service.Db.Records.Active", 0, |
| 1); |
| histogram_tester.ExpectBucketCount("Download.Service.Db.Records.Paused", 0, |
| 1); |
| histogram_tester.ExpectBucketCount("Download.Service.Db.Records.Complete", 0, |
| 1); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, BadInit) { |
| EXPECT_CALL(client_, OnModelReady(false)).Times(1); |
| |
| model_->Initialize(&client_); |
| EXPECT_TRUE(store_->init_called()); |
| store_->TriggerInit(false, std::make_unique<std::vector<Entry>>()); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, HardRecoverGoodModel) { |
| Entry entry1 = test::BuildBasicEntry(); |
| Entry entry2 = test::BuildBasicEntry(); |
| std::vector<Entry> entries = {entry1, entry2}; |
| |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| |
| model_->Initialize(&client_); |
| EXPECT_TRUE(store_->init_called()); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>(entries)); |
| |
| EXPECT_CALL(client_, OnModelHardRecoverComplete(true)); |
| |
| model_->HardRecover(); |
| store_->TriggerHardRecover(true); |
| EXPECT_TRUE(model_->PeekEntries().empty()); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, HardRecoverBadModel) { |
| EXPECT_CALL(client_, OnModelReady(false)).Times(1); |
| |
| model_->Initialize(&client_); |
| EXPECT_TRUE(store_->init_called()); |
| store_->TriggerInit(false, std::make_unique<std::vector<Entry>>()); |
| |
| EXPECT_CALL(client_, OnModelHardRecoverComplete(true)); |
| |
| model_->HardRecover(); |
| store_->TriggerHardRecover(true); |
| EXPECT_TRUE(model_->PeekEntries().empty()); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, HardRecoverFailsGoodModel) { |
| Entry entry1 = test::BuildBasicEntry(); |
| Entry entry2 = test::BuildBasicEntry(); |
| std::vector<Entry> entries = {entry1, entry2}; |
| |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| |
| model_->Initialize(&client_); |
| EXPECT_TRUE(store_->init_called()); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>(entries)); |
| |
| EXPECT_CALL(client_, OnModelHardRecoverComplete(false)); |
| |
| model_->HardRecover(); |
| store_->TriggerHardRecover(false); |
| EXPECT_TRUE(model_->PeekEntries().empty()); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, HardRecoverFailsBadModel) { |
| EXPECT_CALL(client_, OnModelReady(false)).Times(1); |
| |
| model_->Initialize(&client_); |
| EXPECT_TRUE(store_->init_called()); |
| store_->TriggerInit(false, std::make_unique<std::vector<Entry>>()); |
| |
| EXPECT_CALL(client_, OnModelHardRecoverComplete(false)); |
| |
| model_->HardRecover(); |
| store_->TriggerHardRecover(false); |
| EXPECT_TRUE(model_->PeekEntries().empty()); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, Add) { |
| Entry entry1 = test::BuildBasicEntry(); |
| Entry entry2 = test::BuildBasicEntry(); |
| |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| EXPECT_CALL(client_, OnItemAdded(true, entry1.client, entry1.guid)).Times(1); |
| EXPECT_CALL(client_, OnItemAdded(false, entry2.client, entry2.guid)).Times(1); |
| |
| model_->Initialize(&client_); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>()); |
| |
| model_->Add(entry1); |
| EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid))); |
| EXPECT_TRUE(test::CompareEntry(&entry1, store_->LastUpdatedEntry())); |
| store_->TriggerUpdate(true); |
| |
| model_->Add(entry2); |
| EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid))); |
| EXPECT_TRUE(test::CompareEntry(&entry2, store_->LastUpdatedEntry())); |
| |
| store_->TriggerUpdate(false); |
| EXPECT_EQ(nullptr, model_->Get(entry2.guid)); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, Update) { |
| Entry entry1 = test::BuildBasicEntry(); |
| |
| Entry entry2(entry1); |
| entry2.state = Entry::State::AVAILABLE; |
| |
| Entry entry3(entry1); |
| entry3.state = Entry::State::ACTIVE; |
| |
| std::vector<Entry> entries = {entry1}; |
| |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| EXPECT_CALL(client_, OnItemUpdated(true, entry1.client, entry1.guid)) |
| .Times(2); |
| EXPECT_CALL(client_, OnItemUpdated(false, entry1.client, entry1.guid)) |
| .Times(1); |
| |
| model_->Initialize(&client_); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>(entries)); |
| std::vector<Entry*> entries_pointers = model_->PeekEntries(); |
| |
| // Update with a different object. |
| model_->Update(entry2); |
| EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid))); |
| EXPECT_TRUE(test::CompareEntry(&entry2, store_->LastUpdatedEntry())); |
| store_->TriggerUpdate(true); |
| |
| // Update with the same object. |
| Entry* entry = model_->Get(entry1.guid); |
| entry->state = Entry::State::NEW; |
| model_->Update(*entry); |
| store_->TriggerUpdate(true); |
| // Peek entries should return the same set of pointers. |
| EXPECT_TRUE(test::CompareEntryList(entries_pointers, model_->PeekEntries())); |
| |
| model_->Update(entry3); |
| EXPECT_TRUE(test::CompareEntry(&entry3, model_->Get(entry3.guid))); |
| EXPECT_TRUE(test::CompareEntry(&entry3, store_->LastUpdatedEntry())); |
| |
| store_->TriggerUpdate(false); |
| EXPECT_TRUE(test::CompareEntry(&entry3, model_->Get(entry3.guid))); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, Remove) { |
| Entry entry1 = test::BuildBasicEntry(); |
| Entry entry2 = test::BuildBasicEntry(); |
| std::vector<Entry> entries = {entry1, entry2}; |
| |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| EXPECT_CALL(client_, OnItemRemoved(true, entry1.client, entry1.guid)) |
| .Times(1); |
| EXPECT_CALL(client_, OnItemRemoved(false, entry2.client, entry2.guid)) |
| .Times(1); |
| |
| model_->Initialize(&client_); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>(entries)); |
| |
| model_->Remove(entry1.guid); |
| EXPECT_EQ(entry1.guid, store_->LastRemovedEntry()); |
| EXPECT_EQ(nullptr, model_->Get(entry1.guid)); |
| store_->TriggerRemove(true); |
| |
| model_->Remove(entry2.guid); |
| EXPECT_EQ(entry2.guid, store_->LastRemovedEntry()); |
| EXPECT_EQ(nullptr, model_->Get(entry2.guid)); |
| store_->TriggerRemove(false); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, Get) { |
| Entry entry = test::BuildBasicEntry(); |
| |
| std::vector<Entry> entries = {entry}; |
| |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| |
| model_->Initialize(&client_); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>(entries)); |
| |
| EXPECT_TRUE(test::CompareEntry(&entry, model_->Get(entry.guid))); |
| EXPECT_EQ(nullptr, |
| model_->Get(base::Uuid::GenerateRandomV4().AsLowercaseString())); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, PeekEntries) { |
| Entry entry1 = test::BuildBasicEntry(); |
| Entry entry2 = test::BuildBasicEntry(); |
| std::vector<Entry> entries = {entry1, entry2}; |
| |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| |
| model_->Initialize(&client_); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>(entries)); |
| |
| std::vector<Entry*> expected_peek = {&entry1, &entry2}; |
| |
| EXPECT_TRUE(test::CompareEntryList(expected_peek, model_->PeekEntries())); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, TestRemoveAfterAdd) { |
| Entry entry = test::BuildBasicEntry(); |
| |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| EXPECT_CALL(client_, OnItemAdded(_, _, _)).Times(0); |
| EXPECT_CALL(client_, OnItemRemoved(true, entry.client, entry.guid)).Times(1); |
| |
| model_->Initialize(&client_); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>()); |
| |
| model_->Add(entry); |
| EXPECT_TRUE(test::CompareEntry(&entry, model_->Get(entry.guid))); |
| |
| model_->Remove(entry.guid); |
| EXPECT_EQ(nullptr, model_->Get(entry.guid)); |
| |
| store_->TriggerUpdate(true); |
| store_->TriggerRemove(true); |
| } |
| |
| TEST_F(DownloadServiceModelImplTest, TestRemoveAfterUpdate) { |
| Entry entry1 = test::BuildBasicEntry(); |
| |
| Entry entry2(entry1); |
| entry2.state = Entry::State::AVAILABLE; |
| |
| std::vector<Entry> entries = {entry1}; |
| |
| InSequence sequence; |
| EXPECT_CALL(client_, OnModelReady(true)).Times(1); |
| EXPECT_CALL(client_, OnItemUpdated(_, _, _)).Times(0); |
| EXPECT_CALL(client_, OnItemRemoved(true, entry1.client, entry1.guid)) |
| .Times(1); |
| |
| model_->Initialize(&client_); |
| store_->TriggerInit(true, std::make_unique<std::vector<Entry>>(entries)); |
| EXPECT_TRUE(test::CompareEntry(&entry1, model_->Get(entry1.guid))); |
| |
| model_->Update(entry2); |
| EXPECT_TRUE(test::CompareEntry(&entry2, model_->Get(entry2.guid))); |
| |
| model_->Remove(entry2.guid); |
| EXPECT_EQ(nullptr, model_->Get(entry2.guid)); |
| |
| store_->TriggerUpdate(true); |
| store_->TriggerRemove(true); |
| } |
| |
| } // namespace download |