chromium/chrome/browser/shortcuts/shortcut_creator_win_unittest.cc

// Copyright 2024 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/shortcuts/shortcut_creator.h"

#include <algorithm>
#include <functional>

#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/hash/hash.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_path_override.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/test/test_shortcut_win.h"
#include "base/win/scoped_com_initializer.h"
#include "chrome/browser/profiles/profile_shortcut_manager_win.h"
#include "chrome/browser/shortcuts/platform_util_win.h"
#include "chrome/browser/shortcuts/shortcut_creation_test_support.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_family.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "url/gurl.h"

namespace shortcuts {

namespace {

constexpr char kShortcutFileName[] = "Test Name.lnk";
constexpr char16_t kShortcutName[] = u"Test Name";
constexpr char16_t kShortcutWithInvalidCharsName[] = u"Test|Name";

struct ImageDesc {
  int size;
  SkColor color;
};

gfx::ImageFamily CreateImageFamily(std::vector<ImageDesc> images) {
  gfx::ImageFamily image_family;
  for (const ImageDesc& image_desc : images) {
    image_family.Add(gfx::test::CreateImage(image_desc.size, image_desc.color));
  }
  return image_family;
}

base::FilePath GetUserDesktopPath() {
  return base::PathService::CheckedGet(base::DIR_USER_DESKTOP);
}

class ShortcutCreatorWinTest : public testing::Test {
 protected:
  const GURL kUrl = GURL("https://example.com/test?query=1s&basdf");
  void SetUp() override {
    ASSERT_TRUE(default_profile_path_.CreateUniqueTempDir());
  }
  void VerifyShortcut(const base::FilePath& current_shortcut_path) const;
  const base::FilePath& default_profile_path() const {
    return default_profile_path_.GetPath();
  }

 private:
  base::ScopedTempDir default_profile_path_;
  base::ScopedPathOverride desktop_override_{base::DIR_USER_DESKTOP};
  base::win::ScopedCOMInitializer com_initializer_;
  base::test::SingleThreadTaskEnvironment task_environment_;
};

void ShortcutCreatorWinTest::VerifyShortcut(
    const base::FilePath& current_shortcut_path) const {
  base::win::ShortcutProperties properties;
  properties.set_target(GetChromeProxyPath());
  std::string url_hash =
      base::NumberToString(base::PersistentHash(kUrl.spec()));
  base::FilePath target_path = default_profile_path()
                                   .Append(kWebShortcutsIconDirName)
                                   .AppendASCII(url_hash)
                                   .Append(L"shortcut.ico");
  properties.set_icon(target_path, 0);
  properties.set_arguments(base::StrCat(
      {profiles::internal::CreateProfileShortcutFlags(default_profile_path(),
                                                      /*incognito=*/false),
       L" --", base::ASCIIToWide(switches::kIgnoreProfileDirectoryIfNotExists),
       L" ", base::ASCIIToWide(kUrl.spec())}));
  const base::FilePath expected_shortcut_path =
      GetUserDesktopPath().AppendASCII(kShortcutFileName);
  ASSERT_EQ(expected_shortcut_path, current_shortcut_path);
  ASSERT_TRUE(base::PathExists(current_shortcut_path));
  base::win::ValidateShortcut(current_shortcut_path, properties);
  // TODO(b/333024272): Verify images in .ico file are correct.

  // Verify that the shortcut matchers work correctly as well.
  EXPECT_THAT(expected_shortcut_path, IsShortcutForUrl(kUrl));
  EXPECT_THAT(expected_shortcut_path,
              IsShortcutForProfile(default_profile_path()));
  EXPECT_THAT(expected_shortcut_path, IsShortcutWithTitle(kShortcutName));
}

TEST_F(ShortcutCreatorWinTest, ShortcutCreated) {
  gfx::ImageFamily images =
      CreateImageFamily({{.size = 16, .color = SK_ColorCYAN},
                         {.size = 32, .color = SK_ColorBLUE},
                         {.size = 256, .color = SK_ColorGREEN}});

  ShortcutMetadata metadata(default_profile_path(), kUrl, kShortcutName,
                            std::move(images));
  base::test::TestFuture<const base::FilePath&, ShortcutCreatorResult> future;

  CreateShortcutOnUserDesktop(std::move(metadata), future.GetCallback());
  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ShortcutCreatorResult::kSuccess,
            future.Get<ShortcutCreatorResult>());

  VerifyShortcut(future.Get<base::FilePath>());
}

TEST_F(ShortcutCreatorWinTest, ShortcutWithInvalidCharsInNameCreated) {
  gfx::ImageFamily images =
      CreateImageFamily({{.size = 16, .color = SK_ColorCYAN},
                         {.size = 32, .color = SK_ColorBLUE},
                         {.size = 256, .color = SK_ColorGREEN}});

  // The '|' in kShortcutWithInvalidCharsName should be replaced with ' ' and
  // match kShortcutFileName.
  ShortcutMetadata metadata(default_profile_path(), kUrl,
                            kShortcutWithInvalidCharsName, std::move(images));
  base::test::TestFuture<const base::FilePath&, ShortcutCreatorResult> future;

  CreateShortcutOnUserDesktop(std::move(metadata), future.GetCallback());
  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(ShortcutCreatorResult::kSuccess,
            future.Get<ShortcutCreatorResult>());

  VerifyShortcut(future.Get<base::FilePath>());
}

}  // namespace

}  // namespace shortcuts