blob: df34b28a6deb4c9ce16cc26b07399b840d0b5345 [file]
// Copyright 2026 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/autofill/android/entity_instance_android.h"
#include <string>
#include <vector>
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/types/optional_ref.h"
#include "chrome/browser/autofill/android/attribute_instance_android.h"
#include "chrome/browser/autofill/android/attribute_type_android.h"
#include "chrome/browser/autofill/android/entity_type_android.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_structured_address_component.h"
#include "components/autofill/core/browser/data_model/autofill_ai/entity_instance.h"
#include "components/autofill/core/browser/data_model/autofill_ai/entity_type.h"
#include "components/autofill/core/browser/data_model/autofill_ai/entity_type_names.h"
#include "components/autofill/core/browser/test_utils/entity_data_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace autofill {
namespace {
// ID of the dummy profile used for filling in tests.
constexpr char kGuid[] = "00000000-0000-0000-0000-000000000001";
constexpr char kNickname[] = "Nickname";
constexpr char16_t kFullName[] = u"John Doe";
AttributeInstanceAndroid CreateAttributeInstanceAndroid(
AttributeTypeName type_name,
std::u16string value,
VerificationStatus status = VerificationStatus::kNoStatus) {
return AttributeInstanceAndroid(
AttributeTypeAndroid(AttributeType(type_name)), std::move(value), status);
}
EntityInstanceAndroid CreatePassportEntityInstanceAndroid(
std::vector<AttributeInstanceAndroid> attributes =
{CreateAttributeInstanceAndroid(AttributeTypeName::kPassportName,
kFullName)},
EntityInstance::RecordType record_type =
EntityInstance::RecordType::kLocal) {
EntityTypeAndroid entity_type_android(
EntityType(EntityTypeName::kPassport),
/*is_enabled=*/true,
/*is_eligible_for_wallet_storage=*/false,
/*is_masked_storage_supported=*/true);
return EntityInstanceAndroid(
std::move(entity_type_android), record_type, std::move(attributes),
kNickname,
EntityMetadataAndroid(kGuid, base::Time::Now(), 0, base::Time::Now()),
/*requires_reauth_to_see=*/false, /*is_masked_server_entity=*/false);
}
class EntityInstanceAndroidTest : public testing::Test {
protected:
base::test::TaskEnvironment task_environment_;
};
TEST_F(EntityInstanceAndroidTest, ToEntityInstance_BasicConversion) {
EntityInstanceAndroid entity_instance_android =
CreatePassportEntityInstanceAndroid();
EntityInstance entity_instance = entity_instance_android.ToEntityInstance(
/*existing_entity=*/std::nullopt);
EXPECT_EQ(entity_instance.type(), EntityType(EntityTypeName::kPassport));
EXPECT_EQ(entity_instance.guid().value(), kGuid);
EXPECT_EQ(entity_instance.record_type(), EntityInstance::RecordType::kLocal);
ASSERT_EQ(entity_instance.attributes().size(), 1u);
EXPECT_EQ(entity_instance.attributes()[0].type(),
AttributeType(AttributeTypeName::kPassportName));
EXPECT_EQ(entity_instance.attributes()[0].GetCompleteRawInfo(), kFullName);
EXPECT_FALSE(entity_instance.IsMaskedEntity());
EXPECT_FALSE(entity_instance.IsServerInstance());
}
// Test that if an existing entity attribute did not change when converting an
// Instance from Java to C++, simply copy its value from the existing entity to
// the new entity.
TEST_F(EntityInstanceAndroidTest, ToEntityInstance_ReuseExistingAttribute) {
// Create an existing entity with the same attribute value.
EntityInstance existing_entity = test::GetEntityInstance(
{test::GetAttributeInstance(AttributeTypeName::kPassportName, kFullName,
VerificationStatus::kObserved)},
{.guid = kGuid});
// Create an Android entity with the same attribute value.
EntityInstanceAndroid entity_instance_android =
CreatePassportEntityInstanceAndroid();
EntityInstance converted_entity =
entity_instance_android.ToEntityInstance(existing_entity);
ASSERT_EQ(converted_entity.attributes().size(), 1u);
// The attribute should be reused, including its verification status.
EXPECT_EQ(converted_entity.attributes()[0].GetVerificationStatus(
AttributeType(AttributeTypeName::kPassportName).field_type()),
VerificationStatus::kObserved);
}
// Test that if an existing entity attribute did change when converting an
// Instance from Java to C++, this updated attribute is used when creating the
// new entity. Otherwise the attribute is copied from the original entity.
TEST_F(EntityInstanceAndroidTest, ToEntityInstance_UpdateExistingAttribute) {
std::u16string number = u"123456";
EntityInstance existing_entity = test::GetEntityInstance(
{test::GetAttributeInstance(AttributeTypeName::kPassportName, kFullName,
VerificationStatus::kObserved),
test::GetAttributeInstance(AttributeTypeName::kPassportNumber, number,
VerificationStatus::kNoStatus)},
{.guid = kGuid});
// Create an Android entity with the attributes (note that only the first one
// was really modified).
std::u16string new_passport_name = u"Jane Doe";
EntityInstanceAndroid entity_instance_android =
CreatePassportEntityInstanceAndroid(
{CreateAttributeInstanceAndroid(AttributeTypeName::kPassportName,
new_passport_name,
VerificationStatus::kUserVerified),
CreateAttributeInstanceAndroid(AttributeTypeName::kPassportNumber,
number,
VerificationStatus::kUserVerified)});
EntityInstance converted_entity =
entity_instance_android.ToEntityInstance(existing_entity);
ASSERT_EQ(converted_entity.attributes().size(), 2u);
// The passport name attribute was updated, therefore it should be recreated
// from scratch, including its verification status.
const base::optional_ref<const AttributeInstance>
updated_entity_passport_name = converted_entity.attribute(
AttributeType(AttributeTypeName::kPassportName));
EXPECT_EQ(updated_entity_passport_name->GetCompleteRawInfo(),
new_passport_name);
EXPECT_EQ(updated_entity_passport_name->GetVerificationStatus(
AttributeType(AttributeTypeName::kPassportName).field_type()),
VerificationStatus::kUserVerified);
// The number attribute was not updated, therefore it should be copied from
// the existing entity, including its verification status.
const base::optional_ref<const AttributeInstance>
updated_entity_passport_number = converted_entity.attribute(
AttributeType(AttributeTypeName::kPassportNumber));
EXPECT_EQ(updated_entity_passport_number->GetCompleteRawInfo(), number);
// The following condition checks that the number attribute was copied from
// the original/existing entity.
EXPECT_EQ(updated_entity_passport_number->GetVerificationStatus(
AttributeType(AttributeTypeName::kPassportNumber).field_type()),
VerificationStatus::kNoStatus);
}
// Makes sure the `EntityInstance` C++ -> Java -> C++ conversion results in the
// object equal to the initial entity. This process has known caveats:
// * The name attributes have a parsed substructure in C++, which is not stored
// in Java. An existing entity instance is provided to the conversion method
// to keep the substructure.
// * The dates (modified date and use date) are stored with microsecond
// precision in C++ and millisecond precision in Java. This means the test
// will work as long as the dates have zero microseconds.
TEST_F(EntityInstanceAndroidTest, DoubleConversion) {
EntityInstance passport = test::GetPassportEntityInstance(
{.record_type = EntityInstance::WalletRecordTypePayload{
.management_url =
GURL("https://wallet.google.com/synthetic_pass?id=fake123")}});
EntityInstanceAndroid entity_instance_android(
passport, /*is_enabled=*/true, /*is_eligible_for_wallet_storage=*/true,
/*requires_reauth_to_see=*/true);
JNIEnv* env = jni_zero::AttachCurrentThread();
jni_zero::ScopedJavaLocalRef<jobject> java_instance =
jni_zero::ToJniType<EntityInstanceAndroid>(env, entity_instance_android);
EntityInstanceAndroid converted_entity =
jni_zero::FromJniType<EntityInstanceAndroid>(env, java_instance);
EntityInstance converted_passport =
converted_entity.ToEntityInstance(passport);
EXPECT_EQ(passport, converted_passport);
}
// Test that the Wallet record type payload is initialized with default data
// when converting an EntityInstanceAndroid back to an EntityInstance
// without providing an existing entity instance (i.e. when creating a new
// entity instance).
TEST_F(EntityInstanceAndroidTest,
ToEntityInstance_NewWalletEntityInitializesPayload) {
EntityInstanceAndroid entity_instance_android =
CreatePassportEntityInstanceAndroid(
{CreateAttributeInstanceAndroid(AttributeTypeName::kPassportName,
kFullName)},
EntityInstance::RecordType::kServerWallet);
EntityInstance entity_instance = entity_instance_android.ToEntityInstance(
/*existing_entity=*/std::nullopt);
EXPECT_EQ(entity_instance.record_type(),
EntityInstance::RecordType::kServerWallet);
EXPECT_EQ(
entity_instance.record_type_data(),
EntityInstance::RecordTypeData(
EntityInstance::WalletRecordTypePayload{.management_url = GURL()}));
}
// Test that the Wallet record type payload is added when converting an
// EntityInstanceAndroid back to an EntityInstance with ToEntityInstance().
TEST_F(EntityInstanceAndroidTest,
ToEntityInstance_ExistingWalletEntityCopiesPayload) {
EntityInstance existing_entity = test::GetPassportEntityInstance(
{.record_type = EntityInstance::WalletRecordTypePayload{
.management_url =
GURL("https://wallet.google.com/synthetic_pass?id=fake123")}});
EntityInstance entity_without_payload = test::GetPassportEntityInstance();
EntityInstanceAndroid entity_instance_android(
entity_without_payload, /*is_enabled=*/true,
/*is_eligible_for_wallet_storage=*/true,
/*requires_reauth_to_see=*/false);
EntityInstance entity_instance =
entity_instance_android.ToEntityInstance(existing_entity);
EXPECT_EQ(entity_instance.record_type(),
EntityInstance::RecordType::kServerWallet);
// The record type data has been copied from existing_entity.
EXPECT_EQ(entity_instance.record_type_data(),
existing_entity.record_type_data());
}
} // namespace
} // namespace autofill