blob: e1d38fd06e17bf894575701dab14649036e4c81d [file] [log] [blame]
// Copyright 2019 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 "chrome/browser/media/history/media_history_store.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/pooled_sequenced_task_runner.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/media/feeds/media_feeds_store.mojom.h"
#include "chrome/browser/media/history/media_history_feed_items_table.h"
#include "chrome/browser/media/history/media_history_feeds_table.h"
#include "chrome/browser/media/history/media_history_images_table.h"
#include "chrome/browser/media/history/media_history_keyed_service.h"
#include "chrome/browser/media/history/media_history_session_images_table.h"
#include "chrome/browser/media/history/media_history_session_table.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/common/pref_names.h"
#include "components/history/core/test/test_history_database.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/media_player_watch_time.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "media/base/media_switches.h"
#include "services/media_session/public/cpp/media_image.h"
#include "services/media_session/public/cpp/media_metadata.h"
#include "services/media_session/public/cpp/media_position.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media_history {
namespace {
// The error margin for double time comparison. It is 10 seconds because it
// might be equal but it might be close too.
const int kTimeErrorMargin = 10000;
// The expected display name for the fetched media feed.
const char kExpectedDisplayName[] = "Test Feed";
// The expected counts and content types for the test feed.
const int kExpectedFetchItemCount = 3;
const int kExpectedFetchPlayNextCount = 2;
const int kExpectedFetchContentTypes =
static_cast<int>(media_feeds::mojom::MediaFeedItemType::kMovie) |
static_cast<int>(media_feeds::mojom::MediaFeedItemType::kTVSeries);
// The expected counts and content types for the alternate test feed.
const int kExpectedAltFetchItemCount = 1;
const int kExpectedAltFetchPlayNextCount = 1;
const int kExpectedAltFetchContentTypes =
static_cast<int>(media_feeds::mojom::MediaFeedItemType::kVideo);
base::FilePath g_temp_history_dir;
std::unique_ptr<KeyedService> BuildTestHistoryService(
content::BrowserContext* context) {
std::unique_ptr<history::HistoryService> service(
new history::HistoryService());
service->Init(history::TestHistoryDatabaseParamsForPath(g_temp_history_dir));
return service;
}
enum class TestState {
kNormal,
// Runs the test in incognito mode.
kIncognito,
// Runs the test with the "SavingBrowserHistoryDisabled" policy enabled.
kSavingBrowserHistoryDisabled,
};
} // namespace
// Runs the test with a param to signify the profile being incognito if true.
class MediaHistoryStoreUnitTest
: public testing::Test,
public testing::WithParamInterface<TestState> {
public:
MediaHistoryStoreUnitTest() = default;
void SetUp() override {
// Set up the profile.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
TestingProfile::Builder profile_builder;
profile_builder.SetPath(temp_dir_.GetPath());
g_temp_history_dir = temp_dir_.GetPath();
profile_ = profile_builder.Build();
if (GetParam() == TestState::kSavingBrowserHistoryDisabled) {
profile_->GetPrefs()->SetBoolean(prefs::kSavingBrowserHistoryDisabled,
true);
}
HistoryServiceFactory::GetInstance()->SetTestingFactory(
profile_.get(), base::BindRepeating(&BuildTestHistoryService));
// Sleep the thread to allow the media history store to asynchronously
// create the database and tables before proceeding with the tests and
// tearing down the temporary directory.
WaitForDB();
// Set up the local DB connection used for assertions.
base::FilePath db_file =
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("Media History"));
ASSERT_TRUE(db_.Open(db_file));
// Set up the media history store for OTR.
otr_service_ = std::make_unique<MediaHistoryKeyedService>(
profile_->GetOffTheRecordProfile());
}
void TearDown() override { WaitForDB(); }
void WaitForDB() {
base::RunLoop run_loop;
MediaHistoryKeyedService::Get(profile_.get())
->PostTaskToDBForTest(run_loop.QuitClosure());
run_loop.Run();
}
mojom::MediaHistoryStatsPtr GetStatsSync(MediaHistoryKeyedService* service) {
base::RunLoop run_loop;
mojom::MediaHistoryStatsPtr stats_out;
service->GetMediaHistoryStats(
base::BindLambdaForTesting([&](mojom::MediaHistoryStatsPtr stats) {
stats_out = std::move(stats);
run_loop.Quit();
}));
run_loop.Run();
return stats_out;
}
std::vector<mojom::MediaHistoryOriginRowPtr> GetOriginRowsSync(
MediaHistoryKeyedService* service) {
base::RunLoop run_loop;
std::vector<mojom::MediaHistoryOriginRowPtr> out;
service->GetOriginRowsForDebug(base::BindLambdaForTesting(
[&](std::vector<mojom::MediaHistoryOriginRowPtr> rows) {
out = std::move(rows);
run_loop.Quit();
}));
run_loop.Run();
return out;
}
std::vector<mojom::MediaHistoryPlaybackRowPtr> GetPlaybackRowsSync(
MediaHistoryKeyedService* service) {
base::RunLoop run_loop;
std::vector<mojom::MediaHistoryPlaybackRowPtr> out;
service->GetMediaHistoryPlaybackRowsForDebug(base::BindLambdaForTesting(
[&](std::vector<mojom::MediaHistoryPlaybackRowPtr> rows) {
out = std::move(rows);
run_loop.Quit();
}));
run_loop.Run();
return out;
}
std::vector<media_feeds::mojom::MediaFeedPtr> GetMediaFeedsSync(
MediaHistoryKeyedService* service) {
base::RunLoop run_loop;
std::vector<media_feeds::mojom::MediaFeedPtr> out;
service->GetMediaFeedsForDebug(base::BindLambdaForTesting(
[&](std::vector<media_feeds::mojom::MediaFeedPtr> rows) {
out = std::move(rows);
run_loop.Quit();
}));
run_loop.Run();
return out;
}
MediaHistoryKeyedService* service() const {
// If the param is true then we use the OTR service to simulate being in
// incognito.
if (GetParam() == TestState::kIncognito)
return otr_service();
return MediaHistoryKeyedService::Get(profile_.get());
}
MediaHistoryKeyedService* otr_service() const { return otr_service_.get(); }
bool IsReadOnly() const { return GetParam() != TestState::kNormal; }
private:
base::ScopedTempDir temp_dir_;
protected:
sql::Database& GetDB() { return db_; }
content::BrowserTaskEnvironment task_environment_;
private:
sql::Database db_;
std::unique_ptr<MediaHistoryKeyedService> otr_service_;
std::unique_ptr<TestingProfile> profile_;
};
INSTANTIATE_TEST_SUITE_P(
All,
MediaHistoryStoreUnitTest,
testing::Values(TestState::kNormal,
TestState::kIncognito,
TestState::kSavingBrowserHistoryDisabled));
TEST_P(MediaHistoryStoreUnitTest, CreateDatabaseTables) {
ASSERT_TRUE(GetDB().DoesTableExist("origin"));
ASSERT_TRUE(GetDB().DoesTableExist("playback"));
ASSERT_TRUE(GetDB().DoesTableExist("playbackSession"));
ASSERT_TRUE(GetDB().DoesTableExist("sessionImage"));
ASSERT_TRUE(GetDB().DoesTableExist("mediaImage"));
ASSERT_FALSE(GetDB().DoesTableExist("mediaFeed"));
}
TEST_P(MediaHistoryStoreUnitTest, SavePlayback) {
const auto now_before =
(base::Time::Now() - base::TimeDelta::FromMinutes(1)).ToJsTime();
// Create a media player watch time and save it to the playbacks table.
GURL url("http://google.com/test");
content::MediaPlayerWatchTime watch_time(url, url.GetOrigin(),
base::TimeDelta::FromSeconds(60),
base::TimeDelta(), true, false);
service()->SavePlayback(watch_time);
const auto now_after_a = base::Time::Now().ToJsTime();
// Save the watch time a second time.
service()->SavePlayback(watch_time);
// Wait until the playbacks have finished saving.
WaitForDB();
const auto now_after_b = base::Time::Now().ToJsTime();
// Verify that the playback table contains the expected number of items.
std::vector<mojom::MediaHistoryPlaybackRowPtr> playbacks =
GetPlaybackRowsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(playbacks.empty());
} else {
EXPECT_EQ(2u, playbacks.size());
EXPECT_EQ("http://google.com/test", playbacks[0]->url.spec());
EXPECT_FALSE(playbacks[0]->has_audio);
EXPECT_TRUE(playbacks[0]->has_video);
EXPECT_EQ(base::TimeDelta::FromSeconds(60), playbacks[0]->watchtime);
EXPECT_LE(now_before, playbacks[0]->last_updated_time);
EXPECT_GE(now_after_a, playbacks[0]->last_updated_time);
EXPECT_EQ("http://google.com/test", playbacks[1]->url.spec());
EXPECT_FALSE(playbacks[1]->has_audio);
EXPECT_TRUE(playbacks[1]->has_video);
EXPECT_EQ(base::TimeDelta::FromSeconds(60), playbacks[1]->watchtime);
EXPECT_LE(now_before, playbacks[1]->last_updated_time);
EXPECT_GE(now_after_b, playbacks[1]->last_updated_time);
}
// Verify that the origin table contains the expected number of items.
std::vector<mojom::MediaHistoryOriginRowPtr> origins =
GetOriginRowsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(origins.empty());
} else {
EXPECT_EQ(1u, origins.size());
EXPECT_EQ("http://google.com", origins[0]->origin.Serialize());
EXPECT_LE(now_before, origins[0]->last_updated_time);
EXPECT_GE(now_after_b, origins[0]->last_updated_time);
}
// The OTR service should have the same data.
EXPECT_EQ(origins, GetOriginRowsSync(otr_service()));
EXPECT_EQ(playbacks, GetPlaybackRowsSync(otr_service()));
}
TEST_P(MediaHistoryStoreUnitTest, GetStats) {
{
// Check all the tables are empty.
mojom::MediaHistoryStatsPtr stats = GetStatsSync(service());
EXPECT_EQ(0, stats->table_row_counts[MediaHistoryOriginTable::kTableName]);
EXPECT_EQ(0,
stats->table_row_counts[MediaHistoryPlaybackTable::kTableName]);
EXPECT_EQ(0, stats->table_row_counts[MediaHistorySessionTable::kTableName]);
EXPECT_EQ(
0, stats->table_row_counts[MediaHistorySessionImagesTable::kTableName]);
EXPECT_EQ(0, stats->table_row_counts[MediaHistoryImagesTable::kTableName]);
// The OTR service should have the same data.
EXPECT_EQ(stats, GetStatsSync(otr_service()));
}
{
// Create a media player watch time and save it to the playbacks table.
GURL url("http://google.com/test");
content::MediaPlayerWatchTime watch_time(
url, url.GetOrigin(), base::TimeDelta::FromMilliseconds(123),
base::TimeDelta::FromMilliseconds(321), true, false);
service()->SavePlayback(watch_time);
}
{
// Check the tables have records in them.
mojom::MediaHistoryStatsPtr stats = GetStatsSync(service());
if (IsReadOnly()) {
EXPECT_EQ(0,
stats->table_row_counts[MediaHistoryOriginTable::kTableName]);
EXPECT_EQ(0,
stats->table_row_counts[MediaHistoryPlaybackTable::kTableName]);
EXPECT_EQ(0,
stats->table_row_counts[MediaHistorySessionTable::kTableName]);
EXPECT_EQ(
0,
stats->table_row_counts[MediaHistorySessionImagesTable::kTableName]);
EXPECT_EQ(0,
stats->table_row_counts[MediaHistoryImagesTable::kTableName]);
} else {
EXPECT_EQ(1,
stats->table_row_counts[MediaHistoryOriginTable::kTableName]);
EXPECT_EQ(1,
stats->table_row_counts[MediaHistoryPlaybackTable::kTableName]);
EXPECT_EQ(0,
stats->table_row_counts[MediaHistorySessionTable::kTableName]);
EXPECT_EQ(
0,
stats->table_row_counts[MediaHistorySessionImagesTable::kTableName]);
EXPECT_EQ(0,
stats->table_row_counts[MediaHistoryImagesTable::kTableName]);
}
// The OTR service should have the same data.
EXPECT_EQ(stats, GetStatsSync(otr_service()));
}
}
TEST_P(MediaHistoryStoreUnitTest, UrlShouldBeUniqueForSessions) {
GURL url_a("https://www.google.com");
GURL url_b("https://www.example.org");
{
mojom::MediaHistoryStatsPtr stats = GetStatsSync(service());
EXPECT_EQ(0, stats->table_row_counts[MediaHistorySessionTable::kTableName]);
// The OTR service should have the same data.
EXPECT_EQ(stats, GetStatsSync(otr_service()));
}
// Save a couple of sessions on different URLs.
service()->SavePlaybackSession(url_a, media_session::MediaMetadata(),
base::nullopt,
std::vector<media_session::MediaImage>());
service()->SavePlaybackSession(url_b, media_session::MediaMetadata(),
base::nullopt,
std::vector<media_session::MediaImage>());
// Wait until the sessions have finished saving.
WaitForDB();
{
mojom::MediaHistoryStatsPtr stats = GetStatsSync(service());
if (IsReadOnly()) {
EXPECT_EQ(0,
stats->table_row_counts[MediaHistorySessionTable::kTableName]);
} else {
EXPECT_EQ(2,
stats->table_row_counts[MediaHistorySessionTable::kTableName]);
sql::Statement s(GetDB().GetUniqueStatement(
"SELECT id FROM playbackSession WHERE url = ?"));
s.BindString(0, url_a.spec());
ASSERT_TRUE(s.Step());
EXPECT_EQ(1, s.ColumnInt(0));
}
// The OTR service should have the same data.
EXPECT_EQ(stats, GetStatsSync(otr_service()));
}
// Save a session on the first URL.
service()->SavePlaybackSession(url_a, media_session::MediaMetadata(),
base::nullopt,
std::vector<media_session::MediaImage>());
// Wait until the sessions have finished saving.
WaitForDB();
{
mojom::MediaHistoryStatsPtr stats = GetStatsSync(service());
if (IsReadOnly()) {
EXPECT_EQ(0,
stats->table_row_counts[MediaHistorySessionTable::kTableName]);
} else {
EXPECT_EQ(2,
stats->table_row_counts[MediaHistorySessionTable::kTableName]);
// The OTR service should have the same data.
EXPECT_EQ(stats, GetStatsSync(otr_service()));
// The row for |url_a| should have been replaced so we should have a new
// ID.
sql::Statement s(GetDB().GetUniqueStatement(
"SELECT id FROM playbackSession WHERE url = ?"));
s.BindString(0, url_a.spec());
ASSERT_TRUE(s.Step());
EXPECT_EQ(3, s.ColumnInt(0));
}
}
}
TEST_P(MediaHistoryStoreUnitTest, SavePlayback_IncrementAggregateWatchtime) {
GURL url("http://google.com/test");
GURL url_alt("http://example.org/test");
const auto url_now_before = base::Time::Now().ToJsTime();
{
// Record a watchtime for audio/video for 30 seconds.
content::MediaPlayerWatchTime watch_time(
url, url.GetOrigin(), base::TimeDelta::FromSeconds(30),
base::TimeDelta(), true /* has_video */, true /* has_audio */);
service()->SavePlayback(watch_time);
WaitForDB();
}
{
// Record a watchtime for audio/video for 60 seconds.
content::MediaPlayerWatchTime watch_time(
url, url.GetOrigin(), base::TimeDelta::FromSeconds(60),
base::TimeDelta(), true /* has_video */, true /* has_audio */);
service()->SavePlayback(watch_time);
WaitForDB();
}
{
// Record an audio-only watchtime for 30 seconds.
content::MediaPlayerWatchTime watch_time(
url, url.GetOrigin(), base::TimeDelta::FromSeconds(30),
base::TimeDelta(), false /* has_video */, true /* has_audio */);
service()->SavePlayback(watch_time);
WaitForDB();
}
{
// Record a video-only watchtime for 30 seconds.
content::MediaPlayerWatchTime watch_time(
url, url.GetOrigin(), base::TimeDelta::FromSeconds(30),
base::TimeDelta(), true /* has_video */, false /* has_audio */);
service()->SavePlayback(watch_time);
WaitForDB();
}
const auto url_now_after = base::Time::Now().ToJsTime();
{
// Record a watchtime for audio/video for 60 seconds on a different origin.
content::MediaPlayerWatchTime watch_time(
url_alt, url_alt.GetOrigin(), base::TimeDelta::FromSeconds(30),
base::TimeDelta(), true /* has_video */, true /* has_audio */);
service()->SavePlayback(watch_time);
WaitForDB();
}
const auto url_alt_after = base::Time::Now().ToJsTime();
{
// Check the playbacks were recorded.
mojom::MediaHistoryStatsPtr stats = GetStatsSync(service());
if (IsReadOnly()) {
EXPECT_EQ(0,
stats->table_row_counts[MediaHistoryOriginTable::kTableName]);
EXPECT_EQ(0,
stats->table_row_counts[MediaHistoryPlaybackTable::kTableName]);
} else {
EXPECT_EQ(2,
stats->table_row_counts[MediaHistoryOriginTable::kTableName]);
EXPECT_EQ(5,
stats->table_row_counts[MediaHistoryPlaybackTable::kTableName]);
}
// The OTR service should have the same data.
EXPECT_EQ(stats, GetStatsSync(otr_service()));
}
std::vector<mojom::MediaHistoryOriginRowPtr> origins =
GetOriginRowsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(origins.empty());
} else {
EXPECT_EQ(2u, origins.size());
EXPECT_EQ("http://google.com", origins[0]->origin.Serialize());
EXPECT_EQ(base::TimeDelta::FromSeconds(90),
origins[0]->cached_audio_video_watchtime);
EXPECT_NEAR(url_now_before, origins[0]->last_updated_time,
kTimeErrorMargin);
EXPECT_GE(url_now_after, origins[0]->last_updated_time);
EXPECT_EQ(origins[0]->cached_audio_video_watchtime,
origins[0]->actual_audio_video_watchtime);
EXPECT_EQ("http://example.org", origins[1]->origin.Serialize());
EXPECT_EQ(base::TimeDelta::FromSeconds(30),
origins[1]->cached_audio_video_watchtime);
EXPECT_NEAR(url_now_before, origins[1]->last_updated_time,
kTimeErrorMargin);
EXPECT_GE(url_alt_after, origins[1]->last_updated_time);
EXPECT_EQ(origins[1]->cached_audio_video_watchtime,
origins[1]->actual_audio_video_watchtime);
}
// The OTR service should have the same data.
EXPECT_EQ(origins, GetOriginRowsSync(otr_service()));
}
TEST_P(MediaHistoryStoreUnitTest, DiscoverMediaFeed_Noop) {
service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
WaitForDB();
{
// Check the feeds were not recorded.
mojom::MediaHistoryStatsPtr stats = GetStatsSync(service());
EXPECT_FALSE(base::Contains(stats->table_row_counts,
MediaHistoryFeedsTable::kTableName));
// The OTR service should have the same data.
EXPECT_EQ(stats, GetStatsSync(otr_service()));
}
}
// Runs the tests with the media feeds feature enabled.
class MediaHistoryStoreFeedsTest : public MediaHistoryStoreUnitTest {
public:
void SetUp() override {
features_.InitAndEnableFeature(media::kMediaFeeds);
MediaHistoryStoreUnitTest::SetUp();
}
std::vector<media_feeds::mojom::MediaFeedItemPtr> GetItemsForMediaFeedSync(
MediaHistoryKeyedService* service,
const int64_t feed_id) {
base::RunLoop run_loop;
std::vector<media_feeds::mojom::MediaFeedItemPtr> out;
service->GetItemsForMediaFeedForDebug(
feed_id,
base::BindLambdaForTesting(
[&](std::vector<media_feeds::mojom::MediaFeedItemPtr> rows) {
out = std::move(rows);
run_loop.Quit();
}));
run_loop.Run();
return out;
}
static media_feeds::mojom::ContentRatingPtr CreateRating(
const std::string& agency,
const std::string& value) {
auto rating = media_feeds::mojom::ContentRating::New();
rating->agency = agency;
rating->value = value;
return rating;
}
static media_feeds::mojom::IdentifierPtr CreateIdentifier(
const media_feeds::mojom::Identifier::Type& type,
const std::string& value) {
auto identifier = media_feeds::mojom::Identifier::New();
identifier->type = type;
identifier->value = value;
return identifier;
}
static std::vector<media_feeds::mojom::MediaFeedItemPtr> GetExpectedItems() {
std::vector<media_feeds::mojom::MediaFeedItemPtr> items;
{
auto item = media_feeds::mojom::MediaFeedItem::New();
item->name = base::ASCIIToUTF16("The Movie");
item->type = media_feeds::mojom::MediaFeedItemType::kMovie;
item->date_published = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMinutes(10));
item->is_family_friendly = true;
item->action_status =
media_feeds::mojom::MediaFeedItemActionStatus::kPotential;
item->genre = base::ASCIIToUTF16("test");
item->duration = base::TimeDelta::FromSeconds(30);
item->live = media_feeds::mojom::LiveDetails::New();
item->live->start_time = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMinutes(20));
item->live->end_time = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMinutes(30));
item->shown_count = 3;
item->clicked = true;
item->author = media_feeds::mojom::Author::New();
item->author->name = "Media Site";
item->author->url = GURL("https://www.example.com");
item->action = media_feeds::mojom::Action::New();
item->action->start_time = base::TimeDelta::FromSeconds(3);
item->action->url = GURL("https://www.example.com");
item->interaction_counters.emplace(
media_feeds::mojom::InteractionCounterType::kLike, 10000);
item->interaction_counters.emplace(
media_feeds::mojom::InteractionCounterType::kDislike, 20000);
item->interaction_counters.emplace(
media_feeds::mojom::InteractionCounterType::kWatch, 30000);
item->content_ratings.push_back(CreateRating("MPAA", "PG-13"));
item->content_ratings.push_back(CreateRating("agency", "TEST2"));
item->identifiers.push_back(CreateIdentifier(
media_feeds::mojom::Identifier::Type::kPartnerId, "TEST1"));
item->identifiers.push_back(CreateIdentifier(
media_feeds::mojom::Identifier::Type::kTMSId, "TEST2"));
item->tv_episode = media_feeds::mojom::TVEpisode::New();
item->tv_episode->name = "TV Episode Name";
item->tv_episode->season_number = 1;
item->tv_episode->episode_number = 2;
item->tv_episode->identifiers.push_back(CreateIdentifier(
media_feeds::mojom::Identifier::Type::kTMSId, "TEST3"));
item->play_next_candidate = media_feeds::mojom::PlayNextCandidate::New();
item->play_next_candidate->name = "Next TV Episode Name";
item->play_next_candidate->season_number = 1;
item->play_next_candidate->episode_number = 3;
item->play_next_candidate->duration = base::TimeDelta::FromMinutes(20);
item->play_next_candidate->action = media_feeds::mojom::Action::New();
item->play_next_candidate->action->start_time =
base::TimeDelta::FromSeconds(3);
item->play_next_candidate->action->url = GURL("https://www.example.com");
item->play_next_candidate->identifiers.push_back(CreateIdentifier(
media_feeds::mojom::Identifier::Type::kTMSId, "TEST4"));
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image1.png");
item->images.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image2.png");
image.sizes.push_back(gfx::Size(10, 10));
item->images.push_back(image);
}
items.push_back(std::move(item));
}
{
auto item = media_feeds::mojom::MediaFeedItem::New();
item->type = media_feeds::mojom::MediaFeedItemType::kTVSeries;
item->name = base::ASCIIToUTF16("The TV Series");
item->action_status =
media_feeds::mojom::MediaFeedItemActionStatus::kActive;
item->author = media_feeds::mojom::Author::New();
item->author->name = "Media Site";
items.push_back(std::move(item));
}
{
auto item = media_feeds::mojom::MediaFeedItem::New();
item->type = media_feeds::mojom::MediaFeedItemType::kTVSeries;
item->name = base::ASCIIToUTF16("The Live TV Series");
item->action_status =
media_feeds::mojom::MediaFeedItemActionStatus::kPotential;
item->live = media_feeds::mojom::LiveDetails::New();
items.push_back(std::move(item));
}
return items;
}
static std::vector<media_feeds::mojom::MediaFeedItemPtr>
GetAltExpectedItems() {
std::vector<media_feeds::mojom::MediaFeedItemPtr> items;
{
auto item = media_feeds::mojom::MediaFeedItem::New();
item->type = media_feeds::mojom::MediaFeedItemType::kVideo;
item->name = base::ASCIIToUTF16("The Video");
item->date_published = base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMinutes(20));
item->is_family_friendly = false;
item->action_status =
media_feeds::mojom::MediaFeedItemActionStatus::kActive;
items.push_back(std::move(item));
}
return items;
}
static std::vector<media_session::MediaImage> GetExpectedLogos() {
std::vector<media_session::MediaImage> logos;
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image1.png");
image.sizes.push_back(gfx::Size(10, 10));
logos.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image2.png");
logos.push_back(image);
}
return logos;
}
private:
base::test::ScopedFeatureList features_;
};
INSTANTIATE_TEST_SUITE_P(All,
MediaHistoryStoreFeedsTest,
testing::Values(TestState::kNormal,
TestState::kIncognito));
TEST_P(MediaHistoryStoreFeedsTest, CreateDatabaseTables) {
ASSERT_TRUE(GetDB().DoesTableExist("mediaFeed"));
ASSERT_TRUE(GetDB().DoesTableExist("mediaFeedItem"));
}
TEST_P(MediaHistoryStoreFeedsTest, DiscoverMediaFeed) {
GURL url_a("https://www.google.com/feed");
GURL url_b("https://www.google.co.uk/feed");
GURL url_c("https://www.google.com/feed2");
service()->DiscoverMediaFeed(url_a);
service()->DiscoverMediaFeed(url_b);
WaitForDB();
{
// Check the feeds were recorded.
std::vector<media_feeds::mojom::MediaFeedPtr> feeds =
GetMediaFeedsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
} else {
EXPECT_EQ(2u, feeds.size());
EXPECT_EQ(1, feeds[0]->id);
EXPECT_EQ(url_a, feeds[0]->url);
EXPECT_FALSE(feeds[0]->last_fetch_time.has_value());
EXPECT_EQ(media_feeds::mojom::FetchResult::kNone,
feeds[0]->last_fetch_result);
EXPECT_EQ(0, feeds[0]->fetch_failed_count);
EXPECT_FALSE(feeds[0]->cache_expiry_time.has_value());
EXPECT_EQ(0, feeds[0]->last_fetch_item_count);
EXPECT_EQ(0, feeds[0]->last_fetch_play_next_count);
EXPECT_EQ(0, feeds[0]->last_fetch_content_types);
EXPECT_TRUE(feeds[0]->logos.empty());
EXPECT_TRUE(feeds[0]->display_name.empty());
EXPECT_EQ(2, feeds[1]->id);
EXPECT_EQ(url_b, feeds[1]->url);
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
}
service()->DiscoverMediaFeed(url_c);
WaitForDB();
{
// Check the feeds were recorded.
std::vector<media_feeds::mojom::MediaFeedPtr> feeds =
GetMediaFeedsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
} else {
EXPECT_EQ(2u, feeds.size());
EXPECT_EQ(2, feeds[0]->id);
EXPECT_EQ(url_b, feeds[0]->url);
EXPECT_EQ(3, feeds[1]->id);
EXPECT_EQ(url_c, feeds[1]->url);
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
}
}
TEST_P(MediaHistoryStoreFeedsTest, StoreMediaFeedFetchResult) {
service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
WaitForDB();
// If we are read only we should use -1 as a placeholder feed id because the
// feed will not have been stored. This is so we can run the rest of the test
// to ensure a no-op.
const int feed_id = IsReadOnly() ? -1 : GetMediaFeedsSync(service())[0]->id;
service()->StoreMediaFeedFetchResult(
feed_id, GetExpectedItems(), media_feeds::mojom::FetchResult::kSuccess,
base::Time::Now(), GetExpectedLogos(), kExpectedDisplayName);
WaitForDB();
{
// The media items should be stored and the feed should be updated.
auto feeds = GetMediaFeedsSync(service());
auto items = GetItemsForMediaFeedSync(service(), feed_id);
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(feed_id, feeds[0]->id);
EXPECT_TRUE(feeds[0]->last_fetch_time.has_value());
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
EXPECT_EQ(0, feeds[0]->fetch_failed_count);
EXPECT_TRUE(feeds[0]->cache_expiry_time.has_value());
EXPECT_EQ(kExpectedFetchItemCount, feeds[0]->last_fetch_item_count);
EXPECT_EQ(kExpectedFetchPlayNextCount,
feeds[0]->last_fetch_play_next_count);
EXPECT_EQ(kExpectedFetchContentTypes, feeds[0]->last_fetch_content_types);
EXPECT_EQ(GetExpectedLogos(), feeds[0]->logos);
EXPECT_EQ(kExpectedDisplayName, feeds[0]->display_name);
EXPECT_EQ(GetExpectedItems(), items);
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
service()->StoreMediaFeedFetchResult(
feed_id, GetAltExpectedItems(), media_feeds::mojom::FetchResult::kSuccess,
base::Time::Now(), std::vector<media_session::MediaImage>(),
kExpectedDisplayName);
WaitForDB();
{
// The media items should be stored and the feed should be updated.
auto feeds = GetMediaFeedsSync(service());
auto items = GetItemsForMediaFeedSync(service(), feed_id);
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(feed_id, feeds[0]->id);
EXPECT_TRUE(feeds[0]->last_fetch_time.has_value());
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
EXPECT_EQ(0, feeds[0]->fetch_failed_count);
EXPECT_TRUE(feeds[0]->cache_expiry_time.has_value());
EXPECT_EQ(kExpectedAltFetchItemCount, feeds[0]->last_fetch_item_count);
EXPECT_EQ(kExpectedAltFetchPlayNextCount,
feeds[0]->last_fetch_play_next_count);
EXPECT_EQ(kExpectedAltFetchContentTypes,
feeds[0]->last_fetch_content_types);
EXPECT_TRUE(feeds[0]->logos.empty());
EXPECT_EQ(kExpectedDisplayName, feeds[0]->display_name);
EXPECT_EQ(GetAltExpectedItems(), items);
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
}
TEST_P(MediaHistoryStoreFeedsTest, StoreMediaFeedFetchResult_WithEmpty) {
service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
WaitForDB();
// If we are read only we should use -1 as a placeholder feed id because the
// feed will not have been stored. This is so we can run the rest of the test
// to ensure a no-op.
const int feed_id = IsReadOnly() ? -1 : GetMediaFeedsSync(service())[0]->id;
service()->StoreMediaFeedFetchResult(
feed_id, GetExpectedItems(), media_feeds::mojom::FetchResult::kSuccess,
base::Time::Now(), std::vector<media_session::MediaImage>(),
std::string());
WaitForDB();
{
// The media items should be stored.
auto items = GetItemsForMediaFeedSync(service(), feed_id);
if (IsReadOnly()) {
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(GetExpectedItems(), items);
}
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
service()->StoreMediaFeedFetchResult(
feed_id, std::vector<media_feeds::mojom::MediaFeedItemPtr>(),
media_feeds::mojom::FetchResult::kSuccess, base::Time::Now(),
std::vector<media_session::MediaImage>(), std::string());
WaitForDB();
{
// There should be no items stored.
auto items = GetItemsForMediaFeedSync(service(), feed_id);
EXPECT_TRUE(items.empty());
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
}
TEST_P(MediaHistoryStoreFeedsTest, StoreMediaFeedFetchResult_MultipleFeeds) {
service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
service()->DiscoverMediaFeed(GURL("https://www.google.co.uk/feed"));
WaitForDB();
// If we are read only we should use -1 as a placeholder feed id because the
// feed will not have been stored. This is so we can run the rest of the test
// to ensure a no-op.
const int feed_id_a = IsReadOnly() ? -1 : GetMediaFeedsSync(service())[0]->id;
const int feed_id_b = IsReadOnly() ? -1 : GetMediaFeedsSync(service())[1]->id;
service()->StoreMediaFeedFetchResult(
feed_id_a, GetExpectedItems(), media_feeds::mojom::FetchResult::kSuccess,
base::Time::Now(), std::vector<media_session::MediaImage>(),
std::string());
WaitForDB();
service()->StoreMediaFeedFetchResult(
feed_id_b, GetAltExpectedItems(),
media_feeds::mojom::FetchResult::kFailedNetworkError, base::Time::Now(),
std::vector<media_session::MediaImage>(), std::string());
WaitForDB();
{
// Check the feeds were updated.
auto feeds = GetMediaFeedsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
} else {
EXPECT_EQ(2u, feeds.size());
EXPECT_EQ(feed_id_a, feeds[0]->id);
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
EXPECT_EQ(0, feeds[0]->fetch_failed_count);
EXPECT_EQ(feed_id_b, feeds[1]->id);
EXPECT_EQ(media_feeds::mojom::FetchResult::kFailedNetworkError,
feeds[1]->last_fetch_result);
EXPECT_EQ(1, feeds[1]->fetch_failed_count);
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
}
{
// The media items should be stored.
auto items = GetItemsForMediaFeedSync(service(), feed_id_a);
if (IsReadOnly()) {
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(GetExpectedItems(), items);
}
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id_a));
}
{
// The media items should be stored.
auto items = GetItemsForMediaFeedSync(service(), feed_id_b);
if (IsReadOnly()) {
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(GetAltExpectedItems(), items);
}
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id_b));
}
}
TEST_P(MediaHistoryStoreFeedsTest, StoreMediaFeedFetchResult_BadType) {
service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
WaitForDB();
// If we are read only we should use -1 as a placeholder feed id because the
// feed will not have been stored. This is so we can run the rest of the test
// to ensure a no-op.
const int feed_id = IsReadOnly() ? -1 : GetMediaFeedsSync(service())[0]->id;
service()->StoreMediaFeedFetchResult(
feed_id, GetExpectedItems(), media_feeds::mojom::FetchResult::kSuccess,
base::Time::Now(), std::vector<media_session::MediaImage>(),
std::string());
WaitForDB();
{
// The media items should be stored.
auto items = GetItemsForMediaFeedSync(service(), feed_id);
if (IsReadOnly()) {
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(GetExpectedItems(), items);
}
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
sql::Statement s(
GetDB().GetUniqueStatement("UPDATE mediaFeedItem SET type = 99"));
ASSERT_TRUE(s.Run());
{
// The items should be skipped because of the invalid type.
auto items = GetItemsForMediaFeedSync(service(), feed_id);
EXPECT_TRUE(items.empty());
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
}
TEST_P(MediaHistoryStoreFeedsTest, RediscoverMediaFeed) {
GURL feed_url("https://www.google.com/feed");
service()->DiscoverMediaFeed(feed_url);
WaitForDB();
// If we are read only we should use -1 as a placeholder feed id because the
// feed will not have been stored. This is so we can run the rest of the test
// to ensure a no-op.
int feed_id = -1;
base::Time feed_last_time;
if (!IsReadOnly()) {
auto feeds = GetMediaFeedsSync(service());
feed_id = feeds[0]->id;
feed_last_time = feeds[0]->last_discovery_time;
EXPECT_LT(base::Time(), feed_last_time);
EXPECT_GT(base::Time::Now(), feed_last_time);
EXPECT_EQ(feed_url, feeds[0]->url);
}
service()->StoreMediaFeedFetchResult(
feed_id, GetExpectedItems(), media_feeds::mojom::FetchResult::kSuccess,
base::Time::Now(), std::vector<media_session::MediaImage>(),
std::string());
WaitForDB();
{
// The media items should be stored.
auto items = GetItemsForMediaFeedSync(service(), feed_id);
if (IsReadOnly()) {
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(GetExpectedItems(), items);
}
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
// Rediscovering the same feed should not replace the feed.
service()->DiscoverMediaFeed(feed_url);
WaitForDB();
if (!IsReadOnly()) {
auto feeds = GetMediaFeedsSync(service());
EXPECT_LE(feed_last_time, feeds[0]->last_discovery_time);
EXPECT_EQ(feed_id, feeds[0]->id);
EXPECT_EQ(feed_url, feeds[0]->url);
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
}
{
// The media items should be stored.
auto items = GetItemsForMediaFeedSync(service(), feed_id);
if (IsReadOnly()) {
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(GetExpectedItems(), items);
}
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
// Finding a new URL should replace the feed.
GURL new_url("https://www.google.com/feed2");
service()->DiscoverMediaFeed(new_url);
WaitForDB();
if (!IsReadOnly()) {
auto feeds = GetMediaFeedsSync(service());
EXPECT_LE(feed_last_time, feeds[0]->last_discovery_time);
EXPECT_LT(feed_id, feeds[0]->id);
EXPECT_EQ(new_url, feeds[0]->url);
EXPECT_EQ(media_feeds::mojom::FetchResult::kNone,
feeds[0]->last_fetch_result);
}
{
// The media items should be deleted.
auto items = GetItemsForMediaFeedSync(service(), feed_id);
EXPECT_TRUE(items.empty());
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
}
TEST_P(MediaHistoryStoreFeedsTest, StoreMediaFeedFetchResult_IncreaseFailed) {
service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
WaitForDB();
// If we are read only we should use -1 as a placeholder feed id because the
// feed will not have been stored. This is so we can run the rest of the test
// to ensure a no-op.
const int feed_id = IsReadOnly() ? -1 : GetMediaFeedsSync(service())[0]->id;
service()->StoreMediaFeedFetchResult(
feed_id, GetExpectedItems(),
media_feeds::mojom::FetchResult::kFailedNetworkError, base::Time::Now(),
GetExpectedLogos(), kExpectedDisplayName);
WaitForDB();
{
// The fetch failed count should have been increased.
auto feeds = GetMediaFeedsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
} else {
EXPECT_EQ(feed_id, feeds[0]->id);
EXPECT_EQ(media_feeds::mojom::FetchResult::kFailedNetworkError,
feeds[0]->last_fetch_result);
EXPECT_EQ(1, feeds[0]->fetch_failed_count);
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
}
service()->StoreMediaFeedFetchResult(
feed_id, GetExpectedItems(),
media_feeds::mojom::FetchResult::kFailedBackendError, base::Time::Now(),
GetExpectedLogos(), kExpectedDisplayName);
WaitForDB();
{
// The fetch failed count should have been increased.
auto feeds = GetMediaFeedsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
} else {
EXPECT_EQ(feed_id, feeds[0]->id);
EXPECT_EQ(media_feeds::mojom::FetchResult::kFailedBackendError,
feeds[0]->last_fetch_result);
EXPECT_EQ(2, feeds[0]->fetch_failed_count);
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
}
service()->StoreMediaFeedFetchResult(
feed_id, GetExpectedItems(), media_feeds::mojom::FetchResult::kSuccess,
base::Time::Now(), GetExpectedLogos(), kExpectedDisplayName);
WaitForDB();
{
// The fetch failed count should have been reset.
auto feeds = GetMediaFeedsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
} else {
EXPECT_EQ(feed_id, feeds[0]->id);
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
EXPECT_EQ(0, feeds[0]->fetch_failed_count);
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
}
}
TEST_P(MediaHistoryStoreFeedsTest, StoreMediaFeedFetchResult_CheckLogoMax) {
service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
WaitForDB();
// If we are read only we should use -1 as a placeholder feed id because the
// feed will not have been stored. This is so we can run the rest of the test
// to ensure a no-op.
const int feed_id = IsReadOnly() ? -1 : GetMediaFeedsSync(service())[0]->id;
std::vector<media_session::MediaImage> logos;
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image1.png");
logos.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image2.png");
logos.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image3.png");
logos.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image4.png");
logos.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image5.png");
logos.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image6.png");
logos.push_back(image);
}
service()->StoreMediaFeedFetchResult(
feed_id, GetExpectedItems(),
media_feeds::mojom::FetchResult::kFailedNetworkError, base::Time::Now(),
logos, kExpectedDisplayName);
WaitForDB();
{
// The feed should have at most 5 logos.
auto feeds = GetMediaFeedsSync(service());
if (IsReadOnly()) {
EXPECT_TRUE(feeds.empty());
} else {
EXPECT_EQ(feed_id, feeds[0]->id);
EXPECT_EQ(5u, feeds[0]->logos.size());
}
// The OTR service should have the same data.
EXPECT_EQ(feeds, GetMediaFeedsSync(otr_service()));
}
}
TEST_P(MediaHistoryStoreFeedsTest, StoreMediaFeedFetchResult_CheckImageMax) {
service()->DiscoverMediaFeed(GURL("https://www.google.com/feed"));
WaitForDB();
// If we are read only we should use -1 as a placeholder feed id because the
// feed will not have been stored. This is so we can run the rest of the test
// to ensure a no-op.
const int feed_id = IsReadOnly() ? -1 : GetMediaFeedsSync(service())[0]->id;
auto item = media_feeds::mojom::MediaFeedItem::New();
item->name = base::ASCIIToUTF16("The Movie");
item->type = media_feeds::mojom::MediaFeedItemType::kMovie;
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image1.png");
item->images.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image2.png");
item->images.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image3.png");
item->images.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image4.png");
item->images.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image5.png");
item->images.push_back(image);
}
{
media_session::MediaImage image;
image.src = GURL("https://www.example.org/image6.png");
item->images.push_back(image);
}
std::vector<media_feeds::mojom::MediaFeedItemPtr> items;
items.push_back(std::move(item));
service()->StoreMediaFeedFetchResult(
feed_id, std::move(items), media_feeds::mojom::FetchResult::kSuccess,
base::Time::Now(), GetExpectedLogos(), kExpectedDisplayName);
WaitForDB();
{
// The item should have at most 5 images.
auto items = GetItemsForMediaFeedSync(service(), feed_id);
if (IsReadOnly()) {
EXPECT_TRUE(items.empty());
} else {
EXPECT_EQ(5u, items[0]->images.size());
}
// The OTR service should have the same data.
EXPECT_EQ(items, GetItemsForMediaFeedSync(otr_service(), feed_id));
}
}
} // namespace media_history