chromium/chromeos/ash/components/drivefs/drivefs_search_unittest.cc

// Copyright 2018 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/drivefs/drivefs_search.h"

#include <memory>
#include <optional>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom-test-utils.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
#include "components/drive/file_errors.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace drivefs {
namespace {

using ::testing::_;
using ::testing::FieldsAre;

class MockMojomQuery : public mojom::SearchQuery {
 public:
  void GetNextPage(GetNextPageCallback callback) override {
    future_.SetValue(std::move(callback));
  }

  void Bind(mojo::PendingReceiver<mojom::SearchQuery> receiver) {
    search_receiver_.Bind(std::move(receiver));
  }

  GetNextPageCallback TakeCallback() { return future_.Take(); }

 private:
  base::test::TestFuture<GetNextPageCallback> future_;
  mojo::Receiver<mojom::SearchQuery> search_receiver_{this};
};

class MockDriveFs : public mojom::DriveFsInterceptorForTesting {
 public:
  MockDriveFs() = default;

  MockDriveFs(const MockDriveFs&) = delete;
  MockDriveFs& operator=(const MockDriveFs&) = delete;

  DriveFs* GetForwardingInterface() override {
    NOTREACHED_IN_MIGRATION();
    return nullptr;
  }

  MOCK_METHOD(void,
              StartSearchQuery,
              (mojo::PendingReceiver<mojom::SearchQuery> query,
               mojom::QueryParametersPtr query_params),
              (override));
};

class DriveFsSearchTest : public testing::Test {
 public:
  DriveFsSearchTest()
      : network_connection_tracker_(
            network::TestNetworkConnectionTracker::CreateInstance()) {
    clock_.SetNow(base::Time::Now());
  }

 protected:
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<network::TestNetworkConnectionTracker>
      network_connection_tracker_;
  MockDriveFs mock_drivefs_;
  base::SimpleTestClock clock_;
};

std::vector<mojom::QueryItemPtr> PopulateSearch(int count) {
  std::vector<mojom::QueryItemPtr> items;
  for (int i = 0; i < count; ++i) {
    items.emplace_back(mojom::QueryItem::New());
    items.back()->metadata = mojom::FileMetadata::New();
    items.back()->metadata->capabilities = mojom::Capabilities::New();
  }
  return items;
}

}  // namespace

MATCHER_P5(MatchQuery, source, text, title, shared, offline, "") {
  if (arg->query_source != source) {
    return false;
  }
  if (text != nullptr) {
    if (!arg->text_content || *arg->text_content != std::string(text)) {
      return false;
    }
  } else {
    if (arg->text_content) {
      return false;
    }
  }
  if (title != nullptr) {
    if (!arg->title || *arg->title != std::string(title)) {
      return false;
    }
  } else {
    if (arg->title) {
      return false;
    }
  }
  return arg->shared_with_me == shared && arg->available_offline == offline;
}

TEST_F(DriveFsSearchTest, Search) {
  DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                       &clock_);

  MockMojomQuery mojom_query;
  EXPECT_CALL(mock_drivefs_, StartSearchQuery)
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        mojom_query.Bind(std::move(query));
      });

  mojom::QueryParametersPtr params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kLocalOnly;

  bool called = false;
  mojom::QueryParameters::QuerySource source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>> items) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_OK, err);
            EXPECT_EQ(3u, items->size());
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kLocalOnly, source);
  mojom_query.TakeCallback().Run(drive::FileError::FILE_ERROR_OK,
                                 PopulateSearch(3));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);
}

TEST_F(DriveFsSearchTest, Search_Fail) {
  DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                       &clock_);

  MockMojomQuery mojom_query;
  EXPECT_CALL(mock_drivefs_, StartSearchQuery)
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        mojom_query.Bind(std::move(query));
      });

  mojom::QueryParametersPtr params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;

  bool called = false;
  mojom::QueryParameters::QuerySource source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>> items) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_ACCESS_DENIED, err);
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
  mojom_query.TakeCallback().Run(drive::FileError::FILE_ERROR_ACCESS_DENIED,
                                 std::nullopt);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);
}

TEST_F(DriveFsSearchTest, Search_OnlineToOffline) {
  DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                       &clock_);

  network_connection_tracker_->SetConnectionType(
      network::mojom::ConnectionType::CONNECTION_NONE);

  MockMojomQuery mojom_query;
  EXPECT_CALL(mock_drivefs_, StartSearchQuery)
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        mojom_query.Bind(std::move(query));
      });

  mojom::QueryParametersPtr params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;

  bool called = false;
  mojom::QueryParameters::QuerySource source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>> items) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_OK, err);
            EXPECT_EQ(3u, items->size());
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kLocalOnly, source);
  mojom_query.TakeCallback().Run(drive::FileError::FILE_ERROR_OK,
                                 PopulateSearch(3));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);
}

TEST_F(DriveFsSearchTest, Search_OnlineToOfflineFallback) {
  DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                       &clock_);

  MockMojomQuery cloud_mojom_query;
  MockMojomQuery local_mojom_query;
  EXPECT_CALL(mock_drivefs_,
              StartSearchQuery(
                  _, MatchQuery(mojom::QueryParameters::QuerySource::kCloudOnly,
                                "foobar", nullptr, false, false)))
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        cloud_mojom_query.Bind(std::move(query));
      });
  EXPECT_CALL(mock_drivefs_,
              StartSearchQuery(
                  _, MatchQuery(mojom::QueryParameters::QuerySource::kLocalOnly,
                                nullptr, "foobar", false, false)))
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        local_mojom_query.Bind(std::move(query));
      });

  mojom::QueryParametersPtr params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;
  params->text_content = "foobar";

  bool called = false;
  mojom::QueryParameters::QuerySource source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>> items) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_OK, err);
            EXPECT_EQ(3u, items->size());
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
  cloud_mojom_query.TakeCallback().Run(
      drive::FileError::FILE_ERROR_NO_CONNECTION, std::nullopt);
  local_mojom_query.TakeCallback().Run(drive::FileError::FILE_ERROR_OK,
                                       PopulateSearch(3));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);
}

TEST_F(DriveFsSearchTest, Search_SharedWithMeCaching) {
  DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                       &clock_);

  MockMojomQuery cloud_mojom_query_1;
  MockMojomQuery cloud_mojom_query_2;
  MockMojomQuery local_mojom_query;
  EXPECT_CALL(mock_drivefs_,
              StartSearchQuery(
                  _, MatchQuery(mojom::QueryParameters::QuerySource::kCloudOnly,
                                nullptr, nullptr, true, false)))
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        cloud_mojom_query_1.Bind(std::move(query));
      })
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        cloud_mojom_query_2.Bind(std::move(query));
      });
  EXPECT_CALL(mock_drivefs_,
              StartSearchQuery(
                  _, MatchQuery(mojom::QueryParameters::QuerySource::kLocalOnly,
                                nullptr, nullptr, true, false)))
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        local_mojom_query.Bind(std::move(query));
      });

  mojom::QueryParametersPtr params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;
  params->shared_with_me = true;

  bool called = false;
  mojom::QueryParameters::QuerySource source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>> items) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_OK, err);
            EXPECT_EQ(3u, items->size());
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
  cloud_mojom_query_1.TakeCallback().Run(drive::FileError::FILE_ERROR_OK,
                                         PopulateSearch(3));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);

  params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;
  params->shared_with_me = true;

  called = false;
  source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>> items) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_OK, err);
            EXPECT_EQ(3u, items->size());
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kLocalOnly, source);
  local_mojom_query.TakeCallback().Run(drive::FileError::FILE_ERROR_OK,
                                       PopulateSearch(3));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);

  // Time has passed...
  clock_.Advance(base::Hours(1));

  params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;
  params->shared_with_me = true;

  called = false;
  source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>> items) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_OK, err);
            EXPECT_EQ(3u, items->size());
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
  cloud_mojom_query_2.TakeCallback().Run(drive::FileError::FILE_ERROR_OK,
                                         PopulateSearch(3));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);
}

TEST_F(DriveFsSearchTest, Search_NoErrorCaching) {
  DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                       &clock_);

  MockMojomQuery mojom_query_1;
  MockMojomQuery mojom_query_2;
  EXPECT_CALL(mock_drivefs_,
              StartSearchQuery(
                  _, MatchQuery(mojom::QueryParameters::QuerySource::kCloudOnly,
                                nullptr, nullptr, true, false)))
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        mojom_query_1.Bind(std::move(query));
      })
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        mojom_query_2.Bind(std::move(query));
      });

  mojom::QueryParametersPtr params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;
  params->shared_with_me = true;

  bool called = false;
  mojom::QueryParameters::QuerySource source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>>) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_FAILED, err);
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
  mojom_query_1.TakeCallback().Run(drive::FileError::FILE_ERROR_FAILED,
                                   std::nullopt);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);

  params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;
  params->shared_with_me = true;

  // As previous call failed this one will go to the cloud again.
  called = false;
  source = search.PerformSearch(
      std::move(params),
      base::BindLambdaForTesting(
          [&called](drive::FileError err,
                    std::optional<std::vector<mojom::QueryItemPtr>> items) {
            called = true;
            EXPECT_EQ(drive::FileError::FILE_ERROR_OK, err);
            EXPECT_EQ(3u, items->size());
          }));
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
  mojom_query_2.TakeCallback().Run(drive::FileError::FILE_ERROR_OK,
                                   PopulateSearch(3));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(called);
}

TEST_F(DriveFsSearchTest, Search_SearchQueryRemoteDisconnected) {
  DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                       &clock_);

  auto mojom_query = std::make_unique<MockMojomQuery>();
  EXPECT_CALL(mock_drivefs_, StartSearchQuery)
      .WillOnce([&](mojo::PendingReceiver<mojom::SearchQuery> query,
                    mojom::QueryParametersPtr query_params) {
        CHECK(mojom_query);
        mojom_query->Bind(std::move(query));
      });

  mojom::QueryParametersPtr params = mojom::QueryParameters::New();
  params->query_source = mojom::QueryParameters::QuerySource::kCloudOnly;

  base::test::TestFuture<drive::FileError,
                         std::optional<std::vector<mojom::QueryItemPtr>>>
      next_page_future;
  mojom::QueryParameters::QuerySource source =
      search.PerformSearch(std::move(params), next_page_future.GetCallback());
  EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
  mojom_query.reset();

  EXPECT_THAT(next_page_future.Take(),
              FieldsAre(drive::FileError::FILE_ERROR_ABORT, _));
}

}  // namespace drivefs