From 44aaea2c08966f9c0f0bcfa96fd6d62926f7397d Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 15 Jan 2016 12:21:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D1=82=D1=80=D0=B5=D1=84=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B5=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D1=8B=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D0=BE=D0=B5=20=D0=BE=D0=BA=D1=80?= =?UTF-8?q?=D1=83=D0=B6=D0=B5=D0=BD=D0=B8=D0=B5=20acceptance=20=D0=A3?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=87=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BF=D0=BE=D1=82=D0=B5=D0=BD=D1=86=D0=B8=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=20=D0=BD=D0=B5=D0=BD=D1=83=D0=B6=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2=20=D0=94=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D1=8B=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=B9=20=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D0=B2=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE=20E-m?= =?UTF-8?q?ail=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D1=84?= =?UTF-8?q?=D0=BE=D1=80=D0=BC=D1=8B=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=A0=D0=B5=D0=BE=D1=80=D0=B3?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=80=D0=BE?= =?UTF-8?q?=D1=83=D1=82=D0=B8=D0=BD=D0=B3=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=20=D0=B4=D0=BB=D1=8F=20ReCaptcha2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/components/ReCaptcha/Component.php | 16 ++ api/components/ReCaptcha/Validator.php | 65 +++++ api/config/main.php | 6 +- .../controllers/AuthenticationController.php | 18 +- api/controllers/Controller.php | 2 + api/controllers/SignupController.php | 41 ++++ api/controllers/SiteController.php | 25 +- api/mails/registration-confirmation-html.php | 8 + api/mails/registration-confirmation-text.php | 9 + .../login/models => messages}/.gitkeep | 0 api/models/LoginForm.php | 79 +++--- api/models/RegistrationForm.php | 111 +++++++++ api/modules/login/Module.php | 9 - .../login/controllers/DefaultController.php | 12 - .../login/models/AuthenticationForm.php | 72 ------ api/traits/ApiNormalize.php | 26 ++ api/views/site/login.php | 39 --- common/components/UserFriendlyRandomKey.php | 18 ++ common/components/UserPass.php | 4 +- common/models/Account.php | 12 +- common/models/EmailActivation.php | 46 ++++ composer.json | 3 +- .../m160114_134716_account_email_keys.php | 23 ++ tests/codeception/api/.gitignore | 1 - tests/codeception/api/_bootstrap.php | 2 +- tests/codeception/api/_pages/AboutPage.php | 14 -- tests/codeception/api/_pages/ContactPage.php | 10 +- tests/codeception/api/_pages/LoginRoute.php | 7 +- .../codeception/api/_pages/RegisterRoute.php | 17 ++ tests/codeception/api/_pages/SignupPage.php | 27 --- tests/codeception/api/acceptance.suite.yml | 28 --- .../codeception/api/acceptance/AboutCept.php | 10 - .../api/acceptance/ContactCept.php | 56 ----- tests/codeception/api/acceptance/HomeCept.php | 12 - .../codeception/api/acceptance/LoginCept.php | 34 --- .../codeception/api/acceptance/SignupCest.php | 82 ------- .../codeception/api/acceptance/_bootstrap.php | 2 - tests/codeception/api/codeception.yml | 2 +- .../codeception/api/functional/AboutCept.php | 10 - tests/codeception/api/functional/HomeCept.php | 12 - .../codeception/api/functional/LoginCept.php | 40 ---- .../codeception/api/functional/LoginCest.php | 137 +++++++++++ .../api/functional/RegisterCest.php | 224 ++++++++++++++++++ .../codeception/api/functional/SignupCest.php | 90 ------- .../unit/fixtures/data/models/accounts.php | 16 -- .../api/unit/models/LoginFormTest.php | 61 +++++ .../api/unit/models/RegistrationFormTest.php | 111 +++++++++ .../api/unit/models/SignupFormTest.php | 51 ---- .../login/models/AuthenticationFormTest.php | 127 ---------- .../api/unit/traits/ApiNormalizerTest.php | 42 ++++ tests/codeception/common/.gitignore | 1 - .../common/_support/FixtureHelper.php | 32 +-- .../common/fixtures/data/accounts.php | 29 +++ .../common/unit/models/LoginFormTest.php | 93 -------- tests/codeception/config/acceptance.php | 7 - tests/codeception/config/api/acceptance.php | 16 -- 56 files changed, 1075 insertions(+), 972 deletions(-) create mode 100644 api/components/ReCaptcha/Component.php create mode 100644 api/components/ReCaptcha/Validator.php rename api/{modules/login => }/controllers/AuthenticationController.php (66%) create mode 100644 api/controllers/SignupController.php create mode 100644 api/mails/registration-confirmation-html.php create mode 100644 api/mails/registration-confirmation-text.php rename api/{modules/login/models => messages}/.gitkeep (100%) create mode 100644 api/models/RegistrationForm.php delete mode 100644 api/modules/login/Module.php delete mode 100644 api/modules/login/controllers/DefaultController.php delete mode 100644 api/modules/login/models/AuthenticationForm.php create mode 100644 api/traits/ApiNormalize.php delete mode 100644 api/views/site/login.php create mode 100644 common/components/UserFriendlyRandomKey.php create mode 100644 common/models/EmailActivation.php create mode 100644 console/migrations/m160114_134716_account_email_keys.php delete mode 100644 tests/codeception/api/_pages/AboutPage.php create mode 100644 tests/codeception/api/_pages/RegisterRoute.php delete mode 100644 tests/codeception/api/_pages/SignupPage.php delete mode 100644 tests/codeception/api/acceptance.suite.yml delete mode 100644 tests/codeception/api/acceptance/AboutCept.php delete mode 100644 tests/codeception/api/acceptance/ContactCept.php delete mode 100644 tests/codeception/api/acceptance/HomeCept.php delete mode 100644 tests/codeception/api/acceptance/LoginCept.php delete mode 100644 tests/codeception/api/acceptance/SignupCest.php delete mode 100644 tests/codeception/api/acceptance/_bootstrap.php delete mode 100644 tests/codeception/api/functional/AboutCept.php delete mode 100644 tests/codeception/api/functional/HomeCept.php delete mode 100644 tests/codeception/api/functional/LoginCept.php create mode 100644 tests/codeception/api/functional/LoginCest.php create mode 100644 tests/codeception/api/functional/RegisterCest.php delete mode 100644 tests/codeception/api/functional/SignupCest.php delete mode 100644 tests/codeception/api/unit/fixtures/data/models/accounts.php create mode 100644 tests/codeception/api/unit/models/LoginFormTest.php create mode 100644 tests/codeception/api/unit/models/RegistrationFormTest.php delete mode 100644 tests/codeception/api/unit/models/SignupFormTest.php delete mode 100644 tests/codeception/api/unit/modules/login/models/AuthenticationFormTest.php create mode 100644 tests/codeception/api/unit/traits/ApiNormalizerTest.php create mode 100644 tests/codeception/common/fixtures/data/accounts.php delete mode 100644 tests/codeception/common/unit/models/LoginFormTest.php delete mode 100644 tests/codeception/config/acceptance.php delete mode 100644 tests/codeception/config/api/acceptance.php diff --git a/api/components/ReCaptcha/Component.php b/api/components/ReCaptcha/Component.php new file mode 100644 index 0000000..bf16832 --- /dev/null +++ b/api/components/ReCaptcha/Component.php @@ -0,0 +1,16 @@ +secret === NULL) { + throw new InvalidConfigException(''); + } + } + +} diff --git a/api/components/ReCaptcha/Validator.php b/api/components/ReCaptcha/Validator.php new file mode 100644 index 0000000..bacb2dc --- /dev/null +++ b/api/components/ReCaptcha/Validator.php @@ -0,0 +1,65 @@ +reCaptcha; + } + + public function init() { + parent::init(); + if ($this->getComponent() === null) { + throw new InvalidConfigException('Required "reCaptcha" component as instance of ' . Component::class . '.'); + } + + if ($this->message === null) { + $this->message = Yii::t('yii', 'The verification code is incorrect.'); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) { + $value = Yii::$app->request->post(self::CAPTCHA_RESPONSE_FIELD); + if (empty($value)) { + return [$this->message, []]; + } + + $requestParams = [ + 'secret' => $this->getComponent()->secret, + 'response' => $value, + 'remoteip' => Yii::$app->request->userIP, + ]; + + $requestUrl = self::SITE_VERIFY_URL . '?' . http_build_query($requestParams); + $response = $this->getResponse($requestUrl); + + if (!isset($response['success'])) { + throw new Exception('Invalid recaptcha verify response.'); + } + + return $response['success'] ? null : [$this->message, []]; + } + + protected function getResponse($request) { + $response = file_get_contents($request); + + return json_decode($response, true); + } + +} diff --git a/api/config/main.php b/api/config/main.php index ca23f7a..bafec86 100644 --- a/api/config/main.php +++ b/api/config/main.php @@ -36,9 +36,9 @@ return [ 'showScriptName' => false, 'rules' => [], ], - ], - 'modules' => [ - 'login' => 'api\modules\login\Module', + 'reCaptcha' => [ + 'class' => 'api\components\ReCaptcha\Component', + ], ], 'params' => $params, ]; diff --git a/api/modules/login/controllers/AuthenticationController.php b/api/controllers/AuthenticationController.php similarity index 66% rename from api/modules/login/controllers/AuthenticationController.php rename to api/controllers/AuthenticationController.php index 5610d91..307c689 100644 --- a/api/modules/login/controllers/AuthenticationController.php +++ b/api/controllers/AuthenticationController.php @@ -1,8 +1,7 @@ [ 'class' => AccessControl::className(), - 'only' => ['login-info'], + 'only' => ['login'], 'rules' => [ [ - 'actions' => ['login-info'], + 'actions' => ['login', 'register'], 'allow' => true, 'roles' => ['?'], ], @@ -26,17 +25,18 @@ class AuthenticationController extends Controller { public function verbs() { return [ - 'loginInfo' => ['post'], + 'login' => ['post'], + 'register' => ['post'], ]; } - public function actionLoginInfo() { - $model = new AuthenticationForm(); + public function actionLogin() { + $model = new LoginForm(); $model->load(Yii::$app->request->post()); if (!$model->login()) { return [ 'success' => false, - 'errors' => $model->getErrors(), + 'errors' => $this->normalizeModelErrors($model->getErrors()), ]; } diff --git a/api/controllers/Controller.php b/api/controllers/Controller.php index c139046..1e02157 100644 --- a/api/controllers/Controller.php +++ b/api/controllers/Controller.php @@ -1,8 +1,10 @@ [ + 'class' => AccessControl::className(), + 'only' => ['register'], + 'rules' => [ + [ + 'actions' => ['register'], + 'allow' => true, + 'roles' => ['?'], + ], + ], + ], + ]); + } + + public function actionRegister() { + $model = new RegistrationForm(); + $model->load(Yii::$app->request->post()); + if (!$model->signup()) { + return [ + 'success' => false, + 'errors' => $this->normalizeModelErrors($model->getErrors()), + ]; + } + + return [ + 'success' => true, + ]; + } + +} diff --git a/api/controllers/SiteController.php b/api/controllers/SiteController.php index 837010c..716f003 100644 --- a/api/controllers/SiteController.php +++ b/api/controllers/SiteController.php @@ -2,7 +2,6 @@ namespace api\controllers; use api\models\ContactForm; -use api\models\LoginForm; use api\models\PasswordResetRequestForm; use api\models\ResetPasswordForm; use api\models\SignupForm; @@ -11,12 +10,11 @@ use yii\base\InvalidParamException; use yii\filters\AccessControl; use yii\filters\VerbFilter; use yii\web\BadRequestHttpException; -use yii\web\Controller; /** * Site controller */ -class SiteController extends Controller +class SiteController extends \yii\web\Controller { /** * @inheritdoc @@ -75,27 +73,6 @@ class SiteController extends Controller return $this->render('index'); } - /** - * Logs in a user. - * - * @return mixed - */ - public function actionLogin() - { - if (!\Yii::$app->user->isGuest) { - return $this->goHome(); - } - - $model = new LoginForm(); - if ($model->load(Yii::$app->request->post()) && $model->login()) { - return $this->goBack(); - } else { - return $this->render('login', [ - 'model' => $model, - ]); - } - } - /** * Logs out the current user. * diff --git a/api/mails/registration-confirmation-html.php b/api/mails/registration-confirmation-html.php new file mode 100644 index 0000000..75ca25f --- /dev/null +++ b/api/mails/registration-confirmation-html.php @@ -0,0 +1,8 @@ + + +

Текст письма

+

Код:

diff --git a/api/mails/registration-confirmation-text.php b/api/mails/registration-confirmation-text.php new file mode 100644 index 0000000..63e582c --- /dev/null +++ b/api/mails/registration-confirmation-text.php @@ -0,0 +1,9 @@ + + +В общем по результату работы сложного PHP скрипта вы успешно зарегистрированы. + +Код активации diff --git a/api/modules/login/models/.gitkeep b/api/messages/.gitkeep similarity index 100% rename from api/modules/login/models/.gitkeep rename to api/messages/.gitkeep diff --git a/api/models/LoginForm.php b/api/models/LoginForm.php index e9ff467..4c42718 100644 --- a/api/models/LoginForm.php +++ b/api/models/LoginForm.php @@ -5,46 +5,45 @@ use common\models\Account; use Yii; use yii\base\Model; -/** - * Login form - */ -class LoginForm extends Model -{ - public $username; +class LoginForm extends Model { + + public $login; public $password; public $rememberMe = true; - private $_user; + private $_account; + public function formName() { + return ''; + } - /** - * @inheritdoc - */ - public function rules() - { + public function rules() { return [ - // username and password are both required - [['username', 'password'], 'required'], - // rememberMe must be a boolean value - ['rememberMe', 'boolean'], - // password is validated by validatePassword() + ['login', 'required', 'message' => 'error.login_required'], + ['login', 'validateLogin'], + + ['password', 'required', 'when' => function(self $model) { + return !$model->hasErrors(); + }, 'message' => 'error.password_required'], ['password', 'validatePassword'], + + ['rememberMe', 'boolean'], ]; } - /** - * Validates the password. - * This method serves as the inline validation for password. - * - * @param string $attribute the attribute currently being validated - * @param array $params the additional name-value pairs given in the rule - */ - public function validatePassword($attribute, $params) - { + public function validateLogin($attribute) { if (!$this->hasErrors()) { - $user = $this->getUser(); - if (!$user || !$user->validatePassword($this->password)) { - $this->addError($attribute, 'Incorrect username or password.'); + if (!$this->getAccount()) { + $this->addError($attribute, 'error.' . $attribute . '_not_exist'); + } + } + } + + public function validatePassword($attribute) { + if (!$this->hasErrors()) { + $account = $this->getAccount(); + if (!$account || !$account->validatePassword($this->password)) { + $this->addError($attribute, 'error.' . $attribute . '_incorrect'); } } } @@ -54,26 +53,24 @@ class LoginForm extends Model * * @return boolean whether the user is logged in successfully */ - public function login() - { - if ($this->validate()) { - return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0); - } else { + public function login() { + if (!$this->validate()) { return false; } + + return Yii::$app->user->login($this->getAccount(), $this->rememberMe ? 3600 * 24 * 30 : 0); } /** - * Finds user by [[username]] - * * @return Account|null */ - protected function getUser() - { - if ($this->_user === null) { - $this->_user = Account::findByEmail($this->username); + protected function getAccount() { + if ($this->_account === NULL) { + $attribute = strpos($this->login, '@') ? 'email' : 'username'; + $this->_account = Account::findOne([$attribute => $this->login]); } - return $this->_user; + return $this->_account; } + } diff --git a/api/models/RegistrationForm.php b/api/models/RegistrationForm.php new file mode 100644 index 0000000..e5cc3e2 --- /dev/null +++ b/api/models/RegistrationForm.php @@ -0,0 +1,111 @@ + 'error.you_must_accept_rules'], + [[], ReCaptchaValidator::class, 'message' => 'error.captcha_invalid', 'when' => !YII_ENV_TEST], + + ['username', 'filter', 'filter' => 'trim'], + ['username', 'required', 'message' => 'error.username_required'], + ['username', 'string', 'min' => 3, 'max' => 21, + 'tooShort' => 'error.username_too_short', + 'tooLong' => 'error.username_too_long', + ], + ['username', 'match', 'pattern' => '/^[\p{L}\d-_\.!?#$%^&*()\[\]:;]+$/u'], + ['username', 'unique', 'targetClass' => Account::class, 'message' => 'error.username_not_available'], + + ['email', 'filter', 'filter' => 'trim'], + ['email', 'required', 'message' => 'error.email_required'], + ['email', 'string', 'max' => 255, 'tooLong' => 'error.email_too_long'], + ['email', 'email', 'checkDNS' => true, 'enableIDN' => true, 'message' => 'error.email_invalid'], + ['email', 'unique', 'targetClass' => Account::class, 'message' => 'error.email_not_available'], + + ['password', 'required', 'message' => 'error.password_required'], + ['rePassword', 'required', 'message' => 'error.rePassword_required'], + ['password', 'string', 'min' => 8, 'tooShort' => 'error.password_too_short'], + ['rePassword', 'validatePasswordAndRePasswordMatch'], + ]; + } + + public function validatePasswordAndRePasswordMatch($attribute) { + if (!$this->hasErrors()) { + if ($this->password !== $this->rePassword) { + $this->addError($attribute, "error.rePassword_does_not_match"); + } + } + } + + /** + * @return Account|null the saved model or null if saving fails + */ + public function signup() { + if (!$this->validate()) { + return null; + } + + $transaction = Yii::$app->db->beginTransaction(); + try { + $account = new Account(); + $account->email = $this->email; + $account->username = $this->username; + $account->password = $this->password; + $account->status = Account::STATUS_REGISTERED; + $account->generateAuthKey(); + if (!$account->save()) { + throw new ErrorException('Account not created.'); + } + + $emailActivation = new EmailActivation(); + $emailActivation->account_id = $account->id; + $emailActivation->type = EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION; + $emailActivation->key = UserFriendlyRandomKey::make(); + + if (!$emailActivation->save()) { + throw new ErrorException('Unable save email-activation model.'); + } + + /** @var \yii\swiftmailer\Mailer $mailer */ + $mailer = Yii::$app->mailer; + /** @var \yii\swiftmailer\Message $message */ + $message = $mailer->compose([ + 'html' => '@app/mails/registration-confirmation-html', + 'text' => '@app/mails/registration-confirmation-text', + ], [ + 'key' => $emailActivation->key, + ])->setFrom(['account@ely.by' => 'Ely.by']); + + if (!$message->send()) { + throw new ErrorException('Unable send email with activation code.'); + } + + $transaction->commit(); + } catch (ErrorException $e) { + $transaction->rollBack(); + throw $e; + } + + return $account; + } + +} diff --git a/api/modules/login/Module.php b/api/modules/login/Module.php deleted file mode 100644 index dfbdc36..0000000 --- a/api/modules/login/Module.php +++ /dev/null @@ -1,9 +0,0 @@ - 'world']; - } - -} diff --git a/api/modules/login/models/AuthenticationForm.php b/api/modules/login/models/AuthenticationForm.php deleted file mode 100644 index d27ff95..0000000 --- a/api/modules/login/models/AuthenticationForm.php +++ /dev/null @@ -1,72 +0,0 @@ - 'error.login_required'], - ['login', 'validateLogin'], - - ['password', 'required', 'when' => function(self $model) { - return !$model->hasErrors(); - }, 'message' => 'error.password_required'], - ['password', 'validatePassword'], - - ['rememberMe', 'boolean'], - ]; - } - - public function validateLogin($attribute) { - if (!$this->hasErrors()) { - if (!$this->getAccount()) { - $this->addError($attribute, 'error.' . $attribute . '_not_exist'); - } - } - } - - public function validatePassword($attribute) { - if (!$this->hasErrors()) { - $account = $this->getAccount(); - if (!$account || !$account->validatePassword($this->password)) { - $this->addError($attribute, 'error.' . $attribute . '_incorrect'); - } - } - } - - /** - * Logs in a user using the provided username and password. - * - * @return boolean whether the user is logged in successfully - */ - public function login() { - if (!$this->validate()) { - return false; - } - - return Yii::$app->user->login($this->getAccount(), $this->rememberMe ? 3600 * 24 * 30 : 0); - } - - /** - * @return Account|null - */ - protected function getAccount() { - if ($this->_account === NULL) { - $attribute = strpos($this->login, '@') ? 'email' : 'username'; - $this->_account = Account::findOne([$attribute => $this->login]); - } - - return $this->_account; - } - -} diff --git a/api/traits/ApiNormalize.php b/api/traits/ApiNormalize.php new file mode 100644 index 0000000..590680c --- /dev/null +++ b/api/traits/ApiNormalize.php @@ -0,0 +1,26 @@ + 'first_error_of_field1', + * 'field2' => 'first_error_of_field2', + * ] + * + * @param array $errors + * @return array + */ + public function normalizeModelErrors(array $errors) { + $normalized = []; + foreach($errors as $attribute => $attrErrors) { + $normalized[$attribute] = $attrErrors[0]; + } + + return $normalized; + } + +} diff --git a/api/views/site/login.php b/api/views/site/login.php deleted file mode 100644 index 60e411a..0000000 --- a/api/views/site/login.php +++ /dev/null @@ -1,39 +0,0 @@ -title = 'Login'; -$this->params['breadcrumbs'][] = $this->title; -?> -
-

title) ?>

- -

Please fill out the following fields to login:

- -
-
- 'login-form']); ?> - - field($model, 'username') ?> - - field($model, 'password')->passwordInput() ?> - - field($model, 'rememberMe')->checkbox() ?> - -
- If you forgot your password you can . -
- -
- 'btn btn-primary', 'name' => 'login-button']) ?> -
- - -
-
-
diff --git a/common/components/UserFriendlyRandomKey.php b/common/components/UserFriendlyRandomKey.php new file mode 100644 index 0000000..cae9958 --- /dev/null +++ b/common/components/UserFriendlyRandomKey.php @@ -0,0 +1,18 @@ +password_reset_token = null; } + public function getEmailActivations() { + return $this->hasMany(EmailActivation::class, ['id' => 'account_id']); + } + } diff --git a/common/models/EmailActivation.php b/common/models/EmailActivation.php new file mode 100644 index 0000000..31cb255 --- /dev/null +++ b/common/models/EmailActivation.php @@ -0,0 +1,46 @@ + TimestampBehavior::class, + 'updatedAtAttribute' => false, + ], + ]; + } + + public function rules() { + return []; + } + + public function getAccount() { + return $this->hasOne(Account::class, ['id' => 'account_id']); + } + +} diff --git a/composer.json b/composer.json index 8fe35f8..7fd250b 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ "yiisoft/yii2-codeception": "*", "yiisoft/yii2-debug": "*", "yiisoft/yii2-gii": "*", - "yiisoft/yii2-faker": "*" + "yiisoft/yii2-faker": "*", + "flow/jsonpath": "^0.3.1" }, "config": { "process-timeout": 1800 diff --git a/console/migrations/m160114_134716_account_email_keys.php b/console/migrations/m160114_134716_account_email_keys.php new file mode 100644 index 0000000..20532bd --- /dev/null +++ b/console/migrations/m160114_134716_account_email_keys.php @@ -0,0 +1,23 @@ +createTable('{{%email_activations}}', [ + 'id' => $this->primaryKey(), + 'account_id' => $this->getDb()->getTableSchema('{{%accounts}}')->getColumn('id')->dbType . ' NOT NULL', + 'key' => $this->string()->unique()->notNull(), + 'type' => $this->smallInteger()->notNull(), + 'created_at' => $this->integer()->notNull(), + ], $this->tableOptions); + + $this->addForeignKey('FK_email_activation_to_account', '{{%email_activations}}', 'account_id', '{{%accounts}}', 'id', 'CASCADE', 'CASCADE'); + } + + public function safeDown() { + $this->dropTable('{{%email_activations}}'); + } + +} diff --git a/tests/codeception/api/.gitignore b/tests/codeception/api/.gitignore index 985dbb4..b5226cf 100644 --- a/tests/codeception/api/.gitignore +++ b/tests/codeception/api/.gitignore @@ -1,4 +1,3 @@ # these files are auto generated by codeception build /unit/UnitTester.php /functional/FunctionalTester.php -/acceptance/AcceptanceTester.php diff --git a/tests/codeception/api/_bootstrap.php b/tests/codeception/api/_bootstrap.php index 0dcc5b6..7a8b56d 100644 --- a/tests/codeception/api/_bootstrap.php +++ b/tests/codeception/api/_bootstrap.php @@ -14,7 +14,7 @@ require_once(YII_APP_BASE_PATH . '/api/config/bootstrap.php'); // set correct script paths -// the entry script file path for functional and acceptance tests +// the entry script file path for functional tests $_SERVER['SCRIPT_FILENAME'] = API_ENTRY_FILE; $_SERVER['SCRIPT_NAME'] = API_ENTRY_URL; $_SERVER['SERVER_NAME'] = parse_url(\Codeception\Configuration::config()['config']['test_entry_url'], PHP_URL_HOST); diff --git a/tests/codeception/api/_pages/AboutPage.php b/tests/codeception/api/_pages/AboutPage.php deleted file mode 100644 index 0d5bf0d..0000000 --- a/tests/codeception/api/_pages/AboutPage.php +++ /dev/null @@ -1,14 +0,0 @@ - $value) { $inputType = $field === 'body' ? 'textarea' : 'input'; $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value); } $this->actor->click('contact-button'); } + } diff --git a/tests/codeception/api/_pages/LoginRoute.php b/tests/codeception/api/_pages/LoginRoute.php index 666dcaa..6cfc5e5 100644 --- a/tests/codeception/api/_pages/LoginRoute.php +++ b/tests/codeception/api/_pages/LoginRoute.php @@ -4,16 +4,15 @@ namespace tests\codeception\api\_pages; use yii\codeception\BasePage; /** - * Represents loging page * @property \tests\codeception\api\FunctionalTester $actor */ class LoginRoute extends BasePage { - public $route = 'login/authentication/login-info'; + public $route = ['authentication/login']; - public function login($email, $password) { + public function login($login = '', $password = '') { $this->actor->sendPOST($this->getUrl(), [ - 'email' => $email, + 'login' => $login, 'password' => $password, ]); } diff --git a/tests/codeception/api/_pages/RegisterRoute.php b/tests/codeception/api/_pages/RegisterRoute.php new file mode 100644 index 0000000..ec9cde7 --- /dev/null +++ b/tests/codeception/api/_pages/RegisterRoute.php @@ -0,0 +1,17 @@ +actor->sendPOST($this->getUrl(), $registrationData); + } + +} diff --git a/tests/codeception/api/_pages/SignupPage.php b/tests/codeception/api/_pages/SignupPage.php deleted file mode 100644 index bfae9ab..0000000 --- a/tests/codeception/api/_pages/SignupPage.php +++ /dev/null @@ -1,27 +0,0 @@ - $value) { - $inputType = $field === 'body' ? 'textarea' : 'input'; - $this->actor->fillField($inputType . '[name="SignupForm[' . $field . ']"]', $value); - } - $this->actor->click('signup-button'); - } -} diff --git a/tests/codeception/api/acceptance.suite.yml b/tests/codeception/api/acceptance.suite.yml deleted file mode 100644 index 1828a04..0000000 --- a/tests/codeception/api/acceptance.suite.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Codeception Test Suite Configuration - -# suite for acceptance tests. -# perform tests in browser using the Selenium-like tools. -# powered by Mink (http://mink.behat.org). -# (tip: that's what your customer will see). -# (tip: test your ajax and javascript by one of Mink drivers). - -# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. - -class_name: AcceptanceTester -modules: - enabled: - - PhpBrowser - - tests\codeception\common\_support\FixtureHelper -# you can use WebDriver instead of PhpBrowser to test javascript and ajax. -# This will require you to install selenium. See http://codeception.com/docs/04-AcceptanceTests#Selenium -# "restart" option is used by the WebDriver to start each time per test-file new session and cookies, -# it is useful if you want to login in your app in each test. -# - WebDriver - config: - PhpBrowser: -# PLEASE ADJUST IT TO THE ACTUAL ENTRY POINT WITHOUT PATH INFO - url: http://localhost:8080 -# WebDriver: -# url: http://localhost:8080 -# browser: firefox -# restart: true diff --git a/tests/codeception/api/acceptance/AboutCept.php b/tests/codeception/api/acceptance/AboutCept.php deleted file mode 100644 index c25ae81..0000000 --- a/tests/codeception/api/acceptance/AboutCept.php +++ /dev/null @@ -1,10 +0,0 @@ -wantTo('ensure that about works'); -AboutPage::openBy($I); -$I->see('About', 'h1'); diff --git a/tests/codeception/api/acceptance/ContactCept.php b/tests/codeception/api/acceptance/ContactCept.php deleted file mode 100644 index 16bdd31..0000000 --- a/tests/codeception/api/acceptance/ContactCept.php +++ /dev/null @@ -1,56 +0,0 @@ -wantTo('ensure that contact works'); - -$contactPage = ContactPage::openBy($I); - -$I->see('Contact', 'h1'); - -$I->amGoingTo('submit contact form with no data'); -$contactPage->submit([]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see validations errors'); -$I->see('Contact', 'h1'); -$I->see('Name cannot be blank', '.help-block'); -$I->see('Email cannot be blank', '.help-block'); -$I->see('Subject cannot be blank', '.help-block'); -$I->see('Body cannot be blank', '.help-block'); -$I->see('The verification code is incorrect', '.help-block'); - -$I->amGoingTo('submit contact form with not correct email'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester.email', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->expectTo('see that email adress is wrong'); -$I->dontSee('Name cannot be blank', '.help-block'); -$I->see('Email is not a valid email address.', '.help-block'); -$I->dontSee('Subject cannot be blank', '.help-block'); -$I->dontSee('Body cannot be blank', '.help-block'); -$I->dontSee('The verification code is incorrect', '.help-block'); - -$I->amGoingTo('submit contact form with correct data'); -$contactPage->submit([ - 'name' => 'tester', - 'email' => 'tester@example.com', - 'subject' => 'test subject', - 'body' => 'test content', - 'verifyCode' => 'testme', -]); -if (method_exists($I, 'wait')) { - $I->wait(3); // only for selenium -} -$I->see('Thank you for contacting us. We will respond to you as soon as possible.'); diff --git a/tests/codeception/api/acceptance/HomeCept.php b/tests/codeception/api/acceptance/HomeCept.php deleted file mode 100644 index c05212f..0000000 --- a/tests/codeception/api/acceptance/HomeCept.php +++ /dev/null @@ -1,12 +0,0 @@ -wantTo('ensure that home page works'); -$I->amOnPage(Yii::$app->homeUrl); -$I->see('My Company'); -$I->seeLink('About'); -$I->click('About'); -$I->see('This is the About page.'); diff --git a/tests/codeception/api/acceptance/LoginCept.php b/tests/codeception/api/acceptance/LoginCept.php deleted file mode 100644 index b117319..0000000 --- a/tests/codeception/api/acceptance/LoginCept.php +++ /dev/null @@ -1,34 +0,0 @@ -wantTo('ensure login page works'); - -$loginPage = LoginRoute::openBy($I); - -$I->amGoingTo('submit login form with no data'); -$loginPage->login('', ''); -$I->expectTo('see validations errors'); -$I->see('Username cannot be blank.', '.help-block'); -$I->see('Password cannot be blank.', '.help-block'); - -$I->amGoingTo('try to login with wrong credentials'); -$I->expectTo('see validations errors'); -$loginPage->login('admin', 'wrong'); -$I->expectTo('see validations errors'); -$I->see('Incorrect username or password.', '.help-block'); - -$I->amGoingTo('try to login with correct credentials'); -$loginPage->login('erau', 'password_0'); -$I->expectTo('see that user is logged'); -$I->seeLink('Logout (erau)'); -$I->dontSeeLink('Login'); -$I->dontSeeLink('Signup'); -/** Uncomment if using WebDriver - * $I->click('Logout (erau)'); - * $I->dontSeeLink('Logout (erau)'); - * $I->seeLink('Login'); - */ diff --git a/tests/codeception/api/acceptance/SignupCest.php b/tests/codeception/api/acceptance/SignupCest.php deleted file mode 100644 index 991a39a..0000000 --- a/tests/codeception/api/acceptance/SignupCest.php +++ /dev/null @@ -1,82 +0,0 @@ - 'tester.email@example.com', - 'username' => 'tester', - ]); - } - - /** - * This method is called when test fails. - * @param \Codeception\Event\FailEvent $event - */ - public function _fail($event) - { - } - - /** - * @param \codeception_api\AcceptanceTester $I - * @param \Codeception\Scenario $scenario - */ - public function testUserSignup($I, $scenario) - { - $I->wantTo('ensure that signup works'); - - $signupPage = SignupPage::openBy($I); - $I->see('Signup', 'h1'); - $I->see('Please fill out the following fields to signup:'); - - $I->amGoingTo('submit signup form with no data'); - - $signupPage->submit([]); - - $I->expectTo('see validation errors'); - $I->see('Username cannot be blank.', '.help-block'); - $I->see('Email cannot be blank.', '.help-block'); - $I->see('Password cannot be blank.', '.help-block'); - - $I->amGoingTo('submit signup form with not correct email'); - $signupPage->submit([ - 'username' => 'tester', - 'email' => 'tester.email', - 'password' => 'tester_password', - ]); - - $I->expectTo('see that email address is wrong'); - $I->dontSee('Username cannot be blank.', '.help-block'); - $I->dontSee('Password cannot be blank.', '.help-block'); - $I->see('Email is not a valid email address.', '.help-block'); - - $I->amGoingTo('submit signup form with correct email'); - $signupPage->submit([ - 'username' => 'tester', - 'email' => 'tester.email@example.com', - 'password' => 'tester_password', - ]); - - $I->expectTo('see that user logged in'); - $I->seeLink('Logout (tester)'); - } -} diff --git a/tests/codeception/api/acceptance/_bootstrap.php b/tests/codeception/api/acceptance/_bootstrap.php deleted file mode 100644 index 11437fe..0000000 --- a/tests/codeception/api/acceptance/_bootstrap.php +++ /dev/null @@ -1,2 +0,0 @@ -wantTo('ensure that about works'); -AboutPage::openBy($I); -$I->see('About', 'h1'); diff --git a/tests/codeception/api/functional/HomeCept.php b/tests/codeception/api/functional/HomeCept.php deleted file mode 100644 index 68059b0..0000000 --- a/tests/codeception/api/functional/HomeCept.php +++ /dev/null @@ -1,12 +0,0 @@ -wantTo('ensure that home page works'); -$I->amOnPage(Yii::$app->homeUrl); -$I->see('My Company'); -$I->seeLink('About'); -$I->click('About'); -$I->see('This is the About page.'); diff --git a/tests/codeception/api/functional/LoginCept.php b/tests/codeception/api/functional/LoginCept.php deleted file mode 100644 index 38be956..0000000 --- a/tests/codeception/api/functional/LoginCept.php +++ /dev/null @@ -1,40 +0,0 @@ -wantTo('ensure login page works'); - -$loginPage = LoginRoute::openBy($I); - -$I->amGoingTo('submit login form with no data'); -$loginPage->login('', ''); -$I->expectTo('see validations errors'); -$I->canSeeResponseContainsJson([ - 'success' => false, - 'errors' => [ - 'email' => [ - 'error.email_required', - ], - 'password' => [ - 'error.password_required', - ], - ], -]); - -/* -$I->amGoingTo('try to login with wrong credentials'); -$I->expectTo('see validations errors'); -$loginPage->login('', 'wrong'); -$I->expectTo('see validations errors'); -$I->see('Incorrect username or password.', '.help-block'); - -$I->amGoingTo('try to login with correct credentials'); -$loginPage->login('erau', 'password_0'); -$I->expectTo('see that user is logged'); -$I->seeLink('Logout (erau)'); -$I->dontSeeLink('Login'); -$I->dontSeeLink('Signup'); -*/ diff --git a/tests/codeception/api/functional/LoginCest.php b/tests/codeception/api/functional/LoginCest.php new file mode 100644 index 0000000..5bb5658 --- /dev/null +++ b/tests/codeception/api/functional/LoginCest.php @@ -0,0 +1,137 @@ +wantTo('see error.login_required expected if login is not set'); + $route->login(); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'login' => 'error.login_required', + ], + ]); + + $I->wantTo('see error.login_not_exist expected if username not exists in database'); + $route->login('non-exist-username'); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'login' => 'error.login_not_exist', + ], + ]); + + $I->wantTo('see error.login_not_exist expected if email not exists in database'); + $route->login('not-exist@user.com'); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'login' => 'error.login_not_exist', + ], + ]); + + $I->wantTo('don\'t see errors on login field if username is correct and exists in database'); + $route->login('Admin'); + $I->canSeeResponseContainsJson([ + 'success' => false, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors.login'); + + $I->wantTo('don\'t see errors on login field if email is correct and exists in database'); + $route->login('admin@ely.by'); + $I->canSeeResponseContainsJson([ + 'success' => false, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors.login'); + } + + public function testLoginPassword(FunctionalTester $I) { + $route = new LoginRoute($I); + + $I->wantTo('see password doesn\'t have errors if email or username not set'); + $route->login(); + $I->canSeeResponseContainsJson([ + 'success' => false, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors.password'); + + $I->wantTo('see password doesn\'t have errors if username not exists in database'); + $route->login('non-exist-username', 'random-password'); + $I->canSeeResponseContainsJson([ + 'success' => false, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors.password'); + + $I->wantTo('see password doesn\'t has errors if email not exists in database'); + $route->login('not-exist@user.com', 'random-password'); + $I->canSeeResponseContainsJson([ + 'success' => false, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors.password'); + + $I->wantTo('see error.password_incorrect if email correct, but password wrong'); + $route->login('admin@ely.by', 'wrong-password'); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'password' => 'error.password_incorrect', + ], + ]); + + $I->wantTo('see error.password_incorrect if username correct, but password wrong'); + $route->login('Admin', 'wrong-password'); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'password' => 'error.password_incorrect', + ], + ]); + } + + public function testLoginByUsernameCorrect(FunctionalTester $I) { + $route = new LoginRoute($I); + + $I->wantTo('login into account using correct username and password'); + $route->login('Admin', 'password_0'); + $I->canSeeResponseContainsJson([ + 'success' => true, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors'); + } + + public function testLoginByEmailCorrect(FunctionalTester $I) { + $route = new LoginRoute($I); + + $I->wantTo('login into account using correct email and password'); + $route->login('admin@ely.by', 'password_0'); + $I->canSeeResponseContainsJson([ + 'success' => true, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors'); + } + + public function testLoginInAccWithPasswordMethod(FunctionalTester $I) { + $route = new LoginRoute($I); + + $I->wantTo('login into account with old password hash function using correct username and password'); + $route->login('AccWithOldPassword', '12345678'); + $I->canSeeResponseContainsJson([ + 'success' => true, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors'); + } + +} diff --git a/tests/codeception/api/functional/RegisterCest.php b/tests/codeception/api/functional/RegisterCest.php new file mode 100644 index 0000000..8526afa --- /dev/null +++ b/tests/codeception/api/functional/RegisterCest.php @@ -0,0 +1,224 @@ + 'erickskrauch@ely.by', + 'username' => 'ErickSkrauch', + ]); + } + + public function testIncorrectRegistration(FunctionalTester $I) { + $route = new RegisterRoute($I); + + $I->wantTo('get error.you_must_accept_rules if we don\'t accept rules'); + $route->send([ + 'username' => 'ErickSkrauch', + 'email' => 'erickskrauch@ely.by', + 'password' => 'some_password', + 'rePassword' => 'some_password', + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'rulesAgreement' => 'error.you_must_accept_rules', + ], + ]); + + $I->wantTo('don\'t see error.you_must_accept_rules if we accept rules'); + $route->send([ + 'rulesAgreement' => true, + ]); + $I->cantSeeResponseContainsJson([ + 'errors' => [ + 'rulesAgreement' => 'error.you_must_accept_rules', + ], + ]); + + $I->wantTo('see error.username_required if username is not set'); + $route->send([ + 'username' => '', + 'email' => '', + 'password' => '', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'username' => 'error.username_required', + ], + ]); + + $I->wantTo('don\'t see error.username_required if username is not set'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => '', + 'password' => '', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->cantSeeResponseContainsJson([ + 'errors' => [ + 'username' => 'error.username_required', + ], + ]); + + $I->wantTo('see error.email_required if email is not set'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => '', + 'password' => '', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.email_required', + ], + ]); + + $I->wantTo('see error.email_invalid if email is set, but invalid'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => 'invalid@email', + 'password' => '', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.email_invalid', + ], + ]); + + $I->wantTo('see error.email_invalid if email is set, valid, but domain doesn\'t exist or don\'t have mx record'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => 'invalid@govnomail.com', + 'password' => '', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.email_invalid', + ], + ]); + + $I->wantTo('see error.email_not_available if email is set, fully valid, but not available for registration'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => 'admin@ely.by', + 'password' => '', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'email' => 'error.email_not_available', + ], + ]); + + $I->wantTo('don\'t see errors on email if all valid'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => 'erickskrauch@ely.by', + 'password' => '', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors.email'); + + $I->wantTo('see error.password_required if password is not set'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => 'erickskrauch@ely.by', + 'password' => '', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'password' => 'error.password_required', + ], + ]); + + $I->wantTo('see error.password_too_short before it will be compared with rePassword'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => 'correct-email@ely.by', + 'password' => 'short', + 'rePassword' => 'password', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'password' => 'error.password_too_short', + ], + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors.rePassword'); + + $I->wantTo('see error.rePassword_required if password valid and rePassword not set'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => 'correct-email@ely.by', + 'password' => 'valid-password', + 'rePassword' => '', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'rePassword' => 'error.rePassword_required', + ], + ]); + + $I->wantTo('see error.rePassword_does_not_match if password valid and rePassword donen\'t match it'); + $route->send([ + 'username' => 'valid_nickname', + 'email' => 'correct-email@ely.by', + 'password' => 'valid-password', + 'rePassword' => 'password', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseContainsJson([ + 'success' => false, + 'errors' => [ + 'rePassword' => 'error.rePassword_does_not_match', + ], + ]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors.password'); + } + + public function testUserCorrectRegistration(FunctionalTester $I) { + $route = new RegisterRoute($I); + + $I->wantTo('ensure that signup works'); + $route->send([ + 'username' => 'some_username', + 'email' => 'some_email@example.com', + 'password' => 'some_password', + 'rePassword' => 'some_password', + 'rulesAgreement' => true, + ]); + $I->canSeeResponseCodeIs(200); + $I->canSeeResponseIsJson(); + $I->canSeeResponseContainsJson(['success' => true]); + $I->cantSeeResponseJsonMatchesJsonPath('$.errors'); + } + +} diff --git a/tests/codeception/api/functional/SignupCest.php b/tests/codeception/api/functional/SignupCest.php deleted file mode 100644 index 0e98e28..0000000 --- a/tests/codeception/api/functional/SignupCest.php +++ /dev/null @@ -1,90 +0,0 @@ - 'tester.email@example.com', - 'username' => 'tester', - ]); - } - - /** - * This method is called when test fails. - * @param \Codeception\Event\FailEvent $event - */ - public function _fail($event) - { - - } - - /** - * - * @param \codeception_api\FunctionalTester $I - * @param \Codeception\Scenario $scenario - */ - public function testUserSignup($I, $scenario) - { - $I->wantTo('ensure that signup works'); - - $signupPage = SignupPage::openBy($I); - $I->see('Signup', 'h1'); - $I->see('Please fill out the following fields to signup:'); - - $I->amGoingTo('submit signup form with no data'); - - $signupPage->submit([]); - - $I->expectTo('see validation errors'); - $I->see('Username cannot be blank.', '.help-block'); - $I->see('Email cannot be blank.', '.help-block'); - $I->see('Password cannot be blank.', '.help-block'); - - $I->amGoingTo('submit signup form with not correct email'); - $signupPage->submit([ - 'username' => 'tester', - 'email' => 'tester.email', - 'password' => 'tester_password', - ]); - - $I->expectTo('see that email address is wrong'); - $I->dontSee('Username cannot be blank.', '.help-block'); - $I->dontSee('Password cannot be blank.', '.help-block'); - $I->see('Email is not a valid email address.', '.help-block'); - - $I->amGoingTo('submit signup form with correct email'); - $signupPage->submit([ - 'username' => 'tester', - 'email' => 'tester.email@example.com', - 'password' => 'tester_password', - ]); - - $I->expectTo('see that user is created'); - $I->seeRecord('common\models\User', [ - 'username' => 'tester', - 'email' => 'tester.email@example.com', - ]); - - $I->expectTo('see that user logged in'); - $I->seeLink('Logout (tester)'); - } -} diff --git a/tests/codeception/api/unit/fixtures/data/models/accounts.php b/tests/codeception/api/unit/fixtures/data/models/accounts.php deleted file mode 100644 index 6c702f3..0000000 --- a/tests/codeception/api/unit/fixtures/data/models/accounts.php +++ /dev/null @@ -1,16 +0,0 @@ - 1, - 'uuid' => 'df936908-b2e1-544d-96f8-2977ec213022', - 'username' => 'Admin', - 'email' => 'admin@ely.by', - 'password_hash' => '$2y$13$CXT0Rkle1EMJ/c1l5bylL.EylfmQ39O5JlHJVFpNn618OUS1HwaIi', # password_0 - 'password_hash_strategy' => 1, - 'password_reset_token' => NULL, - 'auth_key' => 'iwTNae9t34OmnK6l4vT4IeaTk-YWI2Rv', - 'status' => 10, - 'created_at' => 1451775316, - 'updated_at' => 1451775316, - ], -]; diff --git a/tests/codeception/api/unit/models/LoginFormTest.php b/tests/codeception/api/unit/models/LoginFormTest.php new file mode 100644 index 0000000..f81d46d --- /dev/null +++ b/tests/codeception/api/unit/models/LoginFormTest.php @@ -0,0 +1,61 @@ +user->logout(); + parent::tearDown(); + } + + public function fixtures() { + return [ + 'account' => [ + 'class' => AccountFixture::className(), + 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', + ], + ]; + } + + protected function createModel($login = '', $password = '') { + return new LoginForm([ + 'login' => $login, + 'password' => $password, + ]); + } + + public function testIncorrectLogin() { + $model = $this->createModel('not-esist-login', 'fully-invalid-password'); + $this->specify('get errors and don\'t log in into account with wrong credentials', function () use ($model) { + expect('model should not login user', $model->login())->false(); + expect('error messages should be set', $model->errors)->notEmpty(); + expect('user should not be logged in', Yii::$app->user->isGuest)->true(); + }); + } + + public function testLoginByUsernameCorrect() { + $model = $this->createModel('Admin', 'password_0'); + $this->specify('user should be able to login with correct username and password', function () use ($model) { + expect('model should login user', $model->login())->true(); + expect('error message should not be set', $model->errors)->isEmpty(); + expect('user should be logged in', Yii::$app->user->isGuest)->false(); + }); + } + + public function testLoginByEmailCorrect() { + $model = $this->createModel('admin@ely.by', 'password_0'); + $this->specify('user should be able to login with correct email and password', function () use ($model) { + expect('model should login user', $model->login())->true(); + expect('error message should not be set', $model->errors)->isEmpty(); + expect('user should be logged in', Yii::$app->user->isGuest)->false(); + }); + } + +} diff --git a/tests/codeception/api/unit/models/RegistrationFormTest.php b/tests/codeception/api/unit/models/RegistrationFormTest.php new file mode 100644 index 0000000..2dfa3c1 --- /dev/null +++ b/tests/codeception/api/unit/models/RegistrationFormTest.php @@ -0,0 +1,111 @@ +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 [ + 'account' => [ + 'class' => AccountFixture::className(), + 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', + ], + ]; + } + + public function testNotCorrectRegistration() { + $model = new RegistrationForm([ + 'username' => 'valid_nickname', + 'email' => 'correct-email@ely.by', + 'password' => 'enough-length', + 'rePassword' => 'password', + 'rulesAgreement' => true, + ]); + $this->specify('username and email in use, passwords not math - model is not created', function() use ($model) { + expect($model->signup())->null(); + expect($model->getErrors())->notEmpty(); + expect_file($this->getMessageFile())->notExists(); + }); + } + + public function testUsernameValidators() { + $shouldBeValid = [ + 'русский_ник', 'русский_ник_на_грани!', 'numbers1132', '*__*-Stars-*__*', '1-_.!?#$%^&*()[]', '[ESP]Эрик', + 'Свят_помидор;', 'зроблена_ў_беларусі:)', + ]; + $shouldBeInvalid = [ + 'nick@name', 'spaced nick', ' ', 'sh', ' sh ', + ]; + + foreach($shouldBeValid as $nickname) { + $model = new RegistrationForm([ + 'username' => $nickname, + ]); + expect($nickname . ' passed validation', $model->validate(['username']))->true(); + } + + foreach($shouldBeInvalid as $nickname) { + $model = new RegistrationForm([ + 'username' => $nickname, + ]); + expect($nickname . ' fail validation', $model->validate('username'))->false(); + } + } + + public function testCorrectSignup() { + $model = new RegistrationForm([ + 'username' => 'some_username', + 'email' => 'some_email@example.com', + 'password' => 'some_password', + 'rePassword' => 'some_password', + 'rulesAgreement' => true, + ]); + + $user = $model->signup(); + + expect('user should be valid', $user)->isInstanceOf(Account::class); + expect('password should be correct', $user->validatePassword('some_password'))->true(); + expect('user model exists in database', Account::find()->andWhere([ + 'username' => 'some_username', + 'email' => 'some_email@example.com', + ])->exists())->true(); + expect('email activation code exists in database', EmailActivation::find()->andWhere([ + 'account_id' => $user->id, + 'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION, + ])->exists())->true(); + expect_file('message file exists', $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/SignupFormTest.php b/tests/codeception/api/unit/models/SignupFormTest.php deleted file mode 100644 index 86751dd..0000000 --- a/tests/codeception/api/unit/models/SignupFormTest.php +++ /dev/null @@ -1,51 +0,0 @@ - 'some_username', - 'email' => 'some_email@example.com', - 'password' => 'some_password', - ]); - - $user = $model->signup(); - - $this->assertInstanceOf('common\models\User', $user, 'user should be valid'); - - expect('email should be correct', $user->email)->equals('some_email@example.com'); - expect('password should be correct', $user->validatePassword('some_password'))->true(); - } - - public function testNotCorrectSignup() - { - $model = new SignupForm([ - 'username' => 'troy.becker', - 'email' => 'nicolas.dianna@hotmail.com', - 'password' => 'some_password', - ]); - - expect('username and email are in use, user should not be created', $model->signup())->null(); - } - - public function fixtures() - { - return [ - 'user' => [ - 'class' => UserFixture::className(), - 'dataFile' => '@tests/codeception/api/unit/fixtures/data/models/user.php', - ], - ]; - } -} diff --git a/tests/codeception/api/unit/modules/login/models/AuthenticationFormTest.php b/tests/codeception/api/unit/modules/login/models/AuthenticationFormTest.php deleted file mode 100644 index aceb455..0000000 --- a/tests/codeception/api/unit/modules/login/models/AuthenticationFormTest.php +++ /dev/null @@ -1,127 +0,0 @@ -user->logout(); - parent::tearDown(); - } - - public function fixtures() { - return [ - 'account' => [ - 'class' => AccountFixture::className(), - 'dataFile' => '@tests/codeception/api/unit/fixtures/data/models/accounts.php' - ], - ]; - } - - protected function createModel($login = '', $password = '') { - return new AuthenticationForm([ - 'login' => $login, - 'password' => $password, - ]); - } - - public function testLoginEmailOrUsername() { - $model = $this->createModel(); - $this->specify('error.login_required expected if login is not set', function() use ($model) { - expect($model->login())->false(); - expect($model->getErrors('login'))->equals(['error.login_required']); - expect(Yii::$app->user->isGuest)->true(); - }); - - $model = $this->createModel('non-exist-username'); - $this->specify('error.login_not_exist expected if username not exists in database', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('login'))->equals(['error.login_not_exist']); - }); - - $model = $this->createModel('not-exist@user.com'); - $this->specify('error.login_not_exist expected if email not exists in database', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('login'))->equals(['error.login_not_exist']); - }); - - $model = $this->createModel('Admin'); - $this->specify('no errors on login field if username is correct and exists in database', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('login'))->isEmpty(); - }); - - $model = $this->createModel('admin@ely.by'); - $this->specify('no errors on login field if email is correct and exists in database', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('login'))->isEmpty(); - }); - } - - public function testLoginPassword() { - $model = $this->createModel(); - $this->specify('password don\'t has errors if email or username not set', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('password'))->isEmpty(); - }); - - $model = $this->createModel('non-exist-username', 'random-password'); - $this->specify('password don\'t has errors if username not exists in database', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('password'))->isEmpty(); - }); - - $model = $this->createModel('not-exist@user.com', 'random-password'); - $this->specify('password don\'t has errors if email not exists in database', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('password'))->isEmpty(); - }); - - $model = $this->createModel('admin@ely.by', 'wrong-password'); - $this->specify('error.password_incorrect expected if email correct, but password wrong', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('password'))->equals(['error.password_incorrect']); - }); - - $model = $this->createModel('Admin', 'wrong-password'); - $this->specify('error.password_incorrect expected if username correct, but password wrong', function() use ($model) { - expect($model->login())->false(); - expect(Yii::$app->user->isGuest)->true(); - expect($model->getErrors('password'))->equals(['error.password_incorrect']); - }); - } - - public function testLoginByUsernameCorrect() { - $model = $this->createModel('Admin', 'password_0'); - $this->specify('user should be able to login with correct username and password', function () use ($model) { - expect('model should login user', $model->login())->true(); - expect('error message should not be set', $model->errors)->isEmpty(); - expect('user should be logged in', Yii::$app->user->isGuest)->false(); - }); - } - - public function testLoginByEmailCorrect() { - $model = $this->createModel('admin@ely.by', 'password_0'); - $this->specify('user should be able to login with correct email and password', function () use ($model) { - expect('model should login user', $model->login())->true(); - expect('error message should not be set', $model->errors)->isEmpty(); - expect('user should be logged in', Yii::$app->user->isGuest)->false(); - }); - } - -} diff --git a/tests/codeception/api/unit/traits/ApiNormalizerTest.php b/tests/codeception/api/unit/traits/ApiNormalizerTest.php new file mode 100644 index 0000000..9700be0 --- /dev/null +++ b/tests/codeception/api/unit/traits/ApiNormalizerTest.php @@ -0,0 +1,42 @@ +specify('', function() use ($object) { + $normalized = $object->normalizeModelErrors([ + 'rulesAgreement' => [ + 'error.you_must_accept_rules', + ], + 'email' => [ + 'error.email_required', + ], + 'username' => [ + 'error.username_too_short', + 'error.username_not_unique', + ], + ]); + + expect($normalized)->equals([ + 'rulesAgreement' => 'error.you_must_accept_rules', + 'email' => 'error.email_required', + 'username' => 'error.username_too_short', + ]); + }); + } + +} diff --git a/tests/codeception/common/.gitignore b/tests/codeception/common/.gitignore index 985dbb4..b5226cf 100644 --- a/tests/codeception/common/.gitignore +++ b/tests/codeception/common/.gitignore @@ -1,4 +1,3 @@ # these files are auto generated by codeception build /unit/UnitTester.php /functional/FunctionalTester.php -/acceptance/AcceptanceTester.php diff --git a/tests/codeception/common/_support/FixtureHelper.php b/tests/codeception/common/_support/FixtureHelper.php index 2c87db1..ccf9861 100644 --- a/tests/codeception/common/_support/FixtureHelper.php +++ b/tests/codeception/common/_support/FixtureHelper.php @@ -2,19 +2,16 @@ namespace tests\codeception\common\_support; -use tests\codeception\common\fixtures\UserFixture; use Codeception\Module; +use tests\codeception\common\fixtures\AccountFixture; use yii\test\FixtureTrait; use yii\test\InitDbFixture; /** * This helper is used to populate the database with needed fixtures before any tests are run. - * In this example, the database is populated with the demo login user, which is used in acceptance - * and functional tests. All fixtures will be loaded before the suite is started and unloaded after it - * completes. + * All fixtures will be loaded before the suite is started and unloaded after it completes. */ -class FixtureHelper extends Module -{ +class FixtureHelper extends Module { /** * Redeclare visibility because codeception includes all public methods that do not start with "_" @@ -31,27 +28,25 @@ class FixtureHelper extends Module /** * Method called before any suite tests run. Loads User fixture login user - * to use in acceptance and functional tests. + * to use in functional tests. + * * @param array $settings */ - public function _beforeSuite($settings = []) - { + public function _beforeSuite($settings = []) { $this->loadFixtures(); } /** * Method is called after all suite tests run */ - public function _afterSuite() - { + public function _afterSuite() { $this->unloadFixtures(); } /** * @inheritdoc */ - public function globalFixtures() - { + public function globalFixtures() { return [ InitDbFixture::className(), ]; @@ -60,13 +55,12 @@ class FixtureHelper extends Module /** * @inheritdoc */ - public function fixtures() - { + public function fixtures() { return [ - //'user' => [ - // 'class' => UserFixture::className(), - // 'dataFile' => '@tests/codeception/common/fixtures/data/init_login.php', - //], + 'accounts' => [ + 'class' => AccountFixture::class, + 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', + ], ]; } } diff --git a/tests/codeception/common/fixtures/data/accounts.php b/tests/codeception/common/fixtures/data/accounts.php new file mode 100644 index 0000000..5215777 --- /dev/null +++ b/tests/codeception/common/fixtures/data/accounts.php @@ -0,0 +1,29 @@ + [ + 'id' => 1, + 'uuid' => 'df936908-b2e1-544d-96f8-2977ec213022', + 'username' => 'Admin', + 'email' => 'admin@ely.by', + 'password_hash' => '$2y$13$CXT0Rkle1EMJ/c1l5bylL.EylfmQ39O5JlHJVFpNn618OUS1HwaIi', # password_0 + 'password_hash_strategy' => 1, + 'password_reset_token' => NULL, + 'auth_key' => 'iwTNae9t34OmnK6l4vT4IeaTk-YWI2Rv', + 'status' => 10, + 'created_at' => 1451775316, + 'updated_at' => 1451775316, + ], + 'user-with-old-password-type' => [ + 'id' => 2, + 'uuid' => 'bdc239f0-8a22-518d-8b93-f02d4827c3eb', + 'username' => 'AccWithOldPassword', + 'email' => 'erickskrauch123@yandex.ru', + 'password_hash' => '133c00c463cbd3e491c28cb653ce4718', # 12345678 + 'password_hash_strategy' => 0, + 'password_reset_token' => NULL, + 'auth_key' => 'ltTNae9t34OmnK6l4vT4IeaTk-YWI2Rv', + 'status' => 10, + 'created_at' => 1385225069, + 'updated_at' => 1385225069, + ], +]; diff --git a/tests/codeception/common/unit/models/LoginFormTest.php b/tests/codeception/common/unit/models/LoginFormTest.php deleted file mode 100644 index d4ff526..0000000 --- a/tests/codeception/common/unit/models/LoginFormTest.php +++ /dev/null @@ -1,93 +0,0 @@ - [ - 'user' => [ - 'class' => 'yii\web\User', - 'identityClass' => 'common\models\User', - ], - ], - ]); - } - - protected function tearDown() - { - Yii::$app->user->logout(); - parent::tearDown(); - } - - public function testLoginNoUser() - { - $model = new LoginForm([ - 'username' => 'not_existing_username', - 'password' => 'not_existing_password', - ]); - - $this->specify('user should not be able to login, when there is no identity', function () use ($model) { - expect('model should not login user', $model->login())->false(); - expect('user should not be logged in', Yii::$app->user->isGuest)->true(); - }); - } - - public function testLoginWrongPassword() - { - $model = new LoginForm([ - 'username' => 'bayer.hudson', - 'password' => 'wrong_password', - ]); - - $this->specify('user should not be able to login with wrong password', function () use ($model) { - expect('model should not login user', $model->login())->false(); - expect('error message should be set', $model->errors)->hasKey('password'); - expect('user should not be logged in', Yii::$app->user->isGuest)->true(); - }); - } - - public function testLoginCorrect() - { - - $model = new LoginForm([ - 'username' => 'bayer.hudson', - 'password' => 'password_0', - ]); - - $this->specify('user should be able to login with correct credentials', function () use ($model) { - expect('model should login user', $model->login())->true(); - expect('error message should not be set', $model->errors)->hasntKey('password'); - expect('user should be logged in', Yii::$app->user->isGuest)->false(); - }); - } - - /** - * @inheritdoc - */ - public function fixtures() - { - return [ - 'user' => [ - 'class' => UserFixture::className(), - 'dataFile' => '@tests/codeception/common/unit/fixtures/data/models/user.php' - ], - ]; - } -} diff --git a/tests/codeception/config/acceptance.php b/tests/codeception/config/acceptance.php deleted file mode 100644 index 9318da5..0000000 --- a/tests/codeception/config/acceptance.php +++ /dev/null @@ -1,7 +0,0 @@ -