blob: 340b8fe19bc39548ac037f39b2369ec47062ec77 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/feed/core/feed_image_database.h"
#include <map>
#include "base/test/scoped_task_environment.h"
#include "base/test/simple_test_clock.h"
#include "components/feed/core/proto/cached_image.pb.h"
#include "components/feed/core/time_serialization.h"
#include "components/leveldb_proto/testing/fake_db.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using leveldb_proto::test::FakeDB;
using testing::Mock;
using testing::_;
namespace feed {
namespace {
// Fixed "now" to make tests more deterministic.
char kNowString[] = "2018-06-11 15:41";
constexpr char kImageURL[] = "http://pie.com/";
constexpr char kImageData[] = "pie image";
} // namespace
class FeedImageDatabaseTest : public testing::Test {
public:
FeedImageDatabaseTest() : image_db_(nullptr) {
base::Time now;
EXPECT_TRUE(base::Time::FromUTCString(kNowString, &now));
test_clock_.SetNow(now);
}
void CreateDatabase() {
// The FakeDBs are owned by |feed_db_|, so clear our pointers before
// resetting |feed_db_| itself.
image_db_ = nullptr;
// Explicitly destroy any existing database before creating a new one.
feed_db_.reset();
auto image_db =
std::make_unique<FakeDB<CachedImageProto>>(&image_db_storage_);
image_db_ = image_db.get();
feed_db_ = std::make_unique<FeedImageDatabase>(
base::FilePath(), std::move(image_db), &test_clock_);
}
int64_t GetImageLastUsedTime(const std::string& url) {
return image_db_storage_[kImageURL].last_used_time();
}
void InjectImageProto(const std::string& url,
const std::string& data,
base::Time time) {
CachedImageProto image_proto;
image_proto.set_url(url);
image_proto.set_data(data);
image_proto.set_last_used_time(ToDatabaseTime(time));
image_db_storage_[url] = image_proto;
}
FakeDB<CachedImageProto>* image_db() { return image_db_; }
FeedImageDatabase* db() { return feed_db_.get(); }
base::SimpleTestClock* test_clock() { return &test_clock_; }
void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
MOCK_METHOD1(OnImageLoaded, void(const std::string&));
MOCK_METHOD1(OnGarbageCollected, void(bool));
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::map<std::string, CachedImageProto> image_db_storage_;
// Owned by |feed_db_|.
FakeDB<CachedImageProto>* image_db_;
base::SimpleTestClock test_clock_;
std::unique_ptr<FeedImageDatabase> feed_db_;
DISALLOW_COPY_AND_ASSIGN(FeedImageDatabaseTest);
};
TEST_F(FeedImageDatabaseTest, Init) {
ASSERT_FALSE(db());
CreateDatabase();
EXPECT_FALSE(db()->IsInitialized());
image_db()->InitCallback(true);
EXPECT_TRUE(db()->IsInitialized());
}
TEST_F(FeedImageDatabaseTest, LoadBeforeInitSuccess) {
CreateDatabase();
EXPECT_FALSE(db()->IsInitialized());
// Start an image load before the database is initialized.
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
EXPECT_CALL(*this, OnImageLoaded(_));
image_db()->InitCallback(true);
EXPECT_TRUE(db()->IsInitialized());
image_db()->GetCallback(true);
}
TEST_F(FeedImageDatabaseTest, LoadBeforeInitFailed) {
CreateDatabase();
EXPECT_FALSE(db()->IsInitialized());
// Start an image load before the database is initialized.
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
EXPECT_CALL(*this, OnImageLoaded(_));
image_db()->InitCallback(false);
EXPECT_FALSE(db()->IsInitialized());
RunUntilIdle();
}
TEST_F(FeedImageDatabaseTest, LoadAfterInitSuccess) {
CreateDatabase();
EXPECT_FALSE(db()->IsInitialized());
EXPECT_CALL(*this, OnImageLoaded(_)).Times(0);
image_db()->InitCallback(true);
EXPECT_TRUE(db()->IsInitialized());
Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnImageLoaded(_));
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
}
TEST_F(FeedImageDatabaseTest, LoadAfterInitFailed) {
CreateDatabase();
EXPECT_FALSE(db()->IsInitialized());
EXPECT_CALL(*this, OnImageLoaded(_)).Times(0);
image_db()->InitCallback(false);
EXPECT_FALSE(db()->IsInitialized());
Mock::VerifyAndClearExpectations(this);
EXPECT_CALL(*this, OnImageLoaded(_));
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
RunUntilIdle();
}
TEST_F(FeedImageDatabaseTest, Save) {
CreateDatabase();
image_db()->InitCallback(true);
ASSERT_TRUE(db()->IsInitialized());
// Store an image.
db()->SaveImage(kImageURL, kImageData);
image_db()->UpdateCallback(true);
// Make sure they're there.
EXPECT_CALL(*this, OnImageLoaded(kImageData));
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
}
TEST_F(FeedImageDatabaseTest, SavePersist) {
CreateDatabase();
image_db()->InitCallback(true);
ASSERT_TRUE(db()->IsInitialized());
// Store an image.
db()->SaveImage(kImageURL, kImageData);
image_db()->UpdateCallback(true);
// They should still exist after recreating the database.
CreateDatabase();
image_db()->InitCallback(true);
ASSERT_TRUE(db()->IsInitialized());
EXPECT_CALL(*this, OnImageLoaded(kImageData));
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
}
TEST_F(FeedImageDatabaseTest, LoadUpdatesTime) {
CreateDatabase();
image_db()->InitCallback(true);
ASSERT_TRUE(db()->IsInitialized());
// Store an image.
InjectImageProto(kImageURL, kImageData, base::Time::UnixEpoch());
int64_t old_time = GetImageLastUsedTime(kImageURL);
// Make sure they're there.
EXPECT_CALL(*this, OnImageLoaded(kImageData));
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
image_db()->UpdateCallback(true);
EXPECT_TRUE(old_time != GetImageLastUsedTime(kImageURL));
}
TEST_F(FeedImageDatabaseTest, Delete) {
CreateDatabase();
image_db()->InitCallback(true);
ASSERT_TRUE(db()->IsInitialized());
// Store the image.
db()->SaveImage(kImageURL, kImageData);
image_db()->UpdateCallback(true);
// Make sure the image is there.
EXPECT_CALL(*this, OnImageLoaded(kImageData));
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
Mock::VerifyAndClearExpectations(this);
// Delete the image.
db()->DeleteImage(kImageURL);
image_db()->UpdateCallback(true);
// Make sure the image is gone.
EXPECT_CALL(*this, OnImageLoaded(std::string()));
db()->LoadImage(kImageURL,
base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
}
TEST_F(FeedImageDatabaseTest, GarbageCollectImagesTest) {
CreateDatabase();
image_db()->InitCallback(true);
ASSERT_TRUE(db()->IsInitialized());
base::Time now = test_clock()->Now();
base::Time expired_time = now - base::TimeDelta::FromDays(30);
base::Time very_old_time = now - base::TimeDelta::FromDays(100);
// Store images.
InjectImageProto("url1", "data1", very_old_time);
InjectImageProto("url2", "data2", now);
InjectImageProto("url3", "data3", very_old_time);
// Garbage collect all except the second.
EXPECT_CALL(*this, OnGarbageCollected(true));
db()->GarbageCollectImages(
expired_time, base::BindOnce(&FeedImageDatabaseTest::OnGarbageCollected,
base::Unretained(this)));
// This will first load all images, then delete the expired ones.
image_db()->LoadCallback(true);
image_db()->UpdateCallback(true);
RunUntilIdle();
// Make sure the images are gone.
EXPECT_CALL(*this, OnImageLoaded(std::string()));
db()->LoadImage("url1", base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
EXPECT_CALL(*this, OnImageLoaded(std::string()));
db()->LoadImage("url3", base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
// Make sure the second still exists.
EXPECT_CALL(*this, OnImageLoaded("data2"));
db()->LoadImage("url2", base::BindOnce(&FeedImageDatabaseTest::OnImageLoaded,
base::Unretained(this)));
image_db()->GetCallback(true);
}
} // namespace feed