chromium/chrome/browser/ash/crosapi/test/file_manager_ash_crosapitest.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/test/test_future.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/ash/crosapi/test/ash_crosapi_tests_env.h"
#include "chrome/browser/ash/crosapi/test/crosapi_test_base.h"
#include "chromeos/crosapi/mojom/app_service.mojom.h"
#include "chromeos/crosapi/mojom/app_service_types.mojom.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "chromeos/crosapi/mojom/file_manager.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"

namespace crosapi {
namespace {

constexpr char kFakeChromeAppName[] = "fake-chrome-app";

class FileManagerCrosapiTestCommandLineModifier
    : public AshCrosapiTestCommandLineModifierDelegate {
 public:
  FileManagerCrosapiTestCommandLineModifier() = default;
  FileManagerCrosapiTestCommandLineModifier(
      const FileManagerCrosapiTestCommandLineModifier&) = delete;
  FileManagerCrosapiTestCommandLineModifier& operator=(
      const FileManagerCrosapiTestCommandLineModifier&) = delete;
  ~FileManagerCrosapiTestCommandLineModifier() override = default;

  void AddExtraCommandLine(base::CommandLine* command_line) override {
    // This switch allows app installation.
    command_line->AppendSwitchASCII(switches::kEnableFeatures,
                                    "WebAppsCrosapi");
  }
};

class FileManagerCrosapiTest : public CrosapiTestBase {
 public:
  FileManagerCrosapiTest()
      : CrosapiTestBase(
            std::make_unique<FileManagerCrosapiTestCommandLineModifier>()) {}

 protected:
  void SetUp() override {
    app_publisher_ = BindCrosapiInterface(&mojom::Crosapi::BindWebAppPublisher);
    file_manager_ = BindCrosapiInterface(&mojom::Crosapi::BindFileManager);
  }

  const base::FilePath GetMyFilesPath() {
    return GetUserDataDir().Append("test-user").Append("MyFiles");
  }

  mojo::Remote<mojom::AppPublisher> app_publisher_;
  mojo::Remote<mojom::FileManager> file_manager_;
};

TEST_F(FileManagerCrosapiTest, ShowItemInFolder) {
  // A non-existent path.
  const base::FilePath bad_path("/does/not/exist");
  base::test::TestFuture<mojom::OpenResult> future1;
  file_manager_->ShowItemInFolder(bad_path, future1.GetCallback());
  EXPECT_EQ(future1.Get(), mojom::OpenResult::kFailedPathNotFound);

  // A valid folder.
  base::test::TestFuture<mojom::OpenResult> future2;
  file_manager_->ShowItemInFolder(GetMyFilesPath(), future2.GetCallback());
  EXPECT_EQ(future2.Get(), mojom::OpenResult::kSucceeded);

  // A valid file.
  const base::FilePath file_path = GetMyFilesPath().Append("test_file.txt");
  {
    base::ScopedAllowBlockingForTesting scoped_allow_blocking;
    EXPECT_TRUE(base::WriteFile(file_path, ""));
  }
  absl::Cleanup scoped_cleanup = [&] {
    base::ScopedAllowBlockingForTesting scoped_allow_blocking;
    EXPECT_TRUE(base::DeleteFile(file_path));
  };

  base::test::TestFuture<mojom::OpenResult> future3;
  file_manager_->ShowItemInFolder(file_path, future3.GetCallback());
  EXPECT_EQ(future3.Get(), mojom::OpenResult::kSucceeded);
}

TEST_F(FileManagerCrosapiTest, OpenFolder) {
  // A non-existent path.
  const base::FilePath bad_path("/does/not/exist");
  base::test::TestFuture<mojom::OpenResult> future1;
  file_manager_->OpenFolder(bad_path, future1.GetCallback());
  EXPECT_EQ(future1.Get(), mojom::OpenResult::kFailedPathNotFound);

  // A valid folder.
  base::test::TestFuture<mojom::OpenResult> future2;
  file_manager_->OpenFolder(GetMyFilesPath(), future2.GetCallback());
  EXPECT_EQ(future2.Get(), mojom::OpenResult::kSucceeded);

  // A valid file.
  const base::FilePath file_path = GetMyFilesPath().Append("test_file.txt");
  {
    base::ScopedAllowBlockingForTesting scoped_allow_blocking;
    EXPECT_TRUE(base::WriteFile(file_path, ""));
  }
  absl::Cleanup scoped_cleanup = [&] {
    base::ScopedAllowBlockingForTesting scoped_allow_blocking;
    EXPECT_TRUE(base::DeleteFile(file_path));
  };
  base::test::TestFuture<mojom::OpenResult> future3;
  file_manager_->OpenFolder(file_path, future3.GetCallback());
  EXPECT_EQ(future3.Get(), mojom::OpenResult::kFailedInvalidType);
}

TEST_F(FileManagerCrosapiTest, OpenFile) {
  // A non-existent path.
  const base::FilePath bad_path("/does/not/exist");
  base::test::TestFuture<mojom::OpenResult> future1;
  file_manager_->OpenFile(bad_path, future1.GetCallback());
  EXPECT_EQ(future1.Get(), mojom::OpenResult::kFailedPathNotFound);

  // A valid folder.
  base::test::TestFuture<mojom::OpenResult> future2;
  file_manager_->OpenFile(GetMyFilesPath(), future2.GetCallback());
  EXPECT_EQ(future2.Get(), mojom::OpenResult::kFailedInvalidType);
}

// TODO(crbug.com/40922738): Re-enable this test
TEST_F(FileManagerCrosapiTest, DISABLED_OpenFileWithAppInstalled) {
  const base::FilePath pakfile_path = GetMyFilesPath().Append("test_file.pak");
  {
    base::ScopedAllowBlockingForTesting scoped_allow_blocking;
    EXPECT_TRUE(base::WriteFile(pakfile_path, ""));
  }
  absl::Cleanup scoped_cleanup = [&] {
    base::ScopedAllowBlockingForTesting scoped_allow_blocking;
    EXPECT_TRUE(base::DeleteFile(pakfile_path));
  };
  // A valid file but there is no application to open .pak file.
  base::test::TestFuture<mojom::OpenResult> future1;
  file_manager_->OpenFile(pakfile_path, future1.GetCallback());
  EXPECT_EQ(future1.Get(), mojom::OpenResult::kFailedNoHandlerForFileType);

  // Register a stub AppController here, so that the registered apps will be
  // published to the subscribers. See WebAppServiceAsh::OnApps for more detail.
  mojo::PendingReceiver<crosapi::mojom::AppController> pending_receiver;
  app_publisher_->RegisterAppController(
      pending_receiver.InitWithNewPipeAndPassRemote());

  // Install an application to open .pak file.
  std::vector<apps::AppPtr> apps1;
  apps::AppPtr app =
      std::make_unique<apps::App>(apps::AppType::kWeb, kFakeChromeAppName);

  apps::ConditionValues values;
  values.push_back(std::make_unique<apps::ConditionValue>(
      "pak", apps::PatternMatchType::kFileExtension));

  apps::IntentFilterPtr intent_filter = std::make_unique<apps::IntentFilter>();
  intent_filter->conditions.push_back(std::make_unique<apps::Condition>(
      apps::ConditionType::kFile, std::move(values)));

  apps::IntentFilters filters;
  filters.push_back(std::move(intent_filter));

  app->intent_filters = std::move(filters);
  app->readiness = apps::Readiness::kReady;
  app->handles_intents = true;
  apps1.push_back(std::move(app));
  app_publisher_->OnApps(std::move(apps1));

  // A valid .pak file and the app which can handle .pak file exists.
  base::test::TestFuture<mojom::OpenResult> future2;
  file_manager_->OpenFile(pakfile_path, future2.GetCallback());
  EXPECT_EQ(future2.Get(), mojom::OpenResult::kSucceeded);

  // Remove the installed app.
  std::vector<apps::AppPtr> apps2;
  // Uninstall the app before removed.
  apps::AppPtr app_uninstall =
      std::make_unique<apps::App>(apps::AppType::kWeb, kFakeChromeAppName);
  app_uninstall->readiness = apps::Readiness::kUninstalledByUser;
  apps2.push_back(std::move(app_uninstall));

  apps::AppPtr app_remove =
      std::make_unique<apps::App>(apps::AppType::kWeb, kFakeChromeAppName);
  app_remove->readiness = apps::Readiness::kRemoved;
  apps2.push_back(std::move(app_remove));

  app_publisher_->OnApps(std::move(apps2));

  // A valid file but there is no application to open .pak file.
  base::test::TestFuture<mojom::OpenResult> future3;
  file_manager_->OpenFile(pakfile_path, future3.GetCallback());
  EXPECT_EQ(future3.Get(), mojom::OpenResult::kFailedNoHandlerForFileType);
}

}  // namespace
}  // namespace crosapi