// 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 "chrome/browser/web_applications/web_app_proto_utils.h"

#include <memory>

#include "base/base64.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "components/sync/protocol/web_app_specifics.pb.h"
#include "components/webapps/common/web_app_id.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace web_app {

namespace {

using Purpose = blink::mojom::ManifestImageResource_Purpose;

const char kStartUrl[] = "https://example.com/launchurl";
const char kAppName[] = "Test name";
const char kScope[] = "https://example.com/scope";
const char kUserPageOrdinal[] = "fake_user_page_ordinal";
const char kUserLaunchOrdinal[] = "fake_user_launch_ordinal";
const char kIconUrl[] = "https://example.com/icon.png";
const int kIconSizePx = 128;

}  // namespace

// Test that a M85 serialized proto parses correctly in the current version.
TEST(WebAppProtoUtilsTest, M85SpecificsProtoParse) {
  // Serialized in branch 4183 (M85) using:
  // sync_proto.set_launch_url(kStartUrl);
  // sync_proto.set_name(kAppName);
  // sync_proto.set_user_display_mode(sync_pb::WebAppSpecifics::BROWSER);
  // sync_pb::WebAppIconInfo* icon_info = sync_proto.add_icon_infos();
  // icon_info->set_url(kIconUrl);
  // sync_proto.SerializeAsString()
  const std::string serialized_proto =
      "\n\035https://example.com/launchurl\022\tTest "
      "name\030\0012\036\022\034https://example.com/icon.png";

  // Parse the proto.
  sync_pb::WebAppSpecifics sync_proto;
  bool parsed = sync_proto.ParseFromString(serialized_proto);

  // Check the proto was parsed.
  ASSERT_TRUE(parsed);
  EXPECT_EQ(kStartUrl, sync_proto.start_url());
  EXPECT_EQ(kAppName, sync_proto.name());
  EXPECT_EQ(sync_pb::WebAppSpecifics::BROWSER,
            sync_proto.user_display_mode_default());
  ASSERT_EQ(1, sync_proto.icon_infos_size());
  EXPECT_EQ(kIconUrl, sync_proto.icon_infos(0).url());
  EXPECT_FALSE(sync_proto.icon_infos(0).has_purpose());

  // Check the fields can be parsed into the IconInfo struct.
  std::optional<std::vector<apps::IconInfo>> parsed_icon_infos =
      ParseAppIconInfos("WebAppProtoUtilsTest", sync_proto.icon_infos());
  ASSERT_TRUE(parsed_icon_infos.has_value());
  ASSERT_EQ(1u, parsed_icon_infos.value().size());
  EXPECT_EQ(kIconUrl, parsed_icon_infos.value()[0].url);
  // Missing `purpose` parsed as `kAny`.
  EXPECT_EQ(apps::IconInfo::Purpose::kAny,
            parsed_icon_infos.value()[0].purpose);

  // Check the proto can be stored on a web app struct.
  std::unique_ptr<WebApp> web_app = test::CreateWebAppFromSyncProto(sync_proto);
}

// Test that a minimal M85 proto (ie. only fields that would always be set in
// M85) is correctly parsed to a apps::IconInfo in the current
// Chromium version.
TEST(WebAppProtoUtilsTest, M85SpecificsProtoToWebApp_Minimal) {
  // Set the minimal proto fields.
  sync_pb::WebAppSpecifics sync_proto;
  sync_proto.set_start_url(kStartUrl);
  sync_proto.set_name(kAppName);
  sync_proto.set_user_display_mode_default(sync_pb::WebAppSpecifics::BROWSER);

  EXPECT_EQ(kAppName, sync_proto.name());

  // Check the fields were parsed into the IconInfo struct.
  std::optional<std::vector<apps::IconInfo>> parsed_icon_infos =
      ParseAppIconInfos("WebAppProtoUtilsTest", sync_proto.icon_infos());

  // Check the fields were parsed.
  ASSERT_TRUE(parsed_icon_infos.has_value());
  ASSERT_EQ(0u, parsed_icon_infos->size());

  // Check the proto can be stored on a web app struct.
  std::unique_ptr<WebApp> web_app = test::CreateWebAppFromSyncProto(sync_proto);
}

// Test that a M85 proto with all fields populated is correctly parsed to a
// apps::IconInfo in the current Chromium version.
TEST(WebAppProtoUtilsTest, M85SpecificsProtoToWebApp_FullyPopulated) {
  // Set all proto fields.
  sync_pb::WebAppSpecifics sync_proto;
  sync_proto.set_start_url(kStartUrl);
  sync_proto.set_name(kAppName);
  sync_proto.set_user_display_mode_default(
      sync_pb::WebAppSpecifics::STANDALONE);
  sync_proto.set_theme_color(SK_ColorRED);
  sync_proto.set_scope(kScope);
  sync_proto.set_user_page_ordinal(kUserPageOrdinal);
  sync_proto.set_user_launch_ordinal(kUserLaunchOrdinal);
  sync_pb::WebAppIconInfo* icon_info_1 = sync_proto.add_icon_infos();
  icon_info_1->set_url(kIconUrl);
  icon_info_1->set_size_in_px(kIconSizePx);
  icon_info_1->set_purpose(sync_pb::WebAppIconInfo_Purpose_ANY);
  sync_pb::WebAppIconInfo* icon_info_2 = sync_proto.add_icon_infos();
  icon_info_2->set_url(kIconUrl);
  icon_info_2->set_size_in_px(kIconSizePx);
  icon_info_2->set_purpose(sync_pb::WebAppIconInfo_Purpose_MASKABLE);

  // Check the fields can be parsed into the IconInfo struct.
  std::optional<std::vector<apps::IconInfo>> icon_infos =
      ParseAppIconInfos("WebAppProtoUtilsTest", sync_proto.icon_infos());
  ASSERT_TRUE(icon_infos.has_value());
  ASSERT_EQ(2u, icon_infos->size());
  EXPECT_EQ(kIconUrl, icon_infos.value()[0].url);
  EXPECT_EQ(kIconSizePx, icon_infos.value()[0].square_size_px);
  EXPECT_EQ(apps::IconInfo::Purpose::kAny, icon_infos.value()[0].purpose);
  EXPECT_EQ(kIconUrl, icon_infos.value()[1].url);
  EXPECT_EQ(kIconSizePx, icon_infos.value()[1].square_size_px);
  EXPECT_EQ(apps::IconInfo::Purpose::kMaskable, icon_infos.value()[1].purpose);
}

// Test that a serialized proto with an unrecognized new field parses correctly
// in the current version and preserves the field value.
TEST(WebAppProtoUtilsTest, SpecificsProtoWithNewFieldParses) {
  // Serialized in M125 by modifying web_app_specifics.proto:
  // +optional string test_new_field = 5372767;
  // Then:
  // sync_pb::WebAppSpecifics sync_proto;
  // sync_proto.set_start_url(kStartUrl);
  // sync_proto.set_name(kAppName);
  // sync_proto.set_user_display_mode_default(
  //     sync_pb::WebAppSpecifics::BROWSER);
  // sync_proto.set_test_new_field("hello");
  // sync_proto.SerializeAsString();
  const std::string serialized_proto =
      "\n\035https://example.com/launchurl\022\tTest "
      "name\030\001\372\265\277\024\005hello";

  // Parse the proto.
  sync_pb::WebAppSpecifics sync_proto;
  bool parsed = sync_proto.ParseFromString(serialized_proto);

  // Check the proto was parsed.
  ASSERT_TRUE(parsed);
  EXPECT_EQ(kStartUrl, sync_proto.start_url());

  // Check the proto can be stored on a web app struct.
  std::unique_ptr<WebApp> web_app = test::CreateWebAppFromSyncProto(sync_proto);
  sync_pb::WebAppSpecifics result_proto = web_app->sync_proto();

  // Clear any extra fields that are set by the web app system during
  // construction.
  result_proto.clear_relative_manifest_id();
  result_proto.clear_scope();

  // Check that the sync proto retained its value, including the unknown field.
  EXPECT_EQ(result_proto.SerializeAsString(), serialized_proto);
}

// Test that a serialized proto with an unrecognized new enum value parses
// correctly in the current version and preserves the enum value.
TEST(WebAppProtoUtilsTest, SpecificsProtoWithNewEnumValueParses) {
  // Serialized in M125 by modifying web_app_specifics.proto:
  // in "enum UserDisplayMode":
  // +TEST_NEW_ENUM = 5372767;
  // Then:
  // sync_pb::WebAppSpecifics sync_proto;
  // sync_proto.set_start_url(kStartUrl);
  // sync_proto.set_name(kAppName);
  // sync_proto.set_user_display_mode_default(
  //     sync_pb::WebAppSpecifics::TEST_NEW_ENUM);
  // sync_proto.SerializeAsString();
  const std::string serialized_proto =
      "\n\035https://example.com/launchurl\022\tTest name\030\337\366\307\002";

  // Parse the proto.
  sync_pb::WebAppSpecifics sync_proto;
  bool parsed = sync_proto.ParseFromString(serialized_proto);

  // Check the proto was parsed.
  ASSERT_TRUE(parsed);
  EXPECT_EQ(kStartUrl, sync_proto.start_url());
  // Unfortunately, proto2 reports unknown field values as unset.
  EXPECT_FALSE(sync_proto.has_user_display_mode_default());
  // Unknown field value parses as 0, ie. "Unspecified".
  EXPECT_EQ(sync_proto.user_display_mode_default(),
            sync_pb::WebAppSpecifics_UserDisplayMode_UNSPECIFIED);

  // Check the proto can be stored on a web app struct.
  std::unique_ptr<WebApp> web_app = test::CreateWebAppFromSyncProto(sync_proto);
  sync_pb::WebAppSpecifics result_proto = web_app->sync_proto();

  // Clear any extra fields that are set by the web app system during
  // construction.
  result_proto.clear_relative_manifest_id();
  result_proto.clear_scope();

  // Check that the sync proto retained its value, including the unknown field.
  EXPECT_EQ(result_proto.SerializeAsString(), serialized_proto);
}

TEST(WebAppProtoUtilsTest, RunOnOsLoginModes) {
  RunOnOsLoginMode mode =
      ToRunOnOsLoginMode(proto::WebApp::RUN_ON_OS_LOGIN_MODE_MINIMIZED);
  EXPECT_EQ(RunOnOsLoginMode::kMinimized, mode);

  mode = ToRunOnOsLoginMode(proto::WebApp::RUN_ON_OS_LOGIN_MODE_WINDOWED);
  EXPECT_EQ(RunOnOsLoginMode::kWindowed, mode);

  mode = ToRunOnOsLoginMode(proto::WebApp::RUN_ON_OS_LOGIN_MODE_NOT_RUN);
  EXPECT_EQ(RunOnOsLoginMode::kNotRun, mode);

  // Any other value should return kNotRun.
  mode =
      ToRunOnOsLoginMode(static_cast<proto::WebApp::RunOnOsLoginMode>(0xCAFE));
  EXPECT_EQ(RunOnOsLoginMode::kNotRun, mode);

  proto::WebApp::RunOnOsLoginMode proto_mode =
      ToWebAppProtoRunOnOsLoginMode(RunOnOsLoginMode::kWindowed);
  EXPECT_EQ(proto::WebApp::RUN_ON_OS_LOGIN_MODE_WINDOWED, proto_mode);

  proto_mode = ToWebAppProtoRunOnOsLoginMode(RunOnOsLoginMode::kMinimized);
  EXPECT_EQ(proto::WebApp::RUN_ON_OS_LOGIN_MODE_MINIMIZED, proto_mode);

  proto_mode = ToWebAppProtoRunOnOsLoginMode(RunOnOsLoginMode::kNotRun);
  EXPECT_EQ(proto::WebApp::RUN_ON_OS_LOGIN_MODE_NOT_RUN, proto_mode);
}

}  // namespace web_app
