text: // Copyright 2018 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chromeos/components/drivefs/drivefs_search.h"
  
  #include <utility>
  
  #include "base/run_loop.h"
  #include "base/test/bind_test_util.h"
  #include "base/test/simple_test_clock.h"
  #include "base/test/task_environment.h"
  #include "chromeos/components/drivefs/mojom/drivefs.mojom-test-utils.h"
  #include "chromeos/components/drivefs/mojom/drivefs.mojom.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::_;
  
  class MockDriveFs : public mojom::DriveFsInterceptorForTesting,
                      public mojom::SearchQuery {
   public:
    MockDriveFs() = default;
  
    DriveFs* GetForwardingInterface() override {
      NOTREACHED();
      return nullptr;
    }
  
    MOCK_CONST_METHOD1(OnStartSearchQuery, void(const mojom::QueryParameters&));
    void StartSearchQuery(mojo::PendingReceiver<mojom::SearchQuery> receiver,
                          mojom::QueryParametersPtr query_params) override {
      search_receiver_.reset();
      OnStartSearchQuery(*query_params);
      search_receiver_.Bind(std::move(receiver));
    }
  
    MOCK_METHOD1(OnGetNextPage,
                 drive::FileError(
                     base::Optional<std::vector<mojom::QueryItemPtr>>* items));
  
    void GetNextPage(GetNextPageCallback callback) override {
      base::Optional<std::vector<mojom::QueryItemPtr>> items;
      auto error = OnGetNextPage(&items);
      std::move(callback).Run(error, std::move(items));
    }
  
   private:
    mojo::Receiver<mojom::SearchQuery> search_receiver_{this};
    DISALLOW_COPY_AND_ASSIGN(MockDriveFs);
  };
  
  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_;
  };
  
  }  // namespace
  
  ACTION_P(PopulateSearch, count) {
    if (count < 0)
      return;
    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();
    }
    *arg0 = std::move(items);
  }
  
  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_);
  
    EXPECT_CALL(mock_drivefs_, OnStartSearchQuery(_));
    EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
        .WillOnce(testing::DoAll(
            PopulateSearch(3), testing::Return(drive::FileError::FILE_ERROR_OK)));
  
    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,
                      base::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);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(called);
  }
  
  TEST_F(DriveFsSearchTest, Search_Fail) {
    DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                         &clock_);
  
    EXPECT_CALL(mock_drivefs_, OnStartSearchQuery(_));
    EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
        .WillOnce(testing::Return(drive::FileError::FILE_ERROR_ACCESS_DENIED));
  
    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,
                      base::Optional<std::vector<mojom::QueryItemPtr>> items) {
              called = true;
              EXPECT_EQ(drive::FileError::FILE_ERROR_ACCESS_DENIED, err);
            }));
    EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
    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);
  
    EXPECT_CALL(mock_drivefs_, OnStartSearchQuery(_));
    EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
        .WillOnce(testing::DoAll(
            PopulateSearch(3), testing::Return(drive::FileError::FILE_ERROR_OK)));
  
    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,
                      base::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);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(called);
  }
  
  TEST_F(DriveFsSearchTest, Search_OnlineToOfflineFallback) {
    DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                         &clock_);
  
    EXPECT_CALL(mock_drivefs_,
                OnStartSearchQuery(
                    MatchQuery(mojom::QueryParameters::QuerySource::kCloudOnly,
                               "foobar", nullptr, false, false)));
    EXPECT_CALL(mock_drivefs_,
                OnStartSearchQuery(
                    MatchQuery(mojom::QueryParameters::QuerySource::kLocalOnly,
                               nullptr, "foobar", false, false)));
  
    EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
        .WillOnce(testing::Return(drive::FileError::FILE_ERROR_NO_CONNECTION))
        .WillOnce(testing::DoAll(
            PopulateSearch(3), testing::Return(drive::FileError::FILE_ERROR_OK)));
  
    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,
                      base::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);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(called);
  }
  
  TEST_F(DriveFsSearchTest, Search_SharedWithMeCaching) {
    DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                         &clock_);
  
    EXPECT_CALL(mock_drivefs_,
                OnStartSearchQuery(
                    MatchQuery(mojom::QueryParameters::QuerySource::kCloudOnly,
                               nullptr, nullptr, true, false)))
        .Times(2);
    EXPECT_CALL(mock_drivefs_,
                OnStartSearchQuery(
                    MatchQuery(mojom::QueryParameters::QuerySource::kLocalOnly,
                               nullptr, nullptr, true, false)))
        .Times(1);
  
    EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
        .WillOnce(testing::DoAll(
            PopulateSearch(3), testing::Return(drive::FileError::FILE_ERROR_OK)))
        .WillOnce(testing::DoAll(
            PopulateSearch(3), testing::Return(drive::FileError::FILE_ERROR_OK)))
        .WillOnce(testing::DoAll(
            PopulateSearch(3), testing::Return(drive::FileError::FILE_ERROR_OK)));
  
    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,
                      base::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);
    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,
                      base::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);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(called);
  
    // Time has passed...
    clock_.Advance(base::TimeDelta::FromHours(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,
                      base::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);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(called);
  }
  
  TEST_F(DriveFsSearchTest, Search_NoErrorCaching) {
    DriveFsSearch search(&mock_drivefs_, network_connection_tracker_.get(),
                         &clock_);
  
    EXPECT_CALL(mock_drivefs_,
                OnStartSearchQuery(
                    MatchQuery(mojom::QueryParameters::QuerySource::kCloudOnly,
                               nullptr, nullptr, true, false)))
        .Times(2);
    EXPECT_CALL(mock_drivefs_,
                OnStartSearchQuery(
                    MatchQuery(mojom::QueryParameters::QuerySource::kLocalOnly,
                               nullptr, nullptr, true, false)))
        .Times(0);
  
    EXPECT_CALL(mock_drivefs_, OnGetNextPage(_))
        .WillOnce(
            testing::DoAll(PopulateSearch(0),
                           testing::Return(drive::FileError::FILE_ERROR_FAILED)))
        .WillOnce(testing::DoAll(
            PopulateSearch(3), testing::Return(drive::FileError::FILE_ERROR_OK)));
  
    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,
                      base::Optional<std::vector<mojom::QueryItemPtr>>) {
              called = true;
              EXPECT_EQ(drive::FileError::FILE_ERROR_FAILED, err);
            }));
    EXPECT_EQ(mojom::QueryParameters::QuerySource::kCloudOnly, source);
    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,
                      base::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);
    base::RunLoop().RunUntilIdle();
    EXPECT_TRUE(called);
  }
  
  }  // namespace drivefs
  