Skip to content

Commit c75fb3f

Browse files
sveraglo74186
authored andcommitted
LYNX-390: Implement New GraphQL Mutation for Resending Email Confirmation
1 parent ea9045b commit c75fb3f

File tree

3 files changed

+206
-7
lines changed

3 files changed

+206
-7
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\CustomerGraphQl\Model\Resolver;
18+
19+
use Magento\Customer\Api\AccountManagementInterface;
20+
use Magento\Framework\Exception\NoSuchEntityException;
21+
use Magento\Framework\Exception\State\InvalidTransitionException;
22+
use Magento\Framework\GraphQl\Config\Element\Field;
23+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
24+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
25+
use Magento\Framework\GraphQl\Query\ResolverInterface;
26+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
27+
use Magento\Framework\Validator\EmailAddress as EmailValidator;
28+
29+
/**
30+
* Customer resend confirmation email, used for GraphQL request processing
31+
*/
32+
class ResendConfirmationEmail implements ResolverInterface
33+
{
34+
/**
35+
* @param AccountManagementInterface $accountManagement
36+
* @param EmailValidator $emailValidator
37+
*/
38+
public function __construct(
39+
private readonly AccountManagementInterface $accountManagement,
40+
private readonly EmailValidator $emailValidator,
41+
) {
42+
}
43+
44+
/**
45+
* Resend confirmation customer email mutation
46+
*
47+
* @param Field $field
48+
* @param ContextInterface $context
49+
* @param ResolveInfo $info
50+
* @param array|null $value
51+
* @param array|null $args
52+
* @return bool
53+
* @throws \Exception
54+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
55+
*/
56+
public function resolve(
57+
Field $field,
58+
$context,
59+
ResolveInfo $info,
60+
array $value = null,
61+
array $args = null
62+
) {
63+
if (!$this->emailValidator->isValid($args['email'])) {
64+
throw new GraphQlInputException(__('Email address is not valid'));
65+
}
66+
try {
67+
$this->accountManagement->resendConfirmation($args['email']);
68+
} catch (InvalidTransitionException $e) {
69+
throw new GraphQlInputException(__($e->getRawMessage()));
70+
} catch (NoSuchEntityException) {
71+
throw new GraphQlInputException(__('There is no user registered with that email address.'));
72+
} catch (\Exception) {
73+
throw new GraphQlInputException(__('There was an error when sending the confirmation email'));
74+
}
75+
return true;
76+
}
77+
}

app/code/Magento/CustomerGraphQl/etc/schema.graphqls

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ type Query {
1818
type Mutation {
1919
generateCustomerToken(email: String! @doc(description: "The customer's email address."), password: String! @doc(description: "The customer's password.")): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\GenerateCustomerToken") @doc(description:"Generate a token for specified customer.")
2020
changeCustomerPassword(currentPassword: String! @doc(description: "The customer's original password."), newPassword: String! @doc(description: "The customer's updated password.")): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ChangePassword") @doc(description:"Change the password for the logged-in customer.")
21-
createCustomer (input: CustomerInput! @doc(description: "An input object that defines the customer to be created.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomer") @doc(description:"Use `createCustomerV2` instead.")
21+
createCustomer (input: CustomerInput! @doc(description: "An input object that defines the customer to be created.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomer") @deprecated(reason:"Use `createCustomerV2` instead.")
2222
createCustomerV2 (input: CustomerCreateInput! @doc(description: "An input object that defines the customer to be created.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomer") @doc(description:"Create a customer account.")
23-
updateCustomer (input: CustomerInput! @doc(description: "An input object that defines the customer characteristics to update.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Use `updateCustomerV2` instead.")
23+
updateCustomer (input: CustomerInput! @doc(description: "An input object that defines the customer characteristics to update.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @deprecated(reason:"Use `updateCustomerV2` instead.")
2424
updateCustomerV2 (input: CustomerUpdateInput! @doc(description: "An input object that defines the customer characteristics to update.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update the customer's personal information.")
2525
deleteCustomer: Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomer") @doc(description:"Delete customer account")
2626
revokeCustomerToken: RevokeCustomerTokenOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke the customer token.")
@@ -31,6 +31,7 @@ type Mutation {
3131
resetPassword(email: String! @doc(description: "The customer's email address."), resetPasswordToken: String! @doc(description: "A runtime token generated by the `requestPasswordResetEmail` mutation."), newPassword: String! @doc(description: "The customer's new password.")): 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`.")
3232
updateCustomerEmail(email: String! @doc(description: "The customer's email address."), password: String! @doc(description: "The customer's password.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerEmail") @doc(description: "Change the email address for the logged-in customer.")
3333
confirmEmail(input: ConfirmEmailInput! @doc(description: "An input object to identify the customer to confirm the email.")): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ConfirmEmail") @doc(description: "Confirms the email address for a customer.")
34+
resendConfirmationEmail(email: String! @doc(description: "The email address to send the confirmation email to.")): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ResendConfirmationEmail") @doc(description: "Resends the confirmation email to a customer.")
3435
}
3536

3637
input ConfirmEmailInput @doc(description: "Contains details about a customer email address to confirm.") {
@@ -47,7 +48,7 @@ input CustomerAddressInput @doc(description: "Contains details about a billing o
4748
city: String @doc(description: "The customer's city or town.")
4849
region: CustomerAddressRegionInput @doc(description: "An object containing the region name, region code, and region ID.")
4950
postcode: String @doc(description: "The customer's ZIP or postal code.")
50-
country_id: CountryCodeEnum @doc(description: "Deprecated: use `country_code` instead.")
51+
country_id: CountryCodeEnum @deprecated(reason: "Use `country_code` instead.")
5152
country_code: CountryCodeEnum @doc(description: "The two-letter code representing the customer's country.")
5253
default_shipping: Boolean @doc(description: "Indicates whether the address is the default shipping address.")
5354
default_billing: Boolean @doc(description: "Indicates whether the address is the default billing address.")
@@ -56,7 +57,7 @@ input CustomerAddressInput @doc(description: "Contains details about a billing o
5657
prefix: String @doc(description: "An honorific, such as Dr., Mr., or Mrs.")
5758
suffix: String @doc(description: "A value such as Sr., Jr., or III.")
5859
vat_id: String @doc(description: "The customer's Tax/VAT number (for corporate customers).")
59-
custom_attributes: [CustomerAddressAttributeInput] @doc(description: "Deprecated. Use custom_attributesV2 instead.") @deprecated(reason: "Use custom_attributesV2 instead.")
60+
custom_attributes: [CustomerAddressAttributeInput] @deprecated(reason: "Use custom_attributesV2 instead.")
6061
custom_attributesV2: [AttributeValueInput] @doc(description: "Custom attributes assigned to the customer address.")
6162
}
6263

@@ -82,7 +83,7 @@ input CustomerInput @doc(description: "An input object that assigns or updates c
8283
lastname: String @doc(description: "The customer's family name.")
8384
suffix: String @doc(description: "A value such as Sr., Jr., or III.")
8485
email: String @doc(description: "The customer's email address. Required when creating a customer.")
85-
dob: String @doc(description: "Deprecated: Use `date_of_birth` instead.")
86+
dob: String @deprecated(reason: "Use `date_of_birth` instead.")
8687
date_of_birth: String @doc(description: "The customer's date of birth.")
8788
taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers).")
8889
gender: Int @doc(description: "The customer's gender (Male - 1, Female - 2).")
@@ -97,7 +98,7 @@ input CustomerCreateInput @doc(description: "An input object for creating a cus
9798
lastname: String! @doc(description: "The customer's family name.")
9899
suffix: String @doc(description: "A value such as Sr., Jr., or III.")
99100
email: String! @doc(description: "The customer's email address.")
100-
dob: String @doc(description: "Deprecated: Use `date_of_birth` instead.")
101+
dob: String @deprecated(reason: "Use `date_of_birth` instead.")
101102
date_of_birth: String @doc(description: "The customer's date of birth.")
102103
taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers).")
103104
gender: Int @doc(description: "The customer's gender (Male - 1, Female - 2).")
@@ -108,7 +109,7 @@ input CustomerCreateInput @doc(description: "An input object for creating a cus
108109

109110
input CustomerUpdateInput @doc(description: "An input object for updating a customer.") {
110111
date_of_birth: String @doc(description: "The customer's date of birth.")
111-
dob: String @doc(description: "Deprecated: Use `date_of_birth` instead.")
112+
dob: String @deprecated(reason: "Use `date_of_birth` instead.")
112113
firstname: String @doc(description: "The customer's first name.")
113114
gender: Int @doc(description: "The customer's gender (Male - 1, Female - 2).")
114115
is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter.")
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\GraphQl\Customer;
18+
19+
use Magento\Customer\Test\Fixture\Customer as CustomerFixture;
20+
use Magento\TestFramework\Fixture\DataFixture;
21+
use Magento\TestFramework\TestCase\GraphQlAbstract;
22+
23+
/**
24+
* Tests for resending confirmation email
25+
*/
26+
class ResendConfirmationEmailTest extends GraphQlAbstract
27+
{
28+
private const QUERY = <<<QUERY
29+
mutation {
30+
resendConfirmationEmail(email: "%s")
31+
}
32+
QUERY;
33+
34+
/**
35+
* @return void
36+
*/
37+
#[
38+
DataFixture(
39+
CustomerFixture::class,
40+
[
41+
'email' => 'customer@example.com',
42+
'confirmation' => 'abcde',
43+
],
44+
'customer'
45+
)
46+
]
47+
public function testResendConfirmationEmail()
48+
{
49+
$response = $this->graphQlMutation(
50+
sprintf(
51+
self::QUERY,
52+
'customer@example.com'
53+
),
54+
);
55+
56+
$this->assertEquals(
57+
[
58+
'resendConfirmationEmail' => true
59+
],
60+
$response
61+
);
62+
}
63+
64+
/**
65+
* @return void
66+
*/
67+
#[
68+
DataFixture(
69+
CustomerFixture::class,
70+
[
71+
'email' => 'customer@example.com',
72+
'confirmation' => null,
73+
],
74+
'customer'
75+
)
76+
]
77+
public function testResendConfirmationAlreadyConfirmedEmail()
78+
{
79+
$this->expectException(\Exception::class);
80+
$this->expectExceptionMessage('Confirmation isn\'t needed.');
81+
82+
$this->graphQlMutation(
83+
sprintf(
84+
self::QUERY,
85+
'customer@example.com'
86+
),
87+
);
88+
}
89+
90+
/**
91+
* @return void
92+
*/
93+
public function testResendConfirmationWrongEmail()
94+
{
95+
$this->expectException(\Exception::class);
96+
$this->expectExceptionMessage('Email address is not valid');
97+
98+
$this->graphQlMutation(
99+
sprintf(
100+
self::QUERY,
101+
'bad-email'
102+
),
103+
);
104+
}
105+
106+
/**
107+
* @return void
108+
*/
109+
public function testResendConfirmationNonExistingEmail()
110+
{
111+
$this->expectException(\Exception::class);
112+
$this->expectExceptionMessage('There is no user registered with that email address.');
113+
114+
$this->graphQlMutation(
115+
sprintf(
116+
self::QUERY,
117+
'nonexisting@example.com'
118+
),
119+
);
120+
}
121+
}

0 commit comments

Comments
 (0)