Skip to content

createCustomer validation requirements #28888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\CustomerGraphQl\Model\Resolver;

use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData;
use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
use Magento\CustomerGraphQl\Model\Customer\UpdateCustomerAccount;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\GraphQl\Model\Query\ContextInterface;

/**
* Customer email update, used for GraphQL request processing
*/
class UpdateCustomerEmail implements ResolverInterface
{
/**
* @var GetCustomer
*/
private $getCustomer;
/**
* @var UpdateCustomerAccount
*/
private $updateCustomerAccount;
/**
* @var ExtractCustomerData
*/
private $extractCustomerData;

/**
* @param GetCustomer $getCustomer
* @param UpdateCustomerAccount $updateCustomerAccount
* @param ExtractCustomerData $extractCustomerData
*/
public function __construct(
GetCustomer $getCustomer,
UpdateCustomerAccount $updateCustomerAccount,
ExtractCustomerData $extractCustomerData
) {
$this->getCustomer = $getCustomer;
$this->updateCustomerAccount = $updateCustomerAccount;
$this->extractCustomerData = $extractCustomerData;
}

/**
* @inheritdoc
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can add a proper description here

*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
) {
/** @var ContextInterface $context */
if (false === $context->getExtensionAttributes()->getIsCustomer()) {
throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.'));
}

$customer = $this->getCustomer->execute($context);
$this->updateCustomerAccount->execute(
$customer,
['email' => $args['email'], 'password' => $args['password']],
Copy link
Contributor

@cpartica cpartica Jun 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unsafe array index, could cause an exception:
we usually do : $email = $args['email'] ?? null
I think the input is enforced by schema
try catch should take care of it, if you really want to show some error to graphql

$context->getExtensionAttributes()->getStore()
);

$data = $this->extractCustomerData->execute($customer);

return ['customer' => $data];
}
}
34 changes: 32 additions & 2 deletions app/code/Magento/CustomerGraphQl/etc/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ type Query {
type Mutation {
generateCustomerToken(email: String!, password: String!): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\GenerateCustomerToken") @doc(description:"Retrieve the customer token")
changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ChangePassword") @doc(description:"Changes the password for the logged-in customer")
createCustomer (input: CustomerInput!): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomer") @doc(description:"Create customer account")
updateCustomer (input: CustomerInput!): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update the customer's personal information")
createCustomer (input: CustomerCreateInput!): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomer") @doc(description:"Create customer account")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can't change inputs on existing queries, so createCustomer (input: CustomerInput!): has to remain like this
and do a createCustomerV2 that uses CustomerCreateInput

updateCustomer (input: CustomerInput!): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Deprecated. Use UpdateCustomerV2 instead.")
updateCustomerV2 (input: CustomerUpdateInput!): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update the customer's personal information")
revokeCustomerToken: RevokeCustomerTokenOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke the customer token")
createCustomerAddress(input: CustomerAddressInput!): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomerAddress") @doc(description: "Create customer address")
updateCustomerAddress(id: Int!, input: CustomerAddressInput): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update customer address")
deleteCustomerAddress(id: Int!): Boolean @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomerAddress") @doc(description: "Delete customer address")
requestPasswordResetEmail(email: String!): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RequestPasswordResetEmail") @doc(description: "Request an email with a reset password token for the registered customer identified by the specified email.")
resetPassword(email: String!, resetPasswordToken: String!, newPassword: String!): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ResetPassword") @doc(description: "Reset a customer's password using the reset password token that the customer received in an email after requesting it using requestPasswordResetEmail.")
updateCustomerEmail(email: String!, password: String!): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerEmail") @doc(description: "")
}

input CustomerAddressInput {
Expand Down Expand Up @@ -78,6 +80,34 @@ input CustomerInput {
is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter")
}

input CustomerCreateInput {
prefix: String @doc(description: "An honorific, such as Dr., Mr., or Mrs.")
firstname: String! @doc(description: "The customer's first name")
middlename: String @doc(description: "The customer's middle name")
lastname: String! @doc(description: "The customer's family name")
suffix: String @doc(description: "A value such as Sr., Jr., or III")
email: String! @doc(description: "The customer's email address. Required for customer creation")
dob: String @doc(description: "Deprecated: Use `date_of_birth` instead")
date_of_birth: String @doc(description: "The customer's date of birth")
taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers)")
gender: Int @doc(description: "The customer's gender (Male - 1, Female - 2)")
password: String @doc(description: "The customer's password")
is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter")
}

input CustomerUpdateInput {
date_of_birth: String @doc(description: "The customer's date of birth")
dob: String @doc(description: "Deprecated: Use `date_of_birth` instead")
firstname: String @doc(description: "The customer's first name")
gender: Int @doc(description: "The customer's gender (Male - 1, Female - 2)")
is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter")
lastname: String @doc(description: "The customer's family name")
middlename: String @doc(description: "The customer's middle name")
prefix: String @doc(description: "An honorific, such as Dr., Mr., or Mrs.")
suffix: String @doc(description: "A value such as Sr., Jr., or III")
taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers)")
}

type CustomerOutput {
customer: Customer!
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,18 @@ public function testCreateCustomerAccountWithoutPassword()
*/
public function testCreateCustomerIfInputDataIsEmpty()
{
$exceptionMessage = 'Field CustomerCreateInput.email of required type String! was not provided.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a string either has "\n" or we can't do multi line strings like this. We usually don't add multi line exceptions

Field CustomerCreateInput.firstname of required type String! was not provided.
Field CustomerCreateInput.lastname of required type String! was not provided.';

$this->expectException(\Exception::class);
$this->expectExceptionMessage('"input" value should be specified');
$this->expectExceptionMessage($exceptionMessage);

$query = <<<QUERY
mutation {
createCustomer(
input: {

}
) {
customer {
Expand All @@ -144,7 +148,7 @@ public function testCreateCustomerIfInputDataIsEmpty()
public function testCreateCustomerIfEmailMissed()
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Required parameters are missing: Email');
$this->expectExceptionMessage('Field CustomerCreateInput.email of required type String! was not provided');

$newFirstname = 'Richard';
$newLastname = 'Rowe';
Expand Down Expand Up @@ -234,7 +238,7 @@ public function invalidEmailAddressDataProvider(): array
public function testCreateCustomerIfPassedAttributeDosNotExistsInCustomerInput()
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Field "test123" is not defined by type CustomerInput.');
$this->expectExceptionMessage('Field "test123" is not defined by type CustomerCreateInput.');

$newFirstname = 'Richard';
$newLastname = 'Rowe';
Expand Down Expand Up @@ -339,7 +343,9 @@ public function testCreateCustomerSubscribed()
public function testCreateCustomerIfCustomerWithProvidedEmailAlreadyExists()
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('A customer with the same email address already exists in an associated website.');
$this->expectExceptionMessage(
'A customer with the same email address already exists in an associated website.'
);

$existedEmail = 'customer@example.com';
$password = 'test123#';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\GraphQl\Customer;

use Exception;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\CustomerGraphQl\Model\Customer\UpdateCustomerAccount;
use Magento\Framework\Exception\AuthenticationException;
use Magento\Integration\Api\CustomerTokenServiceInterface;
use Magento\Store\Api\StoreRepositoryInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;

/**
* Test for update customer's email
*/
class UpdateCustomerEmailTest extends GraphQlAbstract
{
/**
* @var CustomerTokenServiceInterface
*/
private $customerTokenService;

/**
* @var CustomerRepositoryInterface
*/
private $customerRepository;
/**
* @var UpdateCustomerAccount
*/
private $updateCustomerAccount;
/**
* @var StoreRepositoryInterface
*/
private $storeRepository;

/**
* Setting up tests
*/
protected function setUp(): void
{
parent::setUp();

$this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class);
$this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class);
$this->updateCustomerAccount = Bootstrap::getObjectManager()->get(UpdateCustomerAccount::class);
$this->storeRepository = Bootstrap::getObjectManager()->get(StoreRepositoryInterface::class);
}

/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
*/
public function testUpdateCustomerEmail(): void
{
$currentEmail = 'customer@example.com';
$currentPassword = 'password';

$newEmail = 'newcustomer@example.com';

$query = <<<QUERY
mutation {
updateCustomerEmail(
email: "{$newEmail}"
password: "{$currentPassword}"
) {
customer {
email
}
}
}
QUERY;

$response = $this->graphQlMutation(
$query,
[],
'',
$this->getCustomerAuthHeaders($currentEmail, $currentPassword)
);

$this->assertEquals($newEmail, $response['updateCustomerEmail']['customer']['email']);

/* $this->updateCustomerAccount->execute(
$this->customerRepository->get($newEmail),
['email' => $currentEmail, 'password' => $currentPassword],
$this->storeRepository->getById(1)
);*/
}

/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
*/
public function testUpdateCustomerEmailIfPasswordIsWrong(): void
{
$this->expectException(Exception::class);
$this->expectExceptionMessage('Invalid login or password.');

$currentEmail = 'customer@example.com';
$currentPassword = 'password';

$newEmail = 'newcustomer@example.com';
$wrongPassword = 'wrongpassword';

$query = <<<QUERY
mutation {
updateCustomerEmail(
email: "{$newEmail}"
password: "{$wrongPassword}"
) {
customer {
email
}
}
}
QUERY;

$this->graphQlMutation(
$query,
[],
'',
$this->getCustomerAuthHeaders($currentEmail, $currentPassword)
);
}

/**
* @magentoApiDataFixture Magento/Customer/_files/two_customers.php
*/
public function testUpdateEmailIfEmailAlreadyExists()
{
$this->expectException(Exception::class);
$this->expectExceptionMessage(
'A customer with the same email address already exists in an associated website.'
);

$currentEmail = 'customer@example.com';
$currentPassword = 'password';
$existedEmail = 'customer_two@example.com';

$query = <<<QUERY
mutation {
updateCustomerEmail(
email: "{$existedEmail}"
password: "{$currentPassword}"
) {
customer {
firstname
}
}
}
QUERY;
$this->graphQlMutation($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword));
}

/**
* Get customer authorization headers
*
* @param string $email
* @param string $password
* @return array
* @throws AuthenticationException
*/
private function getCustomerAuthHeaders(string $email, string $password): array
{
$customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password);
return ['Authorization' => 'Bearer ' . $customerToken];
}
}
Loading