blob: df6b2e5e000aed8d9f9b7a5f09c9c554ff789e60 [file] [log] [blame]
// Copyright 2021 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/ui/ash/projector/pending_screencast_manager.h"
#include "ash/components/drivefs/mojom/drivefs.mojom.h"
#include "ash/constants/ash_features.h"
#include "ash/webui/projector_app/projector_app_client.h"
#include "ash/webui/projector_app/test/mock_xhr_sender.h"
#include "base/callback_helpers.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drivefs_test_support.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/ui/user_adding_screen.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/projector/projector_app_client_impl.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/abseil-cpp/absl/utility/utility.h"
namespace ash {
namespace {
constexpr char kTestScreencastPath[] = "/root/test_screencast";
constexpr char kTestScreencastName[] = "test_screencast";
constexpr char kTestMediaFile[] = "test_screencast.webm";
constexpr char kTestMetadataFile[] = "test_screencast.projector";
constexpr char kDefaultMetadataFilePath[] =
"/root/test_screencast/test_screencast.projector";
constexpr char kProjectorPendingScreencastBatchIOTaskDurationHistogramName[] =
"Ash.Projector.PendingScreencastBatchIOTaskDuration";
constexpr char kProjectorPendingScreencastChangeIntervalHistogramName[] =
"Ash.Projector.PendingScreencastChangeInterval";
// The test media file is 0.7 mb.
constexpr int64_t kTestMediaFileBytes = 700 * 1024;
// The test metadata file is 0.1 mb.
constexpr int64_t kTestMetadataFileBytes = 100 * 1024;
} // namespace
// TODO(b/211000693) Replace all RunAllTasksUntilIdle with a waiting condition.
class PendingScreencastMangerBrowserTest : public InProcessBrowserTest {
public:
PendingScreencastMangerBrowserTest() {
scoped_feature_list_.InitWithFeatures(
{features::kProjectorUpdateIndexableText}, {});
}
PendingScreencastMangerBrowserTest(
const PendingScreencastMangerBrowserTest&) = delete;
PendingScreencastMangerBrowserTest& operator=(
const PendingScreencastMangerBrowserTest&) = delete;
~PendingScreencastMangerBrowserTest() override = default;
bool SetUpUserDataDirectory() override {
return drive::SetUpUserDataDirectoryForDriveFsTest();
}
void SetUpInProcessBrowserTestFixture() override {
create_drive_integration_service_ = base::BindRepeating(
&PendingScreencastMangerBrowserTest::CreateDriveIntegrationService,
base::Unretained(this));
service_factory_for_test_ = std::make_unique<
drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>(
&create_drive_integration_service_);
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
pending_screencast_manager_ = std::make_unique<
PendingScreencastManager>(base::BindRepeating(
&PendingScreencastMangerBrowserTest::PendingScreencastChangeCallback,
base::Unretained(this)));
}
void TearDownOnMainThread() override {
pending_screencast_manager_.reset();
InProcessBrowserTest::TearDownOnMainThread();
}
protected:
virtual drive::DriveIntegrationService* CreateDriveIntegrationService(
Profile* profile) {
// Ignore non-regular profile.
if (!ProfileHelper::IsRegularProfile(profile))
return nullptr;
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath mount_path = profile->GetPath().Append("drivefs");
fake_drivefs_helper_ =
std::make_unique<drive::FakeDriveFsHelper>(profile, mount_path);
auto* integration_service = new drive::DriveIntegrationService(
profile, std::string(), mount_path,
fake_drivefs_helper_->CreateFakeDriveFsListenerFactory());
return integration_service;
}
drivefs::FakeDriveFs* GetFakeDriveFs() {
return &fake_drivefs_helper_->fake_drivefs();
}
// Creates file under the Drive relative `file_path` whose size is
// `total_bytes`.
void CreateFileInDriveFsFolder(const std::string& file_path,
int64_t total_bytes) {
CreateFileInDriveFsFolder(file_path, std::string(total_bytes, 'a'));
}
// Creates file under the Drive relative `file_path` and write `file_content`
// to the file.
void CreateFileInDriveFsFolder(const std::string& file_path,
const std::string& file_content) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath relative_file_path(file_path);
base::FilePath folder_path =
GetDriveFsAbsolutePath(relative_file_path.DirName().value());
// base::CreateDirectory returns 'true' on successful creation, or if the
// directory already exists.
EXPECT_TRUE(base::CreateDirectory(folder_path));
base::File file(folder_path.Append(relative_file_path.BaseName()),
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
EXPECT_EQ(file_content.size(), file.Write(/*offset=*/0, file_content.data(),
/*size=*/file_content.size()));
EXPECT_TRUE(file.IsValid());
file.Close();
}
// Create a file for given `file_path`, which is a relative file path of
// drivefs. Write `total_bytes` to this file. Create a drivefs syncing event
// for this file with `transferred_bytes` transferred and add this event to
// `syncing_status`.
void CreateFileAndTransferItemEvent(
const std::string& file_path,
int64_t total_bytes,
int64_t transferred_bytes,
drivefs::mojom::SyncingStatus& syncing_status) {
CreateFileInDriveFsFolder(file_path, total_bytes);
AddTransferItemEvent(syncing_status, file_path, total_bytes,
transferred_bytes);
}
base::Time GetFileCreatedTime(const std::string& relative_file_path) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::File::Info info;
return base::GetFileInfo(GetDriveFsAbsolutePath(relative_file_path), &info)
? info.creation_time
: base::Time();
}
base::FilePath GetDriveFsAbsolutePath(const std::string& relative_path) {
base::ScopedAllowBlockingForTesting allow_blocking;
drive::DriveIntegrationService* service =
drive::DriveIntegrationServiceFactory::FindForProfile(
browser()->profile());
EXPECT_TRUE(service->IsMounted());
EXPECT_TRUE(base::PathExists(service->GetMountPointPath()));
base::FilePath root("/");
base::FilePath absolute_path(service->GetMountPointPath());
root.AppendRelativePath(base::FilePath(relative_path), &absolute_path);
return absolute_path;
}
void AddTransferItemEvent(drivefs::mojom::SyncingStatus& syncing_status,
const std::string& path,
int64_t total_bytes,
int64_t transferred_bytes) {
syncing_status.item_events.emplace_back(
absl::in_place, /*stable_id=*/1, /*group_id=*/1, path,
total_bytes == transferred_bytes
? drivefs::mojom::ItemEvent::State::kCompleted
: drivefs::mojom::ItemEvent::State::kInProgress,
/*bytes_transferred=*/transferred_bytes,
/*bytes_to_transfer=*/total_bytes,
drivefs::mojom::ItemEventReason::kTransfer);
}
// Notifies `pending_screencast_manager_` that a file with `file_path` and
// `total_size` with an uploading event and a completed event.
void MockSyncFileCompleted(const std::string& file_path,
const int64_t total_size) {
drivefs::mojom::SyncingStatus syncing_status;
// Notifies with uploading event:
AddTransferItemEvent(syncing_status, file_path,
/*total_bytes=*/0,
/*transferred_bytes=*/total_size);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
syncing_status.item_events.clear();
// Notifies with completed event:
AddTransferItemEvent(syncing_status, file_path,
/*total_bytes=*/total_size,
/*transferred_bytes=*/total_size);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
}
void TestGetFileIdFailed() {
// Sets get file id callback:
base::RunLoop run_loop;
pending_screencast_manager()->SetOnGetFileIdCallbackForTest(
base::BindLambdaForTesting([&](const base::FilePath& local_file_path,
const std::string& file_id) {
EXPECT_EQ(GetDriveFsAbsolutePath(kDefaultMetadataFilePath),
local_file_path);
EXPECT_EQ(std::string(), file_id);
run_loop.Quit();
}));
// Mocks a metadata file finishes upload:
MockSyncFileCompleted(kDefaultMetadataFilePath, kTestMetadataFileBytes);
run_loop.Run();
}
void ExpectEmptyRequestBodyForProjectorFileContent(
const std::string& file_content) {
CreateFileInDriveFsFolder(kDefaultMetadataFilePath, file_content);
GetFakeDriveFs()->SetMetadata(base::FilePath(kDefaultMetadataFilePath),
"text/plain", kTestMetadataFile, false, false,
{}, {}, "abc123",
/*alternate_url=*/
"https://drive.google.com/open?id=fileId");
// Sets get file id callback:
base::RunLoop run_loop;
pending_screencast_manager()->SetOnGetRequestBodyCallbackForTest(
base::BindLambdaForTesting(
[&](const std::string& file_id, const std::string& request_body) {
EXPECT_EQ(std::string(), request_body);
EXPECT_EQ("fileId", file_id);
run_loop.Quit();
}));
// Mocks a metadata file finishes upload:
MockSyncFileCompleted(kDefaultMetadataFilePath, kTestMetadataFileBytes);
run_loop.Run();
}
MOCK_METHOD1(PendingScreencastChangeCallback,
void(const PendingScreencastSet&));
PendingScreencastManager* pending_screencast_manager() {
return pending_screencast_manager_.get();
}
base::HistogramTester histogram_tester_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
drive::DriveIntegrationServiceFactory::FactoryCallback
create_drive_integration_service_;
std::unique_ptr<drive::DriveIntegrationServiceFactory::ScopedFactoryForTest>
service_factory_for_test_;
std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
std::unique_ptr<PendingScreencastManager> pending_screencast_manager_;
};
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest, ValidScreencast) {
const std::string media_file =
base::StrCat({kTestScreencastPath, "/", kTestMediaFile});
drivefs::mojom::SyncingStatus syncing_status;
{
// Create a valid pending screencast.
CreateFileAndTransferItemEvent(media_file,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
CreateFileAndTransferItemEvent(kDefaultMetadataFilePath,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/0, syncing_status);
}
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(1);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
histogram_tester_.ExpectTotalCount(
kProjectorPendingScreencastChangeIntervalHistogramName,
/*count=*/0);
const base::TimeTicks last_pending_screencast_change_tick =
pending_screencast_manager()->last_pending_screencast_change_tick();
EXPECT_NE(base::TimeTicks(), last_pending_screencast_change_tick);
const PendingScreencastSet pending_screencasts =
pending_screencast_manager()->GetPendingScreencasts();
EXPECT_EQ(pending_screencasts.size(), 1u);
ash::PendingScreencast ps = *(pending_screencasts.begin());
EXPECT_EQ(ps.container_dir, base::FilePath(kTestScreencastPath));
EXPECT_EQ(ps.name, kTestScreencastName);
EXPECT_EQ(ps.created_time, GetFileCreatedTime(media_file));
// Tests PendingScreencastChangeCallback won't be invoked if pending
// screencast status doesn't change.
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(0);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
// Expects no report since PendingScreencastChangeCallback wasn't invoked.
histogram_tester_.ExpectTotalCount(
kProjectorPendingScreencastChangeIntervalHistogramName,
/*count=*/0);
EXPECT_EQ(
last_pending_screencast_change_tick,
pending_screencast_manager()->last_pending_screencast_change_tick());
// Tests PendingScreencastChangeCallback will be invoked if pending
// screencast status changes.
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(1);
syncing_status.item_events.clear();
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
// Since pending screencast set is empty, the last pending screencast change
// tick is reset to null:
EXPECT_EQ(
base::TimeTicks(),
pending_screencast_manager()->last_pending_screencast_change_tick());
const base::TimeDelta elapsed_time =
base::TimeTicks::Now() - last_pending_screencast_change_tick;
auto change_interval_samples = histogram_tester_.GetAllSamples(
kProjectorPendingScreencastChangeIntervalHistogramName);
// Expects only 1 sample.
EXPECT_EQ(1u, change_interval_samples.size());
// Expects the sample only have 1 count.
EXPECT_EQ(1, change_interval_samples.front().count);
// Since the end of `elapsed_time` is gotten from "base::TimeTicks::Now()"
// after PendingScreencastChangeCallback gets invoked. Expects `elapsed_time`
// is greater than the sample.
EXPECT_GT(elapsed_time.InMicroseconds(), change_interval_samples.front().min);
histogram_tester_.ExpectTotalCount(
kProjectorPendingScreencastBatchIOTaskDurationHistogramName,
/*count=*/3);
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest, InvalidScreencasts) {
const std::string media_only_path = "/root/media_only/example.webm";
const std::string metadata_only_path =
"/root/metadata_only/example.projector";
const std::string avi = "/root/non_screencast_files/example.avi";
const std::string mov = "/root/non_screencast_files/example.mov";
const std::string mp4 = "/root/non_screencast_files/example.mp4";
drivefs::mojom::SyncingStatus syncing_status;
{
// Create an invalid screencast that only has webm medida file.
CreateFileAndTransferItemEvent(media_only_path,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
// Create an invalid screencast that only has metadata file.
CreateFileAndTransferItemEvent(metadata_only_path,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/0, syncing_status);
// Create an invalid screencast that does not have webm media and metadata
// files but have other media files.
CreateFileAndTransferItemEvent(avi, /*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
CreateFileAndTransferItemEvent(mov, /*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
CreateFileAndTransferItemEvent(mp4, /*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
}
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(0);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
EXPECT_TRUE(pending_screencast_manager()->GetPendingScreencasts().empty());
histogram_tester_.ExpectTotalCount(
kProjectorPendingScreencastBatchIOTaskDurationHistogramName,
/*count=*/1);
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
IgnoreCompletedEvent) {
const std::string media_file =
base::StrCat({kTestScreencastPath, "/", kTestMediaFile});
drivefs::mojom::SyncingStatus syncing_status;
{
// Create a valid uploaded screencast.
CreateFileAndTransferItemEvent(media_file,
/*total_bytes=*/kTestMediaFileBytes,
kTestMediaFileBytes, syncing_status);
CreateFileAndTransferItemEvent(kDefaultMetadataFilePath,
/*total_bytes=*/kTestMetadataFileBytes,
kTestMetadataFileBytes, syncing_status);
}
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(0);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
EXPECT_TRUE(pending_screencast_manager()->GetPendingScreencasts().empty());
// There is no IO task for complete events.
histogram_tester_.ExpectTotalCount(
kProjectorPendingScreencastBatchIOTaskDurationHistogramName,
/*count=*/0);
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
MultipleValidAndInvalidScreencasts) {
drivefs::mojom::SyncingStatus syncing_status;
size_t num_of_screencasts = 10;
{
// Create multiple valid pending screencasts.
for (size_t i = 0; i < num_of_screencasts; ++i) {
const std::string test_screencast_path =
base::StrCat({kTestScreencastPath, base::NumberToString(i)});
const std::string media =
base::StrCat({test_screencast_path, "/", kTestMediaFile});
const std::string metadata =
base::StrCat({test_screencast_path, "/", kTestMetadataFile});
CreateFileAndTransferItemEvent(media, /*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
CreateFileAndTransferItemEvent(metadata,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/0, syncing_status);
}
// Tests with a invalid screencast does not have metadata file.
const std::string no_metadata_screencast = "/root/no_metadata/example.webm";
CreateFileAndTransferItemEvent(no_metadata_screencast,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
// Tests with a invalid screencast does not have media file.
const std::string no_media_screencast = "/root/no_media/example.projector";
CreateFileAndTransferItemEvent(no_media_screencast,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
// Tests with a non-screencast file.
const std::string non_screencast = "/root/non_screencast/example.txt";
CreateFileAndTransferItemEvent(non_screencast, /*total_bytes=*/100,
/*transferred_bytes=*/0, syncing_status);
}
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(1);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
const PendingScreencastSet pending_screencasts =
pending_screencast_manager()->GetPendingScreencasts();
int64_t total_size = kTestMediaFileBytes + kTestMetadataFileBytes;
// Only valid screencasts could be processed.
EXPECT_EQ(pending_screencasts.size(), num_of_screencasts);
for (size_t i = 0; i < num_of_screencasts; ++i) {
const std::string container_dir =
base::StrCat({kTestScreencastPath, base::NumberToString(i)});
const std::string name =
base::StrCat({kTestScreencastName, base::NumberToString(i)});
ash::PendingScreencast ps{base::FilePath(container_dir), name, total_size,
0};
EXPECT_TRUE(pending_screencasts.find(ps) != pending_screencasts.end());
}
histogram_tester_.ExpectTotalCount(
kProjectorPendingScreencastBatchIOTaskDurationHistogramName,
/*count=*/1);
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest, UploadProgress) {
const std::string media_file_path =
base::StrCat({kTestScreencastPath, "/", kTestMediaFile});
drivefs::mojom::SyncingStatus syncing_status;
{
// Create a valid pending screencast.
CreateFileAndTransferItemEvent(media_file_path,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
CreateFileAndTransferItemEvent(kDefaultMetadataFilePath,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/0, syncing_status);
}
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(1);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
const PendingScreencastSet pending_screencasts_1 =
pending_screencast_manager()->GetPendingScreencasts();
EXPECT_EQ(pending_screencasts_1.size(), 1u);
ash::PendingScreencast ps = *(pending_screencasts_1.begin());
const int total_size = kTestMediaFileBytes + kTestMetadataFileBytes;
EXPECT_EQ(total_size, ps.total_size_in_bytes);
EXPECT_EQ(0, ps.bytes_transferred);
// Tests the metadata file finished transferred.
// PendingScreencastChangeCallback won't be invoked if the difference is less
// than kPendingScreencastDiffThresholdInBytes.
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(0);
syncing_status.item_events.clear();
int64_t media_transferred_1_bytes = 1;
int64_t metadata_transferred_bytes = kTestMetadataFileBytes;
AddTransferItemEvent(syncing_status, media_file_path,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/media_transferred_1_bytes);
// Create a completed transferred event for metadata.
AddTransferItemEvent(syncing_status, kDefaultMetadataFilePath,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/metadata_transferred_bytes);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
const PendingScreencastSet pending_screencasts_2 =
pending_screencast_manager()->GetPendingScreencasts();
ps = *(pending_screencasts_2.begin());
// The screencast status unchanged.
EXPECT_EQ(total_size, ps.total_size_in_bytes);
EXPECT_EQ(0, ps.bytes_transferred);
// Tests PendingScreencastChangeCallback will be invoked if the difference of
// transferred bytes is greater than kPendingScreencastDiffThresholdInBytes.
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(1);
syncing_status.item_events.clear();
AddTransferItemEvent(syncing_status, media_file_path,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/kTestMediaFileBytes - 1);
// Create a completed transferred event for metadata.
AddTransferItemEvent(syncing_status, kDefaultMetadataFilePath,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/metadata_transferred_bytes);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
const PendingScreencastSet pending_screencasts_3 =
pending_screencast_manager()->GetPendingScreencasts();
ps = *(pending_screencasts_3.begin());
// The screencast status changed.
EXPECT_EQ(total_size, ps.total_size_in_bytes);
// TODO(b/209854146) After fix b/209854146, the `ps.bytes_transferred` is
// `total_size -1`.
EXPECT_EQ(kTestMediaFileBytes - 1, ps.bytes_transferred);
// Tests PendingScreencastChangeCallback will be invoked when all files
// finished transferred.
EXPECT_CALL(*this, PendingScreencastChangeCallback(testing::_)).Times(1);
syncing_status.item_events.clear();
// Create completed transferred events for both files.
AddTransferItemEvent(syncing_status, media_file_path,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/kTestMediaFileBytes);
AddTransferItemEvent(syncing_status, kDefaultMetadataFilePath,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/kTestMetadataFileBytes);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
EXPECT_TRUE(pending_screencast_manager()->GetPendingScreencasts().empty());
histogram_tester_.ExpectTotalCount(
kProjectorPendingScreencastBatchIOTaskDurationHistogramName,
/*count=*/4);
}
// Test the comparison of pending screencast in a std::set.
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
PendingScreencastSet) {
// The `name` and `total_size_in_bytes` of screencast will not be compare in a
// set.
const base::FilePath container_dir_a = base::FilePath("/root/a");
const std::string screencast_a_name = "a";
const int64_t screencast_a_total_bytes = 2 * 1024 * 1024;
ash::PendingScreencast screencast_a_1_byte_transferred{
container_dir_a, screencast_a_name, screencast_a_total_bytes,
/*bytes_transferred=*/1};
ash::PendingScreencast screencast_a_1kb_transferred{
container_dir_a, screencast_a_name, screencast_a_total_bytes,
/*bytes_transferred=*/1024};
ash::PendingScreencast screencast_a_700kb_transferred{
container_dir_a, screencast_a_name, screencast_a_total_bytes,
/*bytes_transferred=*/700 * 1024};
const base::FilePath container_dir_b = base::FilePath("/root/b");
const std::string screencast_b_name = "b";
const int64_t screencast_b_total_bytes = 2 * 1024 * 1024;
ash::PendingScreencast screencast_b_1_byte_transferred{
container_dir_b, screencast_b_name, screencast_b_total_bytes,
/*bytes_transferred=*/1};
ash::PendingScreencast screencast_b_1kb_transferred{
container_dir_b, screencast_b_name, screencast_b_total_bytes,
/*bytes_transferred=*/1024};
ash::PendingScreencast screencast_b_700kb_transferred{
container_dir_b, screencast_b_name, screencast_b_total_bytes,
/*bytes_transferred=*/700 * 1024};
PendingScreencastSet set1{screencast_a_1_byte_transferred,
screencast_b_1_byte_transferred};
PendingScreencastSet set2{screencast_a_1_byte_transferred,
screencast_b_1_byte_transferred};
PendingScreencastSet set3{screencast_a_1kb_transferred,
screencast_b_1_byte_transferred};
PendingScreencastSet set4{screencast_a_700kb_transferred,
screencast_b_1_byte_transferred};
PendingScreencastSet set5{screencast_a_1_byte_transferred,
screencast_a_700kb_transferred};
PendingScreencastSet set6{screencast_a_700kb_transferred,
screencast_a_1_byte_transferred};
PendingScreencastSet set7{screencast_a_1_byte_transferred,
screencast_a_1kb_transferred};
EXPECT_EQ(set1, set2);
EXPECT_EQ(set1, set3);
EXPECT_NE(set1, set4);
EXPECT_NE(set1, set5);
EXPECT_EQ(set5, set6);
EXPECT_EQ(2u, set5.size());
EXPECT_EQ(2u, set7.size());
}
// Test a screencast failed to upload will remain a "fail to upload" error state
// until it get successfully uploaded.
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
DriveOutOfSpaceError) {
const std::string media_file_path =
base::StrCat({kTestScreencastPath, "/", kTestMediaFile});
drivefs::mojom::SyncingStatus syncing_status;
// Create a valid pending screencast.
CreateFileAndTransferItemEvent(media_file_path,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/0, syncing_status);
CreateFileAndTransferItemEvent(kDefaultMetadataFilePath,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/0, syncing_status);
content::RunAllTasksUntilIdle();
// Mock DriveFs sends an out of space error for media file.
drivefs::mojom::DriveError error{
drivefs::mojom::DriveError::Type::kCantUploadStorageFull,
base::FilePath(media_file_path)};
pending_screencast_manager()->OnError(error);
// Even there's DriveError, DriveFs will keep trying to sync both metadata and
// media file.
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
// Verify we have a fail status screencast.
const PendingScreencastSet pending_screencasts =
pending_screencast_manager()->GetPendingScreencasts();
EXPECT_EQ(1, pending_screencasts.size());
ash::PendingScreencast ps = *(pending_screencasts.begin());
EXPECT_TRUE(ps.upload_failed);
// Mock both metadata and media file get uploaded.
syncing_status.item_events.clear();
// Create completed transferred events for both files.
AddTransferItemEvent(syncing_status, media_file_path,
/*total_bytes=*/kTestMediaFileBytes,
/*transferred_bytes=*/kTestMediaFileBytes);
AddTransferItemEvent(syncing_status, kDefaultMetadataFilePath,
/*total_bytes=*/kTestMetadataFileBytes,
/*transferred_bytes=*/kTestMetadataFileBytes);
pending_screencast_manager()->OnSyncingStatusUpdate(syncing_status);
content::RunAllTasksUntilIdle();
// Expect the screencast get removed from pending screencasts set .
EXPECT_TRUE(pending_screencast_manager()->GetPendingScreencasts().empty());
histogram_tester_.ExpectTotalCount(
kProjectorPendingScreencastBatchIOTaskDurationHistogramName,
/*count=*/2);
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
UpdateIndexableTextSuccess) {
// Prepares a ".projector" file and it's metadata:
const std::string kProjectorFileContent =
"{\"captionLanguage\":\"en\",\"captions\":[{\"endOffset\":1260,"
"\"hypothesisParts\":[],\"startOffset\":760,\"text\":\"metadata "
"file.\"},{\"endOffset\":2300,"
"\"hypothesisParts\":[],\"startOffset\":2000,\"text\":\"another sentence."
"\"}],\"tableOfContent\":[]}";
CreateFileInDriveFsFolder(kDefaultMetadataFilePath, kProjectorFileContent);
GetFakeDriveFs()->SetMetadata(
base::FilePath(kDefaultMetadataFilePath), "text/plain", kTestMetadataFile,
false, false, {}, {}, "abc123",
/*alternate_url=*/"https://drive.google.com/open?id=fileId");
// Sets get file id callback:
base::RunLoop run_loop;
network::TestURLLoaderFactory test_url_loader_factory;
pending_screencast_manager()->SetProjectorXhrSenderForTest(
std::make_unique<MockXhrSender>(
base::BindLambdaForTesting([&](const GURL& url,
const std::string& method,
const std::string& request_body) {
EXPECT_EQ(
"{\"contentHints\":{\"indexableText\":\" metadata file. "
"another sentence.\"}}",
request_body);
EXPECT_EQ("PATCH", method);
EXPECT_EQ(GURL("https://www.googleapis.com/drive/v3/files/fileId"),
url);
run_loop.Quit();
}),
&test_url_loader_factory));
// Mocks a metadata file finishes upload:
MockSyncFileCompleted(kDefaultMetadataFilePath, kTestMetadataFileBytes);
run_loop.Run();
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
UpdateIndexableTextFailByEmptyFileId) {
// Does not create ".projector", which leads to drive::FILE_ERROR_NOT_FOUND.
TestGetFileIdFailed();
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
UpdateIndexableTextFailByEmptyAlternateUrl) {
CreateFileInDriveFsFolder(kDefaultMetadataFilePath, kTestMetadataFileBytes);
// Sets empty alternate url in metadata, which could happen when metadata is
// not fully populated.
GetFakeDriveFs()->SetMetadata(base::FilePath(kDefaultMetadataFilePath),
"text/plain", kTestMetadataFile, false, false,
{}, {}, "abc123",
/*alternate_url=*/std::string());
TestGetFileIdFailed();
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
UpdateIndexableTextFailByInCorrectAlternateUrl) {
CreateFileInDriveFsFolder(kDefaultMetadataFilePath, kTestMetadataFileBytes);
// Sets incorrect alternate url in metadata.
GetFakeDriveFs()->SetMetadata(base::FilePath(kDefaultMetadataFilePath),
"text/plain", kTestMetadataFile, false, false,
{}, {}, "abc123",
/*alternate_url=*/"alternate_url");
TestGetFileIdFailed();
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
MalformedProjectorFileNoCaption) {
// Prepares a ".projector" file with no captions.
const std::string kProjectorFileContentNoCaption =
"{\"captionLanguage\":\"en\",\"tableOfContent\":[]}";
ExpectEmptyRequestBodyForProjectorFileContent(kProjectorFileContentNoCaption);
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
MalformedProjectorFileNotJson) {
// Prepares a ".projector" file with no captions.
const std::string kProjectorFileContentNotJson =
"{\"captionLanguage\":\"en\",\"captions\":[{\"endOffset\":1260,"
"\"hypothesisParts\":[],\"startOffset\":760,\"text\":\"metadata "
"file.\"}],\"tableOfContent\":[]";
ExpectEmptyRequestBodyForProjectorFileContent(kProjectorFileContentNotJson);
}
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerBrowserTest,
ProjectorFileEmptyCaption) {
// Prepares a ".projector" file and it's metadata:
const std::string kProjectorFileContentEmptyCaption =
"{\"captionLanguage\":\"en\",\"captions\":[],\"tableOfContent\":[]}";
ExpectEmptyRequestBodyForProjectorFileContent(
kProjectorFileContentEmptyCaption);
}
class PendingScreencastMangerMultiProfileTest : public LoginManagerTest {
public:
PendingScreencastMangerMultiProfileTest() : LoginManagerTest() {
login_mixin_.AppendRegularUsers(2);
account_id1_ = login_mixin_.users()[0].account_id;
account_id2_ = login_mixin_.users()[1].account_id;
}
void SetUpOnMainThread() override {
LoginManagerTest::SetUpOnMainThread();
pending_screencast_manager_ =
std::make_unique<PendingScreencastManager>(base::BindLambdaForTesting(
[&](const PendingScreencastSet& set) { base::DoNothing(); }));
}
void TearDownOnMainThread() override {
pending_screencast_manager_.reset();
LoginManagerTest::TearDownOnMainThread();
}
protected:
AccountId account_id1_;
AccountId account_id2_;
ash::LoginManagerMixin login_mixin_{&mixin_host_};
std::unique_ptr<PendingScreencastManager> pending_screencast_manager_;
};
IN_PROC_BROWSER_TEST_F(PendingScreencastMangerMultiProfileTest,
SwitchActiveUser) {
LoginUser(account_id1_);
// Verify DriveFsHost observation is observing user 1's DriveFsHost.
Profile* profile1 = ProfileHelper::Get()->GetProfileByAccountId(account_id1_);
drive::DriveIntegrationService* service_for_account1 =
drive::DriveIntegrationServiceFactory::FindForProfile(profile1);
EXPECT_TRUE(pending_screencast_manager_->IsDriveFsObservationObservingSource(
service_for_account1->GetDriveFsHost()));
// Add user 2.
ash::UserAddingScreen::Get()->Start();
AddUser(account_id2_);
// Verify DriveFsHost observation is observing user 2's DriveFsHost.
Profile* profile2 = ProfileHelper::Get()->GetProfileByAccountId(account_id2_);
drive::DriveIntegrationService* service_for_account2 =
drive::DriveIntegrationServiceFactory::FindForProfile(profile2);
EXPECT_TRUE(pending_screencast_manager_->IsDriveFsObservationObservingSource(
service_for_account2->GetDriveFsHost()));
// Switch back to user1.
user_manager::UserManager::Get()->SwitchActiveUser(account_id1_);
// Verify DriveFsHost observation is observing user 1's DriveFsHost.
EXPECT_TRUE(pending_screencast_manager_->IsDriveFsObservationObservingSource(
service_for_account1->GetDriveFsHost()));
}
} // namespace ash