// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/data_migration/file_transfer.h"
#include <cstdint>
#include <optional>
#include <utility>
#include "base/base_paths.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_path_override.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "chromeos/ash/components/data_migration/constants.h"
#include "chromeos/ash/components/data_migration/pending_file_transfer_queue.h"
#include "chromeos/ash/components/data_migration/testing/connection_barrier.h"
#include "chromeos/ash/components/data_migration/testing/fake_nearby_connections.h"
#include "chromeos/ash/components/data_migration/testing/fake_nearby_process_manager.h"
#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
#include "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl.h"
#include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace data_migration {
namespace {
constexpr char kRemoteEndpointId[] = "test-remote-endpoint";
class FileTransferTest : public ::testing::Test {
protected:
FileTransferTest()
: nearby_process_manager_(kRemoteEndpointId),
nearby_connections_manager_(&nearby_process_manager_, kServiceId) {}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
home_dir_override_.emplace(base::DIR_HOME, temp_dir_.GetPath());
ConnectionBarrier connection_barrier(&nearby_connections_manager_);
nearby_connection_ = connection_barrier.Wait();
ASSERT_TRUE(nearby_connection_);
ASSERT_TRUE(base::CreateDirectory(GetFilePayloadDirectory()));
}
base::FilePath GetFilePayloadDirectory() {
return temp_dir_.GetPath().Append(kPayloadTargetDir);
}
base::FilePath BuildFilePayloadName(int64_t payload_id) {
return base::FilePath(base::StringPrintf("payload_%ld", payload_id));
}
void InitializeFileTransfer() {
completion_future_.Clear();
file_transfer_.emplace(
nearby_connection_.get(), &nearby_connections_manager_,
pending_file_transfer_queue_, completion_future_.GetCallback());
}
void VerifyCTSMessage(::nearby::connections::mojom::PayloadPtr cts_payload,
int64_t expected_file_payload_id) {
ASSERT_TRUE(cts_payload);
ASSERT_TRUE(cts_payload->content->is_bytes());
const std::vector<uint8_t>& cts_bytes =
cts_payload->content->get_bytes()->bytes;
ASSERT_EQ(std::string(cts_bytes.begin(), cts_bytes.end()),
base::NumberToString(expected_file_payload_id));
}
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
std::optional<base::ScopedPathOverride> home_dir_override_;
FakeNearbyProcessManager nearby_process_manager_;
NearbyConnectionsManagerImpl nearby_connections_manager_;
raw_ptr<NearbyConnection> nearby_connection_;
PendingFileTransferQueue pending_file_transfer_queue_;
std::optional<FileTransfer> file_transfer_;
base::test::TestFuture<bool> completion_future_;
};
TEST_F(FileTransferTest, SuccessfulTransfers) {
auto run_transfer_file_test = [this](int64_t file_payload_id) {
InitializeFileTransfer();
pending_file_transfer_queue_.Push(file_payload_id);
std::vector<uint8_t> expected_file_bytes;
nearby_process_manager_.fake_nearby_connections()
.set_local_to_remote_payload_listener(base::BindLambdaForTesting(
[this, &expected_file_bytes, file_payload_id](
::nearby::connections::mojom::PayloadPtr cts_payload) {
// Once CTS is received on the remote device, start transferring
// the file.
VerifyCTSMessage(std::move(cts_payload), file_payload_id);
ASSERT_TRUE(
nearby_process_manager_.fake_nearby_connections().SendFile(
file_payload_id, &expected_file_bytes));
}));
ASSERT_TRUE(completion_future_.Get<0>());
EXPECT_EQ(base::ReadFileToBytes(GetFilePayloadDirectory().Append(
BuildFilePayloadName(file_payload_id))),
expected_file_bytes);
};
run_transfer_file_test(1);
run_transfer_file_test(2);
}
TEST_F(FileTransferTest, FailedTransfer) {
constexpr int64_t kFilePayloadId = 1;
nearby_process_manager_.fake_nearby_connections().SetFinalFilePayloadStatus(
::nearby::connections::mojom::PayloadStatus::kFailure, /*payload_id=*/1);
InitializeFileTransfer();
pending_file_transfer_queue_.Push(kFilePayloadId);
nearby_process_manager_.fake_nearby_connections()
.set_local_to_remote_payload_listener(base::BindLambdaForTesting(
[this](::nearby::connections::mojom::PayloadPtr cts_payload) {
// Once CTS is received on the remote device, start transferring
// the file.
VerifyCTSMessage(std::move(cts_payload), kFilePayloadId);
ASSERT_TRUE(
nearby_process_manager_.fake_nearby_connections().SendFile(
kFilePayloadId));
}));
EXPECT_FALSE(completion_future_.Get<0>());
}
} // namespace
} // namespace data_migration