diff --git a/api/models/authentication/RegistrationForm.php b/api/models/authentication/RegistrationForm.php index 9d80b2d..c85b518 100644 --- a/api/models/authentication/RegistrationForm.php +++ b/api/models/authentication/RegistrationForm.php @@ -9,6 +9,7 @@ use common\models\Account; use common\models\confirmations\RegistrationConfirmation; use common\models\EmailActivation; use common\models\UsernameHistory; +use common\validators\EmailValidator; use common\validators\LanguageValidator; use common\validators\PasswordValidate; use common\validators\UsernameValidator; @@ -42,7 +43,7 @@ class RegistrationForm extends ApiForm { ['rulesAgreement', 'required', 'message' => E::RULES_AGREEMENT_REQUIRED], ['username', UsernameValidator::class], - ['email', 'validateEmail', 'skipOnEmpty' => false], + ['email', EmailValidator::class], ['password', 'required', 'message' => E::PASSWORD_REQUIRED], ['rePassword', 'required', 'message' => E::RE_PASSWORD_REQUIRED], @@ -54,14 +55,6 @@ class RegistrationForm extends ApiForm { ]; } - public function validateEmail() { - $account = new Account(); - $account->email = $this->email; - if (!$account->validate(['email'])) { - $this->addErrors($account->getErrors()); - } - } - public function validatePasswordAndRePasswordMatch($attribute) { if (!$this->hasErrors()) { if ($this->password !== $this->rePassword) { diff --git a/api/models/profile/ChangeEmail/NewEmailForm.php b/api/models/profile/ChangeEmail/NewEmailForm.php index c87ba6c..9dd8b76 100644 --- a/api/models/profile/ChangeEmail/NewEmailForm.php +++ b/api/models/profile/ChangeEmail/NewEmailForm.php @@ -2,10 +2,10 @@ namespace api\models\profile\ChangeEmail; use api\models\base\KeyConfirmationForm; -use common\helpers\Error as E; use common\models\Account; use common\models\confirmations\NewEmailConfirmation; use common\models\EmailActivation; +use common\validators\EmailValidator; use Yii; use yii\base\ErrorException; use yii\base\Exception; @@ -25,26 +25,14 @@ class NewEmailForm extends KeyConfirmationForm { parent::__construct($config); } - /** - * @return Account - */ - public function getAccount() { - return $this->account; - } - public function rules() { return array_merge(parent::rules(), [ - ['email', 'required', 'message' => E::EMAIL_REQUIRED], - ['email', 'validateEmail'], + ['email', EmailValidator::class], ]); } - public function validateEmail() { - $account = new Account(); - $account->email = $this->email; - if (!$account->validate(['email'])) { - $this->addErrors($account->getErrors()); - } + public function getAccount() : Account { + return $this->account; } public function sendNewEmailConfirmation() { diff --git a/common/models/Account.php b/common/models/Account.php index 0a04312..d61acf6 100644 --- a/common/models/Account.php +++ b/common/models/Account.php @@ -1,9 +1,7 @@ 'trim'], - [['email'], 'required', 'message' => E::EMAIL_REQUIRED], - [['email'], 'string', 'max' => 255, 'tooLong' => E::EMAIL_TOO_LONG], - [['email'], 'email', 'checkDNS' => true, 'enableIDN' => true, 'message' => E::EMAIL_INVALID], - [['email'], TempmailValidator::class, 'message' => E::EMAIL_IS_TEMPMAIL], - [['email'], 'unique', 'message' => E::EMAIL_NOT_AVAILABLE], - ]; - } - /** * Validates password * diff --git a/common/validators/EmailValidator.php b/common/validators/EmailValidator.php new file mode 100644 index 0000000..4309352 --- /dev/null +++ b/common/validators/EmailValidator.php @@ -0,0 +1,64 @@ + 'trim']); + + $required = new validators\RequiredValidator(); + $required->message = E::EMAIL_REQUIRED; + + $length = new validators\StringValidator(); + $length->max = 255; + $length->tooLong = E::EMAIL_TOO_LONG; + + $email = new validators\EmailValidator(); + $email->checkDNS = true; + $email->enableIDN = true; + $email->message = E::EMAIL_INVALID; + + $tempmail = new TempmailValidator(); + $tempmail->message = E::EMAIL_IS_TEMPMAIL; + + $unique = new validators\UniqueValidator(); + $unique->message = E::EMAIL_NOT_AVAILABLE; + $unique->targetClass = Account::class; + $unique->targetAttribute = 'email'; + if ($this->accountCallback !== null) { + $unique->filter = function(QueryInterface $query) { + $query->andWhere(['NOT', ['id' => ($this->accountCallback)()]]); + }; + } + + $this->executeValidation($filter, $model, $attribute) && + $this->executeValidation($required, $model, $attribute) && + $this->executeValidation($length, $model, $attribute) && + $this->executeValidation($email, $model, $attribute) && + $this->executeValidation($tempmail, $model, $attribute) && + $this->executeValidation($unique, $model, $attribute); + } + + protected function executeValidation(Validator $validator, Model $model, string $attribute) { + $validator->validateAttribute($model, $attribute); + + return !$model->hasErrors($attribute); + } + +} diff --git a/tests/codeception/common/unit/models/AccountTest.php b/tests/codeception/common/unit/models/AccountTest.php index 9e5beb5..2b791e3 100644 --- a/tests/codeception/common/unit/models/AccountTest.php +++ b/tests/codeception/common/unit/models/AccountTest.php @@ -4,8 +4,6 @@ namespace tests\codeception\common\unit\models; use Codeception\Specify; use common\components\UserPass; use common\models\Account; -use tests\codeception\common\fixtures\AccountFixture; -use tests\codeception\common\fixtures\MojangUsernameFixture; use tests\codeception\common\unit\TestCase; use Yii; use const common\LATEST_RULES_VERSION; @@ -13,59 +11,12 @@ use const common\LATEST_RULES_VERSION; class AccountTest extends TestCase { use Specify; - public function _fixtures() { - return [ - 'accounts' => AccountFixture::class, - 'mojangAccounts' => MojangUsernameFixture::class, - ]; - } - - public function testValidateEmail() { - // TODO: пропускать этот тест, если падает ошибка с недостпуностью интернет соединения - $this->specify('email required', function() { - $model = new Account(['email' => null]); - expect($model->validate(['email']))->false(); - expect($model->getErrors('email'))->equals(['error.email_required']); - }); - - $this->specify('email should be not more 255 symbols (I hope it\'s impossible to register)', function() { - $model = new Account([ - 'email' => 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . - 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . - 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . - 'emailemail', // = 256 symbols - ]); - expect($model->validate(['email']))->false(); - expect($model->getErrors('email'))->equals(['error.email_too_long']); - }); - - $this->specify('email should be email (it test can fail, if you don\'t have internet connection)', function() { - $model = new Account(['email' => 'invalid_email']); - expect($model->validate(['email']))->false(); - expect($model->getErrors('email'))->equals(['error.email_invalid']); - }); - - $this->specify('email should be not tempmail', function() { - $model = new Account(['email' => 'ibrpycwyjdnt@dropmail.me']); - expect($model->validate(['email']))->false(); - expect($model->getErrors('email'))->equals(['error.email_is_tempmail']); - }); - - $this->specify('email should be unique', function() { - $model = new Account(['email' => $this->tester->grabFixture('accounts', 'admin')['email']]); - expect($model->validate('email'))->false(); - expect($model->getErrors('email'))->equals(['error.email_not_available']); - }); - } - public function testSetPassword() { - $this->specify('calling method should change password and set latest password hash algorithm', function() { - $model = new Account(); - $model->setPassword('12345678'); - expect('hash should be set', $model->password_hash)->notEmpty(); - expect('validation should be passed', $model->validatePassword('12345678'))->true(); - expect('latest password hash should be used', $model->password_hash_strategy)->equals(Account::PASS_HASH_STRATEGY_YII2); - }); + $model = new Account(); + $model->setPassword('12345678'); + $this->assertNotEmpty($model->password_hash, 'hash should be set'); + $this->assertTrue($model->validatePassword('12345678'), 'validation should be passed'); + $this->assertEquals(Account::PASS_HASH_STRATEGY_YII2, $model->password_hash_strategy, 'latest password hash should be used'); } public function testValidatePassword() { diff --git a/tests/codeception/common/unit/validators/EmailValidatorTest.php b/tests/codeception/common/unit/validators/EmailValidatorTest.php new file mode 100644 index 0000000..4bf7256 --- /dev/null +++ b/tests/codeception/common/unit/validators/EmailValidatorTest.php @@ -0,0 +1,109 @@ +validator = new EmailValidator(); + } + + public function testValidateAttributeRequired() { + $model = $this->createModel(''); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_required'], $model->getErrors('field')); + + $model = $this->createModel('email'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_required'], $model->getErrors('field')); + } + + public function testValidateAttributeLength() { + $model = $this->createModel( + 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . + 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . + 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . + '@gmail.com' // = 256 symbols + ); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_too_long'], $model->getErrors('field')); + + $model = $this->createModel('some-email@gmail.com'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_too_long'], $model->getErrors('field')); + } + + public function testValidateAttributeEmail() { + $model = $this->createModel('non-email'); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_invalid'], $model->getErrors('field')); + + $model = $this->createModel('non-email@etot-domen-ne-suschestrvyet.de'); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_invalid'], $model->getErrors('field')); + + $model = $this->createModel('valid-email@gmail.com'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_invalid'], $model->getErrors('field')); + } + + public function testValidateAttributeTempmail() { + $model = $this->createModel('ibrpycwyjdnt@dropmail.me'); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_is_tempmail'], $model->getErrors('field')); + + $model = $this->createModel('valid-email@gmail.com'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_is_tempmail'], $model->getErrors('field')); + } + + public function testValidateAttributeUnique() { + $this->tester->haveFixtures([ + 'accounts' => AccountFixture::class, + ]); + + /** @var \common\models\Account $accountFixture */ + $accountFixture = $this->tester->grabFixture('accounts', 'admin'); + + $model = $this->createModel($accountFixture->email); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_not_available'], $model->getErrors('field')); + + $model = $this->createModel($accountFixture->email); + $this->validator->accountCallback = function() use ($accountFixture) { + return $accountFixture->id; + }; + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_not_available'], $model->getErrors('field')); + $this->validator->accountCallback = null; + + $model = $this->createModel('some-unique-email@gmail.com'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_not_available'], $model->getErrors('field')); + } + + /** + * @param string $fieldValue + * @return Model + */ + private function createModel(string $fieldValue) : Model { + $class = new class extends Model { + public $field; + }; + + $class->field = $fieldValue; + + return $class; + } + +}