From 50439fdaeb1e5366ca31d8131ac18507e9f2c278 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Mon, 16 May 2016 01:33:19 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=88=D0=B0=D0=B3=D0=BE=D0=B2=20=D1=81?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8B=20E-mail=20=D0=B0=D0=B4=D1=80=D0=B5?= =?UTF-8?q?=D1=81=D0=B0,=20=D0=BF=D0=BE=D0=BA=D1=80=D1=8B=D1=82=D1=8B=20un?= =?UTF-8?q?it-=D1=82=D0=B5=D1=81=D1=82=D0=B0=D0=BC=D0=B8=20=D0=A3=20EmailA?= =?UTF-8?q?ctivation=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=BE=D0=BB=D0=B5=20$=5Fdata=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=BD=D0=B8?= =?UTF-8?q?=D0=BC=20=D0=A3=D0=BF=D1=80=D0=BE=D1=89=D0=B5=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=84=D0=B8=D0=BA=D1=81=D1=82=D1=83=D1=80=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20EmailActivations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/mails/current-email-confirmation-html.php | 10 ++ api/mails/current-email-confirmation-text.php | 9 ++ api/mails/new-email-confirmation-html.php | 12 ++ api/mails/new-email-confirmation-text.php | 10 ++ .../ChangeEmail/ConfirmNewEmailForm.php | 55 +++++++++ .../profile/ChangeEmail/InitStateForm.php | 101 ++++++++++++++++ .../profile/ChangeEmail/NewEmailForm.php | 112 ++++++++++++++++++ common/behaviors/DataBehavior.php | 47 ++++++++ common/models/EmailActivation.php | 38 ++++-- .../CurrentEmailConfirmation.php | 23 ++++ .../confirmations/NewEmailConfirmation.php | 29 +++++ .../NewEmailConfirmationBehavior.php | 19 +++ .../m160515_153724_email_activation_data.php | 15 +++ .../authentication/ConfirmEmailFormTest.php | 5 +- .../authentication/ForgotPasswordFormTest.php | 5 +- .../RecoverPasswordFormTest.php | 5 +- .../RepeatAccountActivationFormTest.php | 5 +- .../ChangeEmail/ConfirmNewEmailFormTest.php | 47 ++++++++ .../profile/ChangeEmail/InitStateFormTest.php | 99 ++++++++++++++++ .../profile/ChangeEmail/NewEmailFormTest.php | 87 ++++++++++++++ .../models/profile/ChangePasswordFormTest.php | 2 +- .../common/_support/FixtureHelper.php | 5 +- .../common/fixtures/data/accounts.php | 12 ++ .../fixtures/data/email-activations.php | 13 ++ .../unit/behaviors/DataBehaviorTest.php | 79 ++++++++++++ .../unit/models/EmailActivationTest.php | 5 +- .../unit/validators/LanguageValidatorTest.php | 11 +- 27 files changed, 818 insertions(+), 42 deletions(-) create mode 100644 api/mails/current-email-confirmation-html.php create mode 100644 api/mails/current-email-confirmation-text.php create mode 100644 api/mails/new-email-confirmation-html.php create mode 100644 api/mails/new-email-confirmation-text.php create mode 100644 api/models/profile/ChangeEmail/ConfirmNewEmailForm.php create mode 100644 api/models/profile/ChangeEmail/InitStateForm.php create mode 100644 api/models/profile/ChangeEmail/NewEmailForm.php create mode 100644 common/behaviors/DataBehavior.php create mode 100644 common/models/confirmations/CurrentEmailConfirmation.php create mode 100644 common/models/confirmations/NewEmailConfirmation.php create mode 100644 common/models/confirmations/NewEmailConfirmationBehavior.php create mode 100644 console/migrations/m160515_153724_email_activation_data.php create mode 100644 tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php create mode 100644 tests/codeception/api/unit/models/profile/ChangeEmail/InitStateFormTest.php create mode 100644 tests/codeception/api/unit/models/profile/ChangeEmail/NewEmailFormTest.php create mode 100644 tests/codeception/common/unit/behaviors/DataBehaviorTest.php diff --git a/api/mails/current-email-confirmation-html.php b/api/mails/current-email-confirmation-html.php new file mode 100644 index 0000000..fa74300 --- /dev/null +++ b/api/mails/current-email-confirmation-html.php @@ -0,0 +1,10 @@ + + +

+ Вы предприняли попытку сменить свой E-mail адрес. Это так? Если да, то код ниже позволит вам пройти 2 шаг. +

+

Код:

diff --git a/api/mails/current-email-confirmation-text.php b/api/mails/current-email-confirmation-text.php new file mode 100644 index 0000000..60e25bb --- /dev/null +++ b/api/mails/current-email-confirmation-text.php @@ -0,0 +1,9 @@ + + +Вы предприняли попытку сменить свой E-mail адрес. Это так? Если да, то код ниже позволит вам пройти 2 шаг. + +Код активации diff --git a/api/mails/new-email-confirmation-html.php b/api/mails/new-email-confirmation-html.php new file mode 100644 index 0000000..b1db92f --- /dev/null +++ b/api/mails/new-email-confirmation-html.php @@ -0,0 +1,12 @@ + + +

+ Этот E-mail адрес был указан как новый для аккаунта username ?>. Чтобы подтвердить это E-mail, + введите код ниже в форму на сайте. +

+

Код:

diff --git a/api/mails/new-email-confirmation-text.php b/api/mails/new-email-confirmation-text.php new file mode 100644 index 0000000..48f090c --- /dev/null +++ b/api/mails/new-email-confirmation-text.php @@ -0,0 +1,10 @@ + + +Этот E-mail адрес был указан как новый для аккаунта username ?>. Чтобы подтвердить это E-mail, введите код ниже в форму на сайте. + +Код: diff --git a/api/models/profile/ChangeEmail/ConfirmNewEmailForm.php b/api/models/profile/ChangeEmail/ConfirmNewEmailForm.php new file mode 100644 index 0000000..efc47d1 --- /dev/null +++ b/api/models/profile/ChangeEmail/ConfirmNewEmailForm.php @@ -0,0 +1,55 @@ +account = $account; + parent::__construct($config); + } + + /** + * @return Account + */ + public function getAccount() { + return $this->account; + } + + public function changeEmail() { + if (!$this->validate()) { + return false; + } + + $transaction = Yii::$app->db->beginTransaction(); + try { + /** @var \common\models\confirmations\NewEmailConfirmation $activation */ + $activation = $this->getActivationCodeModel(); + $activation->delete(); + + $account = $this->getAccount(); + $account->email = $activation->newEmail; + if (!$account->save()) { + throw new ErrorException('Cannot save new account email value'); + } + + $transaction->commit(); + } catch (Exception $e) { + $transaction->rollBack(); + throw $e; + } + + return true; + } + +} diff --git a/api/models/profile/ChangeEmail/InitStateForm.php b/api/models/profile/ChangeEmail/InitStateForm.php new file mode 100644 index 0000000..f956b2b --- /dev/null +++ b/api/models/profile/ChangeEmail/InitStateForm.php @@ -0,0 +1,101 @@ +account = $account; + parent::__construct($config); + } + + public function getAccount() { + return $this->account; + } + + public function rules() { + // TODO: поверить наличие уже отправленных подтверждений смены E-mail + return [ + ['!email', 'validateAccountPasswordHashStrategy'], + ]; + } + + public function validateAccountPasswordHashStrategy($attribute) { + $account = $this->getAccount(); + if ($account->password_hash_strategy === Account::PASS_HASH_STRATEGY_OLD_ELY) { + $this->addError($attribute, 'error.old_hash_strategy'); + } + } + + public function sendCurrentEmailConfirmation() { + if (!$this->validate()) { + return false; + } + + $transaction = Yii::$app->db->beginTransaction(); + try { + $activation = $this->createCode(); + $this->sendCode($activation); + + $transaction->commit(); + } catch (Exception $e) { + $transaction->rollBack(); + throw $e; + } + + return true; + } + + /** + * @return CurrentEmailConfirmation + * @throws ErrorException + */ + public function createCode() { + $account = $this->getAccount(); + $emailActivation = new CurrentEmailConfirmation(); + $emailActivation->account_id = $account->id; + if (!$emailActivation->save()) { + throw new ErrorException('Cannot save email activation model'); + } + + return $emailActivation; + } + + public function sendCode(EmailActivation $code) { + /** @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 = $code->account; + /** @var \yii\swiftmailer\Message $message */ + $message = $mailer->compose([ + 'html' => '@app/mails/current-email-confirmation-html', + 'text' => '@app/mails/current-email-confirmation-text', + ], [ + 'key' => $code->key, + ]) + ->setTo([$acceptor->email => $acceptor->username]) + ->setFrom([$fromEmail => 'Ely.by Accounts']) + ->setSubject('Ely.by Account change E-mail confirmation'); + + if (!$message->send()) { + throw new ErrorException('Unable send email with activation code.'); + } + } + +} diff --git a/api/models/profile/ChangeEmail/NewEmailForm.php b/api/models/profile/ChangeEmail/NewEmailForm.php new file mode 100644 index 0000000..07950ea --- /dev/null +++ b/api/models/profile/ChangeEmail/NewEmailForm.php @@ -0,0 +1,112 @@ +account = $account; + parent::__construct($config); + } + + /** + * @return Account + */ + public function getAccount() { + return $this->account; + } + + public function rules() { + return array_merge(parent::rules(), [ + ['email', 'required', 'message' => 'error.email_required'], + ['email', 'validateEmail'], + ]); + } + + public function validateEmail() { + $account = new Account(); + $account->email = $this->email; + if (!$account->validate(['email'])) { + $this->addErrors($account->getErrors()); + } + } + + public function sendNewEmailConfirmation() { + if (!$this->validate()) { + return false; + } + + $transaction = Yii::$app->db->beginTransaction(); + try { + $previousActivation = $this->getActivationCodeModel(); + $previousActivation->delete(); + + $activation = $this->createCode(); + $this->sendCode($activation); + + $transaction->commit(); + } catch (Exception $e) { + $transaction->rollBack(); + throw $e; + } + + return true; + } + + /** + * @return NewEmailConfirmation + * @throws ErrorException + */ + public function createCode() { + $emailActivation = new NewEmailConfirmation(); + $emailActivation->account_id = $this->getAccount()->id; + $emailActivation->newEmail = $this->email; + if (!$emailActivation->save()) { + throw new ErrorException('Cannot save email activation model'); + } + + return $emailActivation; + } + + public function sendCode(EmailActivation $code) { + /** @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 = $code->account; + /** @var \yii\swiftmailer\Message $message */ + $message = $mailer->compose([ + 'html' => '@app/mails/new-email-confirmation-html', + 'text' => '@app/mails/new-email-confirmation-text', + ], [ + 'key' => $code->key, + 'account' => $acceptor, + ]) + ->setTo([$this->email => $acceptor->username]) + ->setFrom([$fromEmail => 'Ely.by Accounts']) + ->setSubject('Ely.by Account new E-mail confirmation'); + + if (!$message->send()) { + throw new ErrorException('Unable send email with activation code.'); + } + } + +} diff --git a/common/behaviors/DataBehavior.php b/common/behaviors/DataBehavior.php new file mode 100644 index 0000000..8c0c2e1 --- /dev/null +++ b/common/behaviors/DataBehavior.php @@ -0,0 +1,47 @@ +getData(); + $data[$key] = $value; + $this->owner->{$this->attribute} = serialize($data); + } + + /** + * @param string $key + * @return mixed + */ + protected function getKey(string $key) { + return ArrayHelper::getValue($this->getData(), $key); + } + + /** + * @return array + * @throws \yii\base\ErrorException Yii2 подхватит Notice от неправильной десериализаци и превратит его + * в свой Exception, благодаря чему программа сможем продолжить нормально работать (вернее ловить Exception) + */ + private function getData() { + $data = $this->owner->{$this->attribute}; + if (is_string($data)) { + $data = unserialize($data); + } else { + $data = []; + } + + return $data; + } + +} diff --git a/common/models/EmailActivation.php b/common/models/EmailActivation.php index 463880c..c6776e0 100644 --- a/common/models/EmailActivation.php +++ b/common/models/EmailActivation.php @@ -1,7 +1,9 @@ 5 * 60, 'expirationTimeout' => -1, ], + 'dataBehavior' => [ + 'class' => DataBehavior::class, + 'attribute' => '_data', + ], ]; } @@ -74,7 +80,23 @@ class EmailActivation extends ActiveRecord { return [ self::TYPE_REGISTRATION_EMAIL_CONFIRMATION => confirmations\RegistrationConfirmation::class, self::TYPE_FORGOT_PASSWORD_KEY => confirmations\ForgotPassword::class, + self::TYPE_CURRENT_EMAIL_CONFIRMATION => confirmations\CurrentEmailConfirmation::class, + self::TYPE_NEW_EMAIL_CONFIRMATION => confirmations\NewEmailConfirmation::class, ]; } + public function beforeSave($insert) { + if (!parent::beforeSave($insert)) { + return false; + } + + if ($this->key === null) { + do { + $this->key = UserFriendlyRandomKey::make(); + } while (EmailActivation::find()->andWhere(['key' => $this->key])->exists()); + } + + return true; + } + } diff --git a/common/models/confirmations/CurrentEmailConfirmation.php b/common/models/confirmations/CurrentEmailConfirmation.php new file mode 100644 index 0000000..c9f6854 --- /dev/null +++ b/common/models/confirmations/CurrentEmailConfirmation.php @@ -0,0 +1,23 @@ + [ + 'repeatTimeout' => 6 * 60 * 60, + 'expirationTimeout' => 1 * 60 * 60, + ], + ]); + } + + public function init() { + parent::init(); + $this->type = EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION; + } + +} diff --git a/common/models/confirmations/NewEmailConfirmation.php b/common/models/confirmations/NewEmailConfirmation.php new file mode 100644 index 0000000..91f0fab --- /dev/null +++ b/common/models/confirmations/NewEmailConfirmation.php @@ -0,0 +1,29 @@ + [ + 'repeatTimeout' => 5 * 60, + ], + 'dataBehavior' => [ + 'class' => NewEmailConfirmationBehavior::class, + ], + ]); + } + + public function init() { + parent::init(); + $this->type = EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION; + } + +} diff --git a/common/models/confirmations/NewEmailConfirmationBehavior.php b/common/models/confirmations/NewEmailConfirmationBehavior.php new file mode 100644 index 0000000..ee804a5 --- /dev/null +++ b/common/models/confirmations/NewEmailConfirmationBehavior.php @@ -0,0 +1,19 @@ +getKey('newEmail'); + } + + public function setNewEmail(string $newEmail) { + $this->setKey('newEmail', $newEmail); + } + +} diff --git a/console/migrations/m160515_153724_email_activation_data.php b/console/migrations/m160515_153724_email_activation_data.php new file mode 100644 index 0000000..6e2ecb0 --- /dev/null +++ b/console/migrations/m160515_153724_email_activation_data.php @@ -0,0 +1,15 @@ +addColumn('{{%email_activations}}', '_data', $this->text()->after('type')); + } + + public function safeDown() { + $this->dropColumn('{{%email_activations}}', '_data'); + } + +} diff --git a/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php b/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php index 3aa6df5..03aec1a 100644 --- a/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php +++ b/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php @@ -17,10 +17,7 @@ class ConfirmEmailFormTest extends DbTestCase { public function fixtures() { return [ - 'emailActivations' => [ - 'class' => EmailActivationFixture::class, - 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', - ], + 'emailActivations' => EmailActivationFixture::class, ]; } diff --git a/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php b/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php index d37f4ed..a5edf8b 100644 --- a/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php +++ b/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php @@ -39,10 +39,7 @@ class ForgotPasswordFormTest extends DbTestCase { 'class' => AccountFixture::class, 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', ], - 'emailActivations' => [ - 'class' => EmailActivationFixture::class, - 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', - ], + 'emailActivations' => EmailActivationFixture::class, ]; } diff --git a/tests/codeception/api/unit/models/authentication/RecoverPasswordFormTest.php b/tests/codeception/api/unit/models/authentication/RecoverPasswordFormTest.php index cf5faba..e06530c 100644 --- a/tests/codeception/api/unit/models/authentication/RecoverPasswordFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RecoverPasswordFormTest.php @@ -17,10 +17,7 @@ class RecoverPasswordFormTest extends DbTestCase { public function fixtures() { return [ - 'emailActivations' => [ - 'class' => EmailActivationFixture::class, - 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', - ], + 'emailActivations' => EmailActivationFixture::class, ]; } diff --git a/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php b/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php index 1bcb90c..8bcf51f 100644 --- a/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php @@ -39,10 +39,7 @@ class RepeatAccountActivationFormTest extends DbTestCase { 'class' => AccountFixture::class, 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', ], - 'activations' => [ - 'class' => EmailActivationFixture::class, - 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', - ], + 'activations' => EmailActivationFixture::class, ]; } diff --git a/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php b/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php new file mode 100644 index 0000000..6ef0dc0 --- /dev/null +++ b/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php @@ -0,0 +1,47 @@ + [ + 'class' => AccountFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', + ], + 'emailActivations' => EmailActivationFixture::class, + ]; + } + + public function testChangeEmail() { + $this->specify('successfully change account email', function() { + /** @var Account $account */ + $account = Account::findOne($this->accounts['account-with-change-email-finish-state']['id']); + $model = new ConfirmNewEmailForm($account, [ + 'key' => $this->emailActivations['newEmailConfirmation']['key'], + ]); + expect($model->changeEmail())->true(); + expect(EmailActivation::findOne([ + 'account_id' => $account->id, + 'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION, + ]))->null(); + $data = unserialize($this->emailActivations['newEmailConfirmation']['_data']); + expect($account->email)->equals($data['newEmail']); + }); + } + +} diff --git a/tests/codeception/api/unit/models/profile/ChangeEmail/InitStateFormTest.php b/tests/codeception/api/unit/models/profile/ChangeEmail/InitStateFormTest.php new file mode 100644 index 0000000..54e0598 --- /dev/null +++ b/tests/codeception/api/unit/models/profile/ChangeEmail/InitStateFormTest.php @@ -0,0 +1,99 @@ +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' => EmailActivationFixture::class, + ]; + } + + public function testValidateAccountPasswordHashStrategy() { + $this->specify('we cannot change password on old password hash strategy', function() { + $account = new Account(); + $account->password_hash_strategy = Account::PASS_HASH_STRATEGY_OLD_ELY; + $model = new InitStateForm($account); + $model->validateAccountPasswordHashStrategy('email'); + expect($model->getErrors('email'))->equals(['error.old_hash_strategy']); + }); + + $this->specify('no errors on modern password hash strategy', function() { + $account = new Account(); + $account->password_hash_strategy = Account::PASS_HASH_STRATEGY_YII2; + $model = new InitStateForm($account); + $model->validateAccountPasswordHashStrategy('email'); + expect($model->getErrors('email'))->isEmpty(); + }); + } + + public function testCreateCode() { + $this->specify('create valid code and store it to database', function() { + /** @var Account $account */ + $account = Account::findOne($this->accounts['admin']['id']); + $model = new InitStateForm($account); + $activationModel = $model->createCode(); + expect($activationModel)->isInstanceOf(CurrentEmailConfirmation::class); + expect($activationModel->account_id)->equals($account->id); + expect(EmailActivation::findOne($activationModel->key))->notNull(); + }); + } + + public function testSendCurrentEmailConfirmation() { + $this->specify('send email', function() { + /** @var Account $account */ + $account = Account::findOne($this->accounts['admin']['id']); + $model = new InitStateForm($account); + expect($model->sendCurrentEmailConfirmation())->true(); + expect(EmailActivation::find()->andWhere([ + 'account_id' => $account->id, + 'type' => EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION, + ])->exists())->true(); + 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'; + } + +} diff --git a/tests/codeception/api/unit/models/profile/ChangeEmail/NewEmailFormTest.php b/tests/codeception/api/unit/models/profile/ChangeEmail/NewEmailFormTest.php new file mode 100644 index 0000000..1846ed6 --- /dev/null +++ b/tests/codeception/api/unit/models/profile/ChangeEmail/NewEmailFormTest.php @@ -0,0 +1,87 @@ +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' => EmailActivationFixture::class, + ]; + } + + public function testCreateCode() { + $this->specify('create valid code and store it to database', function() { + /** @var Account $account */ + $account = Account::findOne($this->accounts['admin']['id']); + $model = new NewEmailForm($account); + $model->email = 'my-new-email@ely.by'; + $activationModel = $model->createCode(); + expect($activationModel)->isInstanceOf(NewEmailConfirmation::class); + expect($activationModel->account_id)->equals($account->id); + expect($activationModel->newEmail)->equals($model->email); + expect(EmailActivation::findOne($activationModel->key))->notNull(); + }); + } + + public function testSendNewEmailConfirmation() { + $this->specify('send email', function() { + /** @var Account $account */ + $account = Account::findOne($this->accounts['admin']['id']); + /** @var NewEmailForm $model */ + $model = new NewEmailForm($account, [ + 'key' => $this->emailActivations['currentEmailConfirmation']['key'], + 'email' => 'my-new-email@ely.by', + ]); + expect($model->sendNewEmailConfirmation())->true(); + expect(EmailActivation::findOne($this->emailActivations['currentEmailConfirmation']['key']))->null(); + expect(EmailActivation::findOne([ + 'account_id' => $account->id, + 'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION, + ]))->notNull(); + 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'; + } + +} diff --git a/tests/codeception/api/unit/models/profile/ChangePasswordFormTest.php b/tests/codeception/api/unit/models/profile/ChangePasswordFormTest.php index 3c9665b..90e4550 100644 --- a/tests/codeception/api/unit/models/profile/ChangePasswordFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangePasswordFormTest.php @@ -9,7 +9,7 @@ use tests\codeception\common\fixtures\AccountFixture; use Yii; /** - * @property array $accounts + * @property AccountFixture $accounts */ class ChangePasswordFormTest extends DbTestCase { use Specify; diff --git a/tests/codeception/common/_support/FixtureHelper.php b/tests/codeception/common/_support/FixtureHelper.php index b3a336b..8631656 100644 --- a/tests/codeception/common/_support/FixtureHelper.php +++ b/tests/codeception/common/_support/FixtureHelper.php @@ -50,10 +50,7 @@ class FixtureHelper extends Module { 'class' => AccountFixture::class, 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', ], - 'emailActivations' => [ - 'class' => EmailActivationFixture::class, - 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', - ], + 'emailActivations' => EmailActivationFixture::class, 'oauthClients' => [ 'class' => OauthClientFixture::class, 'dataFile' => '@tests/codeception/common/fixtures/data/oauth-clients.php', diff --git a/tests/codeception/common/fixtures/data/accounts.php b/tests/codeception/common/fixtures/data/accounts.php index 12112cb..9919b88 100644 --- a/tests/codeception/common/fixtures/data/accounts.php +++ b/tests/codeception/common/fixtures/data/accounts.php @@ -75,4 +75,16 @@ return [ 'created_at' => 1462891612, 'updated_at' => 1462891612, ], + 'account-with-change-email-finish-state' => [ + 'id' => 7, + 'uuid' => '4c34f2cc-4bd9-454b-9583-bb52f020ec16', + 'username' => 'CrafterGameplays', + 'email' => 'crafter@gmail.com', + 'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0 + 'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2, + 'lang' => 'en', + 'status' => \common\models\Account::STATUS_ACTIVE, + 'created_at' => 1463349615, + 'updated_at' => 1463349615, + ], ]; diff --git a/tests/codeception/common/fixtures/data/email-activations.php b/tests/codeception/common/fixtures/data/email-activations.php index 3e66d47..17083a7 100644 --- a/tests/codeception/common/fixtures/data/email-activations.php +++ b/tests/codeception/common/fixtures/data/email-activations.php @@ -24,4 +24,17 @@ return [ 'type' => \common\models\EmailActivation::TYPE_FORGOT_PASSWORD_KEY, 'created_at' => time() - (new \common\models\confirmations\ForgotPassword())->repeatTimeout - 10, ], + 'currentEmailConfirmation' => [ + 'key' => 'H26HBDCHHAG2HGHGHS', + 'account_id' => 1, + 'type' => \common\models\EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION, + 'created_at' => time() - 10, + ], + 'newEmailConfirmation' => [ + 'key' => 'H27HBDCHHAG2HGHGHS', + 'account_id' => 7, + 'type' => \common\models\EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION, + '_data' => serialize(['newEmail' => 'my-new-email@ely.by']), + 'created_at' => time() - 10, + ], ]; diff --git a/tests/codeception/common/unit/behaviors/DataBehaviorTest.php b/tests/codeception/common/unit/behaviors/DataBehaviorTest.php new file mode 100644 index 0000000..6ffaa27 --- /dev/null +++ b/tests/codeception/common/unit/behaviors/DataBehaviorTest.php @@ -0,0 +1,79 @@ +specify('setting value should change model data field', function() { + $model = $this->createModel(); + /** @var DataBehavior $behavior */ + $behavior = $model->behaviors['dataBehavior']; + $this->callProtected($behavior, 'setKey', 'my-key', 'my-value'); + expect($model->_data)->equals(serialize(['my-key' => 'my-value'])); + }); + } + + public function testGetKey() { + $this->specify('getting value from exists data should work', function() { + $model = $this->createModel(); + $model->_data = serialize(['some-key' => 'some-value']); + /** @var DataBehavior $behavior */ + $behavior = $model->behaviors['dataBehavior']; + expect($this->callProtected($behavior, 'getKey', 'some-key'))->equals('some-value'); + }); + } + + public function testGetData() { + $this->specify('getting value from null field should return empty array', function() { + $model = $this->createModel(); + /** @var DataBehavior $behavior */ + $behavior = $model->behaviors['dataBehavior']; + expect($this->callProtected($behavior, 'getData'))->equals([]); + }); + + $this->specify('getting value from serialized data field should return encoded value', function() { + $model = $this->createModel(); + $data = ['foo' => 'bar']; + $model->_data = serialize($data); + /** @var DataBehavior $behavior */ + $behavior = $model->behaviors['dataBehavior']; + expect($this->callProtected($behavior, 'getData'))->equals($data); + }); + + $this->specify('getting value from invalid serialization string', function() { + $model = $this->createModel(); + $model->_data = 'this is invalid serialization of string'; + /** @var DataBehavior $behavior */ + $behavior = $model->behaviors['dataBehavior']; + $this->expectException(ErrorException::class); + $this->callProtected($behavior, 'getData'); + }); + } + + /** + * @return Model + */ + private function createModel() { + return new class extends Model { + public $_data; + + public function behaviors() { + return [ + 'dataBehavior' => [ + 'class' => DataBehavior::class, + ], + ]; + } + }; + } + +} diff --git a/tests/codeception/common/unit/models/EmailActivationTest.php b/tests/codeception/common/unit/models/EmailActivationTest.php index 12c221f..d604e1f 100644 --- a/tests/codeception/common/unit/models/EmailActivationTest.php +++ b/tests/codeception/common/unit/models/EmailActivationTest.php @@ -13,10 +13,7 @@ class EmailActivationTest extends DbTestCase { public function fixtures() { return [ - 'emailActivations' => [ - 'class' => EmailActivationFixture::class, - 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', - ], + 'emailActivations' => EmailActivationFixture::class, ]; } diff --git a/tests/codeception/common/unit/validators/LanguageValidatorTest.php b/tests/codeception/common/unit/validators/LanguageValidatorTest.php index 3148306..5d1b0ea 100644 --- a/tests/codeception/common/unit/validators/LanguageValidatorTest.php +++ b/tests/codeception/common/unit/validators/LanguageValidatorTest.php @@ -3,11 +3,12 @@ namespace codeception\common\unit\validators; use Codeception\Specify; use common\validators\LanguageValidator; -use ReflectionClass; +use tests\codeception\common\_support\ProtectedCaller; use tests\codeception\common\unit\TestCase; class LanguageValidatorTest extends TestCase { use Specify; + use ProtectedCaller; public function testGetFilesNames() { $this->specify('get list of 2 languages: ru and en', function() { @@ -42,12 +43,4 @@ class LanguageValidatorTest extends TestCase { }; } - private function callProtected($object, string $function, ...$args) { - $class = new ReflectionClass($object); - $method = $class->getMethod($function); - $method->setAccessible(true); - - return $method->invokeArgs($object, $args); - } - }