chromium/chrome/updater/win/installer/msi_custom_action_unittest.cc

// Copyright 2023 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/updater/win/installer/msi_custom_action.h"

#include <cstdint>
#include <string>
#include <vector>

#include "base/files/file_path.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_reg_util_win.h"
#include "chrome/updater/test/unit_test_util.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/installer_api.h"
#include "chrome/updater/win/win_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace updater {
namespace {

using ::testing::_;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SetArgReferee;
using ::testing::TestWithParam;
using ::testing::ValuesIn;

class MockMsiHandle : public MsiHandleInterface {
 public:
  ~MockMsiHandle() override = default;
  MOCK_METHOD(UINT,
              GetProperty,
              (const std::wstring& name,
               std::vector<wchar_t>& value,
               DWORD& value_length),
              (const, override));
  MOCK_METHOD(UINT,
              SetProperty,
              (const std::string& name, const std::string& value),
              (override));
  MOCK_METHOD(MSIHANDLE, CreateRecord, (UINT field_count), (override));
  MOCK_METHOD(UINT,
              RecordSetString,
              (MSIHANDLE record_handle,
               UINT field_index,
               const std::wstring& value),
              (override));
  MOCK_METHOD(int,
              ProcessMessage,
              (INSTALLMESSAGE message_type, MSIHANDLE record_handle),
              (override));
};

struct MsiSetTagsTestCase {
  const std::string msi_file_name;
  const std::string expected_tag_string;
};

}  // namespace

class MsiSetTagsTest : public TestWithParam<MsiSetTagsTestCase> {};

INSTANTIATE_TEST_SUITE_P(
    MsiSetTagsTestCases,
    MsiSetTagsTest,
    ValuesIn(std::vector<MsiSetTagsTestCase>{
        // single tag parameter.
        {"GUH-brand-only.msi", "brand=QAQA"},

        // single tag parameter ending in an ampersand.
        {"GUH-ampersand-ending.msi", "brand=QAQA&"},

        // multiple tag parameters.
        {"GUH-multiple.msi",
         "appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&iid={2D8C18E9-8D3A-"
         "4EFC-6D61-AE23E3530EA2}&lang=en&browser=4&usagestats=0&appname="
         "Google%20Chrome&needsadmin=prefers&brand=CHMB&installdataindex="
         "defaultbrowser"},

        // MSI file size greater than `kMaxBufferLength` of 80KB.
        {"GUH-size-greater-than-max.msi",
         "appguid={8237E44A-0054-442C-B6B6-EA0509993955}&appname=Google%"
         "20Chrome%20Beta&needsAdmin=True&brand=GGLL"},

        // special character in the tag value.
        {"GUH-special-value.msi", "brand=QA*A"},

        // untagged msi.
        {"GUH-untagged.msi", {}},

        // invalid magic signature "Gact2.0Foo".
        {"GUH-invalid-marker.msi", {}},

        // invalid characters in the tag key.
        {"GUH-invalid-key.msi", {}},

        // invalid tag format.
        {"GUH-bad-format.msi", {}},

        // invalid tag format.
        {"GUH-bad-format2.msi", {}},

        // untagged.
        {"GUH-untagged.msi", {}},
    }));

TEST_P(MsiSetTagsTest, MsiSetTags) {
  MockMsiHandle mock_msi_handle;
  const std::wstring msi_file_path = test::GetTestFilePath("tagged_msi")
                                         .AppendASCII(GetParam().msi_file_name)
                                         .value();
  EXPECT_CALL(mock_msi_handle,
              GetProperty(std::wstring(L"OriginalDatabase"), _, Eq(1U)))
      .WillOnce(DoAll(SetArgReferee<2>(msi_file_path.length()),
                      Return(ERROR_MORE_DATA)));
  EXPECT_CALL(mock_msi_handle, GetProperty(std::wstring(L"OriginalDatabase"), _,
                                           Eq(msi_file_path.length() + 1U)))
      .WillOnce(DoAll(SetArgReferee<1>(std::vector(msi_file_path.begin(),
                                                   msi_file_path.end())),
                      SetArgReferee<2>(msi_file_path.length()),
                      Return(ERROR_SUCCESS)));
  if (!GetParam().expected_tag_string.empty()) {
    EXPECT_CALL(mock_msi_handle, SetProperty(std::string("TAGSTRING"),
                                             GetParam().expected_tag_string))
        .WillOnce(Return(ERROR_SUCCESS));
  }

  MsiSetTags(mock_msi_handle);
}

TEST(MsiCustomActionTest, ExtractTagInfoFromInstaller) {
  EXPECT_EQ(ExtractTagInfoFromInstaller(0), static_cast<UINT>(ERROR_SUCCESS));
}

class MsiSetInstallerResultTest
    : public ::testing::TestWithParam<std::tuple<bool, bool, bool>> {
 protected:
  void SetUp() override {
    ASSERT_NO_FATAL_FAILURE(
        registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
    if (SetResults()) {
      InstallerOutcome installer_outcome = {};
      installer_outcome.installer_result = InstallerApiResult::kCustomError;
      installer_outcome.installer_text = "some text";
      EXPECT_TRUE(SetInstallerOutcomeForTesting(
          UpdaterScope::kSystem, base::WideToASCII(kAppId), installer_outcome));
      ASSERT_TRUE(GetInstallerOutcome(UpdaterScope::kSystem,
                                      base::WideToASCII(kAppId)));

      if (OnlyInUpdaterKey()) {
        EXPECT_EQ(base::win::RegKey(HKEY_LOCAL_MACHINE, L"", Wow6432(KEY_WRITE))
                      .DeleteKey(GetAppClientStateKey(kAppId).c_str()),
                  ERROR_SUCCESS);
      }
    }
  }

  bool SetResults() const { return std::get<0>(GetParam()); }
  bool OnlyInUpdaterKey() const { return std::get<1>(GetParam()); }
  bool ValidCustomActionData() const { return std::get<2>(GetParam()); }

  const std::wstring kAppId = L"{55d6c27c-8b97-4b76-a691-2df8810004ed}";
  registry_util::RegistryOverrideManager registry_override_manager_;
};

INSTANTIATE_TEST_SUITE_P(SetResults_OnlyInUpdaterKey_ValidCustomActionData,
                         MsiSetInstallerResultTest,
                         ::testing::Combine(::testing::Bool(),
                                            ::testing::Bool(),
                                            ::testing::Bool()));

TEST_P(MsiSetInstallerResultTest, MsiSetInstallerResult) {
  MockMsiHandle mock_msi_handle;
  if (ValidCustomActionData()) {
    EXPECT_CALL(mock_msi_handle,
                GetProperty(std::wstring(L"CustomActionData"), _, Eq(1U)))
        .WillOnce(
            DoAll(SetArgReferee<2>(kAppId.length()), Return(ERROR_MORE_DATA)));
    EXPECT_CALL(mock_msi_handle, GetProperty(std::wstring(L"CustomActionData"),
                                             _, Eq(kAppId.length() + 1U)))
        .WillOnce(
            DoAll(SetArgReferee<1>(std::vector(kAppId.begin(), kAppId.end())),
                  SetArgReferee<2>(kAppId.length()), Return(ERROR_SUCCESS)));
  } else {
    EXPECT_CALL(mock_msi_handle,
                GetProperty(std::wstring(L"CustomActionData"), _, Eq(1U)))
        .WillOnce(Return(ERROR_SUCCESS));
  }
  if (ValidCustomActionData() && SetResults()) {
    EXPECT_CALL(mock_msi_handle, CreateRecord(0)).WillOnce(Return(33));
    EXPECT_CALL(mock_msi_handle,
                RecordSetString(33, 0, std::wstring(L"some text")))
        .WillOnce(Return(ERROR_SUCCESS));
    EXPECT_CALL(mock_msi_handle, ProcessMessage(INSTALLMESSAGE_ERROR, 33))
        .WillOnce(Return(ERROR_SUCCESS));
  }

  MsiSetInstallerResult(mock_msi_handle);
}

TEST(MsiCustomActionTest, ShowInstallerResultUIString) {
  EXPECT_EQ(ShowInstallerResultUIString(0), static_cast<UINT>(ERROR_SUCCESS));
}

}  // namespace updater