chromium/chrome/browser/webshare/win/fake_data_transfer_manager_unittest.cc

// 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