chromium/chrome/browser/ash/app_mode/web_app/web_kiosk_app_data_browsertest.cc

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

#include "base/test/repeating_test_future.h"
#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_data.h"

#include "base/path_service.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/app_mode/kiosk_app_data_delegate.h"
#include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_test.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

const char kAppId[] = "123";
const char kAppUrl[] = "https://example.com/";
const char kAppKey[] = "apps";
const char kAppTitle[] = "Title";
const char16_t kAppTitle16[] = u"Title";
const char kAppTitle2[] = "Title2";
const char kTitleKey[] = "name";
const char kIconKey[] = "icon";
const char kLaunchUrlKey[] = "launch_url";
const char kIconPath[] = "chrome/test/data/load_image/image.png";
const char kIconBadPath[] = "chrome/test/data/load_image/image.html";
const char kIconUrl[] = "/load_image/image.png";
const char kIconUrl2[] = "/load_image/fail_image.png";
const char kIconExampleUrl1[] = "https://example.com/icon1.png";
const char kIconExampleUrl2[] = "https://example.com/icon2.png";
const char kLastIconUrlKey[] = "last_icon_url";
const char kLaunchUrl[] = "https://example.com/launch";
const char kStartUrl[] = "https://example.com/start";

base::FilePath GetFullPathToImage(bool valid) {
  base::FilePath test_data_dir;
  CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &test_data_dir));
  return test_data_dir.Append(valid ? kIconPath : kIconBadPath);
}

void PopulateIcon(web_app::WebAppInstallInfo* web_app_info,
                  const std::string& icon_url_str) {
  web_app::IconsMap icons_map;
  const GURL icon_url(icon_url_str);
  std::vector<SkBitmap> bmp = {web_app::CreateSquareIcon(32, SK_ColorWHITE)};
  icons_map.emplace(icon_url, bmp);

  web_app::PopulateProductIcons(web_app_info, &icons_map);
}

const std::string* GetLastIconUrlForAppId() {
  return g_browser_process->local_state()
      ->GetDict(WebKioskAppManager::kWebKioskDictionaryName)
      .FindDict(KioskAppDataBase::kKeyApps)
      ->FindDict(kAppId)
      ->FindString(kLastIconUrlKey);
}

}  // namespace

class WebKioskAppDataTest : public InProcessBrowserTest,
                            public KioskAppDataDelegate {
 public:
  void WaitForAppDataChange(int count) {
    for (int i = 0; i < count; i++) {
      waiter_.Take();
    }
  }

  void SetCached(bool installed, bool icon_valid = true) {
    const std::string app_key = std::string(kAppKey) + '.' + kAppId;
    auto app_dict =
        base::Value::Dict()
            .SetByDottedPath(app_key + '.' + std::string(kTitleKey), kAppTitle)
            .SetByDottedPath(app_key + '.' + std::string(kIconKey),
                             GetFullPathToImage(icon_valid).value());
    if (installed) {
      app_dict.SetByDottedPath(app_key + '.' + std::string(kLaunchUrlKey),
                               kLaunchUrl);
    }
    g_browser_process->local_state()->Set(
        WebKioskAppManager::kWebKioskDictionaryName,
        base::Value(std::move(app_dict)));
  }

 private:
  // KioskAppDataDelegate:
  void GetKioskAppIconCacheDir(base::FilePath* cache_dir) override {
    base::FilePath user_data_dir;
    bool has_dir =
        base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
    DCHECK(has_dir);
    *cache_dir = user_data_dir;
  }

  void OnKioskAppDataChanged(const std::string& app_id) override {
    waiter_.AddValue(true);
  }

  void OnKioskAppDataLoadFailure(const std::string& app_id) override {}

  void OnExternalCacheDamaged(const std::string& app_id) override {}

  base::test::RepeatingTestFuture<bool> waiter_;
};

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, NoIconCached) {
  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           std::string(), /*icon_url=*/GURL());
  EXPECT_FALSE(app_data.LoadFromCache());
  // The app will stay in the INIT state if there is nothing to be loaded from
  // cache.
  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kInit);
  EXPECT_EQ(app_data.name(), kAppUrl);
  EXPECT_TRUE(app_data.icon().isNull());
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, LoadCachedIcon) {
  SetCached(/*installed=*/false);
  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           std::string(), /*icon_url=*/GURL());
  EXPECT_TRUE(app_data.LoadFromCache());
  app_data.LoadIcon();
  WaitForAppDataChange(2);

  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kLoaded);
  EXPECT_EQ(app_data.name(), kAppTitle);
  EXPECT_FALSE(app_data.icon().isNull());
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, PRE_DownloadedIconPersists) {
  // Start test server.
  net::EmbeddedTestServer test_server;
  test_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(test_server.Start());

  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           kAppTitle,
                           /*icon_url=*/test_server.GetURL(kIconUrl));
  app_data.LoadFromCache();
  app_data.LoadIcon();
  WaitForAppDataChange(1);

  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kLoaded);
  EXPECT_EQ(app_data.name(), kAppTitle);
  const std::string* icon_url_string = GetLastIconUrlForAppId();
  ASSERT_TRUE(icon_url_string);
  ASSERT_EQ(*icon_url_string, test_server.GetURL(kIconUrl).spec());
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, DownloadedIconPersists) {
  // No test server is launched intentionaly to verify that we are using the
  // cached icon.
  // We should still find the correct icon url in order to not initiate a
  // redownload.
  const std::string* icon_url_string = GetLastIconUrlForAppId();
  ASSERT_TRUE(icon_url_string);
  const GURL icon_url = GURL(*icon_url_string);

  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           kAppTitle2, /*icon_url=*/icon_url);
  EXPECT_TRUE(app_data.LoadFromCache());
  // Icon is stored in cache.
  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kLoading);

  app_data.LoadIcon();
  WaitForAppDataChange(2);

  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kLoaded);
  // The title should not persist.
  EXPECT_EQ(app_data.name(), kAppTitle2);
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest,
                       PRE_RedownloadIconWhenDifferentUrl) {
  // Start test server.
  net::EmbeddedTestServer test_server;
  test_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(test_server.Start());

  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           kAppTitle,
                           /*icon_url=*/test_server.GetURL(kIconUrl));
  app_data.LoadFromCache();
  app_data.LoadIcon();
  WaitForAppDataChange(1);

  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kLoaded);
  EXPECT_EQ(app_data.name(), kAppTitle);
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, RedownloadIconWhenDifferentUrl) {
  // Start test server.
  net::EmbeddedTestServer test_server;
  test_server.AddDefaultHandlers(GetChromeTestDataDir());
  ASSERT_TRUE(test_server.Start());

  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           kAppTitle2,
                           /*icon_url=*/test_server.GetURL(kIconUrl2));

  EXPECT_FALSE(app_data.LoadFromCache());
  // No icon was loaded from cache because urls are different.
  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kInit);

  app_data.LoadIcon();
  WaitForAppDataChange(1);

  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kLoaded);
  EXPECT_EQ(app_data.name(), kAppTitle2);
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, AlreadyInstalled) {
  SetCached(/*installed=*/true);
  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           kAppTitle2, /*icon_url=*/GURL());
  app_data.LoadFromCache();
  app_data.LoadIcon();
  WaitForAppDataChange(2);

  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kInstalled);
  EXPECT_EQ(app_data.name(), kAppTitle);
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, LaunchableUrl) {
  SetCached(/*installed=*/true);

  // `launch_url` is treated as launchable URL if the app hasn't been installed.
  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           kAppTitle, /*icon_url=*/GURL());
  EXPECT_NE(app_data.status(), WebKioskAppData::Status::kInstalled);
  EXPECT_EQ(app_data.GetLaunchableUrl(), GURL(kAppUrl));

  // `start_url` is treated as launchable URL if the app has been installed.
  auto app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(GURL(kStartUrl));
  app_data.UpdateFromWebAppInfo(*app_info);
  app_data.LoadFromCache();
  WaitForAppDataChange(1);
  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kInstalled);
  EXPECT_EQ(app_data.GetLaunchableUrl(), GURL(kStartUrl));
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest,
                       PRE_CanLoadFromCacheAfterUpdatingFromWebAppInfo) {
  // We do not use `icon_url` for loading icon in this test, it is set to
  // correctly test `LoadFromCache` function.
  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl), "",
                           /*icon_url=*/GURL(kIconExampleUrl1));

  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kInit);
  EXPECT_EQ(app_data.GetLaunchableUrl(), GURL(kAppUrl));
  EXPECT_TRUE(app_data.icon().isNull());

  auto app_info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(GURL(kStartUrl));
  app_info->title = kAppTitle16;
  PopulateIcon(app_info.get(), kIconExampleUrl1);

  app_data.UpdateFromWebAppInfo(*app_info);
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest,
                       CanLoadFromCacheAfterUpdatingFromWebAppInfo) {
  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl), "",
                           /*icon_url=*/GURL(kIconExampleUrl2));

  EXPECT_TRUE(app_data.LoadFromCache());
}

IN_PROC_BROWSER_TEST_F(WebKioskAppDataTest, InvalidIcon) {
  SetCached(/*installed=*/false, /*icon_valid=*/false);
  WebKioskAppData app_data(this, kAppId, EmptyAccountId(), GURL(kAppUrl),
                           std::string(), /*icon_url*/ GURL());
  base::test::TestFuture<void> waiter;
  app_data.SetOnLoadedCallbackForTesting(waiter.GetCallback());

  app_data.LoadFromCache();
  app_data.LoadIcon();
  EXPECT_TRUE(waiter.Wait());
  EXPECT_EQ(app_data.status(), WebKioskAppData::Status::kLoaded);
  const std::string* icon_url_string = GetLastIconUrlForAppId();
  ASSERT_FALSE(icon_url_string);
}
}  // namespace ash