From 7343a3b506c8329620615c51482ffbc414b17878 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 13 Mar 2016 21:24:49 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D1=80=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B5=D0=BD=D0=B0=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D0=B0=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D1=8C=D0=BC=D0=B0=20=D1=81=20=D0=B0=D0=BA=D1=82=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B5=D0=B9=20=D0=B0=D0=BA=D0=BA=D0=B0=D1=83?= =?UTF-8?q?=D0=BD=D1=82=D0=B0,=20=D0=B4=D0=BE=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=8E=D0=BD=D0=B8=D1=82=20=D0=B8=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/controllers/SignupController.php | 16 +-- api/exceptions/Exception.php | 6 + .../ThisShouldNotHaveHappenedException.php | 11 ++ ...rm.php => RepeatAccountActivationForm.php} | 30 +++-- tests/codeception/api/_pages/SignupRoute.php | 4 +- .../functional/NewAccountActivationCest.php | 21 ---- .../RepeatAccountActivationCest.php | 71 ++++++++++++ .../RepeatAccountActivationFormTest.php | 106 ++++++++++++++++++ .../common/fixtures/data/accounts.php | 15 ++- .../fixtures/data/email-activations.php | 6 + 10 files changed, 238 insertions(+), 48 deletions(-) create mode 100644 api/exceptions/Exception.php create mode 100644 api/exceptions/ThisShouldNotHaveHappenedException.php rename api/models/{NewAccountActivationForm.php => RepeatAccountActivationForm.php} (76%) delete mode 100644 tests/codeception/api/functional/NewAccountActivationCest.php create mode 100644 tests/codeception/api/functional/RepeatAccountActivationCest.php create mode 100644 tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php diff --git a/api/controllers/SignupController.php b/api/controllers/SignupController.php index dcb73b9..f29cff5 100644 --- a/api/controllers/SignupController.php +++ b/api/controllers/SignupController.php @@ -2,7 +2,7 @@ namespace api\controllers; use api\models\ConfirmEmailForm; -use api\models\NewAccountActivationForm; +use api\models\RepeatAccountActivationForm; use api\models\RegistrationForm; use Yii; use yii\filters\AccessControl; @@ -13,13 +13,13 @@ class SignupController extends Controller { public function behaviors() { return ArrayHelper::merge(parent::behaviors(), [ 'authenticator' => [ - 'except' => ['index', 'new-message', 'confirm'], + 'except' => ['index', 'repeat-message', 'confirm'], ], 'access' => [ 'class' => AccessControl::class, 'rules' => [ [ - 'actions' => ['index', 'new-message', 'confirm'], + 'actions' => ['index', 'repeat-message', 'confirm'], 'allow' => true, 'roles' => ['?'], ], @@ -51,10 +51,10 @@ class SignupController extends Controller { ]; } - public function actionNewMessage() { - $model = new NewAccountActivationForm(); + public function actionRepeatMessage() { + $model = new RepeatAccountActivationForm(); $model->load(Yii::$app->request->post()); - if (!$model->sendNewMessage()) { + if (!$model->sendRepeatMessage()) { $response = [ 'success' => false, 'errors' => $this->normalizeModelErrors($model->getErrors()), @@ -63,8 +63,8 @@ class SignupController extends Controller { if ($response['errors']['email'] === 'error.recently_sent_message') { $activeActivation = $model->getActiveActivation(); $response['data'] = [ - 'can_repeat_in' => $activeActivation->created_at - time() + NewAccountActivationForm::REPEAT_FREQUENCY, - 'repeat_frequency' => NewAccountActivationForm::REPEAT_FREQUENCY, + 'canRepeatIn' => $activeActivation->created_at - time() + RepeatAccountActivationForm::REPEAT_FREQUENCY, + 'repeatFrequency' => RepeatAccountActivationForm::REPEAT_FREQUENCY, ]; } diff --git a/api/exceptions/Exception.php b/api/exceptions/Exception.php new file mode 100644 index 0000000..50972ae --- /dev/null +++ b/api/exceptions/Exception.php @@ -0,0 +1,6 @@ + 'trim'], ['email', 'required', 'message' => 'error.email_required'], - ['email', 'validateAccountForEmail'], + ['email', 'validateEmailForAccount'], ['email', 'validateExistsActivation'], ]; } - public function validateAccountForEmail($attribute) { + public function validateEmailForAccount($attribute) { if (!$this->hasErrors($attribute)) { $account = $this->getAccount(); - if ($account && $account->status === Account::STATUS_ACTIVE) { - $this->addError($attribute, "error.account_already_activated"); - } elseif (!$account) { + if ($account === null) { $this->addError($attribute, "error.{$attribute}_not_found"); + } elseif ($account->status === Account::STATUS_ACTIVE) { + $this->addError($attribute, "error.account_already_activated"); + } elseif ($account->status !== Account::STATUS_REGISTERED) { + // TODO: такие аккаунты следует логировать за попытку к саботажу + $this->addError($attribute, "error.account_cannot_resend_message"); } } } @@ -42,7 +45,7 @@ class NewAccountActivationForm extends BaseApiForm { } } - public function sendNewMessage() { + public function sendRepeatMessage() { if (!$this->validate()) { return false; } @@ -50,15 +53,10 @@ class NewAccountActivationForm extends BaseApiForm { $account = $this->getAccount(); $transaction = Yii::$app->db->beginTransaction(); try { - // Удаляем все активации аккаунта для пользователя этого E-mail адреса - /** @var EmailActivation[] $activations */ - $activations = $account->getEmailActivations() - ->andWhere(['type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION]) - ->all(); - - foreach ($activations as $activation) { - $activation->delete(); - } + EmailActivation::deleteAll([ + 'account_id' => $account->id, + 'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, + ]); $activation = new EmailActivation(); $activation->account_id = $account->id; diff --git a/tests/codeception/api/_pages/SignupRoute.php b/tests/codeception/api/_pages/SignupRoute.php index 6f796bd..a74c460 100644 --- a/tests/codeception/api/_pages/SignupRoute.php +++ b/tests/codeception/api/_pages/SignupRoute.php @@ -13,8 +13,8 @@ class SignupRoute extends BasePage { $this->actor->sendPOST($this->getUrl(), $registrationData); } - public function sendNewMessage($email = '') { - $this->route = ['signup/new-message']; + public function sendRepeatMessage($email = '') { + $this->route = ['signup/repeat-message']; $this->actor->sendPOST($this->getUrl(), ['email' => $email]); } diff --git a/tests/codeception/api/functional/NewAccountActivationCest.php b/tests/codeception/api/functional/NewAccountActivationCest.php deleted file mode 100644 index a4291ce..0000000 --- a/tests/codeception/api/functional/NewAccountActivationCest.php +++ /dev/null @@ -1,21 +0,0 @@ -wantTo('ensure that signup works'); - $route->sendNewMessage('achristiansen@gmail.com'); - $I->canSeeResponseCodeIs(200); - $I->canSeeResponseIsJson(); - $I->canSeeResponseContainsJson(['success' => true]); - $I->cantSeeResponseJsonMatchesJsonPath('$.errors'); - } - -} diff --git a/tests/codeception/api/functional/RepeatAccountActivationCest.php b/tests/codeception/api/functional/RepeatAccountActivationCest.php new file mode 100644 index 0000000..8f24d67 --- /dev/null +++ b/tests/codeception/api/functional/RepeatAccountActivationCest.php @@ -0,0 +1,71 @@ +wantTo('error.email_required on empty for submitting'); + $route->sendRepeatMessage(); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.email_required', + ], + ]); + + $I->wantTo('error.email_not_found if email is not presented in db'); + $route->sendRepeatMessage('im-not@exists.net'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.email_not_found', + ], + ]); + + $I->wantTo('error.account_already_activated if passed email matches with already activated account'); + $route->sendRepeatMessage('admin@ely.by'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.account_already_activated', + ], + ]); + + $I->wantTo('error.recently_sent_message if last message was send too recently'); + $route->sendRepeatMessage('achristiansen@gmail.com'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.recently_sent_message', + ], + ]); + $I->canSeeResponseJsonMatchesJsonPath('$.data.canRepeatIn'); + $I->canSeeResponseJsonMatchesJsonPath('$.data.repeatFrequency'); + } + + public function testSuccess(FunctionalTester $I) { + $route = new SignupRoute($I); + + $I->wantTo('successfully resend account activation message'); + $route->sendRepeatMessage('jon@ely.by'); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson(['success' => true]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors'); + } + +} diff --git a/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php b/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php new file mode 100644 index 0000000..07ebe07 --- /dev/null +++ b/tests/codeception/api/unit/models/RepeatAccountActivationFormTest.php @@ -0,0 +1,106 @@ +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', + ], + 'activations' => [ + 'class' => EmailActivationFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php', + ], + ]; + } + + public function testValidateEmailForAccount() { + $this->specify('error.email_not_found if passed valid email, but it don\'t exists in database', function() { + $model = new RepeatAccountActivationForm(['email' => 'me-is-not@exists.net']); + $model->validateEmailForAccount('email'); + expect($model->getErrors('email'))->equals(['error.email_not_found']); + }); + + $this->specify('error.account_already_activated if passed valid email, but account already activated', function() { + $model = new RepeatAccountActivationForm(['email' => $this->accounts['admin']['email']]); + $model->validateEmailForAccount('email'); + expect($model->getErrors('email'))->equals(['error.account_already_activated']); + }); + + $this->specify('no errors if passed valid email for not activated account', function() { + $model = new RepeatAccountActivationForm(['email' => $this->accounts['not-activated-account']['email']]); + $model->validateEmailForAccount('email'); + expect($model->getErrors('email'))->isEmpty(); + }); + } + + 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->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->validateExistsActivation('email'); + expect($model->getErrors('email'))->isEmpty(); + }); + } + + public function testSendRepeatMessage() { + $this->specify('no magic if we don\'t pass validation', function() { + $model = new RepeatAccountActivationForm(); + expect($model->sendRepeatMessage())->false(); + expect_file($this->getMessageFile())->notExists(); + }); + + $this->specify('successfully send new message if previous message has expired', function() { + $email = $this->accounts['not-activated-account-with-expired-message']['email']; + $model = new RepeatAccountActivationForm(['email' => $email]); + expect($model->sendRepeatMessage())->true(); + expect($model->getActiveActivation())->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/common/fixtures/data/accounts.php b/tests/codeception/common/fixtures/data/accounts.php index eb70aef..fe8bf7a 100644 --- a/tests/codeception/common/fixtures/data/accounts.php +++ b/tests/codeception/common/fixtures/data/accounts.php @@ -38,5 +38,18 @@ return [ 'status' => \common\models\Account::STATUS_REGISTERED, 'created_at' => 1453146616, 'updated_at' => 1453146616, - ] + ], + 'not-activated-account-with-expired-message' => [ + 'id' => 4, + 'uuid' => '58a7bfdc-ad0f-44c3-9197-759cb9220895', + 'username' => 'Jon', + 'email' => 'jon@ely.by', + 'password_hash' => '$2y$13$2rYkap5T6jG8z/mMK8a3Ou6aZxJcmAaTha6FEuujvHEmybSHRzW5e', # password_0 + 'password_hash_strategy' => \common\models\Account::PASS_HASH_STRATEGY_YII2, + 'password_reset_token' => null, + 'auth_key' => '45DsaEQ7U8lU9umIyCWk5iCnpdPvZ8Up', + 'status' => \common\models\Account::STATUS_REGISTERED, + 'created_at' => 1457890086, + 'updated_at' => 1457890086, + ], ]; diff --git a/tests/codeception/common/fixtures/data/email-activations.php b/tests/codeception/common/fixtures/data/email-activations.php index f0a859c..09c1aed 100644 --- a/tests/codeception/common/fixtures/data/email-activations.php +++ b/tests/codeception/common/fixtures/data/email-activations.php @@ -6,4 +6,10 @@ return [ 'type' => \common\models\EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, 'created_at' => time(), ], + [ + 'key' => 'H23HBDCHHAG2HGHGHS', + 'account_id' => 4, + 'type' => \common\models\EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, + 'created_at' => time() - \api\models\RepeatAccountActivationForm::REPEAT_FREQUENCY - 10, + ], ];