From a29cb76cbf896cf04e38b121673accf00eb2dc4f Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 10 May 2016 22:40:06 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D1=80=D0=B0=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=20trait=20AccountFinder=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=20=D0=B5=D0=B3=D0=BE=20=D0=BD=D0=B8=D0=BA=D1=83\=D0=BC=D1=8B?= =?UTF-8?q?=D0=BB=D1=83=20=D0=9C=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20EmailActi?= =?UTF-8?q?vation=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=B5=D1=82=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BC=D0=B0=D1=82?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=20=D1=81=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D0=B2=D0=B0=D1=82=D1=8C=20=D1=81=D0=B2=D0=BE=D0=B8=D1=85?= =?UTF-8?q?=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BC=D0=BA=D0=BE=D0=B2=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=81=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82?= =?UTF-8?q?=D0=B2=D1=83=D1=8E=D1=89=D0=B5=D0=BC=D1=83=20=D1=82=D0=B8=D0=BF?= =?UTF-8?q?=D1=83=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=20=D0=B2=D0=BE=D1=81=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8F=20=D0=B8=20=D0=B5=D1=91=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=87=D0=B8=D0=BA=20?= =?UTF-8?q?(=D0=B1=D0=B5=D0=B7=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D1=80=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/SignupController.php | 6 +- api/mails/forgot-password-html.php | 11 ++ api/mails/forgot-password-text.php | 10 ++ api/models/ForgotPasswordForm.php | 122 ++++++++++++++ api/models/LoginForm.php | 23 +-- api/models/RegistrationForm.php | 4 +- api/models/RepeatAccountActivationForm.php | 31 ++-- api/traits/AccountFinder.php | 27 ++++ .../EmailActivationExpirationBehavior.php | 85 ++++++++++ common/models/EmailActivation.php | 41 ++++- .../models/confirmations/RecoverPassword.php | 23 +++ .../RegistrationConfirmation.php | 13 ++ .../api/unit/models/ConfirmEmailFormTest.php | 2 +- .../unit/models/ForgotPasswordFormTest.php | 149 ++++++++++++++++++ .../api/unit/models/LoginFormTest.php | 12 -- .../RepeatAccountActivationFormTest.php | 22 ++- .../models/base/KeyConfirmationFormTest.php | 2 +- .../api/unit/traits/AccountFinderTest.php | 67 ++++++++ .../common/fixtures/data/accounts.php | 24 +++ .../fixtures/data/email-activations.php | 18 ++- .../unit/models/EmailActivationTest.php | 34 ++++ 21 files changed, 664 insertions(+), 62 deletions(-) create mode 100644 api/mails/forgot-password-html.php create mode 100644 api/mails/forgot-password-text.php create mode 100644 api/models/ForgotPasswordForm.php create mode 100644 api/traits/AccountFinder.php create mode 100644 common/behaviors/EmailActivationExpirationBehavior.php create mode 100644 common/models/confirmations/RecoverPassword.php create mode 100644 common/models/confirmations/RegistrationConfirmation.php create mode 100644 tests/codeception/api/unit/models/ForgotPasswordFormTest.php create mode 100644 tests/codeception/api/unit/traits/AccountFinderTest.php create mode 100644 tests/codeception/common/unit/models/EmailActivationTest.php diff --git a/api/controllers/SignupController.php b/api/controllers/SignupController.php index f29cff5..2a09336 100644 --- a/api/controllers/SignupController.php +++ b/api/controllers/SignupController.php @@ -61,10 +61,10 @@ class SignupController extends Controller { ]; if ($response['errors']['email'] === 'error.recently_sent_message') { - $activeActivation = $model->getActiveActivation(); + $activation = $model->getActivation(); $response['data'] = [ - 'canRepeatIn' => $activeActivation->created_at - time() + RepeatAccountActivationForm::REPEAT_FREQUENCY, - 'repeatFrequency' => RepeatAccountActivationForm::REPEAT_FREQUENCY, + 'canRepeatIn' => $activation->canRepeatIn(), + 'repeatFrequency' => $activation->repeatTimeout, ]; } diff --git a/api/mails/forgot-password-html.php b/api/mails/forgot-password-html.php new file mode 100644 index 0000000..266dc02 --- /dev/null +++ b/api/mails/forgot-password-html.php @@ -0,0 +1,11 @@ + + +

+ Вы забыли пароль и воспользовались формой по его восстановлению, не так ли? Введите этот код в форму и вы сможете + сменить свой пароль на новый +

+

Код:

diff --git a/api/mails/forgot-password-text.php b/api/mails/forgot-password-text.php new file mode 100644 index 0000000..4bbef40 --- /dev/null +++ b/api/mails/forgot-password-text.php @@ -0,0 +1,10 @@ + + +Вы забыли пароль и воспользовались формой по его восстановлению, не так ли? Введите этот код в форму и вы сможете +сменить свой пароль на новый + +Код активации diff --git a/api/models/ForgotPasswordForm.php b/api/models/ForgotPasswordForm.php new file mode 100644 index 0000000..a11676b --- /dev/null +++ b/api/models/ForgotPasswordForm.php @@ -0,0 +1,122 @@ + 'error.login_required'], + ['login', 'validateLogin'], + ['login', 'validateActivity'], + ['login', 'validateFrequency'], + ]; + } + + public function validateLogin($attribute) { + if (!$this->hasErrors()) { + if ($this->getAccount() === null) { + $this->addError($attribute, 'error.' . $attribute . '_not_exist'); + } + } + } + + public function validateActivity($attribute) { + if (!$this->hasErrors()) { + $account = $this->getAccount(); + if ($account->status !== Account::STATUS_ACTIVE) { + $this->addError($attribute, 'error.account_not_activated'); + } + } + } + + public function validateFrequency($attribute) { + if (!$this->hasErrors()) { + $emailConfirmation = $this->getEmailActivation(); + if ($emailConfirmation !== null && !$emailConfirmation->canRepeat()) { + $this->addError($attribute, 'error.email_frequency'); + } + } + } + + public function forgotPassword() { + if (!$this->validate()) { + return false; + } + + $account = $this->getAccount(); + $emailActivation = $this->getEmailActivation(); + if ($emailActivation === null) { + $emailActivation = new RecoverPassword(); + $emailActivation->account_id = $account->id; + } else { + $emailActivation->created_at = time(); + } + + $emailActivation->key = UserFriendlyRandomKey::make(); + if (!$emailActivation->save()) { + throw new ErrorException('Cannot create email activation for forgot password form'); + } + + $this->sendMail($emailActivation); + + return true; + } + + public function sendMail(EmailActivation $emailActivation) { + /** @var \yii\swiftmailer\Mailer $mailer */ + $mailer = Yii::$app->mailer; + $fromEmail = Yii::$app->params['fromEmail']; + if (!$fromEmail) { + throw new InvalidConfigException('Please specify fromEmail app in app params'); + } + + $acceptor = $emailActivation->account; + /** @var \yii\swiftmailer\Message $message */ + $message = $mailer->compose([ + 'html' => '@app/mails/forgot-password-html', + 'text' => '@app/mails/forgot-password-text', + ], [ + 'key' => $emailActivation->key, + ]) + ->setTo([$acceptor->email => $acceptor->username]) + ->setFrom([$fromEmail => 'Ely.by Accounts']) + ->setSubject('Ely.by Account forgot password'); + + if (!$message->send()) { + throw new ErrorException('Unable send email with activation code.'); + } + } + + public function getLogin() { + return $this->login; + } + + /** + * @return EmailActivation|null + * @throws ErrorException + */ + public function getEmailActivation() { + $account = $this->getAccount(); + if ($account === null) { + throw new ErrorException('Account not founded'); + } + + return $account->getEmailActivations() + ->andWhere(['type' => EmailActivation::TYPE_FORGOT_PASSWORD_KEY]) + ->one(); + } + +} diff --git a/api/models/LoginForm.php b/api/models/LoginForm.php index 2c7ef1d..6b3f8f2 100644 --- a/api/models/LoginForm.php +++ b/api/models/LoginForm.php @@ -2,17 +2,17 @@ namespace api\models; use api\models\base\ApiForm; +use api\traits\AccountFinder; use common\models\Account; use Yii; class LoginForm extends ApiForm { + use AccountFinder; public $login; public $password; public $rememberMe = true; - private $_account; - public function rules() { return [ ['login', 'required', 'message' => 'error.login_required'], @@ -55,6 +55,10 @@ class LoginForm extends ApiForm { } } + public function getLogin() { + return $this->login; + } + /** * @return bool|string JWT с информацией об аккаунте */ @@ -66,19 +70,4 @@ class LoginForm extends ApiForm { return $this->getAccount()->getJWT(); } - /** - * @return Account|null - */ - public function getAccount() { - if ($this->_account === NULL) { - $this->_account = Account::findOne([$this->getLoginAttribute() => $this->login]); - } - - return $this->_account; - } - - public function getLoginAttribute() { - return strpos($this->login, '@') ? 'email' : 'username'; - } - } diff --git a/api/models/RegistrationForm.php b/api/models/RegistrationForm.php index 739dba6..c762475 100644 --- a/api/models/RegistrationForm.php +++ b/api/models/RegistrationForm.php @@ -5,6 +5,7 @@ use api\components\ReCaptcha\Validator as ReCaptchaValidator; use api\models\base\ApiForm; use common\components\UserFriendlyRandomKey; use common\models\Account; +use common\models\confirmations\RegistrationConfirmation; use common\models\EmailActivation; use Ramsey\Uuid\Uuid; use Yii; @@ -78,9 +79,8 @@ class RegistrationForm extends ApiForm { throw new ErrorException('Account not created.'); } - $emailActivation = new EmailActivation(); + $emailActivation = new RegistrationConfirmation(); $emailActivation->account_id = $account->id; - $emailActivation->type = EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION; $emailActivation->key = UserFriendlyRandomKey::make(); if (!$emailActivation->save()) { diff --git a/api/models/RepeatAccountActivationForm.php b/api/models/RepeatAccountActivationForm.php index 9efd297..568404d 100644 --- a/api/models/RepeatAccountActivationForm.php +++ b/api/models/RepeatAccountActivationForm.php @@ -4,17 +4,17 @@ namespace api\models; use api\models\base\ApiForm; use common\components\UserFriendlyRandomKey; use common\models\Account; +use common\models\confirmations\RegistrationConfirmation; use common\models\EmailActivation; use Yii; use yii\base\ErrorException; class RepeatAccountActivationForm extends ApiForm { - // Частота повтора отправки нового письма - const REPEAT_FREQUENCY = 5 * 60; - public $email; + private $emailActivation; + public function rules() { return [ ['email', 'filter', 'filter' => 'trim'], @@ -40,7 +40,8 @@ class RepeatAccountActivationForm extends ApiForm { public function validateExistsActivation($attribute) { if (!$this->hasErrors($attribute)) { - if ($this->getActiveActivation() !== null) { + $activation = $this->getActivation(); + if ($activation !== null && !$activation->canRepeat()) { $this->addError($attribute, 'error.recently_sent_message'); } } @@ -59,9 +60,8 @@ class RepeatAccountActivationForm extends ApiForm { 'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, ]); - $activation = new EmailActivation(); + $activation = new RegistrationConfirmation(); $activation->account_id = $account->id; - $activation->type = EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION; $activation->key = UserFriendlyRandomKey::make(); if (!$activation->save()) { throw new ErrorException('Unable save email-activation model.'); @@ -84,19 +84,22 @@ class RepeatAccountActivationForm extends ApiForm { */ public function getAccount() { return Account::find() - ->andWhere(['email' => $this->email]) - ->one(); + ->andWhere(['email' => $this->email]) + ->one(); } /** * @return EmailActivation|null */ - public function getActiveActivation() { - return $this->getAccount() - ->getEmailActivations() - ->andWhere(['type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION]) - ->andWhere(['>=', 'created_at', time() - self::REPEAT_FREQUENCY]) - ->one(); + public function getActivation() { + if ($this->emailActivation === null) { + $this->emailActivation = $this->getAccount() + ->getEmailActivations() + ->andWhere(['type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION]) + ->one(); + } + + return $this->emailActivation; } } diff --git a/api/traits/AccountFinder.php b/api/traits/AccountFinder.php new file mode 100644 index 0000000..5465536 --- /dev/null +++ b/api/traits/AccountFinder.php @@ -0,0 +1,27 @@ +account === null) { + $this->account = Account::findOne([$this->getLoginAttribute() => $this->getLogin()]); + } + + return $this->account; + } + + public function getLoginAttribute() { + return strpos($this->getLogin(), '@') ? 'email' : 'username'; + } + +} diff --git a/common/behaviors/EmailActivationExpirationBehavior.php b/common/behaviors/EmailActivationExpirationBehavior.php new file mode 100644 index 0000000..3412455 --- /dev/null +++ b/common/behaviors/EmailActivationExpirationBehavior.php @@ -0,0 +1,85 @@ +0 будет проверять, сколько секунд прошло с момента создания модели + * + * @see EmailActivation::compareTime() + * @return bool + */ + public function canRepeat() : bool { + return $this->compareTime($this->repeatTimeout); + } + + /** + * Истёк ли срок кода? + * Для проверки используется значение EmailActivation::$expirationTimeout и интерпретируется как: + * - <0 означает, что код никогда не истечёт + * - =0 всегда будет говорить, что код истёк + * - >0 будет проверять, сколько секунд прошло с момента создания модели + * + * @see EmailActivation::compareTime() + * @return bool + */ + public function isExpired() : bool { + return $this->compareTime($this->expirationTimeout); + } + + /** + * Вычисляет, во сколько можно будет выполнить повторную отправку кода + * + * @return int + */ + public function canRepeatIn() : int { + return $this->calculateTime($this->repeatTimeout); + } + + /** + * Вычисляет, во сколько код истечёт + * + * @return int + */ + public function expireIn() : int { + return $this->calculateTime($this->expirationTimeout); + } + + protected function compareTime(int $value) : bool { + if ($value < 0) { + return false; + } + + if ($value === 0) { + return true; + } + + return time() > $this->calculateTime($value); + } + + protected function calculateTime(int $value) : int { + return $this->owner->created_at + $value; + } + +} diff --git a/common/models/EmailActivation.php b/common/models/EmailActivation.php index 8b14bbd..9599282 100644 --- a/common/models/EmailActivation.php +++ b/common/models/EmailActivation.php @@ -1,8 +1,12 @@ TimestampBehavior::class, 'updatedAtAttribute' => false, ], + 'expirationBehavior' => [ + 'class' => EmailActivationExpirationBehavior::class, + 'repeatTimeout' => 5 * 60, + 'expirationTimeout' => -1, + ], ]; } - public function rules() { - return []; - } - public function getAccount() { return $this->hasOne(Account::class, ['id' => 'account_id']); } + /** + * @inheritdoc + */ + public static function instantiate($row) { + $type = ArrayHelper::getValue($row, 'type'); + if ($type === null) { + return new static; + } + + $classMap = self::getClassMap(); + if (!isset($classMap[$type])) { + throw new InvalidConfigException('Unexpected type'); + } + + return new $classMap[$type]; + } + + public static function getClassMap() { + return [ + self::TYPE_REGISTRATION_EMAIL_CONFIRMATION => confirmations\RegistrationConfirmation::class, + self::TYPE_FORGOT_PASSWORD_KEY => confirmations\RecoverPassword::class, + ]; + } + } diff --git a/common/models/confirmations/RecoverPassword.php b/common/models/confirmations/RecoverPassword.php new file mode 100644 index 0000000..c57334f --- /dev/null +++ b/common/models/confirmations/RecoverPassword.php @@ -0,0 +1,23 @@ + [ + 'repeatTimeout' => 30 * 60, + 'expirationTimeout' => 1 * 60 * 60, + ], + ]); + } + + public function init() { + parent::init(); + $this->type = EmailActivation::TYPE_FORGOT_PASSWORD_KEY; + } + +} diff --git a/common/models/confirmations/RegistrationConfirmation.php b/common/models/confirmations/RegistrationConfirmation.php new file mode 100644 index 0000000..bf0f8f6 --- /dev/null +++ b/common/models/confirmations/RegistrationConfirmation.php @@ -0,0 +1,13 @@ +type = EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION; + } + +} diff --git a/tests/codeception/api/unit/models/ConfirmEmailFormTest.php b/tests/codeception/api/unit/models/ConfirmEmailFormTest.php index 2c71720..2b07530 100644 --- a/tests/codeception/api/unit/models/ConfirmEmailFormTest.php +++ b/tests/codeception/api/unit/models/ConfirmEmailFormTest.php @@ -31,7 +31,7 @@ class ConfirmEmailFormTest extends DbTestCase { } public function testConfirm() { - $fixture = $this->emailActivations[0]; + $fixture = $this->emailActivations['freshRegistrationConfirmation']; $model = $this->createModel($fixture['key']); $this->specify('expect true result', function() use ($model, $fixture) { expect('model return successful result', $model->confirm())->notEquals(false); diff --git a/tests/codeception/api/unit/models/ForgotPasswordFormTest.php b/tests/codeception/api/unit/models/ForgotPasswordFormTest.php new file mode 100644 index 0000000..0738b16 --- /dev/null +++ b/tests/codeception/api/unit/models/ForgotPasswordFormTest.php @@ -0,0 +1,149 @@ +mailer; + $mailer->fileTransportCallback = function () { + return 'testing_message.eml'; + }; + } + + protected function tearDown() { + if (file_exists($this->getMessageFile())) { + unlink($this->getMessageFile()); + } + + parent::tearDown(); + } + + public function fixtures() { + return [ + 'accounts' => [ + 'class' => AccountFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', + ], + 'emailActivations' => [ + 'class' => EmailActivationFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', + ], + ]; + } + + public function testValidateAccount() { + $this->specify('error.login_not_exist if login is invalid', function() { + $model = new ForgotPasswordForm(['login' => 'unexist']); + $model->validateLogin('login'); + expect($model->getErrors('login'))->equals(['error.login_not_exist']); + }); + + $this->specify('empty errors if login is exists', function() { + $model = new ForgotPasswordForm(['login' => $this->accounts['admin']['username']]); + $model->validateLogin('login'); + expect($model->getErrors('login'))->isEmpty(); + }); + } + + public function testValidateActivity() { + $this->specify('error.account_not_activated if account is not confirmed', function() { + $model = new ForgotPasswordForm(['login' => $this->accounts['not-activated-account']['username']]); + $model->validateActivity('login'); + expect($model->getErrors('login'))->equals(['error.account_not_activated']); + }); + + $this->specify('empty errors if login is exists', function() { + $model = new ForgotPasswordForm(['login' => $this->accounts['admin']['username']]); + $model->validateLogin('login'); + expect($model->getErrors('login'))->isEmpty(); + }); + } + + public function testValidateFrequency() { + $this->specify('error.account_not_activated if recently was message', function() { + $model = new DummyForgotPasswordForm([ + 'login' => $this->accounts['admin']['username'], + 'key' => $this->emailActivations['freshPasswordRecovery']['key'], + ]); + + $model->validateFrequency('login'); + expect($model->getErrors('login'))->equals(['error.email_frequency']); + }); + + $this->specify('empty errors if email was sent a long time ago', function() { + $model = new DummyForgotPasswordForm([ + 'login' => $this->accounts['admin']['username'], + 'key' => $this->emailActivations['oldPasswordRecovery']['key'], + ]); + + $model->validateFrequency('login'); + expect($model->getErrors('login'))->isEmpty(); + }); + + $this->specify('empty errors if previous confirmation model not founded', function() { + $model = new DummyForgotPasswordForm([ + 'login' => $this->accounts['admin']['username'], + 'key' => 'invalid-key', + ]); + + $model->validateFrequency('login'); + expect($model->getErrors('login'))->isEmpty(); + }); + } + + public function testForgotPassword() { + $this->specify('successfully send message with restore password key', function() { + $model = new ForgotPasswordForm(['login' => $this->accounts['admin']['username']]); + expect($model->forgotPassword())->true(); + expect($model->getEmailActivation())->notNull(); + expect_file($this->getMessageFile())->exists(); + }); + } + + public function testForgotPasswordResend() { + $this->specify('successfully renew and send message with restore password key', function() { + $model = new ForgotPasswordForm([ + 'login' => $this->accounts['account-with-expired-forgot-password-message']['username'], + ]); + $callTime = time(); + expect($model->forgotPassword())->true(); + $emailActivation = $model->getEmailActivation(); + expect($emailActivation)->notNull(); + expect($emailActivation->created_at)->greaterOrEquals($callTime); + expect_file($this->getMessageFile())->exists(); + }); + } + + private function getMessageFile() { + /** @var \yii\swiftmailer\Mailer $mailer */ + $mailer = Yii::$app->mailer; + + return Yii::getAlias($mailer->fileTransportPath) . '/testing_message.eml'; + } + +} + +class DummyForgotPasswordForm extends ForgotPasswordForm { + + public $key; + + public function getEmailActivation() { + return EmailActivation::findOne($this->key); + } + +} diff --git a/tests/codeception/api/unit/models/LoginFormTest.php b/tests/codeception/api/unit/models/LoginFormTest.php index 32b2061..71af2d1 100644 --- a/tests/codeception/api/unit/models/LoginFormTest.php +++ b/tests/codeception/api/unit/models/LoginFormTest.php @@ -84,18 +84,6 @@ class LoginFormTest extends DbTestCase { }); } - public function testGetLoginAttribute() { - $this->specify('email if login look like email value', function() { - $model = new DummyLoginForm(['login' => 'erickskrauch@ely.by']); - expect($model->getLoginAttribute())->equals('email'); - }); - - $this->specify('username in any other case', function() { - $model = new DummyLoginForm(['login' => 'erickskrauch']); - expect($model->getLoginAttribute())->equals('username'); - }); - } - } class DummyLoginForm extends LoginForm { diff --git a/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php b/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php index 07ebe07..840d4d0 100644 --- a/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php +++ b/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php @@ -3,6 +3,7 @@ namespace tests\codeception\api\models; use api\models\RepeatAccountActivationForm; use Codeception\Specify; +use common\models\EmailActivation; use tests\codeception\api\unit\DbTestCase; use tests\codeception\common\fixtures\AccountFixture; use tests\codeception\common\fixtures\EmailActivationFixture; @@ -67,14 +68,17 @@ class RepeatAccountActivationFormTest extends DbTestCase { public function testValidateExistsActivation() { $this->specify('error.recently_sent_message if passed email has recently sent message', function() { - $model = new RepeatAccountActivationForm(['email' => $this->accounts['not-activated-account']['email']]); + $model = new DummyRepeatAccountActivationForm([ + 'emailKey' => $this->activations['freshRegistrationConfirmation']['key'], + ]); $model->validateExistsActivation('email'); expect($model->getErrors('email'))->equals(['error.recently_sent_message']); }); $this->specify('no errors if passed email has expired activation message', function() { - $email = $this->accounts['not-activated-account-with-expired-message']['email']; - $model = new RepeatAccountActivationForm(['email' => $email]); + $model = new DummyRepeatAccountActivationForm([ + 'emailKey' => $this->activations['oldRegistrationConfirmation']['key'], + ]); $model->validateExistsActivation('email'); expect($model->getErrors('email'))->isEmpty(); }); @@ -91,7 +95,7 @@ class RepeatAccountActivationFormTest extends DbTestCase { $email = $this->accounts['not-activated-account-with-expired-message']['email']; $model = new RepeatAccountActivationForm(['email' => $email]); expect($model->sendRepeatMessage())->true(); - expect($model->getActiveActivation())->notNull(); + expect($model->getActivation())->notNull(); expect_file($this->getMessageFile())->exists(); }); } @@ -104,3 +108,13 @@ class RepeatAccountActivationFormTest extends DbTestCase { } } + +class DummyRepeatAccountActivationForm extends RepeatAccountActivationForm { + + public $emailKey; + + public function getActivation() { + return EmailActivation::findOne($this->emailKey); + } + +} diff --git a/tests/codeception/api/unit/models/base/KeyConfirmationFormTest.php b/tests/codeception/api/unit/models/base/KeyConfirmationFormTest.php index 0cac58c..8be5f9c 100644 --- a/tests/codeception/api/unit/models/base/KeyConfirmationFormTest.php +++ b/tests/codeception/api/unit/models/base/KeyConfirmationFormTest.php @@ -53,7 +53,7 @@ class KeyConfirmationFormTest extends DbTestCase { } public function testCorrectKey() { - $model = $this->createModel($this->emailActivations[0]['key']); + $model = $this->createModel($this->emailActivations['freshRegistrationConfirmation']['key']); $this->specify('no errors if key exists', function () use ($model) { expect('model should pass validation', $model->validate())->true(); }); diff --git a/tests/codeception/api/unit/traits/AccountFinderTest.php b/tests/codeception/api/unit/traits/AccountFinderTest.php new file mode 100644 index 0000000..6fb913b --- /dev/null +++ b/tests/codeception/api/unit/traits/AccountFinderTest.php @@ -0,0 +1,67 @@ + [ + 'class' => AccountFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', + ], + ]; + } + + public function testGetAccount() { + $this->specify('founded account for passed login data', function() { + $model = new AccountFinderTestTestClass(); + $model->login = $this->accounts['admin']['email']; + $account = $model->getAccount(); + expect($account)->isInstanceOf(Account::class); + expect($account->id)->equals($this->accounts['admin']['id']); + }); + + $this->specify('null, if account not founded', function() { + $model = new AccountFinderTestTestClass(); + $model->login = 'unexpected'; + expect($account = $model->getAccount())->null(); + }); + } + + public function testGetLoginAttribute() { + $this->specify('if login look like email value, then \'email\'', function() { + $model = new AccountFinderTestTestClass(); + $model->login = 'erickskrauch@ely.by'; + expect($model->getLoginAttribute())->equals('email'); + }); + + $this->specify('username in any other case', function() { + $model = new AccountFinderTestTestClass(); + $model->login = 'erickskrauch'; + expect($model->getLoginAttribute())->equals('username'); + }); + } + +} + +class AccountFinderTestTestClass { + use AccountFinder; + + public $login; + + public function getLogin() { + return $this->login; + } + +} diff --git a/tests/codeception/common/fixtures/data/accounts.php b/tests/codeception/common/fixtures/data/accounts.php index 52bd45e..3ff2ebd 100644 --- a/tests/codeception/common/fixtures/data/accounts.php +++ b/tests/codeception/common/fixtures/data/accounts.php @@ -51,4 +51,28 @@ return [ 'created_at' => 1457890086, 'updated_at' => 1457890086, ], + 'account-with-fresh-forgot-password-message' => [ + 'id' => 5, + 'uuid' => '4aaf4f00-3b5b-4d36-9252-9e8ee0c86679', + 'username' => 'Notch', + 'email' => 'notch@mojang.com', + 'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0 + 'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2, + 'password_reset_token' => null, + 'status' => \common\models\Account::STATUS_ACTIVE, + 'created_at' => 1462891432, + 'updated_at' => 1462891432, + ], + 'account-with-expired-forgot-password-message' => [ + 'id' => 6, + 'uuid' => '26187ae7-bc96-421f-9766-6517f8ee52b7', + 'username' => '23derevo', + 'email' => '23derevo@gmail.com', + 'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0 + 'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2, + 'password_reset_token' => null, + 'status' => \common\models\Account::STATUS_ACTIVE, + 'created_at' => 1462891612, + 'updated_at' => 1462891612, + ], ]; diff --git a/tests/codeception/common/fixtures/data/email-activations.php b/tests/codeception/common/fixtures/data/email-activations.php index 09c1aed..b57952b 100644 --- a/tests/codeception/common/fixtures/data/email-activations.php +++ b/tests/codeception/common/fixtures/data/email-activations.php @@ -1,15 +1,27 @@ [ 'key' => 'HABGCABHJ1234HBHVD', 'account_id' => 3, 'type' => \common\models\EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, 'created_at' => time(), ], - [ + 'oldRegistrationConfirmation' => [ 'key' => 'H23HBDCHHAG2HGHGHS', 'account_id' => 4, 'type' => \common\models\EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, - 'created_at' => time() - \api\models\RepeatAccountActivationForm::REPEAT_FREQUENCY - 10, + 'created_at' => time() - (new \common\models\confirmations\RegistrationConfirmation())->repeatTimeout - 10, + ], + 'freshPasswordRecovery' => [ + 'key' => 'H24HBDCHHAG2HGHGHS', + 'account_id' => 5, + 'type' => \common\models\EmailActivation::TYPE_FORGOT_PASSWORD_KEY, + 'created_at' => time(), + ], + 'oldPasswordRecovery' => [ + 'key' => 'H25HBDCHHAG2HGHGHS', + 'account_id' => 6, + 'type' => \common\models\EmailActivation::TYPE_FORGOT_PASSWORD_KEY, + 'created_at' => time() - (new \common\models\confirmations\RecoverPassword())->repeatTimeout - 10, ], ]; diff --git a/tests/codeception/common/unit/models/EmailActivationTest.php b/tests/codeception/common/unit/models/EmailActivationTest.php new file mode 100644 index 0000000..e9ea7ac --- /dev/null +++ b/tests/codeception/common/unit/models/EmailActivationTest.php @@ -0,0 +1,34 @@ + [ + 'class' => EmailActivationFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', + ], + ]; + } + + public function testInstantiate() { + $this->specify('return valid model type', function() { + expect(EmailActivation::findOne([ + 'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, + ]))->isInstanceOf(RegistrationConfirmation::class); + expect(EmailActivation::findOne([ + 'type' => EmailActivation::TYPE_FORGOT_PASSWORD_KEY, + ]))->isInstanceOf(RecoverPassword::class); + }); + } + +}