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

#include <limits.h>
#include <memory>
#include <vector>

#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/task_runner.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_types.h"
#include "components/services/app_service/public/cpp/icon_effects.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "components/services/app_service/public/cpp/intent_filter_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace apps {

using AppInfo = AppStorageFileHandler::AppInfo;

namespace {

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

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

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

constexpr char kAppShortName[] = "b";

}  // namespace

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

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

  void SetUp() override {
    ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
    file_handler_ =
        base::MakeRefCounted<AppStorageFileHandler>(tmp_dir_.GetPath());
  }

  // Call AppStorageFileHandler::ReadFromFile to read the app info data from the
  // AppStorage file.
  std::unique_ptr<AppInfo> ReadFromFile() {
    base::test::TestFuture<std::unique_ptr<AppInfo>> result;
    file_handler_->owning_task_runner()->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(&AppStorageFileHandler::ReadFromFile,
                       file_handler_.get()),
        result.GetCallback());
    return result.Take();
  }

  // Call AppStorageFileHandler::WriteToFile to write `apps` to the AppStorage
  // file.
  void WriteToFile(std::vector<AppPtr> apps) {
    base::test::TestFuture<void> result;
    file_handler_->owning_task_runner()->PostTaskAndReply(
        FROM_HERE,
        base::BindOnce(&AppStorageFileHandler::WriteToFile, file_handler_.get(),
                       std::move(apps)),
        result.GetCallback());
    EXPECT_TRUE(result.Wait());
  }

  // Call base::WriteFile directly to create the fake AppStorage file. E.g.
  // create the AppStorage file with the wrong JSON format, or the wrong app
  // info data.
  void WriteToFile(const std::string& data) {
    ASSERT_TRUE(base::CreateDirectory(file_handler_->GetFilePath().DirName()));
    ASSERT_TRUE(base::WriteFile(file_handler_->GetFilePath(), data));
  }

  std::vector<AppPtr> CreateOneApp() {
    std::vector<AppPtr> apps;
    AppPtr app = std::make_unique<App>(kAppType1, kAppId1);
    app->has_badge = false;
    app->paused = false;
    apps.push_back(std::move(app));
    return apps;
  }

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

    AppPtr app1 = std::make_unique<App>(kAppType1, kAppId1);
    app1->readiness = Readiness::kReady;
    app1->name = kAppName1;
    app1->has_badge = false;
    app1->paused = false;
    apps.push_back(std::move(app1));

    AppPtr app2 = std::make_unique<App>(kAppType2, kAppId2);
    app2->readiness = Readiness::kDisabledByUser;
    app2->name = kAppName2;
    app2->short_name = kAppShortName;
    app2->publisher_id = "publisher_id";
    app2->installer_package_id = PackageId(PackageType::kWeb, "publisher_id");
    app2->description = "description";
    app2->version = "version";
    app2->additional_search_terms = {"item1", "item2"};
    app2->icon_key =
        apps::IconKey(/*resource_id=*/65535, apps::IconEffects::kNone);
    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->has_badge = false;
    app2->paused = 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=*/true);
    app2->allow_close = false;
    app2->app_size_in_bytes = ULLONG_MAX;
    app2->data_size_in_bytes = ULLONG_MAX - 1;
    app2->supported_locales = {"a", "b", "c"};
    app2->selected_locale = "c";
    app2->SetExtraField("vm_name", "vm_name_value");
    app2->SetExtraField("scales", true);
    app2->SetExtraField("number", 100);
    apps.push_back(std::move(app2));

    return apps;
  }

  void VerifyIconKey(IconKey& icon_key) {
    std::vector<AppPtr> apps = CreateOneApp();
    apps[0]->icon_key = std::move(*icon_key.Clone());
    WriteToFile(std::move(apps));
    auto app_info = ReadFromFile();
    ASSERT_TRUE(app_info);
    EXPECT_EQ(1u, app_info->apps.size());
    // Set `update_version` to the init false value.
    icon_key.update_version = false;
    // Clear the kPaused icon effect.
    icon_key.icon_effects = icon_key.icon_effects & (~IconEffects::kPaused);
    EXPECT_EQ(icon_key, app_info->apps[0]->icon_key.value());
  }

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

  scoped_refptr<AppStorageFileHandler> file_handler_;
};

// Test AppStorageFileHandler can work from an unavailable file.
TEST_F(AppStorageFileHandlerTest, ReadFromNotValidFile) {
  auto app_info = ReadFromFile();
  EXPECT_FALSE(app_info);
}

// Test AppStorageFileHandler won't crash when the file is empty.
TEST_F(AppStorageFileHandlerTest, ReadFromEmptyFile) {
  WriteToFile("");
  auto app_info = ReadFromFile();
  EXPECT_FALSE(app_info);
}

// Test AppStorageFileHandler won't crash when the file isn't a json format.
TEST_F(AppStorageFileHandlerTest, ReadFromWrongJSONFile) {
  const char kAppInfoData[] = "\"abc\":{\"type\":5}";
  WriteToFile(kAppInfoData);
  auto app_info = ReadFromFile();
  EXPECT_FALSE(app_info);
}

// Test AppStorageFileHandler can work when the data format isn't correct.
TEST_F(AppStorageFileHandlerTest, ReadFromWrongDataFile) {
  const char kAppInfoData[] =
      "{\"abc\":{}, \"aaa\":{\"type\":2, \"readiness\":100}}";
  WriteToFile(kAppInfoData);
  auto app_info = ReadFromFile();

  // The app type for "abc" is empty, so we can get one app only {app_id =
  // "aaa", app_type = kBuiltIn}.
  ASSERT_TRUE(app_info);
  EXPECT_EQ(1u, app_info->apps.size());
  EXPECT_EQ("aaa", app_info->apps[0]->app_id);
  EXPECT_EQ(AppType::kBuiltIn, app_info->apps[0]->app_type);
  // The readiness for the app "aaa" is wrong, so readiness is set as the
  // default value.
  EXPECT_EQ(Readiness::kUnknown, app_info->apps[0]->readiness);

  EXPECT_EQ(1u, app_info->app_types.size());
  EXPECT_TRUE(base::Contains(app_info->app_types, AppType::kBuiltIn));
}

// Test AppStorageFileHandler can work when the app type isn't correct.
TEST_F(AppStorageFileHandlerTest, ReadFromWrongAppType) {
  const char kAppInfoData[] = "{\"abc\":{\"type\":100}, \"aaa\":{\"type\":2}}";
  WriteToFile(kAppInfoData);
  auto app_info = ReadFromFile();

  // The app type for "abc" is wrong, so we can get one app only {app_id =
  // "aaa", app_type = kBuiltIn}.
  ASSERT_TRUE(app_info);
  EXPECT_EQ(1u, app_info->apps.size());
  EXPECT_EQ("aaa", app_info->apps[0]->app_id);
  EXPECT_EQ(AppType::kBuiltIn, app_info->apps[0]->app_type);

  EXPECT_EQ(1u, app_info->app_types.size());
  EXPECT_TRUE(base::Contains(app_info->app_types, AppType::kBuiltIn));
}

// Test AppStorageFileHandler can read and write the empty app info data.
TEST_F(AppStorageFileHandlerTest, ReadAndWriteEmptyData) {
  WriteToFile(std::vector<AppPtr>());
  auto app_info = ReadFromFile();
  ASSERT_TRUE(app_info);
  EXPECT_TRUE(app_info->apps.empty());
}

// Test AppStorageFileHandler can read and write one app.
TEST_F(AppStorageFileHandlerTest, ReadAndWriteOneApp) {
  WriteToFile(CreateOneApp());
  auto app_info = ReadFromFile();
  ASSERT_TRUE(app_info);
  EXPECT_TRUE(IsEqual(CreateOneApp(), app_info->apps));
  EXPECT_EQ(1u, app_info->app_types.size());
  EXPECT_TRUE(base::Contains(app_info->app_types, kAppType1));
}

// Test AppStorageFileHandler can read and write multiple apps.
TEST_F(AppStorageFileHandlerTest, ReadAndWriteMultipleApps) {
  WriteToFile(CreateTwoApps());
  auto app_info = ReadFromFile();
  ASSERT_TRUE(app_info);
  EXPECT_TRUE(IsEqual(CreateTwoApps(), app_info->apps));
  EXPECT_EQ(2u, app_info->app_types.size());
  EXPECT_TRUE(base::Contains(app_info->app_types, kAppType1));
  EXPECT_TRUE(base::Contains(app_info->app_types, kAppType2));
}

// Test AppStorageFileHandler can read and write the app info for icon updates.
TEST_F(AppStorageFileHandlerTest, VerifyIconUpdates) {
  {
    // Verify for the none icon effect.
    IconKey icon_key;
    VerifyIconKey(icon_key);
  }
  {
    // Verify for the multiple icon effects.
    IconKey icon_key =
        IconKey(/*resource_id=*/65535,
                IconEffects::kCrOsStandardIcon | IconEffects::kBlocked);
    VerifyIconKey(icon_key);
  }
  {
    // Verify the kPaused icon effect can be cleared.
    IconKey icon_key =
        IconKey(IconEffects::kCrOsStandardIcon | IconEffects::kPaused);
    VerifyIconKey(icon_key);
  }
  {
    // Verify `update_version` isn't saved, and it can be set as the init false
    // value.
    IconKey icon_key = IconKey(IconEffects::kCrOsStandardIcon);
    icon_key.update_version = 10;
    VerifyIconKey(icon_key);
  }
}

}  // namespace apps