// 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/mac/setup/keystone.h"

#import <Foundation/Foundation.h>

#include <vector>

#include "base/apple/foundation_util.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "base/version.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/updater_branding.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace updater {

class KeystoneTest : public testing::Test {
 protected:
  void SetUp() override {
    ASSERT_TRUE(temp_keystone_dir_.CreateUniqueTempDir());

    base::FilePath ticket_path =
        temp_keystone_dir_.GetPath().Append("TicketStore");
    ASSERT_TRUE(base::CreateDirectory(ticket_path));

    base::FilePath test_data_path;
    ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_path));
    test_data_path = test_data_path.Append("updater");

    ASSERT_TRUE(
        base::CopyFile(test_data_path.Append("Keystone.legacy.ticketstore"),
                       ticket_path.Append("Keystone.ticketstore")));
    ASSERT_TRUE(base::CopyFile(
        test_data_path.Append("CountingMetrics.plist"),
        temp_keystone_dir_.GetPath().Append("CountingMetrics.plist")));
  }

  base::ScopedTempDir temp_keystone_dir_;
};

TEST_F(KeystoneTest, CreateEmptyPlistFile) {
  static constexpr int kPermissionsMask = base::FILE_PERMISSION_READ_BY_USER |
                                          base::FILE_PERMISSION_WRITE_BY_USER |
                                          base::FILE_PERMISSION_READ_BY_GROUP |
                                          base::FILE_PERMISSION_READ_BY_OTHERS;
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  // Verify plist file is created if not present.
  const base::FilePath plist_path = temp_dir.GetPath().Append("subdir").Append(
      LEGACY_GOOGLE_UPDATE_APPID ".plist");
  EXPECT_TRUE(CreateLegacyPlistFileForTesting(
      UpdaterScope::kUser, temp_dir.GetPath(), "subdir", ".plist"));
  EXPECT_TRUE(base::PathExists(plist_path));
  int mode = 0;
  EXPECT_TRUE(base::GetPosixFilePermissions(plist_path, &mode));
  EXPECT_EQ(mode, kPermissionsMask);

  {
    // Verify the plist is not re-created when contents didn't change.
    base::Time previous_mtime = base::Time::Now() - base::Days(1);
    EXPECT_TRUE(base::TouchFile(plist_path, previous_mtime, previous_mtime));
    EXPECT_TRUE(CreateLegacyPlistFileForTesting(
        UpdaterScope::kUser, temp_dir.GetPath(), "subdir", ".plist"));
    base::File::Info info;
    EXPECT_TRUE(base::GetFileInfo(plist_path, &info));
    EXPECT_EQ(info.last_modified, previous_mtime);
  }

  @autoreleasepool {
    // Verify the plist is re-created when contents needs update.
    NSURL* const url = base::apple::FilePathToNSURL(plist_path);
    EXPECT_TRUE([@{@"foo" : @2} writeToURL:url error:nil]);
    base::Time previous_mtime = base::Time::Now() - base::Days(1);
    EXPECT_TRUE(base::TouchFile(plist_path, previous_mtime, previous_mtime));
    EXPECT_TRUE(CreateLegacyPlistFileForTesting(
        UpdaterScope::kUser, temp_dir.GetPath(), "subdir", ".plist"));
    base::File::Info info;
    EXPECT_TRUE(base::GetFileInfo(plist_path, &info));
    EXPECT_NE(info.last_modified, previous_mtime);
    mode = 0;
    EXPECT_TRUE(base::GetPosixFilePermissions(plist_path, &mode));
    EXPECT_EQ(mode, kPermissionsMask);
  }
}

TEST_F(KeystoneTest, MigrateKeystoneApps) {
  std::vector<RegistrationRequest> registration_requests;
  MigrateKeystoneApps(
      temp_keystone_dir_.GetPath(),
      base::BindLambdaForTesting(
          [&registration_requests](const RegistrationRequest& request) {
            registration_requests.push_back(request);
          }));

  EXPECT_EQ(registration_requests.size(), 4u);

  EXPECT_EQ(registration_requests[0].app_id, "com.chromium.corruptedapp");
  EXPECT_TRUE(registration_requests[0].brand_code.empty());
  EXPECT_TRUE(registration_requests[0].brand_path.empty());
  EXPECT_EQ(registration_requests[0].ap, "canary");
  EXPECT_EQ(registration_requests[0].version, "1.2.1");
  EXPECT_EQ(registration_requests[0].existence_checker_path,
            base::FilePath("/"));
  EXPECT_FALSE(registration_requests[0].dla);   // Value is too big.
  EXPECT_FALSE(registration_requests[0].dlrc);  // Value is too small.

  EXPECT_EQ(registration_requests[1].app_id, "com.chromium.popularapp");
  EXPECT_TRUE(registration_requests[1].brand_code.empty());
  EXPECT_EQ(registration_requests[1].brand_path, base::FilePath("/"));
  EXPECT_EQ(registration_requests[1].ap, "GOOG");
  EXPECT_EQ(registration_requests[1].version, "101.100.1000.9999");
  EXPECT_EQ(registration_requests[1].existence_checker_path,
            base::FilePath("/"));
  EXPECT_EQ(registration_requests[1].cohort, "TestCohort");
  EXPECT_EQ(registration_requests[1].cohort_hint, "TestCohortHint");
  EXPECT_EQ(registration_requests[1].cohort_name, "TestCohortName");
  EXPECT_EQ(registration_requests[1].dla.value(), 5921);
  EXPECT_EQ(registration_requests[1].dlrc.value(), 5922);

  EXPECT_EQ(registration_requests[2].app_id, "com.chromium.kipple");
  EXPECT_TRUE(registration_requests[2].brand_path.empty());
  EXPECT_EQ(registration_requests[2].existence_checker_path,
            base::FilePath("/"));
  EXPECT_FALSE(registration_requests[2].dla);   // No data.
  EXPECT_FALSE(registration_requests[2].dlrc);  // String value is ignored.

  EXPECT_EQ(registration_requests[3].app_id, "com.chromium.nonexistapp");
  EXPECT_TRUE(registration_requests[3].brand_path.empty());
}

}  // namespace updater
