blob: f61dca185e77163814b387d072be8eec297cfe07 [file] [log] [blame]
// 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/plus_addresses/plus_address_parsing_utils.h"
#include <optional>
#include "base/json/json_reader.h"
#include "base/strings/string_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/expected.h"
#include "components/plus_addresses/core/browser/plus_address_types.h"
#include "components/plus_addresses/core/common/features.h"
#include "components/plus_addresses/plus_address_test_utils.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace plus_addresses {
using ::testing::ElementsAre;
using ::testing::Optional;
// PlusAddressParsing tests validate the ParsePlusAddressFrom* methods
// Returns empty when the DataDecoder fails to parse the JSON.
TEST(PlusAddressParsing, NotValidJson) {
EXPECT_EQ(ParsePlusProfileFromV1Create(base::unexpected("error!")),
std::nullopt);
}
TEST(PlusAddressParsing, FromV1Create_ParsesSuccessfully) {
const std::string kProfileId = "123";
const affiliations::FacetURI kFacet =
affiliations::FacetURI::FromPotentiallyInvalidSpec(
"https://www.apple.com");
const std::string kPlusAddress = "fubar@plus.com";
// Test when the plusMode should set is_confirmed to true.
std::optional<base::Value> valid_mode =
base::JSONReader::Read(base::ReplaceStringPlaceholders(
R"(
{
"plusProfile": {
"unwanted": 123,
"ProfileId": "$1",
"facet": "$2",
"plusEmail" : {
"plusAddress": "$3",
"plusMode": "validMode"
}
},
"unwanted": "abc"
}
)",
{kProfileId, kFacet.canonical_spec(), kPlusAddress},
/*offsets=*/nullptr));
ASSERT_TRUE(valid_mode.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(valid_mode.value());
std::optional<PlusProfile> valid_result =
ParsePlusProfileFromV1Create(std::move(value));
ASSERT_TRUE(valid_result.has_value());
EXPECT_EQ(valid_result->profile_id, kProfileId);
EXPECT_EQ(valid_result->facet, kFacet);
EXPECT_EQ(valid_result->plus_address, PlusAddress(kPlusAddress));
EXPECT_EQ(valid_result->is_confirmed, true);
// Test when the plusMode should set is_confirmed to false.
std::optional<base::Value> invalid_mode =
base::JSONReader::Read(base::ReplaceStringPlaceholders(
R"(
{
"plusProfile": {
"unwanted": 123,
"ProfileId": "$1",
"facet": "$2",
"plusEmail" : {
"plusAddress": "$3",
"plusMode": "MODE_UNSPECIFIED"
}
},
"unwanted": "abc"
}
)",
{kProfileId, kFacet.canonical_spec(), kPlusAddress},
/*offsets=*/nullptr));
ASSERT_TRUE(invalid_mode.has_value());
data_decoder::DataDecoder::ValueOrError decoded =
std::move(invalid_mode.value());
std::optional<PlusProfile> invalid_result =
ParsePlusProfileFromV1Create(std::move(decoded));
ASSERT_TRUE(invalid_result.has_value());
EXPECT_EQ(invalid_result->profile_id, kProfileId);
EXPECT_EQ(invalid_result->facet, kFacet);
EXPECT_EQ(invalid_result->plus_address, PlusAddress(kPlusAddress));
EXPECT_EQ(invalid_result->is_confirmed, false);
}
// Validate that there is a plusAddress field in the plusEmail object.
TEST(PlusAddressParsing, FromV1Create_FailsWithoutPlusAddress) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"plusProfile": {
"plusEmail" : {
"plusMode": "validMode"
}
}
}
)");
ASSERT_TRUE(json.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(json.value());
EXPECT_EQ(ParsePlusProfileFromV1Create(std::move(value)), std::nullopt);
}
// Validate that there is a plusMode field in the plusEmail object.
TEST(PlusAddressParsing, FromV1Create_FailsWithoutPlusMode) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"plusProfile": {
"plusEmail" : {
"plusAddress": "plus@plus.plus"
}
}
}
)");
ASSERT_TRUE(json.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(json.value());
EXPECT_EQ(ParsePlusProfileFromV1Create(std::move(value)), std::nullopt);
}
// Validate that there is a plusEmail object.
TEST(PlusAddressParsing, FromV1Create_FailsWithoutEmailObject) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"plusProfile": {
"address": "foobar"
}
}
)");
ASSERT_TRUE(json.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(json.value());
EXPECT_EQ(ParsePlusProfileFromV1Create(std::move(value)), std::nullopt);
}
TEST(PlusAddressParsing, FromV1Create_FailsForEmptyDict) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"plusProfile": {}
}
)");
ASSERT_TRUE(json.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(json.value());
EXPECT_EQ(ParsePlusProfileFromV1Create(std::move(value)), std::nullopt);
}
TEST(PlusAddressParsing, FromV1Create_FailsWithoutPlusProfileKey) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"plusAddress": "wouldnt this be nice?"
}
)");
ASSERT_TRUE(json.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(json.value());
EXPECT_EQ(ParsePlusProfileFromV1Create(std::move(value)), std::nullopt);
}
TEST(PlusAddressParsing, FromV1Create_FailsIfPlusProfileIsNotDict) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"plusProfile": "not a dict"
}
)");
ASSERT_TRUE(json.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(json.value());
EXPECT_EQ(ParsePlusProfileFromV1Create(std::move(value)), std::nullopt);
}
// Tests that parsing the create response returns the first existing profile if
// there is no newly created profile.
TEST(PlusAddressParsing, FromV1Create_ParseExistingPlusProfiles) {
base::test::ScopedFeatureList feature_list{
features::kPlusAddressParseExistingProfilesFromCreateResponse};
const std::string kProfileId1 = "123";
const std::string kProfileId2 = "123";
const affiliations::FacetURI kFacet =
affiliations::FacetURI::FromPotentiallyInvalidSpec(
"https://www.apple.com");
const std::string kPlusAddress1 = "fubar@plus.com";
const std::string kPlusAddress2 = "fubar2@plus.com";
std::optional<base::Value> json =
base::JSONReader::Read(base::ReplaceStringPlaceholders(
R"(
{
"existingPlusProfiles": [
{
"ProfileId": "$1",
"facet": "$2",
"plusEmail": {
"plusAddress": "$3",
"plusMode": "validMode"
}
},
{
"ProfileId": "$4",
"facet": "$5",
"plusEmail": {
"plusAddress": "$6",
"plusMode": "validMode"
}
}
]
})",
{kProfileId1, kFacet.canonical_spec(), kPlusAddress1, kProfileId2,
kFacet.canonical_spec(), kPlusAddress2},
/*offsets=*/nullptr));
ASSERT_TRUE(json.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(json.value());
std::optional<PlusProfile> profile =
ParsePlusProfileFromV1Create(std::move(value));
EXPECT_EQ(profile,
PlusProfile(kProfileId1, kFacet, PlusAddress(kPlusAddress1),
/*is_confirmed=*/true));
}
// Tests that parsing the create response returns `std::nullopt` if there are no
// newly created or existing profiles.
TEST(PlusAddressParsing, FromV1Create_ParseEmptyExistingPlusProfiles) {
base::test::ScopedFeatureList feature_list{
features::kPlusAddressParseExistingProfilesFromCreateResponse};
std::optional<base::Value> json = base::JSONReader::Read(
R"(
{
"existingPlusProfiles": []
})");
ASSERT_TRUE(json.has_value());
data_decoder::DataDecoder::ValueOrError value = std::move(json.value());
EXPECT_EQ(ParsePlusProfileFromV1Create(std::move(value)), std::nullopt);
}
TEST(PlusAddressParsing, ParsePreallocatedPlusAddresses) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"emailAddresses": [
{
"emailAddress": "foo@foo.com",
"reservationLifetime": "123s"
},
{
"emailAddress": "foo@bar.com",
"reservationLifetime": "15552000s"
}
]
}
)");
ASSERT_TRUE(json);
std::optional<std::vector<PreallocatedPlusAddress>> addresses =
ParsePreallocatedPlusAddresses(std::move(*json));
EXPECT_THAT(
addresses,
Optional(ElementsAre(
PreallocatedPlusAddress(PlusAddress("foo@foo.com"),
/*lifetime=*/base::Seconds(123)),
PreallocatedPlusAddress(PlusAddress("foo@bar.com"),
/*lifetime=*/base::Seconds(15552000)))));
}
TEST(PlusAddressParsing, ParsePreallocatedPlusAddressesWithInvalidJSON) {
EXPECT_EQ(
ParsePreallocatedPlusAddresses(base::unexpected("An error occurred")),
std::nullopt);
}
// Tests that `ParsePreallocatedPlusAddresses` ignores malformed entries.
TEST(PlusAddressParsing, ParsePreallocatedPlusAddressesWithMalformedEntries) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"emailAddresses": [
{
"emailAddress": "foo@foo.com",
"reservationLifetime": "123s"
},
{
"emailAddress1": "foo@bar.com",
"reservationLifetime": "15552000s"
},
{
"emailAddress": "foo@bar.com",
"reservationLifetime1": "15552000s"
},
{
"emailAddress": "foo@bar.com",
"reservationLifetime": "15552000"
},
{
"emailAddress": "foo@bar.com",
"reservationLifetime": ""
},
{
"emailAddress": "foo@bar.com",
"reservationLifetime": "as"
},
"asd",
{
"emailAddress": "foo@goo.com",
"reservationLifetime": "312s"
}
]
}
)");
ASSERT_TRUE(json);
std::optional<std::vector<PreallocatedPlusAddress>> addresses =
ParsePreallocatedPlusAddresses(std::move(*json));
EXPECT_THAT(addresses,
Optional(ElementsAre(
PreallocatedPlusAddress(PlusAddress("foo@foo.com"),
/*lifetime=*/base::Seconds(123)),
PreallocatedPlusAddress(PlusAddress("foo@goo.com"),
/*lifetime=*/base::Seconds(312)))));
}
// Tests that `ParsePreallocatedPlusAddresses` returns `std::nullopt` if the
// top-level entry does not have the expected format.
TEST(PlusAddressParsing,
ParsePreallocatedPlusAddressesWithMalformedTopLevelEntry) {
std::optional<base::Value> json = base::JSONReader::Read(R"(
{
"Addresses": [
{
"emailAddress": "foo@foo.com",
"reservationLifetime": "123s"
}
],
"emailAddresses": "asd"
}
)");
ASSERT_TRUE(json);
std::optional<std::vector<PreallocatedPlusAddress>> addresses =
ParsePreallocatedPlusAddresses(std::move(*json));
EXPECT_THAT(addresses, std::nullopt);
}
} // namespace plus_addresses