chromium/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils_unittest.cc

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

#include "ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"

#include <string>

#include "ash/public/cpp/test/in_process_data_decoder.h"
#include "ash/webui/common/mojom/sea_pen.mojom.h"
#include "base/files/file_path.h"
#include "base/i18n/rtl.h"
#include "base/json/json_writer.h"
#include "base/json/values_util.h"
#include "base/test/icu_test_util.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time_override.h"
#include "base/values.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace {

std::string user_search_query = "search query";
std::string escaped_user_search_query = "search%20query";
std::string user_visible_query_text = "test template query text";
std::string user_visible_query_template = "test template title";

base::Value::Dict GetTestTemplateQueryDict(
    base::Time time = base::Time::Now()) {
  return base::Value::Dict()
      .Set("creation_time", base::TimeToValue(time))
      .Set("template_id",
           base::NumberToString(static_cast<int32_t>(
               ash::personalization_app::mojom::SeaPenTemplateId::kFlower)))
      .Set("options", base::Value::Dict()
                          .Set(base::NumberToString(static_cast<int32_t>(
                                   ash::personalization_app::mojom::
                                       SeaPenTemplateChip::kFlowerColor)),
                               base::NumberToString(static_cast<int32_t>(
                                   ash::personalization_app::mojom::
                                       SeaPenTemplateOption::kFlowerColorBlue)))
                          .Set(base::NumberToString(static_cast<int32_t>(
                                   ash::personalization_app::mojom::
                                       SeaPenTemplateChip::kFlowerType)),
                               base::NumberToString(static_cast<int32_t>(
                                   ash::personalization_app::mojom::
                                       SeaPenTemplateOption::kFlowerTypeRose))))
      .Set("user_visible_query_text", user_visible_query_text)
      .Set("user_visible_query_template", user_visible_query_template);
}

base::Value::Dict GetTestFreeformQueryDict(
    base::Time time = base::Time::Now()) {
  return base::Value::Dict()
      .Set("creation_time", base::TimeToValue(time))
      .Set("freeform_query", escaped_user_search_query);
}

base::Value::Dict GetTestInvalidTemplateQueryDict(
    const std::string& missing_field) {
  base::Value::Dict template_query_dict = GetTestTemplateQueryDict();
  if (template_query_dict.contains(missing_field)) {
    template_query_dict.Remove(missing_field);
  }
  return template_query_dict;
}

base::subtle::ScopedTimeClockOverrides CreateScopedTimeNowOverride() {
  return base::subtle::ScopedTimeClockOverrides(
      []() -> base::Time {
        base::Time fake_now;
        bool success =
            base::Time::FromString("2023-04-05T01:23:45Z", &fake_now);
        DCHECK(success);
        return fake_now;
      },
      nullptr, nullptr);
}

personalization_app::mojom::RecentSeaPenImageInfoPtr
SeaPenQueryDictToRecentImageInfo(const base::Value::Dict& dict) {
  std::string json_string = base::WriteJson(dict).value_or(std::string());
  base::test::TestFuture<personalization_app::mojom::RecentSeaPenImageInfoPtr>
      future;
  DecodeJsonMetadata(json_string, future.GetCallback());
  return future.Take();
}

class SeaPenMetadataUtilsTest : public testing::Test {
 public:
  SeaPenMetadataUtilsTest() = default;

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

  ~SeaPenMetadataUtilsTest() override = default;

 private:
  base::test::TaskEnvironment task_environment_;
  InProcessDataDecoder decoder_;
};

TEST_F(SeaPenMetadataUtilsTest, SeaPenTextQueryToDict) {
  auto time_override = CreateScopedTimeNowOverride();

  ash::personalization_app::mojom::SeaPenQueryPtr search_query =
      ash::personalization_app::mojom::SeaPenQuery::NewTextQuery(
          user_search_query);

  base::Value::Dict result = SeaPenQueryToDict(search_query);

  EXPECT_EQ(GetTestFreeformQueryDict(), result);
}

TEST_F(SeaPenMetadataUtilsTest, QueryDictEscapesFreeformQuery) {
  auto time_override = CreateScopedTimeNowOverride();
  std::string freeform_query = "<div>test</div>";
  std::string escaped_freeform_query = "%3Cdiv%3Etest%3C%2Fdiv%3E";
  ash::personalization_app::mojom::SeaPenQueryPtr search_query =
      ash::personalization_app::mojom::SeaPenQuery::NewTextQuery(
          freeform_query);

  base::Value::Dict result = SeaPenQueryToDict(search_query);

  EXPECT_EQ(escaped_freeform_query, *result.FindString("freeform_query"));
}

TEST_F(SeaPenMetadataUtilsTest, SeaPenTemplateQueryToDict) {
  auto time_override = CreateScopedTimeNowOverride();

  base::flat_map<ash::personalization_app::mojom::SeaPenTemplateChip,
                 ash::personalization_app::mojom::SeaPenTemplateOption>
      options(
          {{ash::personalization_app::mojom::SeaPenTemplateChip::kFlowerColor,
            ash::personalization_app::mojom::SeaPenTemplateOption::
                kFlowerColorBlue},
           {ash::personalization_app::mojom::SeaPenTemplateChip::kFlowerType,
            ash::personalization_app::mojom::SeaPenTemplateOption::
                kFlowerTypeRose}});
  ash::personalization_app::mojom::SeaPenQueryPtr search_query =
      ash::personalization_app::mojom::SeaPenQuery::NewTemplateQuery(
          ash::personalization_app::mojom::SeaPenTemplateQuery::New(
              ash::personalization_app::mojom::SeaPenTemplateId::kFlower,
              options,
              ash::personalization_app::mojom::SeaPenUserVisibleQuery::New(
                  user_visible_query_text, user_visible_query_template)));

  base::Value::Dict result = SeaPenQueryToDict(search_query);

  EXPECT_EQ(GetTestTemplateQueryDict(), result);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoVerifyCreationTime) {
  const base::test::ScopedRestoreICUDefaultLocale locale("en_US");
  const base::test::ScopedRestoreDefaultTimezone la_time("America/Los_Angeles");

  base::Time test_creation_time;
  ASSERT_TRUE(base::Time::FromString("Fri, 01 Dec 2023 00:00:00 GMT",
                                     &test_creation_time));

  auto recent_image_info = SeaPenQueryDictToRecentImageInfo(
      GetTestFreeformQueryDict(test_creation_time));

  // creation time in recent_image_info should be "Nov 30, 2023" which is
  // converted from "Fri, 01 Dec 2023 00:00:00 GMT" to America/Los_Angeles time.
  EXPECT_EQ(u"Nov 30, 2023", recent_image_info->creation_time);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoInvalidCreationTime) {
  base::Value::Dict invalid_creation_time_query_dict =
      GetTestFreeformQueryDict().Set("creation_time", "invalid creation time");
  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(invalid_creation_time_query_dict);

  EXPECT_FALSE(recent_image_info->creation_time);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoValidTemplateData) {
  base::flat_map<ash::personalization_app::mojom::SeaPenTemplateChip,
                 ash::personalization_app::mojom::SeaPenTemplateOption>
      options(
          {{ash::personalization_app::mojom::SeaPenTemplateChip::kFlowerColor,
            ash::personalization_app::mojom::SeaPenTemplateOption::
                kFlowerColorBlue},
           {ash::personalization_app::mojom::SeaPenTemplateChip::kFlowerType,
            ash::personalization_app::mojom::SeaPenTemplateOption::
                kFlowerTypeRose}});
  ash::personalization_app::mojom::SeaPenQueryPtr expected_template_query =
      ash::personalization_app::mojom::SeaPenQuery::NewTemplateQuery(
          ash::personalization_app::mojom::SeaPenTemplateQuery::New(
              ash::personalization_app::mojom::SeaPenTemplateId::kFlower,
              options,
              ash::personalization_app::mojom::SeaPenUserVisibleQuery::New(
                  user_visible_query_text, user_visible_query_template)));

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(GetTestTemplateQueryDict());

  EXPECT_TRUE(recent_image_info->query.Equals(expected_template_query));
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoValidFreeformData) {
  ash::personalization_app::mojom::SeaPenQueryPtr expected_freeform_query =
      ash::personalization_app::mojom::SeaPenQuery::NewTextQuery(
          user_search_query);

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(GetTestFreeformQueryDict());

  EXPECT_TRUE(recent_image_info->query.Equals(expected_freeform_query));
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoMissingCreationTime) {
  base::Value::Dict invalid_template_query_dict =
      GetTestInvalidTemplateQueryDict(/*missing_field=*/"creation_time");

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(invalid_template_query_dict);

  EXPECT_FALSE(recent_image_info);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoMissingTemplateId) {
  base::Value::Dict invalid_template_query_dict =
      GetTestInvalidTemplateQueryDict(/*missing_field=*/"template_id");

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(invalid_template_query_dict);

  EXPECT_FALSE(recent_image_info);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoInvalidTemplateId) {
  base::Value::Dict invalid_template_id_query_dict =
      GetTestTemplateQueryDict().Set("template_id", 10000);

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(invalid_template_id_query_dict);

  EXPECT_FALSE(recent_image_info);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoMissingOptions) {
  base::Value::Dict invalid_template_query_dict =
      GetTestInvalidTemplateQueryDict(/*missing_field=*/"options");

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(invalid_template_query_dict);

  EXPECT_FALSE(recent_image_info);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoInvalidOptionsChipId) {
  base::Value::Dict template_query_dict = GetTestTemplateQueryDict();
  auto* options = template_query_dict.FindDict("options");
  ASSERT_TRUE(options);
  // Update `options` Value::Dict with an invalid chip id.
  options->Set("10000", base::NumberToString(static_cast<int32_t>(
                            ash::personalization_app::mojom::
                                SeaPenTemplateOption::kFlowerColorYellow)));

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(template_query_dict);

  EXPECT_FALSE(recent_image_info);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoInvalidOptionsOptionId) {
  base::Value::Dict template_query_dict = GetTestTemplateQueryDict();
  auto* options = template_query_dict.FindDict("options");
  ASSERT_TRUE(options);
  // Update `options` Value::Dict with an invalid option id.
  options->Set(base::NumberToString(static_cast<int32_t>(
                   ash::personalization_app::mojom::SeaPenTemplateChip::
                       kCharactersColor)),
               "10000");

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(template_query_dict);

  EXPECT_FALSE(recent_image_info);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoMissingUserVisibleQueryText) {
  base::Value::Dict invalid_template_query_dict =
      GetTestInvalidTemplateQueryDict(
          /*missing_field=*/"user_visible_query_text");

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(invalid_template_query_dict);

  EXPECT_FALSE(recent_image_info);
}

TEST_F(SeaPenMetadataUtilsTest,
       SeaPenQueryDictToRecentImageInfoMissingUserVisibleQueryTemplate) {
  base::Value::Dict invalid_template_query_dict =
      GetTestInvalidTemplateQueryDict(
          /*missing_field=*/"user_visible_query_template");

  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(invalid_template_query_dict);

  EXPECT_FALSE(recent_image_info);
}

TEST_F(SeaPenMetadataUtilsTest, GetIdFromValidFilePath) {
  std::vector<std::pair<std::string, uint32_t>> cases = {
      {"97531", 97531u},
      {"24680.jpg", 24680},
      {"abcd/1234.jpg", 1234u},
      {"a/b/c/d/575757.png", 575757u}};
  for (const auto& [path, expected] : cases) {
    EXPECT_EQ(expected, GetIdFromFileName(base::FilePath(path)));
  }
}

TEST_F(SeaPenMetadataUtilsTest, GetIdFromInvalidFilePath) {
  std::vector<std::string> cases = {"a", "b.jpg", "-21", "a/21.jpg/c"};
  for (const auto& path : cases) {
    EXPECT_FALSE(GetIdFromFileName(base::FilePath(path)).has_value());
  }
}

TEST_F(SeaPenMetadataUtilsTest, GetQueryStringFromTextQuery) {
  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(GetTestFreeformQueryDict());
  EXPECT_EQ(user_search_query, GetQueryString(recent_image_info));
}

TEST_F(SeaPenMetadataUtilsTest, GetUnescapedQueryStringFromEscapedTextQuery) {
  auto time_override = CreateScopedTimeNowOverride();
  std::string escaped_freeform_query = "%3Cdiv%3Etest%3C%2Fdiv%3E";
  std::string freeform_query = "<div>test</div>";
  auto query_dict = GetTestFreeformQueryDict();
  query_dict.Set("freeform_query", escaped_freeform_query);

  auto recent_image_info = SeaPenQueryDictToRecentImageInfo(query_dict);

  EXPECT_EQ(freeform_query, GetQueryString(recent_image_info));
}

TEST_F(SeaPenMetadataUtilsTest, GetQueryStringFromTemplateQuery) {
  auto recent_image_info =
      SeaPenQueryDictToRecentImageInfo(GetTestTemplateQueryDict());
  EXPECT_EQ(user_visible_query_text, GetQueryString(recent_image_info));
}

TEST_F(SeaPenMetadataUtilsTest, GetQueryStringFromNullPtr) {
  EXPECT_EQ(std::string(), GetQueryString(nullptr));
}

}  // namespace
}  // namespace ash