blob: 6c29e4baa153c786c39088f761deb43ad00cb1cd [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/download/model/download_file_service.h"
#import <memory>
#import <string>
#import "base/files/file_util.h"
#import "base/files/scoped_temp_dir.h"
#import "base/functional/bind.h"
#import "base/run_loop.h"
#import "base/test/bind.h"
#import "base/test/scoped_feature_list.h"
#import "ios/chrome/browser/download/model/download_directory_util.h"
#import "ios/chrome/browser/download/model/download_record.h"
#import "ios/chrome/browser/download/model/download_record_service.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
using testing::_;
using testing::StrictMock;
namespace {
const std::string kTestDownloadId = "test_download_id";
const char kTestFileName[] = "test_file.pdf";
const char kTestFileContent[] = "test file content";
const char kConflictFileName[] = "conflict_file.txt";
// Mock implementation of DownloadRecordService for testing.
class MockDownloadRecordService : public DownloadRecordService {
public:
MockDownloadRecordService() = default;
~MockDownloadRecordService() override = default;
// Method that will be actually tested.
MOCK_METHOD(void,
UpdateDownloadFilePathAsync,
(const std::string& download_id,
const base::FilePath& file_path,
CompletionCallback callback),
(override));
// Mock all other methods but don't set expectations for them.
MOCK_METHOD(void, RecordDownload, (web::DownloadTask * task), (override));
MOCK_METHOD(void,
GetAllDownloadsAsync,
(DownloadRecordsCallback callback),
(override));
MOCK_METHOD(void,
GetDownloadByIdAsync,
(const std::string& download_id, DownloadRecordCallback callback),
(override));
MOCK_METHOD(void,
RemoveDownloadByIdAsync,
(const std::string& download_id, CompletionCallback callback),
(override));
MOCK_METHOD(web::DownloadTask*,
GetDownloadTaskById,
(std::string_view download_id),
(const, override));
MOCK_METHOD(void,
AddObserver,
(DownloadRecordObserver * observer),
(override));
MOCK_METHOD(void,
RemoveObserver,
(DownloadRecordObserver * observer),
(override));
};
} // namespace
class DownloadFileServiceTest : public PlatformTest {
protected:
void SetUp() override {
PlatformTest::SetUp();
feature_list_.InitAndEnableFeature(kDownloadList);
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
// Create strict mock download record service (fails on unexpected calls).
mock_download_record_service_ =
std::make_unique<StrictMock<MockDownloadRecordService>>();
// Create service with mock dependency.
service_ = std::make_unique<DownloadFileService>(
mock_download_record_service_.get());
// Create test directories.
source_dir_ = temp_dir_.GetPath().AppendASCII("source");
dest_dir_ = temp_dir_.GetPath().AppendASCII("destination");
ASSERT_TRUE(base::CreateDirectory(source_dir_));
ASSERT_TRUE(base::CreateDirectory(dest_dir_));
// Set up test downloads directory override.
test::SetDownloadsDirectoryForTesting(&dest_dir_);
}
void TearDown() override {
// Reset downloads directory override.
test::SetDownloadsDirectoryForTesting(nullptr);
service_.reset();
mock_download_record_service_.reset();
PlatformTest::TearDown();
}
// Helper method to create a test file with given content.
base::FilePath CreateTestFile(const base::FilePath& dir,
const std::string& filename,
const std::string& content) {
base::FilePath file_path = dir.AppendASCII(filename);
EXPECT_TRUE(base::WriteFile(file_path, content));
EXPECT_TRUE(base::PathExists(file_path));
return file_path;
}
web::WebTaskEnvironment task_environment_;
base::test::ScopedFeatureList feature_list_;
base::ScopedTempDir temp_dir_;
base::FilePath source_dir_;
base::FilePath dest_dir_;
std::unique_ptr<StrictMock<MockDownloadRecordService>>
mock_download_record_service_;
std::unique_ptr<DownloadFileService> service_;
};
// Tests moving a download file successfully.
TEST_F(DownloadFileServiceTest, MoveDownloadFileSuccess) {
// Create source file.
base::FilePath source_path =
CreateTestFile(source_dir_, kTestFileName, kTestFileContent);
base::FilePath dest_path = dest_dir_.AppendASCII(kTestFileName);
// Expect UpdateDownloadFilePathAsync to be called.
base::FilePath expected_relative_path =
ConvertToRelativeDownloadPath(dest_path);
EXPECT_CALL(
*mock_download_record_service_,
UpdateDownloadFilePathAsync(kTestDownloadId, expected_relative_path, _))
.Times(1);
base::RunLoop run_loop;
bool callback_success = false;
std::string callback_download_id;
base::FilePath callback_source_path;
base::FilePath callback_final_path;
service_->MoveDownloadFile(
kTestDownloadId, source_path, dest_path,
base::BindLambdaForTesting([&](bool success,
const std::string& download_id,
const base::FilePath& actual_source_path,
const base::FilePath& actual_final_path) {
callback_success = success;
callback_download_id = download_id;
callback_source_path = actual_source_path;
callback_final_path = actual_final_path;
run_loop.Quit();
}));
run_loop.Run();
// Verify callback results.
EXPECT_TRUE(callback_success);
EXPECT_EQ(kTestDownloadId, callback_download_id);
EXPECT_EQ(source_path, callback_source_path);
EXPECT_EQ(dest_path, callback_final_path);
// Verify file operations.
EXPECT_FALSE(base::PathExists(source_path));
EXPECT_TRUE(base::PathExists(dest_path));
// Verify file content.
std::string content;
EXPECT_TRUE(base::ReadFileToString(dest_path, &content));
EXPECT_EQ(kTestFileContent, content);
}
// Tests moving a non-existent source file.
TEST_F(DownloadFileServiceTest, MoveDownloadFileNonExistentSource) {
base::FilePath source_path = source_dir_.AppendASCII("non_existent.txt");
base::FilePath dest_path = dest_dir_.AppendASCII(kTestFileName);
// Should not call UpdateDownloadFilePathAsync on failure.
EXPECT_CALL(*mock_download_record_service_,
UpdateDownloadFilePathAsync(_, _, _))
.Times(0);
base::RunLoop run_loop;
bool callback_success =
true; // Initialize to true to verify it becomes false
service_->MoveDownloadFile(
kTestDownloadId, source_path, dest_path,
base::BindLambdaForTesting([&](bool success,
const std::string& download_id,
const base::FilePath& actual_source_path,
const base::FilePath& actual_final_path) {
callback_success = success;
run_loop.Quit();
}));
run_loop.Run();
EXPECT_FALSE(callback_success);
EXPECT_FALSE(base::PathExists(dest_path));
}
// Tests moving a download file with empty download id.
TEST_F(DownloadFileServiceTest, MoveDownloadFileEmptyParameters) {
base::RunLoop run_loop;
bool callback_success = true;
service_->MoveDownloadFile(
"", source_dir_.AppendASCII("test.txt"),
dest_dir_.AppendASCII("test.txt"),
base::BindLambdaForTesting([&](bool success,
const std::string& download_id,
const base::FilePath& actual_source_path,
const base::FilePath& actual_final_path) {
callback_success = success;
run_loop.Quit();
}));
run_loop.Run();
EXPECT_FALSE(callback_success);
}
// Tests moving a download file without DownloadRecordService.
TEST_F(DownloadFileServiceTest, MoveDownloadFileWithoutRecordService) {
// Create service without DownloadRecordService.
auto service_without_record = std::make_unique<DownloadFileService>(nullptr);
base::FilePath source_path =
CreateTestFile(source_dir_, kTestFileName, kTestFileContent);
base::FilePath dest_path = dest_dir_.AppendASCII(kTestFileName);
base::RunLoop run_loop;
bool callback_success = false;
service_without_record->MoveDownloadFile(
kTestDownloadId, source_path, dest_path,
base::BindLambdaForTesting([&](bool success,
const std::string& download_id,
const base::FilePath& actual_source_path,
const base::FilePath& actual_final_path) {
callback_success = success;
run_loop.Quit();
}));
run_loop.Run();
// Should still succeed even without record service.
EXPECT_TRUE(callback_success);
EXPECT_TRUE(base::PathExists(dest_path));
}
// Tests resolving available file path with no conflict.
TEST_F(DownloadFileServiceTest, ResolveAvailableFilePathNoConflict) {
base::FilePath suggested_filename =
base::FilePath::FromUTF8Unsafe(kTestFileName);
base::RunLoop run_loop;
base::FilePath resolved_path;
service_->ResolveAvailableFilePath(
dest_dir_, suggested_filename,
base::BindLambdaForTesting([&](base::FilePath path) {
resolved_path = path;
run_loop.Quit();
}));
run_loop.Run();
base::FilePath expected_path = dest_dir_.AppendASCII(kTestFileName);
EXPECT_EQ(expected_path, resolved_path);
}
// Tests resolving available file path with conflicts.
TEST_F(DownloadFileServiceTest, ResolveAvailableFilePathWithConflicts) {
// Create conflicting files.
CreateTestFile(dest_dir_, kConflictFileName, kTestFileContent);
CreateTestFile(dest_dir_, "conflict_file (1).txt", kTestFileContent);
base::FilePath suggested_filename =
base::FilePath::FromUTF8Unsafe(kConflictFileName);
base::RunLoop run_loop;
base::FilePath resolved_path;
service_->ResolveAvailableFilePath(
dest_dir_, suggested_filename,
base::BindLambdaForTesting([&](base::FilePath path) {
resolved_path = path;
run_loop.Quit();
}));
run_loop.Run();
base::FilePath expected_path = dest_dir_.AppendASCII("conflict_file (2).txt");
EXPECT_EQ(expected_path, resolved_path);
}
// Check if file exists.
TEST_F(DownloadFileServiceTest, CheckFileExists) {
base::FilePath test_file =
CreateTestFile(source_dir_, kTestFileName, kTestFileContent);
base::FilePath non_existent_file =
source_dir_.AppendASCII("non_existent.txt");
// Test existing file.
{
base::RunLoop run_loop;
bool file_exists = false;
service_->CheckFileExists(test_file,
base::BindLambdaForTesting([&](bool exists) {
file_exists = exists;
run_loop.Quit();
}));
run_loop.Run();
EXPECT_TRUE(file_exists);
}
// Test non-existent file.
{
base::RunLoop run_loop;
bool file_exists = true;
service_->CheckFileExists(non_existent_file,
base::BindLambdaForTesting([&](bool exists) {
file_exists = exists;
run_loop.Quit();
}));
run_loop.Run();
EXPECT_FALSE(file_exists);
}
}
// Tests moving a download file to a non-existent directory.
// The directory should be created and the file moved successfully.
TEST_F(DownloadFileServiceTest, MoveDownloadFileCreateDestinationDirectory) {
base::FilePath source_path =
CreateTestFile(source_dir_, kTestFileName, kTestFileContent);
base::FilePath new_dest_dir = dest_dir_.AppendASCII("new_subdirectory");
base::FilePath dest_path = new_dest_dir.AppendASCII(kTestFileName);
// Verify destination directory doesn't exist initially.
EXPECT_FALSE(base::PathExists(new_dest_dir));
base::FilePath expected_relative_path =
ConvertToRelativeDownloadPath(dest_path);
EXPECT_CALL(
*mock_download_record_service_,
UpdateDownloadFilePathAsync(kTestDownloadId, expected_relative_path, _))
.Times(1);
base::RunLoop run_loop;
bool callback_success = false;
service_->MoveDownloadFile(
kTestDownloadId, source_path, dest_path,
base::BindLambdaForTesting([&](bool success,
const std::string& download_id,
const base::FilePath& actual_source_path,
const base::FilePath& actual_final_path) {
callback_success = success;
run_loop.Quit();
}));
run_loop.Run();
// Should succeed and create destination directory.
EXPECT_TRUE(callback_success);
EXPECT_TRUE(base::PathExists(new_dest_dir));
EXPECT_TRUE(base::PathExists(dest_path));
}