chromium/chrome/browser/ash/guest_os/guest_os_shelf_utils_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/ash/guest_os/guest_os_shelf_utils.h"

#include <iterator>
#include <memory>
#include <optional>

#include "base/types/optional_util.h"
#include "chrome/browser/ash/crostini/crostini_test_helper.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
#include "chrome/browser/ash/guest_os/guest_os_session_tracker.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace guest_os {

namespace {

constexpr char container_token[] = "test_container_token";
constexpr char container_name[] = "container";

struct App {
  std::string desktop_file_id;
  std::string vm_name = crostini::kCrostiniDefaultVmName;
  std::string container_name = "container";
  std::string app_name;
  std::optional<std::string> startup_wm_class;
  std::optional<bool> startup_notify;
  std::optional<bool> no_display;
};

struct WindowIds {
  std::optional<std::string> app_id;
  std::optional<std::string> startup_id;
};

std::string GenAppId(const App& app) {
  return crostini::CrostiniTestHelper::GenerateAppId(
      app.desktop_file_id, app.vm_name, app.container_name);
}

std::string TestWaylandWindowIdWithToken(
    const std::string& app_suffix,
    const std::string& token = container_token) {
  return "org.chromium.guest_os." + token + ".wayland." + app_suffix;
}

std::string TestXWindowIdWithToken(const std::string& app_suffix,
                                   const std::string& token = container_token) {
  return "org.chromium.guest_os." + token + ".wmclass." + app_suffix;
}
}  // namespace

class GuestOsShelfUtilsTest : public testing::Test {
 public:
  std::string GetGuestOsShelfAppIdUsingProfile(Profile* profile,
                                               WindowIds window_ids) const {
    return GetGuestOsShelfAppId(profile, base::OptionalToPtr(window_ids.app_id),
                                base::OptionalToPtr(window_ids.startup_id));
  }

  std::string GetShelfAppId(WindowIds window_ids) {
    return GetGuestOsShelfAppIdUsingProfile(&testing_profile_, window_ids);
  }

  void SetGuestOsRegistry(std::vector<App> apps) {
    using AppLists = std::map<std::pair<std::string, std::string>,
                              vm_tools::apps::ApplicationList>;
    AppLists app_lists;
    for (App& in_app : apps) {
      AppLists::key_type key(std::move(in_app.vm_name),
                             std::move(in_app.container_name));
      AppLists::iterator app_list_it = app_lists.find(key);
      if (app_list_it == app_lists.cend()) {
        vm_tools::apps::ApplicationList app_list;
        app_list.set_vm_name(key.first);
        app_list.set_container_name(key.second);
        app_list_it = app_lists.emplace_hint(app_list_it, std::move(key),
                                             std::move(app_list));
      }
      vm_tools::apps::App& out_app = *app_list_it->second.add_apps();
      out_app.set_desktop_file_id(std::move(in_app.desktop_file_id));
      out_app.mutable_name()->add_values()->set_value(
          std::move(in_app.app_name));
      if (in_app.startup_wm_class)
        out_app.set_startup_wm_class(std::move(*in_app.startup_wm_class));
      if (in_app.startup_notify)
        out_app.set_startup_notify(*in_app.startup_notify);
      if (in_app.no_display)
        out_app.set_no_display(*in_app.no_display);
    }
    guest_os::GuestOsRegistryService service(&testing_profile_);
    for (AppLists::value_type& value : app_lists) {
      service.UpdateApplicationList(std::move(value.second));
    }
  }

  Profile* GetOffTheRecordProfile() {
    return testing_profile_.GetOffTheRecordProfile(
        Profile::OTRProfileID::CreateUniqueForTesting(), true);
  }

  void SetUp() override {
    guest_os::GuestOsSessionTracker::GetForProfile(&testing_profile_)
        ->AddGuestForTesting(
            guest_os::GuestId{guest_os::VmType::TERMINA,
                              crostini::kCrostiniDefaultVmName, container_name},
            container_token);
  }

 private:
  content::BrowserTaskEnvironment task_environment_;
  TestingProfile testing_profile_;
};

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdReturnsEmptyIdWhenCalledWithoutAnyParametersSet) {
  SetGuestOsRegistry({});

  EXPECT_EQ(GetShelfAppId(WindowIds()), "");
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdReturnsEmptyIdForIneligibleProfile) {
  EXPECT_EQ(
      GetGuestOsShelfAppIdUsingProfile(GetOffTheRecordProfile(), WindowIds()),
      "");
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdFindsAppWithEitherWindowIdOrAppId) {
  SetGuestOsRegistry({
      {.desktop_file_id = "cool.app"},
  });

  // App is found using wm app_id.
  EXPECT_EQ(GetShelfAppId({.app_id = TestXWindowIdWithToken("cool.app")}),
            GenAppId({.desktop_file_id = "cool.app"}));

  // App is found using app_id (For native Wayland apps).
  EXPECT_EQ(GetShelfAppId({.app_id = TestWaylandWindowIdWithToken("cool.app")}),
            GenAppId({.desktop_file_id = "cool.app"}));
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdFindsAppForDefaultContainerToken) {
  SetGuestOsRegistry({
      {.desktop_file_id = "cool.app"},
  });

  // App is found using wm app_id.
  EXPECT_EQ(
      GetShelfAppId({.app_id = TestXWindowIdWithToken("cool.app", "termina")}),
      GenAppId({.desktop_file_id = "cool.app"}));

  // App is found using app_id (For native Wayland apps).
  EXPECT_EQ(GetShelfAppId({.app_id = TestWaylandWindowIdWithToken("cool.app",
                                                                  "termina")}),
            GenAppId({.desktop_file_id = "cool.app"}));
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdCantFindAppWhenTokenIsInvalid) {
  EXPECT_EQ(GetShelfAppId({.app_id = TestXWindowIdWithToken("cool.app",
                                                            "invalid_token")}),
            "crostini:" + TestXWindowIdWithToken("cool.app", "invalid_token"));
}

TEST_F(GuestOsShelfUtilsTest, GetGuestOsShelfAppIdIgnoresWindowAppIdsCase) {
  SetGuestOsRegistry({
      {.desktop_file_id = "app"},
  });

  // App is found using capitalized App.
  EXPECT_EQ(GetShelfAppId({.app_id = TestXWindowIdWithToken("App")}),
            GenAppId({.desktop_file_id = "app"}));
}

TEST_F(
    GuestOsShelfUtilsTest,
    GetGuestOsShelfAppIdFindsAppWhenMultipleAppsInDifferentVmsShareDesktopFileIds) {
  SetGuestOsRegistry({
      {.desktop_file_id = "duplicate"},
      {.desktop_file_id = "duplicate", .vm_name = "vm 2"},
  });

  EXPECT_EQ(GetShelfAppId({.app_id = TestXWindowIdWithToken("duplicate")}),
            GenAppId({.desktop_file_id = "duplicate"}));
}

TEST_F(
    GuestOsShelfUtilsTest,
    GetGuestOsShelfAppIdFindsAppWhenMultipleAppsInDifferentContainersShareDesktopFileIds) {
  SetGuestOsRegistry({
      {.desktop_file_id = "duplicate"},
      {.desktop_file_id = "duplicate", .container_name = "container 2"},
  });

  EXPECT_EQ(GetShelfAppId({.app_id = TestXWindowIdWithToken("duplicate")}),
            GenAppId({.desktop_file_id = "duplicate"}));
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdDoesntFindAppWhenGivenUnregisteredAppIds) {
  SetGuestOsRegistry({});

  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.guest_os.test_container_"
                                     "token.wmclientleader.1234"}),
            "crostini:org.chromium.guest_os.test_container_token."
            "wmclientleader.1234");

  EXPECT_EQ(
      GetShelfAppId(
          {.app_id = "org.chromium.guest_os.test_container_token.xid.654321"}),
      "crostini:org.chromium.guest_os.test_container_token.xid.654321");

  EXPECT_EQ(
      GetShelfAppId(
          {.app_id =
               "org.chromium.guest_os.test_container_token.wayland.fancy.app"}),
      "crostini:org.chromium.guest_os.test_container_token.wayland.fancy.app");
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdPrependsCorrectGenericPrefixForUnregisteredAppIds) {
  SetGuestOsRegistry({});

  EXPECT_EQ(
      GetShelfAppId({.app_id = "org.chromium.guest_os.termina.wmclass.1"}),
      "crostini:org.chromium.guest_os.termina.wmclass.1");

  EXPECT_EQ(GetShelfAppId({.app_id = "unregistered_app"}),
            "crostini:unregistered_app");

  EXPECT_EQ(GetShelfAppId({.app_id = "org.chromium.guest_os.borealis"
                                     ".xid.1"}),
            "borealis_anon:org.chromium.guest_os.borealis.xid.1");
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdCanFindAppUsingItsStartupWmClass) {
  SetGuestOsRegistry({
      {.desktop_file_id = "app", .startup_wm_class = "app_start"},
  });

  // App is found using it's startup_wm_class.
  EXPECT_EQ(
      GetShelfAppId(
          {.app_id =
               "org.chromium.guest_os.test_container_token.wmclass.app_start"}),
      GenAppId({.desktop_file_id = "app"}));
}

TEST_F(
    GuestOsShelfUtilsTest,
    GetGuestOsShelfAppIdCantFindAppIfMultipleAppsStartupWmClassesAreTheSame) {
  SetGuestOsRegistry({
      {.desktop_file_id = "app2", .startup_wm_class = "app2"},
      {.desktop_file_id = "app3", .startup_wm_class = "app2"},
  });

  // Neither app is found, as they can't be disambiguated.
  EXPECT_EQ(
      GetShelfAppId(
          {.app_id =
               "org.chromium.guest_os.test_container_token.wmclass.app2"}),
      "crostini:org.chromium.guest_os.test_container_token.wmclass.app2");
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdCanFindAppUsingStartupIdIfStartupNotifyIsTrue) {
  SetGuestOsRegistry({
      {.desktop_file_id = "app", .startup_notify = true},
      {.desktop_file_id = "app2", .startup_notify = false},
  });

  // App's startup_notify is true, so it can be found using startup_id.
  EXPECT_EQ(GetShelfAppId({.startup_id = "app"}),
            GenAppId({.desktop_file_id = "app"}));

  // App's startup_notify is true, so it can be found using startup_id, even if
  // app_id is set.
  EXPECT_EQ(GetShelfAppId({.app_id = "unknown_app_id", .startup_id = "app"}),
            GenAppId({.desktop_file_id = "app"}));

  // App's startup_notify is false, so startup_id is ignored and no app was
  // found.
  EXPECT_EQ(GetShelfAppId({.app_id = "unknown_app_id", .startup_id = "app2"}),
            "crostini:unknown_app_id");
}

TEST_F(
    GuestOsShelfUtilsTest,
    GetGuestOsShelfAppIdFindsAppUsingStartupIdsIfMultipleAppsInDifferentContainersShareDesktopFileIds) {
  SetGuestOsRegistry({
      {.desktop_file_id = "duplicate", .startup_notify = true},
      {.desktop_file_id = "duplicate",
       .container_name = "container 2",
       .startup_notify = true},
  });

  EXPECT_EQ(GetShelfAppId({.app_id = TestXWindowIdWithToken("duplicate"),
                           .startup_id = "duplicate"}),
            GenAppId({.desktop_file_id = "duplicate"}));
}

TEST_F(GuestOsShelfUtilsTest, GetGuestOsShelfAppIdCanFindAppsByName) {
  SetGuestOsRegistry({
      {.desktop_file_id = "app", .app_name = "name"},
  });

  // App found by app_name: "name".
  EXPECT_EQ(
      GetShelfAppId(
          {.app_id =
               "org.chromium.guest_os.test_container_token.wmclass.name"}),
      GenAppId({.desktop_file_id = "app"}));
}

TEST_F(GuestOsShelfUtilsTest,
       GetGuestOsShelfAppIdDoesntFindAppsByNameIfTheyHaveNoDisplaySet) {
  // One no_display app.
  SetGuestOsRegistry({
      {.desktop_file_id = "another_app",
       .app_name = "name",
       .no_display = true},
  });

  // No app is found.
  EXPECT_EQ(
      GetShelfAppId(
          {.app_id =
               "org.chromium.guest_os.test_container_token.wmclass.name"}),
      "crostini:org.chromium.guest_os.test_container_token.wmclass.name");

  // Two apps with the same name, where one is no_display.
  SetGuestOsRegistry({
      {.desktop_file_id = "app", .app_name = "name"},
      {.desktop_file_id = "another_app",
       .app_name = "name",
       .no_display = true},
  });

  // The app without no_display set is found.
  EXPECT_EQ(
      GetShelfAppId(
          {.app_id =
               "org.chromium.guest_os.test_container_token.wmclass.name"}),
      GenAppId({.desktop_file_id = "app"}));
}

}  // namespace guest_os