So I've developed a small module that creates a new CLI to be able to change the customer password.
Here's my DigitalPianism\ChangePassword\Console\Command\ChangePassword.php
class:
<?php
namespace DigitalPianism\ChangePassword\Console\Command;
use DigitalPianism\ChangePassword\Model\Customer\AccountManagement;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Magento\Customer\Model\CustomerRegistry;
use Magento\Customer\Model\Customer;
use Symfony\Component\Console\Output\OutputInterface;
use Magento\Framework\App\State as AppState;
use Exception;
/**
* Class ChangePassword
* @package DigitalPianism\ChangePassword\Console\Command
*/
class ChangePassword extends Command
{
/**#@+
* Data keys
*/
const CUSTOMER_ID_ARGUMENT = 'customer-id';
const CUSTOMER_PASSWORD_ARGUMENT = 'customer-password';
/**
* @var CustomerRegistry
*/
private $_customerRegistry;
/**
* @var Customer
*/
private $_customer;
/**
* @var AppState
*/
private $_appState;
/**
* @var AccountManagement
*/
private $_accountManagement;
/**
* @param CustomerRegistry $customerRegistry
* @param AppState $appState
* @param AccountManagement $accountManagement
*/
public function __construct(
CustomerRegistry $customerRegistry,
AppState $appState,
AccountManagement $accountManagement
) {
$this->_accountManagement = $accountManagement;
$this->_appState = $appState;
$this->_customerRegistry = $customerRegistry;
parent::__construct();
}
/**
* Initialization of the command
*
* @return void
*/
protected function configure()
{
$this->setName('customer:changepassword')
->setDescription('Change customer password')
->setDefinition($this->getOptionsList());
parent::configure();
}
/**
* Get list of arguments for the command
*
* @return InputArgument[]
*/
public function getOptionsList()
{
return [
new InputArgument(
self::CUSTOMER_ID_ARGUMENT,
InputArgument::REQUIRED,
'(Required) Customer ID'
),
new InputArgument(
self::CUSTOMER_PASSWORD_ARGUMENT,
InputArgument::REQUIRED,
'(Required) Customer password'
)
];
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->_appState->setAreaCode('adminhtml');
$errors = $this->validate($input);
if ($errors) {
$output->writeln('<error>' . implode('</error>' . PHP_EOL . '<error>', $errors) . '</error>');
// we must have an exit code higher than zero to indicate something was wrong
return \Magento\Framework\Console\Cli::RETURN_FAILURE;
}
$this->_accountManagement->replaceCustomerPassword($this->_customer, $input->getArgument(self::CUSTOMER_PASSWORD_ARGUMENT));
$output->writeln(
'<info>Password for customer #' . $input->getArgument(self::CUSTOMER_ID_ARGUMENT) . ' has been successfully changed</info>'
);
}
/**
* Check if all options are provided
*
* @param InputInterface $input
* @return string[]
*/
public function validate(InputInterface $input)
{
$errors = [];
try {
$this->_accountManagement->passwordStrengthCheck($input->getArgument(self::CUSTOMER_PASSWORD_ARGUMENT));
/** @var Customer $customer */
$this->_customer = $this->_customerRegistry->retrieve($input->getArgument(self::CUSTOMER_ID_ARGUMENT));
} catch (Exception $e) {
$errors[] = $e->getMessage();
}
return $errors;
}
}
And here is my DigitalPianism\ChangePassword\Model\Customer\AccountManagement.php
class
<?php
namespace DigitalPianism\ChangePassword\Model\Customer;
use Magento\Framework\Exception\InputException;
/**
* Class AccountManagement
* @package DigitalPianism\ChangePassword\Model\Customer
*/
class AccountManagement extends \Magento\Customer\Model\AccountManagement
{
/**
* Make sure that password complies with minimum security requirements.
*
* @param string $password
* @return void
* @throws InputException
*/
public function passwordStrengthCheck($password)
{
try {
$result = $this->checkPasswordStrength($password);
return $result;
} catch(InputException $e) {
return $e->getMessage();
}
}
/**
* Replace customer password without the old password
*
* @param $customer
* @param $password
*/
public function replaceCustomerPassword($customer, $password)
{
$customer
->changePassword($password)
->save();
}
}
And if you need it the di.xml
:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Framework\Console\CommandListInterface">
<arguments>
<argument name="commands" xsi:type="array">
<item name="change_password" xsi:type="object">DigitalPianism\ChangePassword\Console\Command\ChangePassword</item>
</argument>
</arguments>
</type>
</config>
So when I run the following from the CLI:
php bin/magento customer:changepassword 3 test
It is supposed to change the password to "test" for customer with id 3.
As I'm using the core Magento password validation via my Model\Customer\AccountManagement
I get the following error which is totally ok:
[Magento\Eav\Model\Entity\Attribute\Exception]
Please enter a password with at least 6 characters.
So I wrote a small unit test for that in Test\Unit\Console\Command\ChangePasswordTest.php
:
<?php
namespace DigitalPianism\ChangePassword\Test\Unit\Console\Command;
use DigitalPianism\ChangePassword\Console\Command\ChangePassword;
use Symfony\Component\Console\Tester\CommandTester;
/**
* Class ChangePasswordTest
* @package DigitalPianism\ChangePassword\Test\Unit\Console\Command
*/
class ChangePasswordTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ChangePassword
*/
private $command;
/**
* @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
private $objectManagerMock;
protected function setUp()
{
$this->objectManagerMock = $this->getMockForAbstractClass('Magento\Framework\ObjectManagerInterface');
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->command = $objectManager->getObject(
'DigitalPianism\ChangePassword\Console\Command\ChangePassword',
['objectManager' => $this->objectManagerMock]
);
}
public function testWithWrongPassword()
{
$tester = new CommandTester($this->command);
$tester->execute([1, "test"]);
$this->assertContains(
"Please enter a password with at least",
$tester->getDisplay()
);
}
}
But when I run the test I get:
Failed asserting that 'Password for customer # has been successfully changed ' contains "Please enter a password with at least".
Which means the error I get when running from the CLI is not being triggered.
I know it's a long shot as I posted quite some code but I'm wondering if that's the right way of using the CommandTester
class and what am I missing here ?