chromium/components/services/app_service/public/cpp/app_storage/app_storage_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 "components/services/app_service/public/cpp/app_storage/app_storage.h"

#include <memory>
#include <set>
#include <vector>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_storage/app_storage_file_handler.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/icon_effects.h"
#include "components/services/app_service/public/cpp/intent_filter_util.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace apps {

namespace {

constexpr char kAppId1[] = "aaa";
constexpr char kAppId2[] = "bbb";
constexpr char kAppId3[] = "ccc";

constexpr AppType kAppType1 = AppType::kArc;
constexpr AppType kAppType2 = AppType::kWeb;

constexpr char kAppName1[] = "AAA";
constexpr char kAppName2[] = "BBB";

constexpr Readiness kReadiness1 = Readiness::kReady;
constexpr Readiness kReadiness2 = Readiness::kDisabledByUser;

constexpr char kAppShortName1[] = "a";
constexpr char kAppShortName2[] = "b";

}  // namespace

#define MODIFY_FIELD(FIELD, VALUE)                                   \
  void Modify##FIELD() {                                             \
    AppPtr app = std::make_unique<App>(kAppType1, kAppId1);          \
    app->FIELD = VALUE;                                              \
    std::vector<AppPtr> apps;                                        \
    apps.push_back(std::move(app));                                  \
    app_registry_cache_.OnApps(std::move(apps), kAppType1,           \
                               /*should_notify_initialized=*/false); \
  }

#define VERIFY_MODIFY_FIELD(FIELD, VALUE)                     \
  Modify##FIELD();                                            \
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2); \
  apps[0]->FIELD = VALUE;                                     \
  VerifySavedApps(apps);

// This fake AppStorage is used to test and track all calls to AppStorage.
class FakeAppStorage : public AppStorage {
 public:
  FakeAppStorage(const base::FilePath& base_path,
                 AppRegistryCache& app_registry_cache,
                 std::unique_ptr<base::RunLoop> run_loop)
      : AppStorage(base_path, app_registry_cache, run_loop->QuitClosure()) {
    // Wait for OnGetAppInfoData to be invoked.
    run_loop->Run();
  }

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

  ~FakeAppStorage() override = default;

  std::vector<AppPtr>& GetAppInfo() { return apps_; }
  std::set<AppType>& GetAppTypeInfo() { return app_types_; }
  bool is_app_changed() { return is_app_changed_; }

  void WaitForSaveFinished(size_t expect_app_count) {
    expect_app_count_ = expect_app_count;
    write_result_ = std::make_unique<base::test::TestFuture<void>>();
    EXPECT_TRUE(write_result_->Wait());
  }

 private:
  // Override to call to AppStorage::OnAppUpdate.
  void OnAppUpdate(const apps::AppUpdate& update) override {
    is_app_changed_ = IsAppChanged(update);
    AppStorage::OnAppUpdate(update);
  }

  // Override to call to AppStorage::OnGetAppInfoData.
  void OnGetAppInfoData(base::OnceCallback<void()> callback,
                        std::unique_ptr<AppInfo> app_info) override {
    if (app_info) {
      for (const auto& app : app_info->apps) {
        apps_.push_back(app->Clone());
      }
    }

    AppStorage::OnGetAppInfoData(std::move(callback), std::move(app_info));
  }

  void OnSaveFinished() override {
    AppStorage::OnSaveFinished();

    if (!io_in_progress_ && expect_app_count_ >= 0 &&
        expect_app_count_ == app_registry_cache_->states_.size()) {
      std::move(write_result_->GetCallback()).Run();
    }
  }

  void OnAppTypeInitialized(apps::AppType app_type) override {
    app_types_.insert(app_type);
  }

  std::vector<AppPtr> apps_;
  std::set<AppType> app_types_;

  std::unique_ptr<base::test::TestFuture<void>> write_result_;
  size_t expect_app_count_ = -1;

  bool is_app_changed_ = false;
};

class AppStorageTest : public testing::Test {
 public:
  AppStorageTest() = default;
  ~AppStorageTest() override = default;

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

  void SetUp() override { ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir()); }

  void CreateAppStorage() {
    app_storage_ = std::make_unique<FakeAppStorage>(
        tmp_dir_.GetPath(), app_registry_cache_,
        std::make_unique<base::RunLoop>());
  }

  AppPtr CreateApp(AppType app_type, const std::string& app_id) {
    AppPtr app = std::make_unique<App>(app_type, app_id);
    app->readiness = kReadiness1;
    app->has_badge = false;
    app->paused = false;
    return app;
  }

  std::vector<AppPtr> CreateOneApp(AppType app_type,
                                   const std::string& app_id) {
    AppPtr app = CreateApp(app_type, app_id);
    std::vector<AppPtr> apps;
    apps.push_back(std::move(app));
    return apps;
  }

  std::vector<AppPtr> CreateTwoApps() {
    std::vector<AppPtr> apps;

    AppPtr app1 = CreateApp(kAppType1, kAppId1);
    app1->name = kAppName1;
    apps.push_back(std::move(app1));

    AppPtr app2 = CreateApp(kAppType2, kAppId2);
    app2->readiness = kReadiness2;
    app2->name = kAppName2;
    app2->short_name = kAppShortName1;
    app2->publisher_id = "publisher_id";
    app2->description = "description";
    app2->version = "version";
    app2->additional_search_terms = {"term1", "term2"};
    app2->icon_key =
        IconKey(/*resource_id=*/65535, IconEffects::kCrOsStandardIcon);
    app2->last_launch_time = base::Time() + base::Days(2);
    app2->install_time = base::Time() + base::Days(1);

    app2->permissions.push_back(std::make_unique<Permission>(
        PermissionType::kLocation, /*PermissionValue=*/false,
        /*is_managed=*/true, "details"));
    app2->permissions.push_back(std::make_unique<Permission>(
        PermissionType::kPrinting, /*PermissionValue=*/TriState::kBlock,
        /*is_managed=*/false));

    app2->install_reason = InstallReason::kUser;
    app2->install_source = InstallSource::kBrowser;
    app2->policy_ids = {"plicy1", "policy2"};
    app2->is_platform_app = false;
    app2->recommendable = true;
    app2->searchable = true;
    app2->show_in_launcher = true;
    app2->show_in_shelf = true;
    app2->show_in_search = true;
    app2->show_in_management = true;
    app2->handles_intents = false;
    app2->allow_uninstall = false;
    app2->intent_filters.push_back(apps_util::MakeIntentFilterForUrlScope(
        GURL("https://www.google.com/abc")));
    app2->window_mode = WindowMode::kBrowser;
    app2->run_on_os_login = RunOnOsLogin(RunOnOsLoginMode::kNotRun,
                                         /*is_managed=*/false);
    app2->allow_close = false;
    app2->app_size_in_bytes = 1024;
    app2->data_size_in_bytes = 2048;
    app2->supported_locales = {"a", "b", "c"};
    app2->selected_locale = "a";
    app2->SetExtraField("vm_name", "vm_name_value");
    app2->SetExtraField("scales", true);
    app2->SetExtraField("number", 100);

    apps.push_back(std::move(app2));

    // TODO(crbug.com/40247021): Add other files in the App structure.
    return apps;
  }

  MODIFY_FIELD(name, kAppName2)
  MODIFY_FIELD(short_name, kAppShortName2)
  MODIFY_FIELD(description, "description")
  MODIFY_FIELD(publisher_id, "publisher_id")
  MODIFY_FIELD(version, "version")
  MODIFY_FIELD(additional_search_terms, {"term1"})
  MODIFY_FIELD(last_launch_time, base::Time() + base::Days(2))
  MODIFY_FIELD(install_time, base::Time() + base::Days(1))
  MODIFY_FIELD(install_reason, InstallReason::kDefault)
  MODIFY_FIELD(install_source, InstallSource::kSync)
  MODIFY_FIELD(policy_ids, {"policy"})
  MODIFY_FIELD(is_platform_app, true)
  MODIFY_FIELD(recommendable, false)
  MODIFY_FIELD(searchable, false)
  MODIFY_FIELD(show_in_launcher, true)
  MODIFY_FIELD(show_in_shelf, true)
  MODIFY_FIELD(show_in_search, true)
  MODIFY_FIELD(show_in_management, true)
  MODIFY_FIELD(handles_intents, false)
  MODIFY_FIELD(allow_uninstall, false)
  MODIFY_FIELD(window_mode, WindowMode::kWindow)
  MODIFY_FIELD(run_on_os_login,
               RunOnOsLogin(RunOnOsLoginMode::kNotRun, /*is_managed=*/false))
  MODIFY_FIELD(allow_close, false)
  MODIFY_FIELD(app_size_in_bytes, 512)
  MODIFY_FIELD(data_size_in_bytes, 0)
  MODIFY_FIELD(supported_locales, {"aa"})
  MODIFY_FIELD(selected_locale, "aa")

  void ModifyIconKey(std::optional<IconKey> icon_key) {
    AppPtr app = std::make_unique<App>(kAppType1, kAppId1);
    app->icon_key = std::move(icon_key);
    std::vector<AppPtr> apps;
    apps.push_back(std::move(app));
    app_registry_cache_.OnApps(std::move(apps), kAppType1,
                               /*should_notify_initialized=*/false);
  }

  void ModifyPermissions() {
    AppPtr app = std::make_unique<App>(kAppType1, kAppId1);
    app->permissions.push_back(std::make_unique<Permission>(
        PermissionType::kLocation, /*PermissionValue=*/false,
        /*is_managed=*/true, "details"));
    std::vector<AppPtr> apps;
    apps.push_back(std::move(app));
    app_registry_cache_.OnApps(std::move(apps), kAppType1,
                               /*should_notify_initialized=*/false);
  }

  void ModifyIntentFilters() {
    AppPtr app = std::make_unique<App>(kAppType1, kAppId1);
    auto intent_filter = std::make_unique<apps::IntentFilter>();
    intent_filter->activity_name = "activity_name";
    app->intent_filters.push_back(std::move(intent_filter));
    std::vector<AppPtr> apps;
    apps.push_back(std::move(app));
    app_registry_cache_.OnApps(std::move(apps), kAppType1,
                               /*should_notify_initialized=*/false);
  }

  void ModifyExtra() {
    AppPtr app = std::make_unique<App>(kAppType1, kAppId1);
    app->SetExtraField("vm_name", "vm_name_value");
    std::vector<AppPtr> apps;
    apps.push_back(std::move(app));
    app_registry_cache_.OnApps(std::move(apps), kAppType1,
                               /*should_notify_initialized=*/false);
  }

  void RemoveOneApp(AppType app_type, const std::string& app_id) {
    AppPtr app = std::make_unique<App>(app_type, app_id);
    app->readiness = Readiness::kUninstalledByUser;
    std::vector<AppPtr> apps;
    apps.push_back(std::move(app));
    app_registry_cache_.OnApps(std::move(apps), app_type,
                               /*should_notify_initialized=*/false);
  }

  void VerifySavedApps(std::vector<AppPtr>& apps,
                       bool should_verify_app_type_init = false) {
    // Create a new AppStorage to read the AppStorage file to verify the app has
    // been written correctly.
    app_storage_.reset();
    app_storage_ = std::make_unique<FakeAppStorage>(
        tmp_dir().GetPath(), app_registry_cache_,
        std::make_unique<base::RunLoop>());
    EXPECT_TRUE(IsEqual(apps, app_storage_->GetAppInfo()));

    if (!should_verify_app_type_init) {
      return;
    }

    std::set<AppType> app_types;
    for (const auto& app : apps) {
      app_types.insert(app->app_type);
    }

    EXPECT_EQ(app_types.size(), app_storage_->GetAppTypeInfo().size());
    for (AppType app_type : app_types) {
      EXPECT_TRUE(base::Contains(app_storage_->GetAppTypeInfo(), app_type));
    }
  }

  void OnApps(std::vector<AppPtr> deltas,
              apps::AppType app_type,
              bool should_notify_initialized) {
    app_registry_cache_.OnApps(std::move(deltas), app_type,
                               should_notify_initialized);
  }

  const base::ScopedTempDir& tmp_dir() { return tmp_dir_; }

  AppRegistryCache& app_registry_cache() { return app_registry_cache_; }

  FakeAppStorage* app_storage() { return app_storage_.get(); }

 private:
  base::test::TaskEnvironment task_environment_;
  base::ScopedTempDir tmp_dir_;

  AppRegistryCache app_registry_cache_;
  std::unique_ptr<FakeAppStorage> app_storage_;
};

// Test AppStorage can work from an unavailable file.
TEST_F(AppStorageTest, ReadFromNotValidFile) {
  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());
  EXPECT_TRUE(app_storage()->GetAppTypeInfo().empty());
}

// Test AppStorage can work when there is no app info in the AppStorage file.
TEST_F(AppStorageTest, ReadFromEmptyFile) {
  // Create an empty AppStorage file.
  scoped_refptr<AppStorageFileHandler> file_handler =
      base::MakeRefCounted<AppStorageFileHandler>(tmp_dir().GetPath());
  ASSERT_TRUE(base::CreateDirectory(file_handler->GetFilePath().DirName()));
  ASSERT_TRUE(base::WriteFile(file_handler->GetFilePath(), ""));

  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());
  EXPECT_TRUE(app_storage()->GetAppTypeInfo().empty());
}

// Test AppStorageTest can read and write one app.
TEST_F(AppStorageTest, ReadAndWriteOneApp) {
  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());

  // Add 1 app.
  OnApps(CreateOneApp(kAppType1, kAppId1), kAppType1,
         /*should_notify_initialized=*/false);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/1);

  // Verify the app is saved correctly.
  auto apps1 = CreateOneApp(kAppType1, kAppId1);
  VerifySavedApps(apps1, /*should_verify_app_type_init=*/true);

  // Remove the app.
  RemoveOneApp(kAppType1, kAppId1);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/1);

  // Verify the app has been removed.
  std::vector<AppPtr> apps2;
  VerifySavedApps(apps2);
}

// Test AppStorageTest can read and write multiple apps.
TEST_F(AppStorageTest, ReadAndWriteMultipleApps) {
  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());

  // Add 2 apps.
  OnApps(CreateTwoApps(), AppType::kUnknown,
         /*should_notify_initialized=*/false);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);

  // Verify the apps are saved correctly.
  auto apps = CreateTwoApps();
  VerifySavedApps(apps, /*should_verify_app_type_init=*/true);

  VERIFY_MODIFY_FIELD(name, kAppName2);
  VERIFY_MODIFY_FIELD(short_name, kAppShortName2);
  VERIFY_MODIFY_FIELD(publisher_id, "publisher_id");
  VERIFY_MODIFY_FIELD(description, "description");
  VERIFY_MODIFY_FIELD(version, "version");
  VERIFY_MODIFY_FIELD(additional_search_terms, {"term1"});
  VERIFY_MODIFY_FIELD(last_launch_time, base::Time() + base::Days(2));
  VERIFY_MODIFY_FIELD(install_time, base::Time() + base::Days(1));
  VERIFY_MODIFY_FIELD(install_reason, InstallReason::kDefault);
  VERIFY_MODIFY_FIELD(install_source, InstallSource::kSync);
  VERIFY_MODIFY_FIELD(policy_ids, {"policy"});
  VERIFY_MODIFY_FIELD(is_platform_app, true);
  VERIFY_MODIFY_FIELD(recommendable, false);
  VERIFY_MODIFY_FIELD(searchable, false);
  VERIFY_MODIFY_FIELD(show_in_launcher, true);
  VERIFY_MODIFY_FIELD(show_in_shelf, true);
  VERIFY_MODIFY_FIELD(show_in_search, true);
  VERIFY_MODIFY_FIELD(show_in_management, true);
  VERIFY_MODIFY_FIELD(handles_intents, false);
  VERIFY_MODIFY_FIELD(allow_uninstall, false);
  VERIFY_MODIFY_FIELD(window_mode, WindowMode::kWindow);
  VERIFY_MODIFY_FIELD(run_on_os_login, RunOnOsLogin(RunOnOsLoginMode::kNotRun,
                                                    /*is_managed=*/false));
  VERIFY_MODIFY_FIELD(allow_close, false);
  VERIFY_MODIFY_FIELD(app_size_in_bytes, 512);
  VERIFY_MODIFY_FIELD(data_size_in_bytes, 0);
  VERIFY_MODIFY_FIELD(supported_locales, {"aa"});
  VERIFY_MODIFY_FIELD(selected_locale, "aa");

  // Verify for the none icon effect.
  IconKey icon_key1(IconEffects::kNone);
  ModifyIconKey(std::move(*(icon_key1.Clone())));
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);
  apps[0]->icon_key = std::move(icon_key1);
  VerifySavedApps(apps);

  // Verify the icon effect modification.
  IconKey icon_key2(IconEffects::kCrOsStandardIcon);
  ModifyIconKey(std::move(*(icon_key2.Clone())));
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);
  apps[0]->icon_key = std::move(icon_key2);
  VerifySavedApps(apps);

  // Verify the kPaused icon effect can be filtered out. We don't need to modify
  // `apps`, because the icon key won't be updated, and we don't need to wait
  // for the saving as well. After modifying the intent filters, the apps can be
  // checked again.
  IconKey icon_key3(IconEffects::kCrOsStandardIcon | IconEffects::kPaused);
  ModifyIconKey(std::move(icon_key3));
  EXPECT_FALSE(app_storage()->is_app_changed());

  // Verify the icon_key's `update_version` can be filtered out. We don't need
  // to modify `apps`, because `update_version` won't be saved,. After modifying
  // the intent filters, the apps can be checked again.
  IconKey icon_key4(IconEffects::kCrOsStandardIcon);
  icon_key4.update_version = true;
  ModifyIconKey(std::move(icon_key4));
  EXPECT_FALSE(app_storage()->is_app_changed());

  ModifyPermissions();
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);
  apps[0]->permissions.push_back(std::make_unique<Permission>(
      PermissionType::kLocation, /*PermissionValue=*/false,
      /*is_managed=*/true, "details"));
  VerifySavedApps(apps);

  ModifyIntentFilters();
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);
  auto intent_filter = std::make_unique<apps::IntentFilter>();
  intent_filter->activity_name = "activity_name";
  apps[0]->intent_filters.push_back(std::move(intent_filter));
  VerifySavedApps(apps);

  ModifyExtra();
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);
  apps[0]->SetExtraField("vm_name", "vm_name_value");
  VerifySavedApps(apps);

  // Verify `apps` are not changed.
  AppPtr app = std::make_unique<App>(kAppType1, kAppId1);
  // Set `paused` as true to call `OnAppUpdate`.
  app->paused = true;
  std::vector<AppPtr> deltas;
  deltas.push_back(std::move(app));
  OnApps(std::move(deltas), AppType::kUnknown,
         /*should_notify_initialized=*/false);
  EXPECT_FALSE(app_storage()->is_app_changed());
  VerifySavedApps(apps);

  RemoveOneApp(kAppType1, kAppId1);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);

  // Verify the apps are saved correctly.
  apps.erase(apps.begin());
  VerifySavedApps(apps);

  RemoveOneApp(kAppType2, kAppId2);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);

  apps.clear();
  VerifySavedApps(apps);
}

// Test AppStorageTest can read and write multiple apps at the same time without
// waiting for writing finish.
TEST_F(AppStorageTest, ReadAndWriteMultipleAppsAtSameTime) {
  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());

  // Add apps.
  OnApps(CreateOneApp(kAppType1, kAppId1), kAppType1,
         /*should_notify_initialized=*/false);
  OnApps(CreateOneApp(kAppType1, kAppId2), kAppType1,
         /*should_notify_initialized=*/false);
  OnApps(CreateOneApp(kAppType1, kAppId3), kAppType1,
         /*should_notify_initialized=*/false);

  Modifyname();
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/3);

  // Verify the apps are saved correctly.
  auto apps1 = CreateOneApp(kAppType1, kAppId1);
  apps1[0]->name = kAppName2;
  auto apps2 = CreateOneApp(kAppType1, kAppId2);
  auto apps3 = CreateOneApp(kAppType1, kAppId3);
  std::vector<AppPtr> apps;
  apps.push_back(std::move(apps1[0]));
  apps.push_back(std::move(apps2[0]));
  apps.push_back(std::move(apps3[0]));
  VerifySavedApps(apps, /*should_verify_app_type_init=*/true);
}

// Test AppStorageTest can handle the removed app case(kRemoved).
TEST_F(AppStorageTest, AddAndRemoveApp) {
  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());

  // Add 1 app.
  OnApps(CreateOneApp(kAppType1, kAppId1), kAppType1,
         /*should_notify_initialized=*/false);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/1);

  // Verify the app is saved correctly.
  auto apps1 = CreateOneApp(kAppType1, kAppId1);
  VerifySavedApps(apps1, /*should_verify_app_type_init=*/true);

  // Remove the app.
  std::vector<AppPtr> apps2;
  RemoveOneApp(kAppType1, kAppId1);
  AppPtr app1 = std::make_unique<App>(kAppType1, kAppId1);
  app1->readiness = Readiness::kUninstalledByUser;
  apps2.push_back(std::move(app1));
  AppPtr app2 = std::make_unique<App>(kAppType1, kAppId1);
  app2->readiness = Readiness::kRemoved;
  apps2.push_back(std::move(app2));

  // Add the app back again.
  AppPtr app3 = std::make_unique<App>(kAppType1, kAppId1);
  app3->readiness = kReadiness1;
  apps2.push_back(std::move(app3));
  OnApps(std::move(apps2), kAppType1,
         /*should_notify_initialized=*/false);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/1);

  // Verify the app has been added.
  auto apps3 = CreateOneApp(kAppType1, kAppId1);
  VerifySavedApps(apps3);
}

// Test AppStorageTest can remove the app when the app is uninstalled.
TEST_F(AppStorageTest, UninstallApp) {
  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());

  // Add 1 app.
  OnApps(CreateOneApp(kAppType1, kAppId1), kAppType1,
         /*should_notify_initialized=*/false);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/1);

  // Verify the app is saved correctly.
  auto apps1 = CreateOneApp(kAppType1, kAppId1);
  VerifySavedApps(apps1, /*should_verify_app_type_init=*/true);

  // Uninstall the app.
  RemoveOneApp(kAppType1, kAppId1);
  // Though the app is uninstalled, it is still saved in `app_registry_cache_`,
  // so `expect_app_count` should be 1. But the apps saved in the AppStorage
  // file should be empty, so we verify that with an empty vector, `apps2`.
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/1);
  std::vector<AppPtr> apps2;
  VerifySavedApps(apps2);

  // Reinstall the app again and change the app type to simulate the system
  // restarts.
  app_registry_cache().ReinitializeForTesting();
  OnApps(CreateOneApp(kAppType2, kAppId1), kAppType2,
         /*should_notify_initialized=*/true);
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/1);

  auto apps3 = CreateOneApp(kAppType2, kAppId1);
  VerifySavedApps(apps3);
}

// Test AppStorageTest can remove the app when calling OnAppsInitialized to
// clear uninstalled apps.
TEST_F(AppStorageTest, ClearUnInstalledApps) {
  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());

  // Add 2 apps.
  {
    std::vector<AppPtr> apps;
    apps.push_back(CreateApp(kAppType1, kAppId1));
    apps.push_back(CreateApp(kAppType1, kAppId2));
    AppPtr app = std::make_unique<App>(kAppType1, kAppId1);
    app->name = kAppName2;
    apps.push_back(std::move(app));
    OnApps(std::move(apps), AppType::kUnknown,
           /*should_notify_initialized=*/false);
    app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);
  }

  // Verify the apps are saved correctly.
  {
    std::vector<AppPtr> apps;
    AppPtr app = CreateApp(kAppType1, kAppId1);
    app->name = kAppName2;
    apps.push_back(std::move(app));
    apps.push_back(CreateApp(kAppType1, kAppId2));
    VerifySavedApps(apps, /*should_verify_app_type_init=*/true);
  }

  // Add 1 app only to uninstall `kAppId2`.
  auto apps1 = CreateOneApp(kAppType1, kAppId1);
  apps1[0]->name = kAppName1;
  OnApps(std::move(apps1), kAppType1,
         /*should_notify_initialized=*/true);
  app_registry_cache().ForOneApp(kAppId2, [&](const apps::AppUpdate& update) {
    EXPECT_FALSE(apps_util::IsInstalled(update.Readiness()));
  });
  // The uninstalled app is still in AppRegistryCache, so the app count is 2.
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/2);

  // Verify `kAppId2` has been removed from AppStorage.
  auto apps2 = CreateOneApp(kAppType1, kAppId1);
  apps2[0]->name = kAppName1;
  app_registry_cache().ReinitializeForTesting();
  VerifySavedApps(apps2);

  // Publish the empty app list to uninstall `kAppId1`.
  OnApps(std::vector<AppPtr>{}, kAppType1,
         /*should_notify_initialized=*/true);
  app_registry_cache().ForOneApp(kAppId1, [&](const apps::AppUpdate& update) {
    EXPECT_FALSE(apps_util::IsInstalled(update.Readiness()));
  });
  // The uninstalled app is still in AppRegistryCache, so the app count is 2.
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/1);

  // Verify `kAppId1` has been removed from AppStorage.
  app_registry_cache().ReinitializeForTesting();
  std::vector<AppPtr> apps3;
  VerifySavedApps(apps3);
}

TEST_F(AppStorageTest, ClearUnInstalledMultipleApps) {
  CreateAppStorage();
  EXPECT_TRUE(app_storage()->GetAppInfo().empty());

  // Add 3 apps.
  {
    std::vector<AppPtr> apps;
    apps.push_back(CreateApp(kAppType1, kAppId1));
    apps.push_back(CreateApp(kAppType1, kAppId2));
    AppPtr app = std::make_unique<App>(kAppType1, kAppId1);
    app->name = kAppName2;
    apps.push_back(std::move(app));
    apps.push_back(CreateApp(kAppType1, kAppId3));
    OnApps(std::move(apps), kAppType1,
           /*should_notify_initialized=*/false);
    app_storage()->WaitForSaveFinished(/*expect_app_count=*/3);
  }

  // Verify the apps are saved correctly.
  {
    std::vector<AppPtr> apps;
    AppPtr app = CreateApp(kAppType1, kAppId1);
    app->name = kAppName2;
    apps.push_back(std::move(app));
    apps.push_back(CreateApp(kAppType1, kAppId2));
    apps.push_back(CreateApp(kAppType1, kAppId3));
    VerifySavedApps(apps, /*should_verify_app_type_init=*/true);
  }

  // Call ClearUnInstalledApps to uninstall `kAppId2`, `kAppId3`.
  std::vector<AppPtr> apps1;
  AppPtr app1 = CreateApp(kAppType1, kAppId1);
  app1->name = kAppName2;
  apps1.push_back(std::move(app1));
  OnApps(std::move(apps1), kAppType1,
         /*should_notify_initialized=*/true);
  app_registry_cache().ForOneApp(kAppId2, [&](const apps::AppUpdate& update) {
    EXPECT_FALSE(apps_util::IsInstalled(update.Readiness()));
  });
  app_registry_cache().ForOneApp(kAppId3, [&](const apps::AppUpdate& update) {
    EXPECT_FALSE(apps_util::IsInstalled(update.Readiness()));
  });
  // The uninstalled app is still in AppRegistryCache, so the app count is 2.
  app_storage()->WaitForSaveFinished(/*expect_app_count=*/3);

  // Verify `kAppId2`, `kAppId3` have been removed from AppStorage.
  app_registry_cache().ReinitializeForTesting();
  std::vector<AppPtr> apps2;
  AppPtr app2 = CreateApp(kAppType1, kAppId1);
  app2->name = kAppName2;
  apps2.push_back(std::move(app2));
  VerifySavedApps(apps2);
  EXPECT_EQ(kAppType1, app_registry_cache().GetAppType(kAppId1));
  EXPECT_EQ(AppType::kUnknown, app_registry_cache().GetAppType(kAppId2));
  EXPECT_EQ(AppType::kUnknown, app_registry_cache().GetAppType(kAppId3));
}

}  // namespace apps