chromium/components/component_updater/android/component_loader_policy_unittests.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/component_updater/android/component_loader_policy.h"

#include <fcntl.h>
#include <jni.h>
#include <stdint.h>

#include <iterator>
#include <string>
#include <utility>
#include <vector>

#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "base/version.h"
#include "components/crash/core/common/crash_key.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace component_updater {

namespace {

constexpr char kComponentId[] = "llkgjffcdpffmhiakmfcdcblohccpfmo";
// This hash corresponds to kComponentId.
constexpr uint8_t kSha256Hash[] = {
    0xbb, 0xa6, 0x95, 0x52, 0x3f, 0x55, 0xc7, 0x80, 0xac, 0x52, 0x32,
    0x1b, 0xe7, 0x22, 0xf5, 0xce, 0x6a, 0xfd, 0x9c, 0x9e, 0xa9, 0x2a,
    0x0b, 0x50, 0x60, 0x2b, 0x7f, 0x6c, 0x64, 0x80, 0x09, 0x04};

constexpr char kMockComponentHistogramName[] =
    "ComponentUpdater.AndroidComponentLoader.LoadStatus.MockComponent";

void GetPkHash(std::vector<uint8_t>* hash) {
  hash->assign(std::begin(kSha256Hash), std::end(kSha256Hash));
}

std::vector<int> OpenFileFds(const base::FilePath& base,
                             const std::vector<std::string>& files) {
  std::vector<int> fds;
  for (const std::string& file : files) {
    base::FilePath path = base.AppendASCII(file);
    fds.push_back(open(path.MaybeAsASCII().c_str(), O_RDONLY));
  }
  return fds;
}

using OnLoadedTestCallBack =
    base::OnceCallback<void(const base::Version&,
                            base::flat_map<std::string, base::ScopedFD>&,
                            base::Value::Dict)>;
using OnFailedTestCallBack = base::OnceCallback<void(ComponentLoadResult)>;

class MockLoaderPolicy : public ComponentLoaderPolicy {
 public:
  explicit MockLoaderPolicy(OnLoadedTestCallBack on_loaded,
                            OnFailedTestCallBack on_failed)
      : on_loaded_(std::move(on_loaded)), on_failed_(std::move(on_failed)) {}

  MockLoaderPolicy()
      : on_loaded_(base::DoNothing()), on_failed_(base::DoNothing()) {}

  ~MockLoaderPolicy() override = default;

  MockLoaderPolicy(const MockLoaderPolicy&) = delete;
  MockLoaderPolicy& operator=(const MockLoaderPolicy&) = delete;

  void ComponentLoaded(const base::Version& version,
                       base::flat_map<std::string, base::ScopedFD>& fd_map,
                       base::Value::Dict manifest) override {
    std::move(on_loaded_).Run(version, fd_map, std::move(manifest));
  }

  void ComponentLoadFailed(ComponentLoadResult error) override {
    std::move(on_failed_).Run(error);
  }

  void GetHash(std::vector<uint8_t>* hash) const override { GetPkHash(hash); }

  std::string GetMetricsSuffix() const override { return "MockComponent"; }

 private:
  OnLoadedTestCallBack on_loaded_;
  OnFailedTestCallBack on_failed_;
};

void VerifyComponentLoaded(base::OnceClosure on_done,
                           const base::Version& version,
                           base::flat_map<std::string, base::ScopedFD>& fd_map,
                           base::Value::Dict manifest) {
  EXPECT_EQ(version.GetString(), "123.456.789");
  EXPECT_EQ(fd_map.size(), 2u);
  EXPECT_NE(fd_map.find("file1.txt"), fd_map.end());
  EXPECT_NE(fd_map.find("file2.txt"), fd_map.end());

  std::move(on_done).Run();
}

}  // namespace

class AndroidComponentLoaderPolicyTest : public testing::Test {
 public:
  AndroidComponentLoaderPolicyTest() {
    crash_reporter::ResetCrashKeysForTesting();
    crash_reporter::InitializeCrashKeysForTesting();
  }
  ~AndroidComponentLoaderPolicyTest() override {
    crash_reporter::ResetCrashKeysForTesting();
  }

  AndroidComponentLoaderPolicyTest(const AndroidComponentLoaderPolicyTest&) =
      delete;
  AndroidComponentLoaderPolicyTest& operator=(
      const AndroidComponentLoaderPolicyTest&) = delete;

  void SetUp() override {
    env_ = base::android::AttachCurrentThread();
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
  }

 protected:
  void WriteFile(const std::string& file_name, const std::string& content) {
    ASSERT_TRUE(
        base::WriteFile(temp_dir_.GetPath().AppendASCII(file_name), content));
    files_.push_back(file_name);
  }

  std::vector<int> GetFileFds() const {
    return OpenFileFds(temp_dir_.GetPath(), files_);
  }

  raw_ptr<JNIEnv> env_ = nullptr;
  std::vector<std::string> files_;
  base::HistogramTester histogram_tester_;

 private:
  base::ScopedTempDir temp_dir_;
};

TEST_F(AndroidComponentLoaderPolicyTest, TestValidManifest) {
  base::test::TaskEnvironment task_environment;

  WriteFile("file1.txt", "1");
  WriteFile("file2.txt", "2");
  WriteFile("manifest.json",
            "{\n\"manifest_version\": 2,\n\"version\": \"123.456.789\"\n}");

  base::RunLoop run_loop;
  auto* android_policy =
      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>(
          base::BindOnce(&VerifyComponentLoaded, run_loop.QuitClosure()),
          base::BindOnce([](ComponentLoadResult) { FAIL(); })));

  android_policy->ComponentLoaded(
      env_, base::android::ToJavaArrayOfStrings(env_, files_),
      base::android::ToJavaIntArray(env_, GetFileFds()));
  run_loop.Run();

  histogram_tester_.ExpectBucketCount(kMockComponentHistogramName,
                                      ComponentLoadResult::kComponentLoaded, 1);
  histogram_tester_.ExpectTotalCount(kMockComponentHistogramName, 1);
  EXPECT_EQ("ORIGIN_TRIALS-123.456.789",
            crash_reporter::GetCrashKeyValue("crx-components"));
}

TEST_F(AndroidComponentLoaderPolicyTest, TestMissingManifest) {
  base::test::TaskEnvironment task_environment;

  WriteFile("file.txt", "test");

  base::RunLoop run_loop;
  auto* android_policy =
      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>(
          base::BindOnce([](const base::Version& version,
                            base::flat_map<std::string, base::ScopedFD>& fd_map,
                            base::Value::Dict manifest) { FAIL(); }),
          base::BindLambdaForTesting([&](ComponentLoadResult error) {
            ASSERT_EQ(error, ComponentLoadResult::kMissingManifest);
            run_loop.Quit();
          })));

  android_policy->ComponentLoaded(
      env_, base::android::ToJavaArrayOfStrings(env_, files_),
      base::android::ToJavaIntArray(env_, GetFileFds()));
  run_loop.Run();

  histogram_tester_.ExpectBucketCount(kMockComponentHistogramName,
                                      ComponentLoadResult::kMissingManifest, 1);
  histogram_tester_.ExpectTotalCount(kMockComponentHistogramName, 1);
}

TEST_F(AndroidComponentLoaderPolicyTest, TestInvalidVersion) {
  base::test::TaskEnvironment task_environment;

  WriteFile("file.txt", "test");
  WriteFile("manifest.json",
            "{\n\"manifest_version\": 2,\n\"version\": \"\"\n}");

  base::RunLoop run_loop;
  auto* android_policy =
      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>(
          base::BindOnce([](const base::Version& version,
                            base::flat_map<std::string, base::ScopedFD>& fd_map,
                            base::Value::Dict manifest) { FAIL(); }),
          base::BindLambdaForTesting([&](ComponentLoadResult error) {
            ASSERT_EQ(error, ComponentLoadResult::kInvalidVersion);
            run_loop.Quit();
          })));

  android_policy->ComponentLoaded(
      env_, base::android::ToJavaArrayOfStrings(env_, files_),
      base::android::ToJavaIntArray(env_, GetFileFds()));
  run_loop.Run();

  histogram_tester_.ExpectBucketCount(kMockComponentHistogramName,
                                      ComponentLoadResult::kInvalidVersion, 1);
  histogram_tester_.ExpectTotalCount(kMockComponentHistogramName, 1);
}

TEST_F(AndroidComponentLoaderPolicyTest, TestInvalidManifest) {
  base::test::TaskEnvironment task_environment;

  WriteFile("file.txt", "test");
  WriteFile("manifest.json", "{\n\"manifest_version\":}");

  base::RunLoop run_loop;
  auto* android_policy =
      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>(
          base::BindOnce([](const base::Version& version,
                            base::flat_map<std::string, base::ScopedFD>& fd_map,
                            base::Value::Dict manifest) { FAIL(); }),
          base::BindLambdaForTesting([&](ComponentLoadResult error) {
            ASSERT_EQ(error, ComponentLoadResult::kMalformedManifest);
            run_loop.Quit();
          })));

  android_policy->ComponentLoaded(
      env_, base::android::ToJavaArrayOfStrings(env_, files_),
      base::android::ToJavaIntArray(env_, GetFileFds()));
  run_loop.Run();

  histogram_tester_.ExpectBucketCount(
      kMockComponentHistogramName, ComponentLoadResult::kMalformedManifest, 1);
  histogram_tester_.ExpectTotalCount(kMockComponentHistogramName, 1);
}

TEST_F(AndroidComponentLoaderPolicyTest, TestGetComponentId) {
  base::test::TaskEnvironment task_environment;

  auto* android_policy =
      new AndroidComponentLoaderPolicy(std::make_unique<MockLoaderPolicy>());

  base::android::ScopedJavaLocalRef<jstring> jcomponentId =
      android_policy->GetComponentId(env_);

  EXPECT_EQ(base::android::ConvertJavaStringToUTF8(jcomponentId), kComponentId);
}

}  // namespace component_updater