chromium/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_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/browser/apps/app_service/promise_apps/promise_app_registry_cache.h"

#include "base/scoped_observation.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_metrics.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_update.h"
#include "chrome/browser/apps/app_service/promise_apps/promise_app_utils.h"
#include "components/services/app_service/public/cpp/package_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace apps {

const PackageId kTestPackageId(PackageType::kArc, "test.package.name");

class PromiseAppRegistryCacheTest : public testing::Test {
 public:
  void SetUp() override {
    cache_ = std::make_unique<PromiseAppRegistryCache>();
  }

  PromiseAppRegistryCache* cache() { return cache_.get(); }

  base::HistogramTester& histogram_tester() { return histogram_tester_; }

 private:
  std::unique_ptr<PromiseAppRegistryCache> cache_;
  base::HistogramTester histogram_tester_;
};

TEST_F(PromiseAppRegistryCacheTest, AddPromiseAppToCache) {
  auto promise_app = std::make_unique<PromiseApp>(kTestPackageId);
  ASSERT_FALSE(cache()->HasPromiseApp(kTestPackageId));
  cache()->OnPromiseApp(std::move(promise_app));
  ASSERT_TRUE(cache()->HasPromiseApp(kTestPackageId));
  histogram_tester().ExpectBucketCount(
      kPromiseAppLifecycleEventHistogram,
      PromiseAppLifecycleEvent::kCreatedInCache, 1);
}

TEST_F(PromiseAppRegistryCacheTest, UpdatePromiseAppProgress) {
  float progress_initial = 0.1;
  float progress_next = 0.9;

  // Check that there aren't any promise apps registered yet.
  EXPECT_EQ(cache()->GetAllPromiseApps().size(), 0u);

  // Pre-register a promise app with no installation progress value.
  auto promise_app = std::make_unique<PromiseApp>(kTestPackageId);
  cache()->OnPromiseApp(std::move(promise_app));
  EXPECT_FALSE(cache()->GetPromiseApp(kTestPackageId)->progress.has_value());
  EXPECT_EQ(cache()->GetAllPromiseApps().size(), 1u);

  // Update the progress value for the correct app and confirm the progress
  // value.
  auto promise_delta = std::make_unique<PromiseApp>(kTestPackageId);
  promise_delta->progress = progress_initial;
  cache()->OnPromiseApp(std::move(promise_delta));
  EXPECT_EQ(cache()->GetPromiseApp(kTestPackageId)->progress, progress_initial);

  // Update the progress value again and check if it is the correct value.
  auto promise_delta_next = std::make_unique<PromiseApp>(kTestPackageId);
  promise_delta_next->progress = progress_next;
  cache()->OnPromiseApp(std::move(promise_delta_next));
  EXPECT_EQ(cache()->GetPromiseApp(kTestPackageId)->progress, progress_next);

  // All these changes should have applied to the same promise app instead
  // of creating new ones.
  EXPECT_EQ(cache()->GetAllPromiseApps().size(), 1u);
}

TEST_F(PromiseAppRegistryCacheTest, GetAllPromiseApps) {
  // There should be no promise apps registered yet.
  EXPECT_EQ(cache()->GetAllPromiseApps().size(), 0u);

  // Register some promise apps.
  auto package_id_1 = PackageId(PackageType::kArc, "test1");
  auto promise_app_1 = std::make_unique<PromiseApp>(package_id_1);
  cache()->OnPromiseApp(std::move(promise_app_1));

  auto package_id_2 = PackageId(PackageType::kArc, "test2");
  auto promise_app_2 = std::make_unique<PromiseApp>(package_id_2);
  cache()->OnPromiseApp(std::move(promise_app_2));

  // Check that all the promise apps are being retrieved.
  auto promise_app_list = cache()->GetAllPromiseApps();
  EXPECT_EQ(promise_app_list.size(), 2u);
  EXPECT_EQ(promise_app_list[0]->package_id, package_id_1);
  EXPECT_EQ(promise_app_list[1]->package_id, package_id_2);
}

TEST_F(PromiseAppRegistryCacheTest, GetPromiseAppForStringPackageId) {
  // There should be no promise apps registered yet.
  EXPECT_EQ(cache()->GetAllPromiseApps().size(), 0u);

  std::string valid_package_id_1 = "android:something.example.test";
  std::string valid_package_id_2 = "android:other.example.test";
  std::string invalid_package_id = "invalid";
  apps::PackageId package_id =
      PackageId::FromString(valid_package_id_1).value();

  // Register a promise app.
  auto promise_app = std::make_unique<PromiseApp>(package_id);
  cache()->OnPromiseApp(std::move(promise_app));

  // Expect nullptr result for invalid string Package ID or when a Package ID
  // isn't registered.
  EXPECT_FALSE(cache()->GetPromiseAppForStringPackageId(invalid_package_id));
  EXPECT_FALSE(cache()->GetPromiseAppForStringPackageId(valid_package_id_2));

  const PromiseApp* promise_app_result =
      cache()->GetPromiseAppForStringPackageId(valid_package_id_1);
  EXPECT_EQ(promise_app_result->package_id, package_id);
}

TEST_F(PromiseAppRegistryCacheTest, RemoveSuccessfullyInstalledPromiseApp) {
  // Register a promise app.
  auto promise_app = std::make_unique<PromiseApp>(kTestPackageId);
  cache()->OnPromiseApp(std::move(promise_app));

  // Confirm that the promise app is registered.
  EXPECT_TRUE(cache()->HasPromiseApp(kTestPackageId));

  // Update the promise app with a kSuccess status.
  auto delta = std::make_unique<PromiseApp>(kTestPackageId);
  delta->status = PromiseStatus::kSuccess;
  cache()->OnPromiseApp(std::move(delta));

  // Confirm that the promise app was removed.
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));
  histogram_tester().ExpectBucketCount(
      kPromiseAppLifecycleEventHistogram,
      PromiseAppLifecycleEvent::kInstallationSucceeded, 1);
}

TEST_F(PromiseAppRegistryCacheTest, RemoveCancelledPromiseApp) {
  // Register a promise app.
  auto promise_app = std::make_unique<PromiseApp>(kTestPackageId);
  cache()->OnPromiseApp(std::move(promise_app));

  // Confirm that the promise app is registered.
  EXPECT_TRUE(cache()->HasPromiseApp(kTestPackageId));

  // Update the promise app with a kCancelled status.
  auto delta = std::make_unique<PromiseApp>(kTestPackageId);
  delta->status = PromiseStatus::kCancelled;
  cache()->OnPromiseApp(std::move(delta));

  // Confirm that the promise app was removed.
  EXPECT_FALSE(cache()->HasPromiseApp(kTestPackageId));
  histogram_tester().ExpectBucketCount(
      kPromiseAppLifecycleEventHistogram,
      PromiseAppLifecycleEvent::kInstallationCancelled, 1);
}

class PromiseAppRegistryCacheObserverTest : public testing::Test,
                                            PromiseAppRegistryCache::Observer {
 public:
  void SetUp() override {
    cache_ = std::make_unique<PromiseAppRegistryCache>();
  }

  // apps::PromiseAppRegistryCache::Observer:
  void OnPromiseAppUpdate(const PromiseAppUpdate& update) override {
    EXPECT_EQ(update, *expected_update_);
    on_promise_app_updated_called_ = true;

    if (IsPromiseAppCompleted(update.Status())) {
      on_promise_app_updated_with_completed_status_called_ = true;
    }
    // Verify that the data in promise app registry cache is already updated.
    ASSERT_TRUE(cache()->HasPromiseApp(update.PackageId()));
    const PromiseApp* promise_app_in_cache =
        cache()->GetPromiseApp(update.PackageId());
    EXPECT_EQ(promise_app_in_cache->package_id, update.PackageId());
    EXPECT_EQ(promise_app_in_cache->progress, update.Progress());
    EXPECT_EQ(promise_app_in_cache->status, update.Status());
    EXPECT_EQ(promise_app_in_cache->should_show, update.ShouldShow());
  }

  void OnPromiseAppRemoved(const PackageId& package_id) override {
    on_promise_app_removed_called_ = true;
    EXPECT_FALSE(cache()->HasPromiseApp(package_id));
  }

  void OnPromiseAppRegistryCacheWillBeDestroyed(
      apps::PromiseAppRegistryCache* cache) override {
    obs_.Reset();
  }

  void ExpectPromiseAppUpdate(std::unique_ptr<PromiseAppUpdate> update) {
    expected_update_ = std::move(update);
    if (!obs_.IsObserving()) {
      obs_.Observe(cache());
    }
    on_promise_app_updated_called_ = false;
    on_promise_app_updated_with_completed_status_called_ = false;
    on_promise_app_removed_called_ = false;
  }

  bool CheckOnPromiseAppUpdatedCalled() const {
    return on_promise_app_updated_called_;
  }

  bool CheckOnPromiseAppUpdatedWithCompletedStatusCalled() const {
    return on_promise_app_updated_with_completed_status_called_;
  }

  bool CheckOnPromiseAppRemovedCalled() const {
    return on_promise_app_removed_called_;
  }

  PromiseAppRegistryCache* cache() { return cache_.get(); }

 private:
  base::ScopedObservation<PromiseAppRegistryCache,
                          PromiseAppRegistryCache::Observer>
      obs_{this};
  std::unique_ptr<PromiseAppUpdate> expected_update_;
  std::unique_ptr<PromiseAppRegistryCache> cache_;
  bool on_promise_app_updated_called_ = false;
  bool on_promise_app_updated_with_completed_status_called_ = false;
  bool on_promise_app_removed_called_ = false;
};

TEST_F(PromiseAppRegistryCacheObserverTest, OnPromiseAppUpdate_NewPromiseApp) {
  auto promise_app = std::make_unique<PromiseApp>(kTestPackageId);
  promise_app->progress = 0;
  promise_app->name = "Test";
  promise_app->status = PromiseStatus::kPending;
  promise_app->should_show = false;

  ASSERT_FALSE(cache()->HasPromiseApp(kTestPackageId));

  // Check that we get the appropriate update when registering a new promise
  // app.
  ExpectPromiseAppUpdate(
      std::make_unique<PromiseAppUpdate>(nullptr, promise_app.get()));
  cache()->OnPromiseApp(std::move(promise_app));
  EXPECT_TRUE(CheckOnPromiseAppUpdatedCalled());
  EXPECT_FALSE(CheckOnPromiseAppUpdatedWithCompletedStatusCalled());
  EXPECT_FALSE(CheckOnPromiseAppRemovedCalled());
}

TEST_F(PromiseAppRegistryCacheObserverTest,
       OnPromiseAppUpdate_ModifyPromiseApp) {
  auto promise_app_pending = std::make_unique<PromiseApp>(kTestPackageId);
  promise_app_pending->status = PromiseStatus::kPending;
  promise_app_pending->should_show = false;
  ExpectPromiseAppUpdate(
      std::make_unique<PromiseAppUpdate>(nullptr, promise_app_pending.get()));
  cache()->OnPromiseApp(promise_app_pending->Clone());
  EXPECT_TRUE(CheckOnPromiseAppUpdatedCalled());
  EXPECT_FALSE(CheckOnPromiseAppUpdatedWithCompletedStatusCalled());
  EXPECT_FALSE(CheckOnPromiseAppRemovedCalled());

  // Check that we get the appropriate update when going from pending to
  // installing.
  auto promise_app_installing = std::make_unique<PromiseApp>(kTestPackageId);
  promise_app_installing->progress = 0.4;
  promise_app_installing->name = "Test";
  promise_app_installing->status = PromiseStatus::kInstalling;
  promise_app_installing->should_show = true;
  ExpectPromiseAppUpdate(std::make_unique<PromiseAppUpdate>(
      promise_app_pending.get(), promise_app_installing.get()));
  EXPECT_FALSE(CheckOnPromiseAppUpdatedCalled());
  cache()->OnPromiseApp(promise_app_installing->Clone());
  EXPECT_TRUE(CheckOnPromiseAppUpdatedCalled());
  EXPECT_FALSE(CheckOnPromiseAppUpdatedWithCompletedStatusCalled());
  EXPECT_FALSE(CheckOnPromiseAppRemovedCalled());

  // Verify that OnPromiseAppRemoved gets called when the promise app gets
  // installed.
  auto promise_app_installed = std::make_unique<PromiseApp>(kTestPackageId);
  promise_app_installed->progress = 1.0;
  promise_app_installed->status = PromiseStatus::kSuccess;
  promise_app_installed->should_show = true;
  promise_app_installed->installed_app_id = kTestPackageId.identifier();
  ExpectPromiseAppUpdate(std::make_unique<PromiseAppUpdate>(
      promise_app_installing.get(), promise_app_installed.get()));
  EXPECT_FALSE(CheckOnPromiseAppUpdatedCalled());
  cache()->OnPromiseApp(std::move(promise_app_installed));
  EXPECT_TRUE(CheckOnPromiseAppUpdatedCalled());
  EXPECT_TRUE(CheckOnPromiseAppUpdatedWithCompletedStatusCalled());
  EXPECT_TRUE(CheckOnPromiseAppRemovedCalled());
}

TEST_F(PromiseAppRegistryCacheObserverTest,
       OnPromiseAppUpdate_CancelPromiseApp) {
  auto promise_app_pending = std::make_unique<PromiseApp>(kTestPackageId);
  promise_app_pending->status = PromiseStatus::kPending;
  promise_app_pending->should_show = false;
  ExpectPromiseAppUpdate(
      std::make_unique<PromiseAppUpdate>(nullptr, promise_app_pending.get()));
  cache()->OnPromiseApp(promise_app_pending->Clone());
  EXPECT_TRUE(CheckOnPromiseAppUpdatedCalled());
  EXPECT_FALSE(CheckOnPromiseAppUpdatedWithCompletedStatusCalled());
  EXPECT_FALSE(CheckOnPromiseAppRemovedCalled());

  // Check that we get the appropriate update when going from pending to
  // installing.
  auto promise_app_installing = std::make_unique<PromiseApp>(kTestPackageId);
  promise_app_installing->progress = 0.4;
  promise_app_installing->status = PromiseStatus::kInstalling;
  promise_app_installing->should_show = true;
  ExpectPromiseAppUpdate(std::make_unique<PromiseAppUpdate>(
      promise_app_pending.get(), promise_app_installing.get()));
  EXPECT_FALSE(CheckOnPromiseAppUpdatedCalled());
  cache()->OnPromiseApp(promise_app_installing->Clone());
  EXPECT_TRUE(CheckOnPromiseAppUpdatedCalled());
  EXPECT_FALSE(CheckOnPromiseAppUpdatedWithCompletedStatusCalled());
  EXPECT_FALSE(CheckOnPromiseAppRemovedCalled());

  // Verify that OnPromiseAppRemoved gets called when the promise app install
  // gets cancelled.
  auto promise_app_cancelled = std::make_unique<PromiseApp>(kTestPackageId);
  promise_app_cancelled->progress = 1.0;
  promise_app_cancelled->status = PromiseStatus::kCancelled;
  promise_app_cancelled->should_show = true;
  ExpectPromiseAppUpdate(std::make_unique<PromiseAppUpdate>(
      promise_app_installing.get(), promise_app_cancelled.get()));
  EXPECT_FALSE(CheckOnPromiseAppUpdatedCalled());
  cache()->OnPromiseApp(std::move(promise_app_cancelled));
  EXPECT_TRUE(CheckOnPromiseAppUpdatedCalled());
  EXPECT_TRUE(CheckOnPromiseAppUpdatedWithCompletedStatusCalled());
  EXPECT_TRUE(CheckOnPromiseAppRemovedCalled());
}

}  // namespace apps