chromium/chrome/browser/chromeos/extensions/wm/wm_desks_private_apitest_lacros.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 "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_base.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/spin_wait.h"
#include "base/test/test_future.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/extensions/wm/wm_desks_private_api.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/crosapi/mojom/desk.mojom.h"
#include "chromeos/crosapi/mojom/test_controller.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/api_test_utils.h"

namespace extensions {

class DesksExtensionApiLacrosTest : public extensions::ExtensionApiTest {
 public:
  DesksExtensionApiLacrosTest() = default;
  ~DesksExtensionApiLacrosTest() override = default;
  bool CheckIsSavedDeskStorageReady(chromeos::LacrosService* service) {
    base::test::TestFuture<bool> future;
    service->GetRemote<crosapi::mojom::TestController>()
        ->IsSavedDeskStorageReady(future.GetCallback());
    return future.Get();
  }

  bool CheckAreDesksModified(chromeos::LacrosService* service) {
    base::test::TestFuture<bool> future;
    service->GetRemote<crosapi::mojom::TestController>()->AreDesksBeingModified(
        future.GetCallback());
    return future.Get();
  }
};

// Use API test for tests require other chrome API.
// API test is flaky when involves animation. For APIs involving animation use
// browser test instead.
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest,
                       DesksExtensionApiLacrosTest) {
  chromeos::LacrosService* lacros_service = chromeos::LacrosService::Get();

  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>() ||
      lacros_service->GetInterfaceVersion<crosapi::mojom::Desk>() <
          static_cast<int>(crosapi::mojom::Desk::MethodMinVersions::
                               kGetSavedDesksMinVersion) ||
      lacros_service->GetInterfaceVersion<crosapi::mojom::TestController>() <
          static_cast<int>(crosapi::mojom::TestController::MethodMinVersions::
                               kIsSavedDeskStorageReadyMinVersion)) {
    GTEST_SKIP() << "Unsupported ash version.";
  }

  SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
      TestTimeouts::action_max_timeout(),
      CheckIsSavedDeskStorageReady(lacros_service) == true);
  // This loads and runs an extension from
  // chrome/test/data/extensions/api_test/wm_desks_private.
  ASSERT_TRUE(RunExtensionTest("wm_desks_private")) << message_;
}

// Tests list all desks.
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest, ListDesksTest) {
  chromeos::LacrosService* lacros_service = chromeos::LacrosService::Get();

  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>()) {
    GTEST_SKIP() << "Unsupported ash version.";
  }

  auto list_desks_function =
      base::MakeRefCounted<WmDesksPrivateGetAllDesksFunction>();
  auto all_desks = api_test_utils::RunFunctionAndReturnSingleResult(
      list_desks_function.get(), "[]", profile());
  EXPECT_TRUE(all_desks->is_list());
}

// Tests launch and close a desk.
// Disable due to flakiness (crbug.com/1442077)
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest,
                       DISABLED_LaunchAndCloseDeskTest) {
  auto* lacros_service = chromeos::LacrosService::Get();
  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>()) {
    GTEST_SKIP() << "Unsupported ash version.";
  }

  // Launch a desk.
  auto launch_desk_function =
      base::MakeRefCounted<WmDesksPrivateLaunchDeskFunction>();
  base::HistogramTester histogram_tester;
  // The RunFunctionAndReturnSingleResult already asserts no error
  auto desk_id = api_test_utils::RunFunctionAndReturnSingleResult(
      launch_desk_function.get(), R"([{"deskName":"test"}])", profile());
  EXPECT_TRUE(desk_id->is_string());
  EXPECT_TRUE(
      base::Uuid::ParseCaseInsensitive(desk_id->GetString()).is_valid());

  // Wait for launch desk animation to settle.
  SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
      TestTimeouts::action_max_timeout(),
      CheckAreDesksModified(lacros_service) == false);

  histogram_tester.ExpectBucketCount("Ash.DeskApi.LaunchDesk.Result", 1, 1);

  // Remove a desk.
  auto remove_desk_function =
      base::MakeRefCounted<WmDesksPrivateRemoveDeskFunction>();
  api_test_utils::RunFunctionAndReturnSingleResult(
      remove_desk_function.get(),
      R"([")" + desk_id->GetString() +
          R"(", { "combineDesks": false, "allowUndo": false }])",
      profile());

  // Wait for remove desk animation to settle.
  SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
      TestTimeouts::action_max_timeout(),
      CheckAreDesksModified(lacros_service) == false);
  histogram_tester.ExpectBucketCount("Ash.DeskApi.RemoveDesk.Result", 1, 1);
}

// Tests launch and removes desk. Waits for undo toast to expire.
// Disabled due to flakiness
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest,
                       DISABLED_LaunchAndAttemptUndo) {
  auto* lacros_service = chromeos::LacrosService::Get();
  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>()) {
    GTEST_SKIP() << "Unsupported ash version.";
  }

  // Launch a desk.
  auto launch_desk_function =
      base::MakeRefCounted<WmDesksPrivateLaunchDeskFunction>();
  base::HistogramTester histogram_tester;
  // The RunFunctionAndReturnSingleResult already asserts no error
  auto desk_id = api_test_utils::RunFunctionAndReturnSingleResult(
      launch_desk_function.get(), R"([{"deskName":"test"}])", profile());
  EXPECT_TRUE(desk_id->is_string());
  EXPECT_TRUE(
      base::Uuid::ParseCaseInsensitive(desk_id->GetString()).is_valid());

  // Wait for launch desk animation to settle.
  SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
      TestTimeouts::action_max_timeout(),
      CheckAreDesksModified(lacros_service) == false);

  histogram_tester.ExpectBucketCount("Ash.DeskApi.LaunchDesk.Result", 1, 1);

  // Remove a desk.
  auto remove_desk_function =
      base::MakeRefCounted<WmDesksPrivateRemoveDeskFunction>();
  api_test_utils::RunFunctionAndReturnSingleResult(
      remove_desk_function.get(),
      R"([")" + desk_id->GetString() +
          R"(", { "combineDesks": false, "allowUndo": true }])",
      profile());

  // Wait for remove desk animation to settle.
  SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
      TestTimeouts::action_max_timeout(),
      CheckAreDesksModified(lacros_service) == false);

  histogram_tester.ExpectBucketCount("Ash.DeskApi.RemoveDesk.Result", 1, 1);
}

// Tests remove desks failed.
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest,
                       RemoveDeskWithInvalidIdTest) {
  auto* lacros_service = chromeos::LacrosService::Get();

  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>()) {
    GTEST_SKIP() << "Unsupported ash version.";
  }
  base::HistogramTester histogram_tester;
  auto remove_desk_function =
      base::MakeRefCounted<WmDesksPrivateRemoveDeskFunction>();
  auto error = api_test_utils::RunFunctionAndReturnError(
      remove_desk_function.get(), R"(["invalid-id"])", profile());

  EXPECT_EQ(error, "InvalidIdError");
  histogram_tester.ExpectBucketCount("Ash.DeskApi.RemoveDesk.Result", 0, 1);
}

// Tests switch to different desk show trigger animation.
// Disable due to flakiness (crbug.com/1442077)
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest,
                       DISABLED_SwitchToDifferentDeskTest) {
  auto* lacros_service = chromeos::LacrosService::Get();

  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>() ||
      lacros_service->GetInterfaceVersion<crosapi::mojom::Desk>() <
          static_cast<int>(
              crosapi::mojom::Desk::MethodMinVersions::kSwitchDeskMinVersion)) {
    GTEST_SKIP() << "Unsupported ash version.";
  }

  // Get the active desk.
  auto get_active_desk_function =
      base::MakeRefCounted<WmDesksPrivateGetActiveDeskFunction>();
  // Asserts no error.
  auto desk_id = api_test_utils::RunFunctionAndReturnSingleResult(
      get_active_desk_function.get(), "[]", profile());
  ASSERT_TRUE(desk_id->is_string());
  EXPECT_TRUE(
      base::Uuid::ParseCaseInsensitive(desk_id->GetString()).is_valid());

  // Launch a desk.
  auto launch_desk_function =
      base::MakeRefCounted<WmDesksPrivateLaunchDeskFunction>();

  // Asserts no error.
  auto desk_id_1 = api_test_utils::RunFunctionAndReturnSingleResult(
      launch_desk_function.get(), R"([{"deskName":"test"}])", profile());
  ASSERT_TRUE(desk_id_1->is_string());
  EXPECT_TRUE(
      base::Uuid::ParseCaseInsensitive(desk_id_1->GetString()).is_valid());
  // Waiting for desk launch animation to settle
  SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
      TestTimeouts::action_max_timeout(),
      CheckAreDesksModified(lacros_service) == false);

  // Switches to the previous desk.
  auto switch_desk_function =
      base::MakeRefCounted<WmDesksPrivateSwitchDeskFunction>();

  api_test_utils::RunFunctionAndReturnSingleResult(
      switch_desk_function.get(), R"([")" + desk_id->GetString() + R"("])",
      profile());
  SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
      TestTimeouts::action_max_timeout(),
      CheckAreDesksModified(lacros_service) == false);

  auto get_active_desk_function_ =
      base::MakeRefCounted<WmDesksPrivateGetActiveDeskFunction>();
  // Asserts no error.
  auto desk_id_2 = api_test_utils::RunFunctionAndReturnSingleResult(
      get_active_desk_function_.get(), "[]", profile());
  ASSERT_TRUE(desk_id_2->is_string());
  EXPECT_EQ(desk_id->GetString(), desk_id_2->GetString());

  // Clean up desks.
  auto remove_desk_function =
      base::MakeRefCounted<WmDesksPrivateRemoveDeskFunction>();
  api_test_utils::RunFunctionAndReturnSingleResult(
      remove_desk_function.get(),
      R"([")" + desk_id_1->GetString() + R"(", { "combineDesks": false }])",
      profile());

  // Wait for remove desk animation to settle.
  SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
      TestTimeouts::action_max_timeout(),
      CheckAreDesksModified(lacros_service) == false);
}

// Tests switch to current desk should skip animation.
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest, SwitchToCurrentDeskTest) {
  auto* lacros_service = chromeos::LacrosService::Get();

  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>() ||
      lacros_service->GetInterfaceVersion<crosapi::mojom::Desk>() <
          static_cast<int>(
              crosapi::mojom::Desk::MethodMinVersions::kSwitchDeskMinVersion)) {
    GTEST_SKIP() << "Unsupported ash version.";
  }

  // Get the active desk.
  auto get_active_desk_function =
      base::MakeRefCounted<WmDesksPrivateGetActiveDeskFunction>();
  // Asserts no error.
  auto desk_id = api_test_utils::RunFunctionAndReturnSingleResult(
      get_active_desk_function.get(), "[]", profile());
  ASSERT_TRUE(desk_id->is_string());
  ASSERT_TRUE(
      base::Uuid::ParseCaseInsensitive(desk_id->GetString()).is_valid());

  // Switches to the current desk.
  auto switch_desk_function =
      base::MakeRefCounted<WmDesksPrivateSwitchDeskFunction>();
  api_test_utils::RunFunctionAndReturnSingleResult(
      switch_desk_function.get(), R"([")" + desk_id->GetString() + R"("])",
      profile());

  // Get the current desk.
  auto get_active_desk_function_ =
      base::MakeRefCounted<WmDesksPrivateGetActiveDeskFunction>();
  auto desk_id_1 = api_test_utils::RunFunctionAndReturnSingleResult(
      get_active_desk_function_.get(), "[]", profile());
  ASSERT_TRUE(desk_id_1->is_string());
  EXPECT_EQ(desk_id->GetString(), desk_id_1->GetString());
}

// Tests retrieve desk with deskID.
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest, GetDeskByIDTest) {
  auto* lacros_service = chromeos::LacrosService::Get();

  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>() ||
      lacros_service->GetInterfaceVersion<crosapi::mojom::Desk>() <
          static_cast<int>(crosapi::mojom::Desk::MethodMinVersions::
                               kGetDeskByIDMinVersion)) {
    GTEST_SKIP() << "Unsupported ash version.";
  }

  // Get desk Id of active desk.
  auto desk_id = api_test_utils::RunFunctionAndReturnSingleResult(
      base::MakeRefCounted<WmDesksPrivateGetActiveDeskFunction>().get(), "[]",
      profile());

  // Retrieve desk by Id.
  auto get_desk_by_id_function =
      base::MakeRefCounted<WmDesksPrivateGetDeskByIDFunction>();
  auto result = api_test_utils::RunFunctionAndReturnSingleResult(
      get_desk_by_id_function.get(), R"([")" + desk_id->GetString() + R"("])",
      profile());
  EXPECT_TRUE(result->is_dict());
  auto* desk_id_1 = result->GetDict().Find("deskUuid");
  auto* desk_name = result->GetDict().Find("deskName");
  ASSERT_TRUE(desk_id_1->is_string());
  EXPECT_EQ(desk_id->GetString(), desk_id_1->GetString());
  EXPECT_TRUE(desk_name->is_string());
}

// Tests retrieve desk with invalid deskID.
IN_PROC_BROWSER_TEST_F(DesksExtensionApiLacrosTest, GetDeskByInvalidIDTest) {
  auto* lacros_service = chromeos::LacrosService::Get();

  if (!lacros_service->IsAvailable<crosapi::mojom::Desk>() ||
      lacros_service->GetInterfaceVersion<crosapi::mojom::Desk>() <
          static_cast<int>(crosapi::mojom::Desk::MethodMinVersions::
                               kGetDeskByIDMinVersion)) {
    GTEST_SKIP() << "Unsupported ash version.";
  }

  auto get_desk_by_id_function =
      base::MakeRefCounted<WmDesksPrivateGetDeskByIDFunction>();
  auto error = api_test_utils::RunFunctionAndReturnError(
      get_desk_by_id_function.get(), R"(["invalid-id"])", profile());
  EXPECT_EQ(error, "InvalidIdError");
}

// TODO(b/254500921): Find a way to create new MRU window in ash-chrome and
// test save&recall in lacros.

}  // namespace extensions