// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webshare/win/fake_data_transfer_manager.h"
#include <windows.storage.h>
#include <wrl/event.h>
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/win/scoped_hstring.h"
#include "base/win/scoped_winrt_initializer.h"
#include "base/win/vector.h"
#include "chrome/browser/webshare/win/fake_storage_file_statics.h"
#include "chrome/browser/webshare/win/fake_uri_runtime_class_factory.h"
#include "testing/gtest/include/gtest/gtest-spi.h"
#include "testing/gtest/include/gtest/gtest.h"
using ABI::Windows::ApplicationModel::DataTransfer::DataRequestedEventArgs;
using ABI::Windows::ApplicationModel::DataTransfer::DataTransferManager;
using ABI::Windows::ApplicationModel::DataTransfer::IDataPackage;
using ABI::Windows::ApplicationModel::DataTransfer::IDataPackage2;
using ABI::Windows::ApplicationModel::DataTransfer::IDataPackagePropertySet;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequest;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequestDeferral;
using ABI::Windows::ApplicationModel::DataTransfer::IDataRequestedEventArgs;
using ABI::Windows::ApplicationModel::DataTransfer::IDataTransferManager;
using ABI::Windows::Foundation::IAsyncOperation;
using ABI::Windows::Foundation::IAsyncOperationCompletedHandler;
using ABI::Windows::Foundation::ITypedEventHandler;
using ABI::Windows::Foundation::IUriRuntimeClass;
using ABI::Windows::Storage::IStorageFile;
using ABI::Windows::Storage::IStorageItem;
using ABI::Windows::Storage::IStreamedFileDataRequestedHandler;
using ABI::Windows::Storage::StorageFile;
using ABI::Windows::Storage::Streams::IOutputStream;
using Microsoft::WRL::Callback;
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::Make;
namespace ABI {
namespace Windows {
namespace Foundation {
namespace Collections {
// Define template specializations for the types used. These uuids were randomly
// generated.
template <>
struct __declspec(uuid("CBE31E85-DEC8-4227-987F-9C63D6AA1A2E"))
IObservableVector<IStorageItem*> : IObservableVector_impl<IStorageItem*> {};
template <>
struct __declspec(uuid("30BE4864-5EE5-4111-916E-15126649F3C9"))
VectorChangedEventHandler<IStorageItem*>
: VectorChangedEventHandler_impl<IStorageItem*> {};
} // namespace Collections
} // namespace Foundation
} // namespace Windows
} // namespace ABI
namespace webshare {
// Provides a DataRequested callback and records the number of times it is
// invoked
class DataRequestedTestCallback {
public:
DataRequestedTestCallback() {
auto weak_ptr = weak_factory_.GetWeakPtr();
callback_ = Callback<
ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>(
[weak_ptr](IDataTransferManager* data_transfer_manager,
IDataRequestedEventArgs* event_args) -> HRESULT {
if (weak_ptr.get())
weak_ptr->invocation_count_++;
return S_OK;
});
}
int invocation_count_ = 0;
ComPtr<ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>
callback_;
private:
base::WeakPtrFactory<DataRequestedTestCallback> weak_factory_{this};
};
class FakeDataTransferManagerTest : public ::testing::Test {
protected:
void SetUp() override {
winrt_initializer_.emplace();
ASSERT_TRUE(winrt_initializer_->Succeeded());
fake_data_transfer_manager_ =
Microsoft::WRL::Make<FakeDataTransferManager>();
}
std::optional<base::win::ScopedWinrtInitializer> winrt_initializer_;
ComPtr<FakeDataTransferManager> fake_data_transfer_manager_;
};
TEST_F(FakeDataTransferManagerTest, RemovingHandlerForInvalidToken) {
// Validate removing an invalid token both fails and creates a test failure
// when there is no listener
EventRegistrationToken invalid_token;
EXPECT_NONFATAL_FAILURE(
ASSERT_HRESULT_FAILED(
fake_data_transfer_manager_->remove_DataRequested(invalid_token)),
"remove_DataRequested");
invalid_token.value = 123;
EXPECT_NONFATAL_FAILURE(
ASSERT_HRESULT_FAILED(
fake_data_transfer_manager_->remove_DataRequested(invalid_token)),
"remove_DataRequested");
// Validate removing an invalid token both fails and creates a test failure
// when there is a listener
EventRegistrationToken valid_token;
DataRequestedTestCallback test_callback;
ASSERT_HRESULT_SUCCEEDED(fake_data_transfer_manager_->add_DataRequested(
test_callback.callback_.Get(), &valid_token));
EXPECT_NONFATAL_FAILURE(
ASSERT_HRESULT_FAILED(
fake_data_transfer_manager_->remove_DataRequested(invalid_token)),
"remove_DataRequested");
// Validate removing a valid token is successful only once, failing and
// creating a test failure on repeated uses
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(valid_token));
EXPECT_NONFATAL_FAILURE(
ASSERT_HRESULT_FAILED(
fake_data_transfer_manager_->remove_DataRequested(valid_token)),
"remove_DataRequested");
}
TEST_F(FakeDataTransferManagerTest, OutOfOrderEventUnsubscribing) {
ASSERT_FALSE(fake_data_transfer_manager_->HasDataRequestedListener());
DataRequestedTestCallback callback_1;
EventRegistrationToken token_1;
ASSERT_HRESULT_SUCCEEDED(fake_data_transfer_manager_->add_DataRequested(
callback_1.callback_.Get(), &token_1));
ASSERT_TRUE(fake_data_transfer_manager_->HasDataRequestedListener());
DataRequestedTestCallback callback_2;
EventRegistrationToken token_2;
ASSERT_HRESULT_SUCCEEDED(fake_data_transfer_manager_->add_DataRequested(
callback_2.callback_.Get(), &token_2));
ASSERT_TRUE(fake_data_transfer_manager_->HasDataRequestedListener());
DataRequestedTestCallback callback_3;
EventRegistrationToken token_3;
ASSERT_HRESULT_SUCCEEDED(fake_data_transfer_manager_->add_DataRequested(
callback_3.callback_.Get(), &token_3));
ASSERT_TRUE(fake_data_transfer_manager_->HasDataRequestedListener());
ASSERT_EQ(callback_1.invocation_count_, 0);
ASSERT_EQ(callback_2.invocation_count_, 0);
ASSERT_EQ(callback_3.invocation_count_, 0);
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(token_2));
ASSERT_TRUE(fake_data_transfer_manager_->HasDataRequestedListener());
std::move(fake_data_transfer_manager_->GetDataRequestedInvoker()).Run();
ASSERT_EQ(callback_1.invocation_count_, 0);
ASSERT_EQ(callback_2.invocation_count_, 0);
ASSERT_EQ(callback_3.invocation_count_, 1);
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(token_3));
ASSERT_TRUE(fake_data_transfer_manager_->HasDataRequestedListener());
std::move(fake_data_transfer_manager_->GetDataRequestedInvoker()).Run();
ASSERT_EQ(callback_1.invocation_count_, 1);
ASSERT_EQ(callback_2.invocation_count_, 0);
ASSERT_EQ(callback_3.invocation_count_, 1);
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(token_1));
ASSERT_FALSE(fake_data_transfer_manager_->HasDataRequestedListener());
EXPECT_NONFATAL_FAILURE(
fake_data_transfer_manager_->GetDataRequestedInvoker(),
"GetDataRequestedInvoker");
}
TEST_F(FakeDataTransferManagerTest, OutOfOrderEventInvocation) {
DataRequestedTestCallback callback_1;
EventRegistrationToken token_1;
ASSERT_HRESULT_SUCCEEDED(fake_data_transfer_manager_->add_DataRequested(
callback_1.callback_.Get(), &token_1));
auto callback_1_invoker =
fake_data_transfer_manager_->GetDataRequestedInvoker();
DataRequestedTestCallback callback_2;
EventRegistrationToken token_2;
ASSERT_HRESULT_SUCCEEDED(fake_data_transfer_manager_->add_DataRequested(
callback_2.callback_.Get(), &token_2));
auto callback_2_invoker =
fake_data_transfer_manager_->GetDataRequestedInvoker();
DataRequestedTestCallback callback_3;
EventRegistrationToken token_3;
ASSERT_HRESULT_SUCCEEDED(fake_data_transfer_manager_->add_DataRequested(
callback_3.callback_.Get(), &token_3));
auto callback_3_invoker =
fake_data_transfer_manager_->GetDataRequestedInvoker();
ASSERT_EQ(callback_1.invocation_count_, 0);
ASSERT_EQ(callback_2.invocation_count_, 0);
ASSERT_EQ(callback_3.invocation_count_, 0);
std::move(callback_2_invoker).Run();
ASSERT_EQ(callback_1.invocation_count_, 0);
ASSERT_EQ(callback_2.invocation_count_, 1);
ASSERT_EQ(callback_3.invocation_count_, 0);
std::move(callback_3_invoker).Run();
ASSERT_EQ(callback_1.invocation_count_, 0);
ASSERT_EQ(callback_2.invocation_count_, 1);
ASSERT_EQ(callback_3.invocation_count_, 1);
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(token_1));
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(token_2));
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(token_3));
std::move(callback_1_invoker).Run();
ASSERT_EQ(callback_1.invocation_count_, 1);
ASSERT_EQ(callback_2.invocation_count_, 1);
ASSERT_EQ(callback_3.invocation_count_, 1);
}
TEST_F(FakeDataTransferManagerTest, PostDataRequestedCallback) {
base::test::SingleThreadTaskEnvironment task_environment;
// Create a StorageFile/Item to provide to the DataRequested event
ComPtr<IStorageFile> storage_file;
{
base::RunLoop run_loop;
auto file_name = base::win::ScopedHString::Create("MyTestFile.abc");
ComPtr<IAsyncOperation<StorageFile*>> create_operation;
auto storage_file_statics = Make<FakeStorageFileStatics>();
storage_file_statics->CreateStreamedFileAsync(
file_name.get(),
Callback<IStreamedFileDataRequestedHandler>([](IOutputStream* stream) {
ADD_FAILURE() << "DataRequestedHandler called for streamed file that "
"should never have been opened";
return S_OK;
}).Get(),
/*thumbnail*/ nullptr, &create_operation);
ASSERT_HRESULT_SUCCEEDED(create_operation->put_Completed(
Callback<IAsyncOperationCompletedHandler<StorageFile*>>(
[&run_loop, &storage_file](
IAsyncOperation<StorageFile*>* async_operation,
AsyncStatus async_status) {
EXPECT_EQ(async_status, AsyncStatus::Completed);
EXPECT_HRESULT_SUCCEEDED(
async_operation->GetResults(&storage_file));
run_loop.Quit();
return S_OK;
})
.Get()));
run_loop.Run();
}
ComPtr<IStorageItem> storage_item;
ASSERT_HRESULT_SUCCEEDED(storage_file.As(&storage_item));
// Set up a handler for the DataRequested event that provides a collection of
// content
auto callback = Callback<
ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>(
[&storage_item](IDataTransferManager* data_transfer_manager,
IDataRequestedEventArgs* event_args) -> HRESULT {
ComPtr<IDataRequest> data_request;
EXPECT_HRESULT_SUCCEEDED(event_args->get_Request(&data_request));
ComPtr<IDataPackage> data_package;
EXPECT_HRESULT_SUCCEEDED(data_request->get_Data(&data_package));
ComPtr<IDataPackagePropertySet> data_prop_sets;
EXPECT_HRESULT_SUCCEEDED(data_package->get_Properties(&data_prop_sets));
auto title_h = base::win::ScopedHString::Create("my title");
EXPECT_HRESULT_SUCCEEDED(data_prop_sets->put_Title(title_h.get()));
auto text_h = base::win::ScopedHString::Create("my text");
EXPECT_HRESULT_SUCCEEDED(data_package->SetText(text_h.get()));
auto uri_factory = Make<FakeUriRuntimeClassFactory>();
auto url_h = base::win::ScopedHString::Create("https://my.url.com");
ComPtr<IUriRuntimeClass> uri;
EXPECT_HRESULT_SUCCEEDED(uri_factory->CreateUri(url_h.get(), &uri));
ComPtr<IDataPackage2> data_package_2;
EXPECT_HRESULT_SUCCEEDED(data_package.As(&data_package_2));
EXPECT_HRESULT_SUCCEEDED(data_package_2->SetWebLink(uri.Get()));
auto storage_items = Make<base::win::Vector<IStorageItem*>>();
storage_items->Append(storage_item.Get());
EXPECT_HRESULT_SUCCEEDED(data_package->SetStorageItems(
storage_items.Get(), true /*readonly*/));
return S_OK;
});
EventRegistrationToken token;
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->add_DataRequested(callback.Get(), &token));
// Set up a handler for the PostDataRequested event that validates all the
// expected data
bool post_data_requested_callback_invoked = false;
fake_data_transfer_manager_->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&post_data_requested_callback_invoked,
&storage_file](const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
ASSERT_EQ(data_requested_content.title, "my title");
ASSERT_EQ(data_requested_content.text, "my text");
ASSERT_EQ(data_requested_content.uri, "https://my.url.com");
ASSERT_EQ(data_requested_content.files.size(), 1u);
ASSERT_EQ(data_requested_content.files[0].name, "MyTestFile.abc");
ASSERT_EQ(data_requested_content.files[0].file, storage_file);
}));
// Run the flow
auto callback_invoker =
fake_data_transfer_manager_->GetDataRequestedInvoker();
ASSERT_FALSE(post_data_requested_callback_invoked);
std::move(callback_invoker).Run();
ASSERT_TRUE(post_data_requested_callback_invoked);
// Cleanup
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(token));
}
TEST_F(FakeDataTransferManagerTest, PostDataRequestedCallback_Deferral) {
// Set up a handler for the DataRequested event that requests a deferral
ComPtr<IDataRequestDeferral> data_request_deferral;
auto callback = Callback<
ITypedEventHandler<DataTransferManager*, DataRequestedEventArgs*>>(
[&data_request_deferral](IDataTransferManager* data_transfer_manager,
IDataRequestedEventArgs* event_args) -> HRESULT {
ComPtr<IDataRequest> data_request;
EXPECT_HRESULT_SUCCEEDED(event_args->get_Request(&data_request));
EXPECT_HRESULT_SUCCEEDED(
data_request->GetDeferral(&data_request_deferral));
return S_OK;
});
EventRegistrationToken token;
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->add_DataRequested(callback.Get(), &token));
// Set up a handler for the PostDataRequested event that records the event
bool post_data_requested_callback_invoked = false;
fake_data_transfer_manager_->SetPostDataRequestedCallback(
base::BindLambdaForTesting(
[&post_data_requested_callback_invoked](
const FakeDataTransferManager::DataRequestedContent&
data_requested_content) {
ASSERT_FALSE(post_data_requested_callback_invoked);
post_data_requested_callback_invoked = true;
}));
// Run the flow
auto callback_invoker =
fake_data_transfer_manager_->GetDataRequestedInvoker();
ASSERT_FALSE(post_data_requested_callback_invoked);
ASSERT_FALSE(data_request_deferral);
std::move(callback_invoker).Run();
ASSERT_FALSE(post_data_requested_callback_invoked);
ASSERT_TRUE(data_request_deferral);
ASSERT_HRESULT_SUCCEEDED(data_request_deferral->Complete());
ASSERT_TRUE(post_data_requested_callback_invoked);
// Cleanup
ASSERT_HRESULT_SUCCEEDED(
fake_data_transfer_manager_->remove_DataRequested(token));
}
} // namespace webshare