Merge branch 'develop'

This commit is contained in:
ErickSkrauch 2016-11-20 17:19:17 +03:00
commit b2e3772c41
105 changed files with 1434 additions and 1384 deletions

72
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,72 @@
stages:
- test
- build
- release
variables:
CONTAINER_IMAGE: registry.ely.by/elyby/accounts
test:backend:
image: jonaskello/docker-and-compose:1.12.1-1.8.0
services:
- docker:1.12.1-dind
stage: test
before_script:
- docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by
- echo "$SSH_PRIVATE_KEY" > id_rsa
- docker-compose -f tests/docker-compose.yml build --pull testphp
after_script:
- docker-compose -f tests/docker-compose.yml down -v
script:
- docker-compose -f tests/docker-compose.yml run --rm testphp ./vendor/bin/codecept run -c tests
test:frontend:
image: node:5.12
stage: test
cache:
paths:
- frontend/node_modules
script:
- cd frontend
- npm i --silent
- npm run test
build:production:
image: docker:latest
stage: build
before_script:
- docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by
- echo "$SSH_PRIVATE_KEY" > id_rsa
script:
- export IMAGE_NAME="$CONTAINER_IMAGE:latest"
- docker build --pull -t $IMAGE_NAME .
only:
- develop
- tags
release:latest:
image: docker:latest
stage: release
variables:
GIT_STRATEGY: none
before_script:
- docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by
script:
- docker push $CONTAINER_IMAGE:latest
only:
- develop
- tags
release:tag:
image: docker:latest
stage: release
variables:
GIT_STRATEGY: none
before_script:
- docker login -u gitlab-ci -p $CI_BUILD_TOKEN registry.ely.by
script:
- export IMAGE_NAME="$CONTAINER_IMAGE:$CI_BUILD_TAG"
- docker tag $CONTAINER_IMAGE:latest $IMAGE_NAME
- docker push $IMAGE_NAME
only:
- tags

View File

@ -1,112 +1,15 @@
FROM php:7.0-fpm
FROM registry.ely.by/elyby/accounts-php:1.0.0
ENV PATH $PATH:/root/.composer/vendor/bin
# Сначала настраиваем всё, что нам нужно на системном уровне
RUN apt-get update \
&& apt-get -y install \
git \
g++ \
libicu-dev \
libmcrypt-dev \
zlib1g-dev \
bzip2 \
libbz2-dev \
liblzma-dev \
xz-utils \
openssh-server \
supervisor \
--no-install-recommends \
--force-yes \
# PHP расширения
&& docker-php-ext-install intl \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install mbstring \
&& docker-php-ext-install mcrypt \
&& docker-php-ext-install zip \
&& docker-php-ext-install bcmath \
&& docker-php-ext-install opcache \
&& apt-get purge -y g++ \
&& apt-get autoremove -y \
&& rm -r /var/lib/apt/lists/* \
# Отключаем сброс глобальных env переменных внутри PHP
&& echo "\nclear_env = no" >> /usr/local/etc/php-fpm.conf \
# Фикс прав на запись для расшаренных папок
&& usermod -u 1000 www-data
# Копипаста из https://github.com/nodejs/docker-node/blob/50b56d39a236fd519eda2231757aa2173e270807/5.12/Dockerfile
# gpg keys listed at https://github.com/nodejs/node
RUN set -ex \
&& for key in \
9554F04D7259F04124DE6B476D5A82AC7E37093B \
94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 \
FD3A5288F042B6850C66B31F09FE44734EB7990E \
71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
B9AE9905FFD7803F25714661B63B535A4C206CA9 \
C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
; do \
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
done
ENV NPM_CONFIG_LOGLEVEL info
ENV NODE_VERSION 5.12.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
ENV COMPOSER_NO_INTERACTION 1
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_DISCARD_CHANGES true
# Composer и его глобальные зависимости
RUN curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/local/bin/composer.phar \
&& echo '{"github-oauth": {"github.com": "***REMOVED***"}}' > ~/.composer/auth.json \
&& composer.phar global require --no-progress "hirak/prestissimo:>=0.3.4"
COPY ./docker/php/composer.sh /usr/local/bin/composer
# Конфиг для php
COPY ./docker/php/php.ini /usr/local/etc/php/
# Конфиг для supervisor
COPY ./docker/php/supervisord.conf /etc/supervisord.conf
# wait-for-it
COPY ./docker/wait-for-it.sh /usr/local/bin/wait-for-it
# Наша кавайная точка входа
COPY ./docker/php/entrypoint.sh /usr/local/bin/
RUN chmod a+x /usr/local/bin/composer \
&& chmod a+x /usr/local/bin/entrypoint.sh \
&& chmod a+x /usr/local/bin/wait-for-it \
&& ln -s /usr/local/bin/entrypoint.sh / \
&& mkdir /root/.ssh
COPY id_rsa /root/.ssh
COPY id_rsa /root/.ssh/id_rsa
# Включаем поддержку ssh
RUN eval $(ssh-agent -s) \
RUN chmod 400 ~/.ssh/id_rsa \
&& eval $(ssh-agent -s) \
&& ssh-add /root/.ssh/id_rsa \
&& touch /root/.ssh/known_hosts \
&& ssh-keyscan gitlab.com >> /root/.ssh/known_hosts
WORKDIR /var/www/html
# Копируем composer.json в родительскую директорию, которая не будет синкатся с хостом через
# Копируем composer.json в родительскую директорию, которая не будет синкаться с хостом через
# volume на dev окружении. В entrypoint эта папка будет скопирована обратно.
COPY ./composer.json /var/www/composer.json
@ -128,6 +31,9 @@ RUN cd ../frontend \
&& npm install \
&& cd -
# Удаляем ключи из production контейнера на всякий случай
RUN rm -rf /root/.ssh
# Наконец переносим все сорцы внутрь контейнера
COPY . /var/www/html
@ -141,9 +47,3 @@ RUN mkdir -p api/runtime api/web/assets console/runtime \
# Копируем билд наружу, чтобы его не затёрло volume в dev режиме
&& cp -r ./dist /var/www/dist \
&& cd -
# Экспозим всё под /var/www/html для дальнейшей связки с nginx
VOLUME ["/var/www/html"]
ENTRYPOINT ["entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf"]

View File

@ -1,125 +1,15 @@
FROM php:7.0-fpm
FROM registry.ely.by/elyby/accounts-php:1.0.0-dev
ENV PATH $PATH:/root/.composer/vendor/bin
# Сначала настраиваем всё, что нам нужно на системном уровне
RUN apt-get update \
&& apt-get -y install \
git \
g++ \
libicu-dev \
libmcrypt-dev \
zlib1g-dev \
bzip2 \
libbz2-dev \
liblzma-dev \
xz-utils \
openssh-server \
supervisor \
--no-install-recommends \
--force-yes \
# PHP расширения
&& docker-php-ext-install intl \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install mbstring \
&& docker-php-ext-install mcrypt \
&& docker-php-ext-install zip \
&& docker-php-ext-install bcmath \
&& docker-php-ext-install opcache \
&& apt-get purge -y g++ \
&& apt-get autoremove -y \
&& rm -r /var/lib/apt/lists/* \
# Отключаем сброс глобальных env переменных внутри PHP
&& echo "\nclear_env = no" >> /usr/local/etc/php-fpm.conf \
# Фикс прав на запись для расшаренных папок
&& usermod -u 1000 www-data
# Копипаста из https://github.com/nodejs/docker-node/blob/50b56d39a236fd519eda2231757aa2173e270807/5.12/Dockerfile
# gpg keys listed at https://github.com/nodejs/node
RUN set -ex \
&& for key in \
9554F04D7259F04124DE6B476D5A82AC7E37093B \
94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 \
FD3A5288F042B6850C66B31F09FE44734EB7990E \
71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
B9AE9905FFD7803F25714661B63B535A4C206CA9 \
C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
; do \
gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
done
ENV NPM_CONFIG_LOGLEVEL info
ENV NODE_VERSION 5.12.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
# Поставим xdebug отдельно, т.к. потом его потенциально придётся отсюда убирать
RUN yes | pecl install xdebug \
&& echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.default_enable=1" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.remote_enable=1" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.remote_handler=dbgp" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.remote_mode=req" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.remote_autostart=1" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.remote_port=9000" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.remote_connect_back=0" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.cli_color=1" >> /usr/local/etc/php/conf.d/xdebug.ini \
&& echo "xdebug.var_display_max_depth=10" >> /usr/local/etc/php/conf.d/xdebug.ini
ENV COMPOSER_NO_INTERACTION 1
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_DISCARD_CHANGES true
# Composer и его глобальные зависимости
RUN curl -sS https://getcomposer.org/installer | php \
&& mv composer.phar /usr/local/bin/composer.phar \
&& echo '{"github-oauth": {"github.com": "***REMOVED***"}}' > ~/.composer/auth.json \
&& composer.phar global require --no-progress "hirak/prestissimo:>=0.3.4"
COPY ./docker/php/composer.sh /usr/local/bin/composer
# Конфиг для php
COPY ./docker/php/php.ini /usr/local/etc/php/
# Конфиг для supervisor
COPY ./docker/php/supervisord.conf /etc/supervisord.conf
# wait-for-it
COPY ./docker/wait-for-it.sh /usr/local/bin/wait-for-it
# Наша кавайная точка входа
COPY ./docker/php/entrypoint.sh /usr/local/bin/
RUN chmod a+x /usr/local/bin/composer \
&& chmod a+x /usr/local/bin/entrypoint.sh \
&& chmod a+x /usr/local/bin/wait-for-it \
&& ln -s /usr/local/bin/entrypoint.sh / \
&& mkdir /root/.ssh
COPY id_rsa /root/.ssh
COPY id_rsa /root/.ssh/id_rsa
# Включаем поддержку ssh
RUN eval $(ssh-agent -s) \
RUN chmod 400 ~/.ssh/id_rsa \
&& eval $(ssh-agent -s) \
&& ssh-add /root/.ssh/id_rsa \
&& touch /root/.ssh/known_hosts \
&& ssh-keyscan gitlab.com >> /root/.ssh/known_hosts
WORKDIR /var/www/html
# Копируем composer.json в родительскую директорию, которая не будет синкатся с хостом через
# Копируем composer.json в родительскую директорию, которая не будет синкаться с хостом через
# volume на dev окружении. В entrypoint эта папка будет скопирована обратно.
COPY ./composer.json /var/www/composer.json
@ -154,9 +44,3 @@ RUN mkdir -p api/runtime api/web/assets console/runtime \
# Копируем билд наружу, чтобы его не затёрло volume в dev режиме
&& cp -r ./dist /var/www/dist \
&& cd -
# Экспозим всё под /var/www/html для дальнейшей связки с nginx
VOLUME ["/var/www/html"]
ENTRYPOINT ["entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisord.conf"]

View File

@ -6,9 +6,12 @@
[docker](https://docs.docker.com/engine/installation/) и его
[docker-compose](https://docs.docker.com/compose/install/).
Кроме того, нужно установить, настроить и запустить [nginx-proxy](https://gitlab.com/elyby/nginx-proxy)
За тем нужно установить, настроить и запустить [nginx-proxy](https://gitlab.com/elyby/nginx-proxy)
контейнер. Это делается один раз в рамках системы и в дальнейшем используется и для других проектов.
Также необходимо иметь доступ к `registry.ely.by`. Для этого выполнить команду `docker login registry.ely.by`,
ввести свой логин и пароль. Если доступа нету, то нужно его попросить у кого-нибудь, кто может его выдать.
За тем сливаем репозиторий:
```sh

View File

@ -3,6 +3,8 @@ namespace api\components\User;
use api\models\AccountIdentity;
use common\models\AccountSession;
use DateInterval;
use DateTime;
use Emarref\Jwt\Algorithm\AlgorithmInterface;
use Emarref\Jwt\Algorithm\Hs256;
use Emarref\Jwt\Claim;
@ -33,7 +35,9 @@ class Component extends YiiUserComponent {
public $secret;
public $expirationTimeout = 3600; // 1h
public $expirationTimeout = 'PT1H';
public $sessionTimeout = 'P7D';
public function init() {
parent::init();
@ -73,7 +77,7 @@ class Component extends YiiUserComponent {
$session = null;
// Если мы не сохраняем сессию, то токен должен жить подольше, чтобы
// не прогорала сессия во время работы с аккаунтом
$token->addClaim(new Claim\Expiration(time() + 60 * 60 * 24 * 7));
$token->addClaim(new Claim\Expiration((new DateTime())->add(new DateInterval($this->sessionTimeout))));
}
$jwt = $this->serializeToken($token);
@ -135,8 +139,8 @@ class Component extends YiiUserComponent {
* - Юзер не авторизован
* - Почему-то нет заголовка с токеном
* - Во время проверки токена возникла ошибка, что привело к исключению
* - В токене не найдено ключа сессии. Такое возможно, если юзер выбрал "не запоминать меня" или просто старые
* токены, без поддержки сохранения используемой сессии
* - В токене не найдено ключа сессии. Такое возможно, если юзер выбрал "не запоминать меня"
* или просто старые токены, без поддержки сохранения используемой сессии
*
* @return AccountSession|null
*/
@ -187,14 +191,14 @@ class Component extends YiiUserComponent {
* @return Claim\AbstractClaim[]
*/
protected function getClaims(IdentityInterface $identity) {
$currentTime = time();
$currentTime = new DateTime();
$hostInfo = Yii::$app->request->hostInfo;
return [
new Claim\Audience($hostInfo),
new Claim\Issuer($hostInfo),
new Claim\IssuedAt($currentTime),
new Claim\Expiration($currentTime + $this->expirationTimeout),
new Claim\Expiration($currentTime->add(new DateInterval($this->expirationTimeout))),
new Claim\JwtId($identity->getId()),
];
}

View File

@ -2,6 +2,8 @@
namespace api\components\User;
use common\models\AccountSession;
use DateInterval;
use DateTime;
use Yii;
use yii\web\IdentityInterface;
@ -45,9 +47,13 @@ class LoginResult {
public function getAsResponse() {
/** @var Component $component */
$component = Yii::$app->user;
$now = new DateTime();
$expiresIn = (clone $now)->add(new DateInterval($component->expirationTimeout));
$response = [
'access_token' => $this->getJwt(),
'expires_in' => $component->expirationTimeout,
'expires_in' => $expiresIn->getTimestamp() - $now->getTimestamp(),
];
$session = $this->getSession();

View File

@ -1,6 +1,8 @@
<?php
namespace api\components\User;
use DateInterval;
use DateTime;
use Yii;
use yii\web\IdentityInterface;
@ -33,9 +35,12 @@ class RenewResult {
/** @var Component $component */
$component = Yii::$app->user;
$now = new DateTime();
$expiresIn = (clone $now)->add(new DateInterval($component->expirationTimeout));
return [
'access_token' => $this->getJwt(),
'expires_in' => $component->expirationTimeout,
'expires_in' => $expiresIn->getTimestamp() - $now->getTimestamp(),
];
}

View File

@ -12,8 +12,5 @@ return [
'class' => yii\debug\Module::class,
'allowedIPs' => ['*'],
],
'gii' => [
'class' => yii\gii\Module::class,
],
],
];

View File

@ -34,12 +34,14 @@ return [
[
'class' => yii\log\FileTarget::class,
'levels' => ['error', 'info'],
'logVars' => [],
'categories' => ['legacy-authserver'],
'logFile' => '@runtime/logs/authserver.log',
],
[
'class' => yii\log\FileTarget::class,
'levels' => ['error', 'info'],
'logVars' => [],
'categories' => ['session'],
'logFile' => '@runtime/logs/session.log',
],

View File

@ -65,7 +65,6 @@ class AccountsController extends Controller {
'username' => $account->username,
'email' => $account->email,
'lang' => $account->lang,
'shouldChangePassword' => $account->password_hash_strategy === Account::PASS_HASH_STRATEGY_OLD_ELY,
'isActive' => $account->status === Account::STATUS_ACTIVE,
'passwordChangedAt' => $account->password_changed_at,
'hasMojangUsernameCollision' => $account->hasMojangUsernameCollision(),

View File

@ -4,6 +4,7 @@ namespace api\controllers;
use api\filters\ActiveUserRule;
use common\components\oauth\Exception\AcceptRequiredException;
use common\components\oauth\Exception\AccessDeniedException;
use common\models\Account;
use common\models\OauthClient;
use common\models\OauthScope;
use League\OAuth2\Server\Exception\OAuthException;
@ -108,7 +109,7 @@ class OauthController extends Controller {
/** @var \common\models\OauthClient $clientModel */
$clientModel = OauthClient::findOne($client->getId());
if (!$account->canAutoApprove($clientModel, $authParams['scopes'])) {
if (!$this->canAutoApprove($account, $clientModel, $authParams)) {
$isAccept = Yii::$app->request->post('accept');
if ($isAccept === null) {
throw new AcceptRequiredException();
@ -197,6 +198,36 @@ class OauthController extends Controller {
$this->getServer()->addGrantType(new RefreshTokenGrant());
}
/**
* Метод проверяет, может ли текущий пользователь быть автоматически авторизован
* для указанного клиента без запроса доступа к необходимому списку прав
*
* @param Account $account
* @param OauthClient $client
* @param array $oauthParams
*
* @return bool
*/
private function canAutoApprove(Account $account, OauthClient $client, array $oauthParams) : bool {
if ($client->is_trusted) {
return true;
}
/** @var \League\OAuth2\Server\Entity\ScopeEntity[] $scopes */
$scopes = $oauthParams['scopes'];
/** @var \common\models\OauthSession|null $session */
$session = $account->getOauthSessions()->andWhere(['client_id' => $client->id])->one();
if ($session !== null) {
$existScopes = $session->getScopes()->members();
if (empty(array_diff(array_keys($scopes), $existScopes))) {
return true;
}
}
return false;
}
/**
* @param array $queryParams
* @param OauthClient $clientModel

View File

@ -3,6 +3,7 @@ namespace api\models\authentication;
use api\models\AccountIdentity;
use api\models\base\KeyConfirmationForm;
use api\models\profile\ChangeUsernameForm;
use common\models\Account;
use common\models\EmailActivation;
use Yii;
@ -16,7 +17,7 @@ class ConfirmEmailForm extends KeyConfirmationForm {
}
$confirmModel = $this->getActivationCodeModel();
if ($confirmModel->type != EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION) {
if ($confirmModel->type !== EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION) {
$confirmModel->delete();
// TODO: вот где-то здесь нужно ещё попутно сгенерировать соответствующую ошибку
return false;
@ -34,6 +35,9 @@ class ConfirmEmailForm extends KeyConfirmationForm {
throw new ErrorException('Unable activate user account.');
}
$changeUsernameForm = new ChangeUsernameForm();
$changeUsernameForm->createEventTask($account->id, $account->username, null);
$transaction->commit();
} catch (ErrorException $e) {
$transaction->rollBack();

View File

@ -77,7 +77,7 @@ class LoginForm extends ApiForm {
}
$account = $this->getAccount();
if ($account->password_hash_strategy === Account::PASS_HASH_STRATEGY_OLD_ELY) {
if ($account->password_hash_strategy !== Account::PASS_HASH_STRATEGY_YII2) {
$account->setPassword($this->password);
$account->save();
}

View File

@ -5,7 +5,7 @@ use api\models\AccountIdentity;
use api\models\base\KeyConfirmationForm;
use common\helpers\Error as E;
use common\models\EmailActivation;
use common\validators\PasswordValidate;
use common\validators\PasswordValidator;
use Yii;
use yii\base\ErrorException;
@ -19,7 +19,7 @@ class RecoverPasswordForm extends KeyConfirmationForm {
return array_merge(parent::rules(), [
['newPassword', 'required', 'message' => E::NEW_PASSWORD_REQUIRED],
['newRePassword', 'required', 'message' => E::NEW_RE_PASSWORD_REQUIRED],
['newPassword', PasswordValidate::class],
['newPassword', PasswordValidator::class],
['newRePassword', 'validatePasswordAndRePasswordMatch'],
]);
}

View File

@ -4,19 +4,21 @@ namespace api\models\authentication;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\base\ApiForm;
use common\helpers\Error as E;
use api\models\profile\ChangeUsernameForm;
use common\components\UserFriendlyRandomKey;
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\PasswordValidator;
use common\validators\UsernameValidator;
use Exception;
use Ramsey\Uuid\Uuid;
use Yii;
use yii\base\ErrorException;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use const common\LATEST_RULES_VERSION;
class RegistrationForm extends ApiForm {
@ -40,34 +42,19 @@ class RegistrationForm extends ApiForm {
['captcha', ReCaptchaValidator::class],
['rulesAgreement', 'required', 'message' => E::RULES_AGREEMENT_REQUIRED],
['username', 'validateUsername', 'skipOnEmpty' => false],
['email', 'validateEmail', 'skipOnEmpty' => false],
['username', UsernameValidator::class],
['email', EmailValidator::class],
['password', 'required', 'message' => E::PASSWORD_REQUIRED],
['rePassword', 'required', 'message' => E::RE_PASSWORD_REQUIRED],
['password', PasswordValidate::class],
['password', PasswordValidator::class],
['rePassword', 'validatePasswordAndRePasswordMatch'],
['lang', LanguageValidator::class],
['lang', 'default', 'value' => 'en'],
];
}
public function validateUsername() {
$account = new Account();
$account->username = $this->username;
if (!$account->validate(['username'])) {
$this->addErrors($account->getErrors());
}
}
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) {
@ -81,7 +68,7 @@ class RegistrationForm extends ApiForm {
* @throws Exception
*/
public function signup() {
if (!$this->validate()) {
if (!$this->validate() && !$this->canContinue($this->getFirstErrors())) {
return null;
}
@ -118,9 +105,6 @@ class RegistrationForm extends ApiForm {
$this->sendMail($emailActivation, $account);
$changeUsernameForm = new ChangeUsernameForm();
$changeUsernameForm->createEventTask($account->id, $account->username, null);
$transaction->commit();
} catch (Exception $e) {
$transaction->rollBack();
@ -161,4 +145,43 @@ class RegistrationForm extends ApiForm {
}
}
/**
* Метод проверяет, можно ли занять указанный при регистрации ник или e-mail. Так случается,
* что пользователи вводят неправильный e-mail или ник, после замечают это и пытаются вновь
* выпонить регистрацию. Мы не будем им мешать и просто удаляем существующие недозарегистрированные
* аккаунты, позволяя им зарегистрироваться.
*
* @param array $errors массив, где ключ - это поле, а значение - первая ошибка из нашего
* стандартного словаря ошибок
*
* @return bool
*/
protected function canContinue(array $errors) : bool {
if (ArrayHelper::getValue($errors, 'username') === E::USERNAME_NOT_AVAILABLE) {
$duplicatedUsername = Account::findOne([
'username' => $this->username,
'status' => Account::STATUS_REGISTERED,
]);
if ($duplicatedUsername !== null) {
$duplicatedUsername->delete();
unset($errors['username']);
}
}
if (ArrayHelper::getValue($errors, 'email') === E::EMAIL_NOT_AVAILABLE) {
$duplicatedEmail = Account::findOne([
'email' => $this->email,
'status' => Account::STATUS_REGISTERED,
]);
if ($duplicatedEmail !== null) {
$duplicatedEmail->delete();
unset($errors['email']);
}
}
return empty($errors);
}
}

View File

@ -21,7 +21,7 @@ class AcceptRulesForm extends ApiForm {
public function agreeWithLatestRules() : bool {
$account = $this->getAccount();
$account->rules_agreement_version = LATEST_RULES_VERSION;
if (!$account->save(false)) {
if (!$account->save()) {
throw new ErrorException('Cannot set user rules version');
}

View File

@ -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() {

View File

@ -5,7 +5,7 @@ use api\models\base\ApiForm;
use api\validators\PasswordRequiredValidator;
use common\helpers\Error as E;
use common\models\Account;
use common\validators\PasswordValidate;
use common\validators\PasswordValidator;
use Yii;
use yii\base\ErrorException;
use yii\helpers\ArrayHelper;
@ -37,7 +37,7 @@ class ChangePasswordForm extends ApiForm {
return ArrayHelper::merge(parent::rules(), [
['newPassword', 'required', 'message' => E::NEW_PASSWORD_REQUIRED],
['newRePassword', 'required', 'message' => E::NEW_RE_PASSWORD_REQUIRED],
['newPassword', PasswordValidate::class],
['newPassword', PasswordValidator::class],
['newRePassword', 'validatePasswordAndRePasswordMatch'],
['logoutAll', 'boolean'],
['password', PasswordRequiredValidator::class, 'account' => $this->_account],

View File

@ -4,11 +4,10 @@ namespace api\models\profile;
use api\models\AccountIdentity;
use api\models\base\ApiForm;
use api\validators\PasswordRequiredValidator;
use common\helpers\Error;
use common\helpers\Amqp;
use common\models\Account;
use common\models\amqp\UsernameChanged;
use common\models\UsernameHistory;
use common\validators\UsernameValidator;
use Exception;
use PhpAmqpLib\Message\AMQPMessage;
use Yii;
@ -22,20 +21,13 @@ class ChangeUsernameForm extends ApiForm {
public function rules() {
return [
['username', 'required', 'message' => Error::USERNAME_REQUIRED],
['username', 'validateUsername'],
['username', UsernameValidator::class, 'accountCallback' => function() {
return $this->getAccount()->id;
}],
['password', PasswordRequiredValidator::class],
];
}
public function validateUsername($attribute) {
$account = new Account();
$account->username = $this->$attribute;
if (!$account->validate(['username'])) {
$this->addErrors($account->getErrors());
}
}
public function change() {
if (!$this->validate()) {
return false;

View File

@ -32,7 +32,7 @@ class ApiController extends Controller {
// ник пользователь не сменил его на нечто иное
$account = null;
if ($record !== null) {
if ($record->findNext($at) !== null || $record->account->username === $record->username) {
if ($record->account->username === $record->username || $record->findNext($at) !== null) {
$account = $record->account;
}
}

View File

@ -90,6 +90,7 @@ class JoinForm extends Model {
}
$validator = new UuidValidator();
$validator->allowNil = false;
$validator->validateAttribute($this, $attribute);
if ($this->hasErrors($attribute)) {

View File

@ -1,7 +1,7 @@
<?php
require __DIR__ . '/../../vendor/autoload.php';
defined('YII_DEBUG') or define('YII_DEBUG', (boolean)getenv('YII_DEBUG'));
defined('YII_DEBUG') or define('YII_DEBUG', in_array(getenv('YII_DEBUG'), ['false', '1']));
defined('YII_ENV') or define('YII_ENV', getenv('YII_ENV'));
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';

View File

@ -4,7 +4,7 @@ namespace common\components\Mojang;
use common\components\Mojang\exceptions\MojangApiException;
use common\components\Mojang\exceptions\NoContentException;
use common\components\Mojang\response\UsernameToUUIDResponse;
use GuzzleHttp\Client as GuzzleClient;
use Yii;
class Api {
@ -23,7 +23,7 @@ class Api {
$query['atTime'] = $atTime;
}
$response = $this->createClient()->get($this->buildUsernameToUUIDRoute($username), $query);
$response = $this->getClient()->get($this->buildUsernameToUUIDRoute($username), $query);
if ($response->getStatusCode() === 204) {
throw new NoContentException('Username not found');
} elseif ($response->getStatusCode() !== 200) {
@ -40,8 +40,11 @@ class Api {
return $responseObj;
}
protected function createClient() {
return new GuzzleClient();
/**
* @return \GuzzleHttp\Client
*/
protected function getClient() {
return Yii::$app->guzzle;
}
protected function buildUsernameToUUIDRoute($username) {

View File

@ -9,7 +9,7 @@ class UserFriendlyRandomKey {
$numChars = strlen($chars);
$key = '';
for ($i = 0; $i < $length; $i++) {
$key .= substr($chars, rand(1, $numChars) - 1, 1);
$key .= $chars[random_int(0, $numChars - 1)];
}
return $key;

View File

@ -54,7 +54,7 @@ class AuthCodeStorage extends AbstractStorage implements AuthCodeInterface {
* @inheritdoc
*/
public function getScopes(OriginalAuthCodeEntity $token) {
$result = (new Set($this->dataTable, $token->getId(), 'scopes'));
$result = new Set($this->dataTable, $token->getId(), 'scopes');
$response = [];
foreach ($result as $scope) {
// TODO: нужно проверить все выданные скоупы на их существование

View File

@ -57,11 +57,10 @@ class AccessTokenStorage extends AbstractStorage implements AccessTokenInterface
* @inheritdoc
*/
public function create($token, $expireTime, $sessionId) {
$model = new OauthAccessToken([
'access_token' => $token,
'expire_time' => $expireTime,
'session_id' => $sessionId,
]);
$model = new OauthAccessToken();
$model->access_token = $token;
$model->expire_time = $expireTime;
$model->session_id = $sessionId;
if (!$model->save()) {
throw new Exception('Cannot save ' . OauthAccessToken::class . ' model.');

View File

@ -4,6 +4,7 @@ namespace common\components\oauth\Storage\Yii2;
use common\components\oauth\Entity\AuthCodeEntity;
use common\components\oauth\Entity\SessionEntity;
use common\models\OauthSession;
use ErrorException;
use League\OAuth2\Server\Entity\AccessTokenEntity as OriginalAccessTokenEntity;
use League\OAuth2\Server\Entity\AuthCodeEntity as OriginalAuthCodeEntity;
use League\OAuth2\Server\Entity\ScopeEntity;
@ -67,7 +68,7 @@ class SessionStorage extends AbstractStorage implements SessionInterface {
*/
public function getByAuthCode(OriginalAuthCodeEntity $authCode) {
if (!$authCode instanceof AuthCodeEntity) {
throw new \ErrorException('This module assumes that $authCode typeof ' . AuthCodeEntity::class);
throw new ErrorException('This module assumes that $authCode typeof ' . AuthCodeEntity::class);
}
return $this->getSession($authCode->getSessionId());
@ -99,11 +100,11 @@ class SessionStorage extends AbstractStorage implements SessionInterface {
])->scalar();
if ($sessionId === false) {
$model = new OauthSession([
'client_id' => $clientId,
'owner_type' => $ownerType,
'owner_id' => $ownerId,
]);
$model = new OauthSession();
$model->client_id = $clientId;
$model->owner_type = $ownerType;
$model->owner_id = $ownerId;
$model->client_redirect_uri = $clientRedirectUri;
if (!$model->save()) {
throw new Exception('Cannot save ' . OauthSession::class . ' model.');

View File

@ -2,10 +2,9 @@
namespace common\components\oauth\Util\KeyAlgorithm;
use League\OAuth2\Server\Util\KeyAlgorithm\DefaultAlgorithm;
use League\OAuth2\Server\Util\KeyAlgorithm\KeyAlgorithmInterface;
use Ramsey\Uuid\Uuid;
class UuidAlgorithm extends DefaultAlgorithm implements KeyAlgorithmInterface {
class UuidAlgorithm extends DefaultAlgorithm {
/**
* @inheritdoc

View File

@ -2,7 +2,6 @@
namespace common\helpers;
use common\components\RabbitMQ\Helper;
use Yii;
class Amqp extends Helper {

View File

@ -1,9 +1,10 @@
<?php
use yii\helpers\Html;
/**
* @var $this \yii\web\View view component instance
* @var $message \yii\mail\MessageInterface the message being composed
* @var $content string main view render result
*/
/* @var $this \yii\web\View view component instance */
/* @var $message \yii\mail\MessageInterface the message being composed */
/* @var $content string main view render result */
?>
<?php $this->beginPage() ?>
<?php $this->beginBody() ?>

View File

@ -1,10 +1,7 @@
<?php
namespace common\models;
use common\helpers\Error as E;
use common\components\UserPass;
use common\validators\LanguageValidator;
use Ely\Yii2\TempmailValidator;
use Yii;
use yii\base\InvalidConfigException;
use yii\behaviors\TimestampBehavior;
@ -60,31 +57,6 @@ class Account extends ActiveRecord {
];
}
public function rules() {
return [
[['username'], 'filter', 'filter' => 'trim'],
[['username'], 'required', 'message' => E::USERNAME_REQUIRED],
[['username'], 'string', 'min' => 3, 'max' => 21,
'tooShort' => E::USERNAME_TOO_SHORT,
'tooLong' => E::USERNAME_TOO_LONG,
],
[['username'], 'match', 'pattern' => '/^[\p{L}\d-_\.!?#$%^&*()\[\]:;]+$/u',
'message' => E::USERNAME_INVALID,
],
[['username'], 'unique', 'message' => E::USERNAME_NOT_AVAILABLE],
[['email'], 'filter', 'filter' => '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],
[['lang'], LanguageValidator::class],
[['lang'], 'default', 'value' => 'en'],
];
}
/**
* Validates password
*
@ -138,34 +110,6 @@ class Account extends ActiveRecord {
return $this->hasMany(AccountSession::class, ['account_id' => 'id']);
}
/**
* Метод проверяет, может ли текущий пользователь быть автоматически авторизован
* для указанного клиента без запроса доступа к необходимому списку прав
*
* @param OauthClient $client
* @param \League\OAuth2\Server\Entity\ScopeEntity[] $scopes
*
* TODO: этому методу здесь не место.
*
* @return bool
*/
public function canAutoApprove(OauthClient $client, array $scopes = []) : bool {
if ($client->is_trusted) {
return true;
}
/** @var OauthSession|null $session */
$session = $this->getOauthSessions()->andWhere(['client_id' => $client->id])->one();
if ($session !== null) {
$existScopes = $session->getScopes()->members();
if (empty(array_diff(array_keys($scopes), $existScopes))) {
return true;
}
}
return false;
}
/**
* Выполняет проверку, принадлежит ли этому нику аккаунт у Mojang
*

View File

@ -5,14 +5,15 @@ use common\components\redis\Set;
use yii\db\ActiveRecord;
/**
* This is the model class for table "oauth_access_tokens".
*
* Поля:
* @property string $access_token
* @property string $session_id
* @property integer $expire_time
*
* Геттеры:
* @property Set $scopes
*
* Отношения:
* @property OauthSession $session
*/
class OauthAccessToken extends ActiveRecord {
@ -26,7 +27,7 @@ class OauthAccessToken extends ActiveRecord {
}
public function getScopes() {
return new Set($this->getDb()->getSchema()->getRawTableName($this->tableName()), $this->access_token, 'scopes');
return new Set(static::getDb()->getSchema()->getRawTableName(static::tableName()), $this->access_token, 'scopes');
}
public function beforeDelete() {

View File

@ -1,7 +1,6 @@
<?php
namespace common\models;
use Yii;
use yii\db\ActiveRecord;
/**

View File

@ -2,7 +2,6 @@
namespace common\models;
use common\components\redis\Set;
use Yii;
use yii\db\ActiveRecord;
/**
@ -38,7 +37,7 @@ class OauthSession extends ActiveRecord {
}
public function getScopes() {
return new Set($this->getDb()->getSchema()->getRawTableName($this->tableName()), $this->id, 'scopes');
return new Set(static::getDb()->getSchema()->getRawTableName(static::tableName()), $this->id, 'scopes');
}
public function beforeDelete() {

View File

@ -2,6 +2,8 @@
namespace common\models;
use common\components\SkinSystem\Api as SkinSystemApi;
use DateInterval;
use DateTime;
class Textures {
@ -38,7 +40,7 @@ class Textures {
public function getTexturesValue($encrypted = true) {
$array = [
'timestamp' => time() + 60 * 60 * 24 * 2,
'timestamp' => (new DateTime())->add(new DateInterval('P2D'))->getTimestamp(),
'profileId' => str_replace('-', '', $this->account->uuid),
'profileName' => $this->account->username,
'textures' => $this->getTextures(),
@ -51,7 +53,7 @@ class Textures {
if (!$encrypted) {
return $array;
} else {
return $this->encrypt($array);
return static::encrypt($array);
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace common\validators;
use common\helpers\Error as E;
use common\models\Account;
use Ely\Yii2\TempmailValidator;
use yii\base\Model;
use yii\db\QueryInterface;
use yii\validators;
use yii\validators\Validator;
class EmailValidator extends Validator {
/**
* @var \Closure функция должна возвращать id аккаунта, относительно которого проводится
* текущая валидация. Позволяет пропустить проверку email для текущего аккаунта.
*/
public $accountCallback;
public $skipOnEmpty = false;
public function validateAttribute($model, $attribute) {
$filter = new validators\FilterValidator(['filter' => '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);
}
}

View File

@ -7,7 +7,7 @@ use yii\validators\StringValidator;
/**
* Класс должен реализовывать в себе все критерии валидации пароля пользователя
*/
class PasswordValidate extends StringValidator {
class PasswordValidator extends StringValidator {
public $min = 8;

View File

@ -0,0 +1,59 @@
<?php
namespace common\validators;
use common\helpers\Error as E;
use common\models\Account;
use yii\base\Model;
use yii\db\QueryInterface;
use yii\validators;
use yii\validators\Validator;
class UsernameValidator extends Validator {
/**
* @var \Closure функция должна возвращать id аккаунта, относительно которого проводится
* текущая валидация. Позволяет пропустить проверку ника для текущего аккаунта.
*/
public $accountCallback;
public $skipOnEmpty = false;
public function validateAttribute($model, $attribute) {
$filter = new validators\FilterValidator(['filter' => 'trim']);
$required = new validators\RequiredValidator();
$required->message = E::USERNAME_REQUIRED;
$length = new validators\StringValidator();
$length->min = 3;
$length->max = 21;
$length->tooShort = E::USERNAME_TOO_SHORT;
$length->tooLong = E::USERNAME_TOO_LONG;
$pattern = new validators\RegularExpressionValidator(['pattern' => '/^[\p{L}\d-_\.!$%^&*()\[\]:;]+$/u']);
$pattern->message = E::USERNAME_INVALID;
$unique = new validators\UniqueValidator();
$unique->message = E::USERNAME_NOT_AVAILABLE;
$unique->targetClass = Account::class;
$unique->targetAttribute = 'username';
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($pattern, $model, $attribute) &&
$this->executeValidation($unique, $model, $attribute);
}
protected function executeValidation(Validator $validator, Model $model, string $attribute) {
$validator->validateAttribute($model, $attribute);
return !$model->hasErrors($attribute);
}
}

View File

@ -7,6 +7,8 @@ use yii\validators\Validator;
class UuidValidator extends Validator {
public $allowNil = true;
public $skipOnEmpty = false;
public $message = '{attribute} must be valid uuid';
@ -18,6 +20,10 @@ class UuidValidator extends Validator {
} catch (InvalidArgumentException $e) {
$this->addError($model, $attribute, $this->message, []);
}
if (isset($uuid) && $this->allowNil === false && $uuid === Uuid::NIL) {
$this->addError($model, $attribute, $this->message, []);
}
}
}

View File

@ -24,13 +24,12 @@
"php-amqplib/php-amqplib": "^2.6.2",
"ely/yii2-tempmail-validator": "~1.0.0",
"emarref/jwt": "~1.0.3",
"ely/amqp-controller": "^0.1.2",
"ely/amqp-controller": "dev-master#d7f8cdbc66c45e477c9c7d5d509bc0c1b11fd3ec",
"ely/email-renderer": "dev-master#38a148cd5081147acc31125ddc49966b149f65cf"
},
"require-dev": {
"yiisoft/yii2-codeception": "*",
"yiisoft/yii2-debug": "*",
"yiisoft/yii2-gii": "*",
"yiisoft/yii2-faker": "*",
"flow/jsonpath": "^0.3.1",
"codeception/codeception": "~2.2.4",

View File

@ -3,10 +3,9 @@ namespace console\controllers;
use common\components\Mojang\Api as MojangApi;
use common\components\Mojang\exceptions\NoContentException;
use common\components\RabbitMQ\Component as RabbitMQComponent;
use common\models\amqp\UsernameChanged;
use common\models\MojangUsername;
use console\controllers\base\AmqpController;
use Ely\Amqp\Builder\Configurator;
use GuzzleHttp\Exception\RequestException;
class AccountQueueController extends AmqpController {
@ -15,19 +14,10 @@ class AccountQueueController extends AmqpController {
return 'events';
}
public function getQueueName() {
return 'accounts-events';
}
protected function getExchangeDeclareArgs() {
return array_replace(parent::getExchangeDeclareArgs(), [
1 => RabbitMQComponent::TYPE_TOPIC, // type -> topic
3 => true, // durable -> true
]);
}
protected function getQueueBindArgs($exchangeName, $queueName) {
return [$queueName, $exchangeName, 'accounts.#'];
public function configure(Configurator $configurator) {
$configurator->exchange->topic()->durable();
$configurator->queue->name('accounts-accounts-events')->durable();
$configurator->bind->routingKey('accounts.username-changed');
}
public function getRoutesMap() {
@ -37,7 +27,7 @@ class AccountQueueController extends AmqpController {
}
public function routeUsernameChanged(UsernameChanged $body) {
$mojangApi = new MojangApi();
$mojangApi = $this->createMojangApi();
try {
$response = $mojangApi->usernameToUUID($body->newUsername);
} catch (NoContentException $e) {
@ -68,4 +58,11 @@ class AccountQueueController extends AmqpController {
return true;
}
/**
* @return MojangApi
*/
protected function createMojangApi() : MojangApi {
return new MojangApi();
}
}

View File

@ -1,5 +1,5 @@
<?php
namespace console\controllers\base;
namespace console\controllers;
use Ely\Amqp\ControllerTrait;
use Yii;

View File

@ -0,0 +1,36 @@
<?php
use console\db\Migration;
class m161030_013122_ely_by_admin_app extends Migration {
const APP_NAME = 'ely_admin';
public function safeUp() {
$exists = $this->db->createCommand('
SELECT COUNT(*)
FROM {{%oauth_clients}}
WHERE id = :app_name
LIMIT 1
', [
'app_name' => self::APP_NAME,
])->queryScalar();
if (!$exists) {
$this->insert('{{%oauth_clients}}', [
'id' => self::APP_NAME,
'secret' => 'change_this_on_production',
'name' => 'Admin Ely.by',
'description' => '',
'redirect_uri' => 'http://admin.ely.by/authorization/oauth',
'is_trusted' => 1,
'created_at' => time(),
]);
}
}
public function safeDown() {
$this->delete('{{%oauth_clients}}', ['id' => self::APP_NAME]);
}
}

View File

@ -0,0 +1,15 @@
<?php
use console\db\Migration;
class m161104_150634_accounts_uuid_index extends Migration {
public function safeUp() {
$this->createIndex('uuid', '{{%accounts}}', 'uuid', true);
}
public function safeDown() {
$this->dropColumn('{{%accounts}}', 'uuid');
}
}

View File

@ -1,7 +1,7 @@
version: '2'
services:
app:
build: .
image: registry.ely.by/elyby/accounts:latest
depends_on:
- db
- redis

View File

@ -30,9 +30,9 @@ fi
if [ "$YII_ENV" != "test" ]
then
wait-for-it db:3306 -- "php /var/www/html/yii migrate/up --interactive=0"
wait-for-it db:3306 -s -- "php /var/www/html/yii migrate/up --interactive=0"
else
wait-for-it testdb:3306 -- "php /var/www/html/tests/codeception/bin/yii migrate/up --interactive=0"
wait-for-it testdb:3306 -s -- "php /var/www/html/tests/codeception/bin/yii migrate/up --interactive=0"
fi
exec "$@"

View File

@ -12,6 +12,7 @@ modules:
config:
Yii2:
configFile: '../config/api/functional.php'
cleanup: true
Redis:
host: testredis
port: 6379

View File

@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\functional;
use Codeception\Specify;
use tests\codeception\api\_pages\AccountsRoute;
use tests\codeception\api\FunctionalTester;

View File

@ -26,7 +26,6 @@ class AccountsCurrentCest {
'username' => 'Admin',
'email' => 'admin@ely.by',
'lang' => 'en',
'shouldChangePassword' => false,
'isActive' => true,
'hasMojangUsernameCollision' => false,
'shouldAcceptRules' => false,

View File

@ -1,7 +1,6 @@
<?php
namespace tests\codeception\api\functional;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\_pages\SignupRoute;
use tests\codeception\api\FunctionalTester;
@ -216,6 +215,40 @@ class RegisterCest {
'rulesAgreement' => true,
'lang' => 'ru',
]);
$this->assertSuccessRegistration($I);
}
public function testUserCorrectRegistrationWithReassignUsername(FunctionalTester $I) {
$route = new SignupRoute($I);
$I->wantTo('ensure that signup allow reassign not finished registration username');
$route->register([
'username' => 'howe.garnett',
'email' => 'custom-email@gmail.com',
'password' => 'some_password',
'rePassword' => 'some_password',
'rulesAgreement' => true,
'lang' => 'ru',
]);
$this->assertSuccessRegistration($I);
}
public function testUserCorrectRegistrationWithReassignEmail(FunctionalTester $I) {
$route = new SignupRoute($I);
$I->wantTo('ensure that signup allow reassign not finished registration email');
$route->register([
'username' => 'CustomUsername',
'email' => 'achristiansen@gmail.com',
'password' => 'some_password',
'rePassword' => 'some_password',
'rulesAgreement' => true,
'lang' => 'ru',
]);
$this->assertSuccessRegistration($I);
}
private function assertSuccessRegistration(FunctionalTester $I) {
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson(['success' => true]);

View File

@ -1,4 +1 @@
<?php
new yii\web\Application(require __DIR__ . '/../../config/api/functional.php');
\Codeception\Util\Autoload::registerSuffix('Steps', __DIR__ . DIRECTORY_SEPARATOR);

View File

@ -111,6 +111,21 @@ class JoinCest {
]);
}
public function joinWithNilUuids(FunctionalTester $I) {
$I->wantTo('join to some server with nil accessToken and selectedProfile');
$this->route->join([
'accessToken' => '00000000-0000-0000-0000-000000000000',
'selectedProfile' => 'df936908-b2e1-544d-96f8-2977ec213022',
'serverId' => Uuid::uuid(),
]);
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'error' => 'IllegalArgumentException',
'errorMessage' => 'credentials can not be null.',
]);
}
private function expectSuccessResponse(FunctionalTester $I) {
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();

View File

@ -84,6 +84,17 @@ class JoinLegacyCest {
$I->canSeeResponseContains('Ely.by authorization required');
}
public function joinWithNilUuids(FunctionalTester $I) {
$I->wantTo('join to some server by legacy protocol with nil accessToken and selectedProfile');
$this->route->joinLegacy([
'sessionId' => 'token:00000000-0000-0000-0000-000000000000:00000000-0000-0000-0000-000000000000',
'user' => 'SomeUser',
'serverId' => Uuid::uuid(),
]);
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseContains('credentials can not be null.');
}
private function expectSuccessResponse(FunctionalTester $I) {
$I->seeResponseCodeIs(200);
$I->canSeeResponseEquals('OK');

View File

@ -1 +1,8 @@
class_name: UnitTester
modules:
enabled:
- Yii2:
part: [orm, email, fixtures]
config:
Yii2:
configFile: '../config/api/unit.php'

View File

@ -1,9 +0,0 @@
<?php
namespace tests\codeception\api\unit;
class DbTestCase extends \yii\codeception\DbTestCase {
public $appConfig = '@tests/codeception/config/api/unit.php';
}

View File

@ -1,9 +1,22 @@
<?php
namespace tests\codeception\api\unit;
class TestCase extends \Codeception\Test\Unit {
class TestCase extends \yii\codeception\TestCase {
/**
* @var \tests\codeception\api\UnitTester
*/
protected $tester;
public $appConfig = '@tests/codeception/config/api/unit.php';
/**
* Список фикстур, что будут загружены перед тестом, но после зачистки базы данных
*
* @url http://codeception.com/docs/modules/Yii2#fixtures
*
* @return array
*/
public function _fixtures() {
return [];
}
}

View File

@ -11,7 +11,7 @@ use Emarref\Jwt\Algorithm\AlgorithmInterface;
use Emarref\Jwt\Claim\ClaimInterface;
use Emarref\Jwt\Claim\Expiration;
use Emarref\Jwt\Token;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\AccountSessionFixture;
@ -19,11 +19,7 @@ use Yii;
use yii\web\HeaderCollection;
use yii\web\Request;
/**
* @property AccountFixture $accounts
* @property AccountSessionFixture $sessions
*/
class ComponentTest extends DbTestCase {
class ComponentTest extends TestCase {
use Specify;
use ProtectedCaller;
@ -37,7 +33,7 @@ class ComponentTest extends DbTestCase {
$this->component = new Component($this->getComponentArguments());
}
public function fixtures() {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'sessions' => AccountSessionFixture::class,
@ -62,7 +58,7 @@ class ComponentTest extends DbTestCase {
$this->specify('success get LoginResult object with session value if rememberMe is true', function() {
/** @var AccountIdentity $account */
$account = AccountIdentity::findOne($this->accounts['admin']['id']);
$account = AccountIdentity::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$result = $this->component->login($account, true);
expect($result)->isInstanceOf(LoginResult::class);
expect($result->getSession())->isInstanceOf(AccountSession::class);
@ -82,7 +78,7 @@ class ComponentTest extends DbTestCase {
$userIP = '192.168.0.1';
$this->mockRequest($userIP);
/** @var AccountSession $session */
$session = AccountSession::findOne($this->sessions['admin']['id']);
$session = AccountSession::findOne($this->tester->grabFixture('sessions', 'admin')['id']);
$callTime = time();
$result = $this->component->renew($session);
expect($result)->isInstanceOf(RenewResult::class);
@ -108,7 +104,7 @@ class ComponentTest extends DbTestCase {
public function testGetActiveSession() {
$this->specify('get used account session', function() {
/** @var AccountIdentity $identity */
$identity = AccountIdentity::findOne($this->accounts['admin']['id']);
$identity = AccountIdentity::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$result = $this->component->login($identity, true);
$this->component->logout();
@ -184,6 +180,7 @@ class ComponentTest extends DbTestCase {
}
/**
* @param string $userIP
* @return \PHPUnit_Framework_MockObject_MockObject
*/
private function mockRequest($userIP = '127.0.0.1') {

View File

@ -7,8 +7,8 @@ use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use const common\LATEST_RULES_VERSION;
use yii\base\Action;
use const common\LATEST_RULES_VERSION;
class ActiveUserRuleTest extends TestCase {
use Specify;

View File

@ -7,7 +7,7 @@ use Emarref\Jwt\Claim;
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
use Emarref\Jwt\Jwt;
use Emarref\Jwt\Token;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use tests\codeception\common\fixtures\AccountFixture;
use Yii;
@ -16,11 +16,11 @@ use yii\web\IdentityInterface;
/**
* @property AccountIdentity $accounts
*/
class AccountIdentityTest extends DbTestCase {
class AccountIdentityTest extends TestCase {
use Specify;
use ProtectedCaller;
public function fixtures() {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
];
@ -29,7 +29,7 @@ class AccountIdentityTest extends DbTestCase {
public function testFindIdentityByAccessToken() {
$identity = AccountIdentity::findIdentityByAccessToken($this->generateToken());
$this->assertInstanceOf(IdentityInterface::class, $identity);
$this->assertEquals($this->accounts['admin']['id'], $identity->getId());
$this->assertEquals($this->tester->grabFixture('accounts', 'admin')['id'], $identity->getId());
}
/**
@ -42,7 +42,7 @@ class AccountIdentityTest extends DbTestCase {
$token->addClaim(new Claim\Issuer('http://localhost'));
$token->addClaim(new Claim\IssuedAt(1464593193));
$token->addClaim(new Claim\Expiration(1464596793));
$token->addClaim(new Claim\JwtId($this->accounts['admin']['id']));
$token->addClaim(new Claim\JwtId($this->tester->grabFixture('accounts', 'admin')['id']));
$expiredToken = (new Jwt())->serialize($token, EncryptionFactory::create(Yii::$app->user->getAlgorithm()));
AccountIdentity::findIdentityByAccessToken($expiredToken);
@ -60,7 +60,7 @@ class AccountIdentityTest extends DbTestCase {
/** @var \api\components\User\Component $component */
$component = Yii::$app->user;
/** @var AccountIdentity $account */
$account = AccountIdentity::findOne($this->accounts['admin']['id']);
$account = AccountIdentity::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$token = $this->callProtected($component, 'createToken', $account);

View File

@ -2,76 +2,48 @@
namespace codeception\api\unit\models;
use api\models\FeedbackForm;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\unit\TestCase;
use Yii;
use yii\swiftmailer\Message;
class FeedbackFormTest extends TestCase {
use Specify;
const FILE_NAME = 'testing_message.eml';
public function setUp() {
parent::setUp();
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$mailer->fileTransportCallback = function() {
return self::FILE_NAME;
};
}
protected function tearDown() {
if (file_exists($this->getMessageFile())) {
unlink($this->getMessageFile());
}
parent::tearDown();
}
public function testSendMessage() {
$this->specify('send email', function() {
$model = new FeedbackForm([
$model = new FeedbackForm([
'subject' => 'Тема обращения',
'email' => 'erickskrauch@ely.by',
'message' => 'Привет мир!',
]);
$this->assertTrue($model->sendMessage());
$this->tester->seeEmailIsSent(1, 'message file exists');
}
public function testSendMessageWithEmail() {
/** @var FeedbackForm|\PHPUnit_Framework_MockObject_MockObject $model */
$model = $this->getMockBuilder(FeedbackForm::class)
->setMethods(['getAccount'])
->setConstructorArgs([[
'subject' => 'Тема обращения',
'email' => 'erickskrauch@ely.by',
'message' => 'Привет мир!',
]);
expect($model->sendMessage())->true();
expect_file('message file exists', $this->getMessageFile())->exists();
});
]])
->getMock();
$this->specify('send email with user info', function() {
/** @var FeedbackForm|\PHPUnit_Framework_MockObject_MockObject $model */
$model = $this->getMockBuilder(FeedbackForm::class)
->setMethods(['getAccount'])
->setConstructorArgs([[
'subject' => 'Тема обращения',
'email' => 'erickskrauch@ely.by',
'message' => 'Привет мир!',
]])
->getMock();
$model
->expects($this->any())
->method('getAccount')
->will($this->returnValue(new Account([
'id' => '123',
'username' => 'Erick',
'email' => 'find-this@email.net',
'created_at' => time() - 86400,
])));
expect($model->sendMessage())->true();
expect_file('message file exists', $this->getMessageFile())->exists();
$data = file_get_contents($this->getMessageFile());
expect(strpos($data, 'find-this@email.net'))->notEquals(false);
});
}
private function getMessageFile() {
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
return Yii::getAlias($mailer->fileTransportPath) . '/' . self::FILE_NAME;
$model
->expects($this->any())
->method('getAccount')
->will($this->returnValue(new Account([
'id' => '123',
'username' => 'Erick',
'email' => 'find-this@email.net',
'created_at' => time() - 86400,
])));
$this->assertTrue($model->sendMessage());
/** @var Message $message */
$message = $this->tester->grabLastSentEmail();
$this->assertInstanceOf(Message::class, $message);
$data = (string)$message;
$this->assertContains('find-this@email.net', $data);
}
}

View File

@ -3,45 +3,37 @@ namespace tests\codeception\api\models\authentication;
use api\components\User\LoginResult;
use api\models\authentication\ConfirmEmailForm;
use Codeception\Specify;
use common\models\Account;
use common\models\AccountSession;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
/**
* @property EmailActivationFixture $emailActivations
*/
class ConfirmEmailFormTest extends DbTestCase {
use Specify;
class ConfirmEmailFormTest extends TestCase {
public function fixtures() {
public function _fixtures() {
return [
'emailActivations' => EmailActivationFixture::class,
];
}
protected function createModel($key) {
public function testConfirm() {
$fixture = $this->tester->grabFixture('emailActivations', 'freshRegistrationConfirmation');
$model = $this->createModel($fixture['key']);
$result = $model->confirm();
$this->assertInstanceOf(LoginResult::class, $result);
$this->assertInstanceOf(AccountSession::class, $result->getSession(), 'session was generated');
$activationExists = EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists();
$this->assertFalse($activationExists, 'email activation key is not exist');
/** @var Account $user */
$user = Account::findOne($fixture['account_id']);
$this->assertEquals(Account::STATUS_ACTIVE, $user->status, 'user status changed to active');
}
private function createModel($key) {
return new ConfirmEmailForm([
'key' => $key,
]);
}
public function testConfirm() {
$fixture = $this->emailActivations['freshRegistrationConfirmation'];
$model = $this->createModel($fixture['key']);
$this->specify('expect true result', function() use ($model, $fixture) {
$result = $model->confirm();
expect($result)->isInstanceOf(LoginResult::class);
expect('session was generated', $result->getSession())->isInstanceOf(AccountSession::class);
$activationExists = EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists();
expect('email activation key is not exist', $activationExists)->false();
/** @var Account $user */
$user = Account::findOne($fixture['account_id']);
expect('user status changed to active', $user->status)->equals(Account::STATUS_ACTIVE);
});
}
}

View File

@ -4,41 +4,16 @@ namespace codeception\api\unit\models\authentication;
use api\models\authentication\ForgotPasswordForm;
use Codeception\Specify;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
/**
* @property AccountFixture $accounts
* @property EmailActivationFixture $emailActivations
*/
class ForgotPasswordFormTest extends DbTestCase {
class ForgotPasswordFormTest extends TestCase {
use Specify;
protected function setUp() {
parent::setUp();
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$mailer->fileTransportCallback = function () {
return 'testing_message.eml';
};
}
protected function tearDown() {
if (file_exists($this->getMessageFile())) {
unlink($this->getMessageFile());
}
parent::tearDown();
}
public function fixtures() {
public function _fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'accounts' => AccountFixture::class,
'emailActivations' => EmailActivationFixture::class,
];
}
@ -51,7 +26,7 @@ class ForgotPasswordFormTest extends DbTestCase {
});
$this->specify('empty errors if login is exists', function() {
$model = new ForgotPasswordForm(['login' => $this->accounts['admin']['username']]);
$model = new ForgotPasswordForm(['login' => $this->tester->grabFixture('accounts', 'admin')['username']]);
$model->validateLogin('login');
expect($model->getErrors('login'))->isEmpty();
});
@ -59,13 +34,17 @@ class ForgotPasswordFormTest extends DbTestCase {
public function testValidateActivity() {
$this->specify('error.account_not_activated if account is not confirmed', function() {
$model = new ForgotPasswordForm(['login' => $this->accounts['not-activated-account']['username']]);
$model = new ForgotPasswordForm([
'login' => $this->tester->grabFixture('accounts', 'not-activated-account')['username'],
]);
$model->validateActivity('login');
expect($model->getErrors('login'))->equals(['error.account_not_activated']);
});
$this->specify('empty errors if login is exists', function() {
$model = new ForgotPasswordForm(['login' => $this->accounts['admin']['username']]);
$model = new ForgotPasswordForm([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
]);
$model->validateLogin('login');
expect($model->getErrors('login'))->isEmpty();
});
@ -74,8 +53,8 @@ class ForgotPasswordFormTest extends DbTestCase {
public function testValidateFrequency() {
$this->specify('error.account_not_activated if recently was message', function() {
$model = $this->createModel([
'login' => $this->accounts['admin']['username'],
'key' => $this->emailActivations['freshPasswordRecovery']['key'],
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => $this->tester->grabFixture('emailActivations', 'freshPasswordRecovery')['key'],
]);
$model->validateFrequency('login');
@ -84,8 +63,8 @@ class ForgotPasswordFormTest extends DbTestCase {
$this->specify('empty errors if email was sent a long time ago', function() {
$model = $this->createModel([
'login' => $this->accounts['admin']['username'],
'key' => $this->emailActivations['oldPasswordRecovery']['key'],
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => $this->tester->grabFixture('emailActivations', 'oldPasswordRecovery')['key'],
]);
$model->validateFrequency('login');
@ -94,7 +73,7 @@ class ForgotPasswordFormTest extends DbTestCase {
$this->specify('empty errors if previous confirmation model not founded', function() {
$model = $this->createModel([
'login' => $this->accounts['admin']['username'],
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => 'invalid-key',
]);
@ -105,34 +84,28 @@ class ForgotPasswordFormTest extends DbTestCase {
public function testForgotPassword() {
$this->specify('successfully send message with restore password key', function() {
$model = new ForgotPasswordForm(['login' => $this->accounts['admin']['username']]);
$model = new ForgotPasswordForm(['login' => $this->tester->grabFixture('accounts', 'admin')['username']]);
expect($model->forgotPassword())->true();
expect($model->getEmailActivation())->notNull();
expect_file($this->getMessageFile())->exists();
$this->tester->canSeeEmailIsSent(1);
});
}
public function testForgotPasswordResend() {
$this->specify('successfully renew and send message with restore password key', function() {
$fixture = $this->tester->grabFixture('accounts', 'account-with-expired-forgot-password-message');
$model = new ForgotPasswordForm([
'login' => $this->accounts['account-with-expired-forgot-password-message']['username'],
'login' => $fixture['username'],
]);
$callTime = time();
expect($model->forgotPassword())->true();
$emailActivation = $model->getEmailActivation();
expect($emailActivation)->notNull();
expect($emailActivation->created_at)->greaterOrEquals($callTime);
expect_file($this->getMessageFile())->exists();
$this->tester->canSeeEmailIsSent(1);
});
}
private function getMessageFile() {
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
return Yii::getAlias($mailer->fileTransportPath) . '/testing_message.eml';
}
/**
* @param array $params
* @return ForgotPasswordForm

View File

@ -6,13 +6,10 @@ use api\models\AccountIdentity;
use api\models\authentication\LoginForm;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
/**
* @property AccountFixture $accounts
*/
class LoginFormTest extends DbTestCase {
class LoginFormTest extends TestCase {
use Specify;
private $originalRemoteAddr;
@ -28,7 +25,7 @@ class LoginFormTest extends DbTestCase {
$_SERVER['REMOTE_ADDR'] = $this->originalRemoteAddr;
}
public function fixtures() {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
];
@ -119,7 +116,7 @@ class LoginFormTest extends DbTestCase {
public function testLoginWithRehashing() {
$this->specify('user, that login using account with old pass hash strategy should update it automatically', function () {
$model = new LoginForm([
'login' => $this->accounts['user-with-old-password-type']['username'],
'login' => $this->tester->grabFixture('accounts', 'user-with-old-password-type')['username'],
'password' => '12345678',
]);
expect($model->login())->isInstanceOf(LoginResult::class);

View File

@ -6,10 +6,10 @@ use api\models\AccountIdentity;
use api\models\authentication\LogoutForm;
use Codeception\Specify;
use common\models\AccountSession;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use Yii;
class LogoutFormTest extends DbTestCase {
class LogoutFormTest extends TestCase {
use Specify;
public function testValidateLogout() {

View File

@ -6,39 +6,32 @@ use api\models\authentication\RecoverPasswordForm;
use Codeception\Specify;
use common\models\Account;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
/**
* @property EmailActivationFixture $emailActivations
*/
class RecoverPasswordFormTest extends DbTestCase {
class RecoverPasswordFormTest extends TestCase {
use Specify;
public function fixtures() {
public function _fixtures() {
return [
'emailActivations' => EmailActivationFixture::class,
];
}
public function testRecoverPassword() {
$fixture = $this->emailActivations['freshPasswordRecovery'];
$this->specify('change user account password by email confirmation key', function() use ($fixture) {
$model = new RecoverPasswordForm([
'key' => $fixture['key'],
'newPassword' => '12345678',
'newRePassword' => '12345678',
]);
$result = $model->recoverPassword();
expect($result)->isInstanceOf(LoginResult::class);
expect('session was not generated', $result->getSession())->null();
$activationExists = EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists();
expect($activationExists)->false();
/** @var Account $account */
$account = Account::findOne($fixture['account_id']);
expect($account->validatePassword('12345678'))->true();
});
$fixture = $this->tester->grabFixture('emailActivations', 'freshPasswordRecovery');
$model = new RecoverPasswordForm([
'key' => $fixture['key'],
'newPassword' => '12345678',
'newRePassword' => '12345678',
]);
$result = $model->recoverPassword();
$this->assertInstanceOf(LoginResult::class, $result);
$this->assertNull($result->getSession(), 'session was not generated');
$this->assertFalse(EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists());
/** @var Account $account */
$account = Account::findOne($fixture['account_id']);
$this->assertTrue($account->validatePassword('12345678'));
}
}

View File

@ -5,16 +5,13 @@ use api\components\User\RenewResult;
use api\models\authentication\RefreshTokenForm;
use Codeception\Specify;
use common\models\AccountSession;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountSessionFixture;
/**
* @property AccountSessionFixture $sessions
*/
class RefreshTokenFormTest extends DbTestCase {
class RefreshTokenFormTest extends TestCase {
use Specify;
public function fixtures() {
public function _fixtures() {
return [
'sessions' => AccountSessionFixture::class,
];
@ -45,11 +42,9 @@ class RefreshTokenFormTest extends DbTestCase {
}
public function testRenew() {
$this->specify('success renew token', function() {
$model = new RefreshTokenForm();
$model->refresh_token = $this->sessions['admin']['refresh_token'];
expect($model->renew())->isInstanceOf(RenewResult::class);
});
$model = new RefreshTokenForm();
$model->refresh_token = $this->tester->grabFixture('sessions', 'admin')['refresh_token'];
$this->assertInstanceOf(RenewResult::class, $model->renew());
}
}

View File

@ -1,53 +1,34 @@
<?php
namespace tests\codeception\api\models\authentication;
use api\components\ReCaptcha\Validator;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\authentication\RegistrationForm;
use Codeception\Specify;
use common\models\Account;
use common\models\EmailActivation;
use common\models\UsernameHistory;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use Yii;
use const common\LATEST_RULES_VERSION;
use yii\web\Request;
use const common\LATEST_RULES_VERSION;
/**
* @property array $accounts
*/
class RegistrationFormTest extends DbTestCase {
class RegistrationFormTest extends TestCase {
use Specify;
public function setUp() {
parent::setUp();
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$mailer->fileTransportCallback = function () {
return 'testing_message.eml';
};
$this->mockRequest();
Yii::$container->set(Validator::class, new class extends Validator {
Yii::$container->set(ReCaptchaValidator::class, new class extends ReCaptchaValidator {
public function validateValue($value) {
return null;
}
});
}
protected function tearDown() {
if (file_exists($this->getMessageFile())) {
unlink($this->getMessageFile());
}
parent::tearDown();
}
public function fixtures() {
public function _fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'accounts' => AccountFixture::class,
];
}
@ -84,7 +65,7 @@ class RegistrationFormTest extends DbTestCase {
$account = $model->signup();
$this->expectSuccessRegistration($account);
expect('lang is set', $account->lang)->equals('ru');
$this->assertEquals('ru', $account->lang, 'lang is set');
}
public function testSignupWithDefaultLanguage() {
@ -99,32 +80,32 @@ class RegistrationFormTest extends DbTestCase {
$account = $model->signup();
$this->expectSuccessRegistration($account);
expect('lang is set', $account->lang)->equals('en');
$this->assertEquals('en', $account->lang, 'lang is set');
}
/**
* @param Account|null $account
*/
private function expectSuccessRegistration($account) {
expect('user should be valid', $account)->isInstanceOf(Account::class);
expect('password should be correct', $account->validatePassword('some_password'))->true();
expect('uuid is set', $account->uuid)->notEmpty();
expect('registration_ip is set', $account->registration_ip)->notNull();
expect('actual rules version is set', $account->rules_agreement_version)->equals(LATEST_RULES_VERSION);
expect('user model exists in database', Account::find()->andWhere([
$this->assertInstanceOf(Account::class, $account, 'user should be valid');
$this->assertTrue($account->validatePassword('some_password'), 'password should be correct');
$this->assertNotEmpty($account->uuid, 'uuid is set');
$this->assertNotNull($account->registration_ip, 'registration_ip is set');
$this->assertEquals(LATEST_RULES_VERSION, $account->rules_agreement_version, 'actual rules version is set');
$this->assertTrue(Account::find()->andWhere([
'username' => 'some_username',
'email' => 'some_email@example.com',
])->exists())->true();
expect('email activation code exists in database', EmailActivation::find()->andWhere([
])->exists(), 'user model exists in database');
$this->assertTrue(EmailActivation::find()->andWhere([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION,
])->exists())->true();
expect('username history record exists in database', UsernameHistory::find()->andWhere([
])->exists(), 'email activation code exists in database');
$this->assertTrue(UsernameHistory::find()->andWhere([
'username' => $account->username,
'account_id' => $account->id,
'applied_in' => $account->created_at,
])->exists())->true();
expect_file('message file exists', $this->getMessageFile())->exists();
])->exists(), 'username history record exists in database');
$this->tester->canSeeEmailIsSent(1);
}
// TODO: там в самой форме есть метод sendMail(), который рано или поздно должен переехать. К нему нужны будут тоже тесты
@ -144,11 +125,4 @@ class RegistrationFormTest extends DbTestCase {
return $request;
}
private function getMessageFile() {
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
return Yii::getAlias($mailer->fileTransportPath) . '/testing_message.eml';
}
}

View File

@ -1,50 +1,30 @@
<?php
namespace tests\codeception\api\models\authentication;
use api\components\ReCaptcha\Validator;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\authentication\RepeatAccountActivationForm;
use Codeception\Specify;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
/**
* @property array $accounts
* @property array $activations
*/
class RepeatAccountActivationFormTest extends DbTestCase {
class RepeatAccountActivationFormTest extends TestCase {
use Specify;
public function setUp() {
parent::setUp();
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$mailer->fileTransportCallback = function () {
return 'testing_message.eml';
};
Yii::$container->set(Validator::class, new class extends Validator {
Yii::$container->set(ReCaptchaValidator::class, new class extends ReCaptchaValidator {
public function validateValue($value) {
return null;
}
});
}
protected function tearDown() {
if (file_exists($this->getMessageFile())) {
unlink($this->getMessageFile());
}
parent::tearDown();
}
public function fixtures() {
public function _fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'accounts' => AccountFixture::class,
'activations' => EmailActivationFixture::class,
];
}
@ -57,13 +37,15 @@ class RepeatAccountActivationFormTest extends DbTestCase {
});
$this->specify('error.account_already_activated if passed valid email, but account already activated', function() {
$model = new RepeatAccountActivationForm(['email' => $this->accounts['admin']['email']]);
$fixture = $this->tester->grabFixture('accounts', 'admin');
$model = new RepeatAccountActivationForm(['email' => $fixture['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']]);
$fixture = $this->tester->grabFixture('accounts', 'not-activated-account');
$model = new RepeatAccountActivationForm(['email' => $fixture['email']]);
$model->validateEmailForAccount('email');
expect($model->getErrors('email'))->isEmpty();
});
@ -71,17 +53,15 @@ class RepeatAccountActivationFormTest extends DbTestCase {
public function testValidateExistsActivation() {
$this->specify('error.recently_sent_message if passed email has recently sent message', function() {
$model = $this->createModel([
'emailKey' => $this->activations['freshRegistrationConfirmation']['key'],
]);
$fixture = $this->tester->grabFixture('activations', 'freshRegistrationConfirmation');
$model = $this->createModel(['emailKey' => $fixture['key']]);
$model->validateExistsActivation('email');
expect($model->getErrors('email'))->equals(['error.recently_sent_message']);
});
$this->specify('no errors if passed email has expired activation message', function() {
$model = $this->createModel([
'emailKey' => $this->activations['oldRegistrationConfirmation']['key'],
]);
$fixture = $this->tester->grabFixture('activations', 'oldRegistrationConfirmation');
$model = $this->createModel(['emailKey' => $fixture['key']]);
$model->validateExistsActivation('email');
expect($model->getErrors('email'))->isEmpty();
});
@ -91,25 +71,18 @@ class RepeatAccountActivationFormTest extends DbTestCase {
$this->specify('no magic if we don\'t pass validation', function() {
$model = new RepeatAccountActivationForm();
expect($model->sendRepeatMessage())->false();
expect_file($this->getMessageFile())->notExists();
$this->tester->cantSeeEmailIsSent();
});
$this->specify('successfully send new message if previous message has expired', function() {
$email = $this->accounts['not-activated-account-with-expired-message']['email'];
$email = $this->tester->grabFixture('accounts', 'not-activated-account-with-expired-message')['email'];
$model = new RepeatAccountActivationForm(['email' => $email]);
expect($model->sendRepeatMessage())->true();
expect($model->getActivation())->notNull();
expect_file($this->getMessageFile())->exists();
$this->tester->canSeeEmailIsSent(1);
});
}
private function getMessageFile() {
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
return Yii::getAlias($mailer->fileTransportPath) . '/testing_message.eml';
}
/**
* @param array $params
* @return RepeatAccountActivationForm

View File

@ -2,19 +2,14 @@
namespace tests\codeception\api\models\base;
use api\models\base\ApiForm;
use Codeception\Specify;
use tests\codeception\api\unit\TestCase;
class ApiFormTest extends TestCase {
use Specify;
public function testLoad() {
$model = new DummyApiForm();
$this->specify('model should load data without ModelName array scope', function () use ($model) {
expect('model successful load data without prefix', $model->load(['field' => 'test-data']))->true();
expect('field is set as passed data', $model->field)->equals('test-data');
});
$this->assertTrue($model->load(['field' => 'test-data']), 'model successful load data without prefix');
$this->assertEquals('test-data', $model->field, 'field is set as passed data');
}
}

View File

@ -4,34 +4,26 @@ namespace tests\codeception\api\models\base;
use api\models\base\KeyConfirmationForm;
use Codeception\Specify;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
/**
* @property EmailActivationFixture $emailActivations
*/
class KeyConfirmationFormTest extends DbTestCase {
class KeyConfirmationFormTest extends TestCase {
use Specify;
public function fixtures() {
public function _fixtures() {
return [
'emailActivations' => EmailActivationFixture::class,
];
}
public function testGetActivationCodeModel() {
$this->specify('should return model, based on passed key', function() {
$model = new KeyConfirmationForm();
$model->key = array_values($this->emailActivations->data)[0]['key'];
expect($model->getActivationCodeModel())->isInstanceOf(EmailActivation::class);
});
$model = new KeyConfirmationForm();
$model->key = $this->tester->grabFixture('emailActivations', 'freshRegistrationConfirmation')['key'];
$this->assertInstanceOf(EmailActivation::class, $model->getActivationCodeModel());
$this->specify('should return null, if passed key is invalid', function() {
$model = new KeyConfirmationForm();
$model->key = 'this-is-invalid-key';
expect($model->getActivationCodeModel())->null();
});
$model = new KeyConfirmationForm();
$model->key = 'this-is-invalid-key';
$this->assertNull($model->getActivationCodeModel());
}
}

View File

@ -2,32 +2,25 @@
namespace codeception\api\unit\models\profile;
use api\models\profile\AcceptRulesForm;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use const common\LATEST_RULES_VERSION;
/**
* @property AccountFixture $accounts
*/
class AcceptRulesFormTest extends DbTestCase {
use Specify;
class AcceptRulesFormTest extends TestCase {
public function fixtures() {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
];
}
public function testApplyLanguage() {
$this->specify('rules version bumped to latest', function() {
/** @var Account $account */
$account = Account::findOne($this->accounts['account-with-old-rules-version']);
$model = new AcceptRulesForm($account);
expect($model->agreeWithLatestRules())->true();
expect($account->rules_agreement_version)->equals(LATEST_RULES_VERSION);
});
public function testAgreeWithLatestRules() {
/** @var Account $account */
$account = Account::findOne($this->tester->grabFixture('accounts', 'account-with-old-rules-version'));
$model = new AcceptRulesForm($account);
$this->assertTrue($model->agreeWithLatestRules());
$this->assertEquals(LATEST_RULES_VERSION, $account->rules_agreement_version);
}
}

View File

@ -2,46 +2,36 @@
namespace codeception\api\unit\models\profile\ChangeEmail;
use api\models\profile\ChangeEmail\ConfirmNewEmailForm;
use Codeception\Specify;
use common\models\Account;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
/**
* @property AccountFixture $accounts
* @property EmailActivationFixture $emailActivations
*/
class ConfirmNewEmailFormTest extends DbTestCase {
use Specify;
class ConfirmNewEmailFormTest extends TestCase {
public function fixtures() {
public function _fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'accounts' => AccountFixture::class,
'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']);
});
$accountId = $this->tester->grabFixture('accounts', 'account-with-change-email-finish-state')['id'];
/** @var Account $account */
$account = Account::findOne($accountId);
$newEmailConfirmationFixture = $this->tester->grabFixture('emailActivations', 'newEmailConfirmation');
$model = new ConfirmNewEmailForm($account, [
'key' => $newEmailConfirmationFixture['key'],
]);
$this->assertTrue($model->changeEmail());
$this->assertNull(EmailActivation::findOne([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION,
]));
$data = unserialize($newEmailConfirmationFixture['_data']);
$this->assertEquals($data['newEmail'], $account->email);
}
}

View File

@ -2,82 +2,44 @@
namespace codeception\api\unit\models\profile\ChangeEmail;
use api\models\profile\ChangeEmail\InitStateForm;
use Codeception\Specify;
use common\models\Account;
use common\models\confirmations\CurrentEmailConfirmation;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
/**
* @property AccountFixture $accounts
* @property EmailActivationFixture $emailActivations
*/
class InitStateFormTest extends DbTestCase {
use Specify;
class InitStateFormTest extends TestCase {
public function setUp() {
parent::setUp();
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$mailer->fileTransportCallback = function () {
return 'testing_message.eml';
};
}
protected function tearDown() {
if (file_exists($this->getMessageFile())) {
unlink($this->getMessageFile());
}
parent::tearDown();
}
public function fixtures() {
public function _fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'accounts' => AccountFixture::class,
'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 InitStateForm($account);
$activationModel = $model->createCode();
expect($activationModel)->isInstanceOf(CurrentEmailConfirmation::class);
expect($activationModel->account_id)->equals($account->id);
expect(EmailActivation::findOne($activationModel->key))->notNull();
});
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new InitStateForm($account);
$activationModel = $model->createCode();
$this->assertInstanceOf(CurrentEmailConfirmation::class, $activationModel);
$this->assertEquals($account->id, $activationModel->account_id);
$this->assertNotNull(EmailActivation::findOne($activationModel->key));
}
public function testSendCurrentEmailConfirmation() {
$this->specify('send email', function() {
/** @var Account $account */
$account = Account::findOne($this->accounts['admin']['id']);
$model = new InitStateForm($account, [
'password' => 'password_0',
]);
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';
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new InitStateForm($account, [
'password' => 'password_0',
]);
$this->assertTrue($model->sendCurrentEmailConfirmation());
$this->assertTrue(EmailActivation::find()->andWhere([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION,
])->exists());
$this->tester->canSeeEmailIsSent();
}
}

View File

@ -2,87 +2,50 @@
namespace codeception\api\unit\models\profile\ChangeEmail;
use api\models\profile\ChangeEmail\NewEmailForm;
use Codeception\Specify;
use common\models\Account;
use common\models\confirmations\NewEmailConfirmation;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
/**
* @property AccountFixture $accounts
* @property EmailActivationFixture $emailActivations
*/
class NewEmailFormTest extends DbTestCase {
use Specify;
class NewEmailFormTest extends TestCase {
public function setUp() {
parent::setUp();
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$mailer->fileTransportCallback = function () {
return 'testing_message.eml';
};
}
protected function tearDown() {
if (file_exists($this->getMessageFile())) {
unlink($this->getMessageFile());
}
parent::tearDown();
}
public function fixtures() {
public function _fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'accounts' => AccountFixture::class,
'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();
});
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new NewEmailForm($account);
$model->email = 'my-new-email@ely.by';
$activationModel = $model->createCode();
$this->assertInstanceOf(NewEmailConfirmation::class, $activationModel);
$this->assertEquals($account->id, $activationModel->account_id);
$this->assertEquals($model->email, $activationModel->newEmail);
$this->assertNotNull(EmailActivation::findOne($activationModel->key));
}
public function testSendNewEmailConfirmation() {
$this->specify('send email', function() {
/** @var Account $account */
$account = Account::findOne($this->accounts['account-with-change-email-init-state']['id']);
/** @var NewEmailForm $model */
$key = $this->emailActivations['currentChangeEmailConfirmation']['key'];
$model = new NewEmailForm($account, [
'key' => $key,
'email' => 'my-new-email@ely.by',
]);
expect($model->sendNewEmailConfirmation())->true();
expect(EmailActivation::findOne($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';
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'account-with-change-email-init-state');
/** @var NewEmailForm $model */
$key = $this->tester->grabFixture('emailActivations', 'currentChangeEmailConfirmation')['key'];
$model = new NewEmailForm($account, [
'key' => $key,
'email' => 'my-new-email@ely.by',
]);
$this->assertTrue($model->sendNewEmailConfirmation());
$this->assertNull(EmailActivation::findOne($key));
$this->assertNotNull(EmailActivation::findOne([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION,
]));
$this->tester->canSeeEmailIsSent();
}
}

View File

@ -2,35 +2,25 @@
namespace codeception\api\unit\models\profile;
use api\models\profile\ChangeLanguageForm;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
/**
* @property AccountFixture $accounts
*/
class ChangeLanguageFormTest extends DbTestCase {
use Specify;
class ChangeLanguageFormTest extends TestCase {
public function fixtures() {
public function _fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'accounts' => AccountFixture::class
];
}
public function testApplyLanguage() {
$this->specify('language changed', function() {
/** @var Account $account */
$account = Account::findOne($this->accounts['admin']);
$model = new ChangeLanguageForm($account);
$model->lang = 'ru';
expect($model->applyLanguage())->true();
expect($account->lang)->equals('ru');
});
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new ChangeLanguageForm($account);
$model->lang = 'ru';
$this->assertTrue($model->applyLanguage());
$this->assertEquals('ru', $account->lang);
}
}

View File

@ -7,19 +7,15 @@ use api\models\profile\ChangePasswordForm;
use Codeception\Specify;
use common\models\Account;
use common\models\AccountSession;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\AccountSessionFixture;
use Yii;
/**
* @property AccountFixture $accounts
* @property AccountSessionFixture $accountSessions
*/
class ChangePasswordFormTest extends DbTestCase {
class ChangePasswordFormTest extends TestCase {
use Specify;
public function fixtures() {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'accountSessions' => AccountSessionFixture::class,
@ -68,7 +64,7 @@ class ChangePasswordFormTest extends DbTestCase {
public function testChangePassword() {
$this->specify('successfully change password with modern hash strategy', function() {
/** @var Account $account */
$account = Account::findOne($this->accounts['admin']['id']);
$account = Account::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$model = new ChangePasswordForm($account, [
'password' => 'password_0',
'newPassword' => 'my-new-password',
@ -83,7 +79,7 @@ class ChangePasswordFormTest extends DbTestCase {
$this->specify('successfully change password with legacy hash strategy', function() {
/** @var Account $account */
$account = Account::findOne($this->accounts['user-with-old-password-type']['id']);
$account = Account::findOne($this->tester->grabFixture('accounts', 'user-with-old-password-type')['id']);
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
@ -111,7 +107,7 @@ class ChangePasswordFormTest extends DbTestCase {
->getMock();
/** @var AccountSession $session */
$session = AccountSession::findOne($this->accountSessions['admin2']['id']);
$session = AccountSession::findOne($this->tester->grabFixture('accountSessions', 'admin2')['id']);
$component
->expects($this->any())
@ -122,7 +118,7 @@ class ChangePasswordFormTest extends DbTestCase {
$this->specify('change password with removing all session, except current', function() use ($session) {
/** @var Account $account */
$account = Account::findOne($this->accounts['admin']['id']);
$account = Account::findOne($this->tester->grabFixture('accounts', 'admin')['id']);
$model = new ChangePasswordForm($account, [
'password' => 'password_0',

View File

@ -6,18 +6,15 @@ use api\models\profile\ChangeUsernameForm;
use Codeception\Specify;
use common\models\Account;
use common\models\UsernameHistory;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\UsernameHistoryFixture;
use Yii;
/**
* @property AccountFixture $accounts
*/
class ChangeUsernameFormTest extends DbTestCase {
class ChangeUsernameFormTest extends TestCase {
use Specify;
public function fixtures() {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'history' => UsernameHistoryFixture::class,
@ -31,64 +28,43 @@ class ChangeUsernameFormTest extends DbTestCase {
}
public function testChange() {
$this->specify('successfully change username to new one', function() {
$model = new ChangeUsernameForm([
'password' => 'password_0',
'username' => 'my_new_nickname',
]);
expect($model->change())->true();
expect(Account::findOne($this->getAccountId())->username)->equals('my_new_nickname');
expect(UsernameHistory::findOne(['username' => 'my_new_nickname']))->isInstanceOf(UsernameHistory::class);
});
$model = new ChangeUsernameForm([
'password' => 'password_0',
'username' => 'my_new_nickname',
]);
$this->assertTrue($model->change());
$this->assertEquals('my_new_nickname', Account::findOne($this->getAccountId())->username);
$this->assertInstanceOf(UsernameHistory::class, UsernameHistory::findOne(['username' => 'my_new_nickname']));
}
public function testChangeWithoutChange() {
$this->specify('no new UsernameHistory record, if we don\'t change nickname', function() {
$model = new ChangeUsernameForm([
'password' => 'password_0',
'username' => $this->accounts['admin']['username'],
]);
$callTime = time();
expect($model->change())->true();
expect(UsernameHistory::findOne([
'AND',
'username' => $this->accounts['admin']['username'],
['>=', 'applied_in', $callTime],
]))->null();
});
$username = $this->tester->grabFixture('accounts', 'admin')['username'];
$model = new ChangeUsernameForm([
'password' => 'password_0',
'username' => $username,
]);
$callTime = time();
$this->assertTrue($model->change());
$this->assertNull(UsernameHistory::findOne([
'AND',
'username' => $username,
['>=', 'applied_in', $callTime],
]), 'no new UsernameHistory record, if we don\'t change nickname');
}
public function testChangeCase() {
$this->specify('username should change, if we change case of some letters', function() {
$newUsername = mb_strtoupper($this->accounts['admin']['username']);
$model = new ChangeUsernameForm([
'password' => 'password_0',
'username' => $newUsername,
]);
expect($model->change())->true();
expect(Account::findOne($this->getAccountId())->username)->equals($newUsername);
expect(UsernameHistory::findOne(['username' => $newUsername]))->isInstanceOf(UsernameHistory::class);
});
}
public function testValidateUsername() {
$this->specify('error.username_not_available expected if username is already taken', function() {
$model = new ChangeUsernameForm([
'password' => 'password_0',
'username' => 'Jon',
]);
$model->validateUsername('username');
expect($model->getErrors('username'))->equals(['error.username_not_available']);
});
$this->specify('error.username_not_available is NOT expected if username is already taken by CURRENT user', function() {
$model = new ChangeUsernameForm([
'password' => 'password_0',
'username' => $this->accounts['admin']['username'],
]);
$model->validateUsername('username');
expect($model->getErrors('username'))->isEmpty();
});
$newUsername = mb_strtoupper($this->tester->grabFixture('accounts', 'admin')['username']);
$model = new ChangeUsernameForm([
'password' => 'password_0',
'username' => $newUsername,
]);
$this->assertTrue($model->change());
$this->assertEquals($newUsername, Account::findOne($this->getAccountId())->username);
$this->assertInstanceOf(
UsernameHistory::class,
UsernameHistory::findOne(['username' => $newUsername]),
'username should change, if we change case of some letters'
);
}
public function testCreateTask() {
@ -99,7 +75,7 @@ class ChangeUsernameFormTest extends DbTestCase {
}
private function getAccountId() {
return $this->accounts['admin']['id'];
return $this->tester->grabFixture('accounts', 'admin')['id'];
}
}

View File

@ -8,19 +8,15 @@ use api\modules\authserver\models\AuthenticationForm;
use common\models\Account;
use common\models\MinecraftAccessKey;
use Ramsey\Uuid\Uuid;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\MinecraftAccessKeyFixture;
/**
* @property AccountFixture $accounts
* @property MinecraftAccessKeyFixture $minecraftAccessKeys
*/
class AuthenticationFormTest extends DbTestCase {
class AuthenticationFormTest extends TestCase {
use ProtectedCaller;
public function fixtures() {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'minecraftAccessKeys' => MinecraftAccessKeyFixture::class,
@ -91,7 +87,7 @@ class AuthenticationFormTest extends DbTestCase {
$authForm = new AuthenticationForm();
$authForm->clientToken = Uuid::uuid4();
/** @var Account $account */
$account = $this->accounts->getModel('admin');
$account = $this->tester->grabFixture('accounts', 'admin');
/** @var MinecraftAccessKey $result */
$result = $this->callProtected($authForm, 'createMinecraftAccessToken', $account);
$this->assertInstanceOf(MinecraftAccessKey::class, $result);
@ -102,15 +98,16 @@ class AuthenticationFormTest extends DbTestCase {
public function testCreateMinecraftAccessTokenWithExistsClientId() {
$authForm = new AuthenticationForm();
$authForm->clientToken = $this->minecraftAccessKeys['admin-token']['client_token'];
$minecraftFixture = $this->tester->grabFixture('minecraftAccessKeys', 'admin-token');
$authForm->clientToken = $minecraftFixture['client_token'];
/** @var Account $account */
$account = $this->accounts->getModel('admin');
$account = $this->tester->grabFixture('accounts', 'admin');
/** @var MinecraftAccessKey $result */
$result = $this->callProtected($authForm, 'createMinecraftAccessToken', $account);
$this->assertInstanceOf(MinecraftAccessKey::class, $result);
$this->assertEquals($account->id, $result->account_id);
$this->assertEquals($authForm->clientToken, $result->client_token);
$this->assertNull(MinecraftAccessKey::findOne($this->minecraftAccessKeys['admin-token']['access_token']));
$this->assertNull(MinecraftAccessKey::findOne($minecraftFixture['access_token']));
$this->assertInstanceOf(MinecraftAccessKey::class, MinecraftAccessKey::findOne($result->access_token));
}

View File

@ -5,32 +5,27 @@ use api\models\AccountIdentity;
use api\traits\AccountFinder;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
/**
* @property \tests\codeception\api\UnitTester $actor
* @property array $accounts
*/
class AccountFinderTest extends DbTestCase {
class AccountFinderTest extends TestCase {
use Specify;
public function fixtures() {
public function _fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'accounts' => AccountFixture::class,
];
}
public function testGetAccount() {
$this->specify('founded account for passed login data', function() {
$model = new AccountFinderTestTestClass();
$model->login = $this->accounts['admin']['email'];
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model->login = $account->email;
$account = $model->getAccount();
expect($account)->isInstanceOf(Account::class);
expect($account->id)->equals($this->accounts['admin']['id']);
expect($account->id)->equals($account->id);
});
$this->specify('founded account for passed login data with changed account model class name', function() {
@ -40,10 +35,12 @@ class AccountFinderTest extends DbTestCase {
return AccountIdentity::class;
}
};
$model->login = $this->accounts['admin']['email'];
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model->login = $account->email;
$account = $model->getAccount();
expect($account)->isInstanceOf(AccountIdentity::class);
expect($account->id)->equals($this->accounts['admin']['id']);
expect($account->id)->equals($account->id);
});
$this->specify('null, if account not founded', function() {

View File

@ -2,41 +2,34 @@
namespace tests\codeception\api\traits;
use api\traits\ApiNormalize;
use Codeception\Specify;
use Codeception\TestCase\Test;
use tests\codeception\api\unit\TestCase;
class ApiNormalizeTestClass {
use ApiNormalize;
}
/**
* @property \tests\codeception\api\UnitTester $actor
*/
class ApiNormalizerTest extends Test {
use Specify;
class ApiNormalizerTest extends TestCase {
public function testNormalizeModelErrors() {
$object = new ApiNormalizeTestClass();
$this->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',
],
]);
$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',
]);
});
$this->assertEquals([
'rulesAgreement' => 'error.you_must_accept_rules',
'email' => 'error.email_required',
'username' => 'error.username_too_short',
], $normalized);
}
}

View File

@ -5,18 +5,15 @@ use api\validators\EmailActivationKeyValidator;
use Codeception\Specify;
use common\models\confirmations\ForgotPassword;
use common\models\EmailActivation;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use tests\codeception\common\fixtures\EmailActivationFixture;
/**
* @property EmailActivationFixture $emailActivations
*/
class EmailActivationKeyValidatorTest extends DbTestCase {
class EmailActivationKeyValidatorTest extends TestCase {
use Specify;
use ProtectedCaller;
public function fixtures() {
public function _fixtures() {
return [
'emailActivations' => EmailActivationFixture::class,
];
@ -24,7 +21,7 @@ class EmailActivationKeyValidatorTest extends DbTestCase {
public function testFindEmailActivationModel() {
$this->specify('get EmailActivation model for exists key', function() {
$key = array_values($this->emailActivations->data)[0]['key'];
$key = $this->tester->grabFixture('emailActivations', 'freshRegistrationConfirmation')['key'];
$model = new EmailActivationKeyValidator();
/** @var EmailActivation $result */
$result = $this->callProtected($model, 'findEmailActivationModel', $key);

View File

@ -4,11 +4,11 @@ namespace codeception\api\unit\validators;
use api\validators\PasswordRequiredValidator;
use Codeception\Specify;
use common\models\Account;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
use common\helpers\Error as E;
class PasswordRequiredValidatorTest extends DbTestCase {
class PasswordRequiredValidatorTest extends TestCase {
use Specify;
use ProtectedCaller;

View File

@ -16,6 +16,8 @@ use yii\test\InitDbFixture;
/**
* This helper is used to populate the database with needed fixtures before any tests are run.
* All fixtures will be loaded before the suite is started and unloaded after it completes.
*
* TODO: try to remove
*/
class FixtureHelper extends Module {
@ -52,14 +54,8 @@ class FixtureHelper extends Module {
'accountSessions' => AccountSessionFixture::class,
'emailActivations' => EmailActivationFixture::class,
'usernamesHistory' => UsernameHistoryFixture::class,
'oauthClients' => [
'class' => OauthClientFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/oauth-clients.php',
],
'oauthSessions' => [
'class' => OauthSessionFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/oauth-sessions.php',
],
'oauthClients' => OauthClientFixture::class,
'oauthSessions' => OauthSessionFixture::class,
'minecraftAccessKeys' => MinecraftAccessKeyFixture::class,
];
}

View File

@ -8,4 +8,6 @@ class MojangUsernameFixture extends ActiveFixture {
public $modelClass = MojangUsername::class;
public $dataFile = '@tests/codeception/common/fixtures/data/mojang-usernames.php';
}

View File

@ -8,6 +8,8 @@ class OauthClientFixture extends ActiveFixture {
public $modelClass = OauthClient::class;
public $dataFile = '@tests/codeception/common/fixtures/data/oauth-clients.php';
public $depends = [
AccountFixture::class,
];

View File

@ -9,6 +9,8 @@ class OauthSessionFixture extends ActiveFixture {
public $modelClass = OauthSession::class;
public $dataFile = '@tests/codeception/common/fixtures/data/oauth-sessions.php';
public $depends = [
OauthClientFixture::class,
AccountFixture::class,

View File

@ -1,6 +1,8 @@
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: UnitTester
modules:
enabled:
- Yii2:
part: [orm, email, fixtures]
config:
Yii2:
configFile: '../config/common/unit.php'

View File

@ -1,11 +0,0 @@
<?php
namespace tests\codeception\common\unit;
/**
* @inheritdoc
*/
class DbTestCase extends \yii\codeception\DbTestCase
{
public $appConfig = '@tests/codeception/config/common/unit.php';
}

View File

@ -1,8 +1,22 @@
<?php
namespace tests\codeception\common\unit;
class TestCase extends \yii\codeception\TestCase {
class TestCase extends \Codeception\Test\Unit {
public $appConfig = '@tests/codeception/config/common/unit.php';
/**
* @var \tests\codeception\common\UnitTester
*/
protected $tester;
/**
* Список фикстур, что будут загружены перед тестом, но после зачистки базы данных
*
* @url http://codeception.com/docs/modules/Yii2#fixtures
*
* @return array
*/
public function _fixtures() {
return [];
}
}

View File

@ -13,23 +13,19 @@ class DataBehaviorTest extends TestCase {
use ProtectedCaller;
public function testSetKey() {
$this->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']));
});
$model = $this->createModel();
/** @var DataBehavior $behavior */
$behavior = $model->behaviors['dataBehavior'];
$this->callProtected($behavior, 'setKey', 'my-key', 'my-value');
$this->assertEquals(serialize(['my-key' => 'my-value']), $model->_data);
}
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');
});
$model = $this->createModel();
$model->_data = serialize(['some-key' => 'some-value']);
/** @var DataBehavior $behavior */
$behavior = $model->behaviors['dataBehavior'];
$this->assertEquals('some-value', $this->callProtected($behavior, 'getKey', 'some-key'));
}
public function testGetData() {

View File

@ -12,12 +12,10 @@ class EmailActivationExpirationBehaviorTest extends TestCase {
use ProtectedCaller;
public function testCalculateTime() {
$this->specify('just use create_time and plus passed time', function() {
$behavior = $this->createBehavior();
$time = time();
$behavior->owner->created_at = $time;
expect($this->callProtected($behavior, 'calculateTime', 10))->equals($time + 10);
});
$behavior = $this->createBehavior();
$time = time();
$behavior->owner->created_at = $time;
$this->assertEquals($time + 10, $this->callProtected($behavior, 'calculateTime', 10));
}
public function testCompareTime() {

View File

@ -0,0 +1,54 @@
<?php
namespace codeception\common\unit\components\Mojang;
use common\components\Mojang\Api;
use common\components\Mojang\response\UsernameToUUIDResponse;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use tests\codeception\common\unit\TestCase;
use Yii;
class ApiTest extends TestCase {
/**
* @var MockHandler
*/
private $handler;
public function _before() {
parent::_before();
$this->handler = new MockHandler();
$handler = HandlerStack::create($this->handler);
Yii::$app->set('guzzle', new GuzzleClient([
'handler' => $handler,
]));
}
public function testUsernameToUUID() {
$this->handler->append(new Response(200, [], '{"id": "7125ba8b1c864508b92bb5c042ccfe2b","name": "KrisJelbring"}'));
$response = (new Api())->usernameToUUID('KrisJelbring');
$this->assertInstanceOf(UsernameToUUIDResponse::class, $response);
$this->assertEquals('7125ba8b1c864508b92bb5c042ccfe2b', $response->id);
$this->assertEquals('KrisJelbring', $response->name);
}
/**
* @expectedException \common\components\Mojang\exceptions\NoContentException
*/
public function testUsernameToUUIDNoContent() {
$this->handler->append(new Response(204));
(new Api())->usernameToUUID('some-non-exists-user');
}
/**
* @expectedException \GuzzleHttp\Exception\RequestException
*/
public function testUsernameToUUID404() {
$this->handler->append(new Response(404, [], '{"error":"Not Found","errorMessage":"The server has not found anything matching the request URI"}'));
(new Api())->usernameToUUID('#hashedNickname');
}
}

View File

@ -1,35 +1,27 @@
<?php
namespace codeception\common\unit\models;
use Codeception\Specify;
use common\models\AccountSession;
use tests\codeception\common\unit\TestCase;
class AccountSessionTest extends TestCase {
use Specify;
public function testGenerateRefreshToken() {
$this->specify('method call will set refresh_token value', function() {
$model = new AccountSession();
$model->generateRefreshToken();
expect($model->refresh_token)->notNull();
});
$model = new AccountSession();
$model->generateRefreshToken();
$this->assertNotNull($model->refresh_token, 'method call will set refresh_token value');
}
public function testSetIp() {
$this->specify('method should convert passed ip string to long', function() {
$model = new AccountSession();
$model->setIp('127.0.0.1');
expect($model->last_used_ip)->equals(2130706433);
});
$model = new AccountSession();
$model->setIp('127.0.0.1');
$this->assertEquals(2130706433, $model->last_used_ip, 'method should convert passed ip string to long');
}
public function testGetReadableIp() {
$this->specify('method should convert stored ip long into readable ip string', function() {
$model = new AccountSession();
$model->last_used_ip = 2130706433;
expect($model->getReadableIp())->equals('127.0.0.1');
});
$model = new AccountSession();
$model->last_used_ip = 2130706433;
$this->assertEquals('127.0.0.1', $model->getReadableIp(), 'method should convert stored long into readable ip');
}
}

View File

@ -4,127 +4,20 @@ 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\DbTestCase;
use tests\codeception\common\unit\TestCase;
use Yii;
use const common\LATEST_RULES_VERSION;
/**
* @property array $accounts
* @property array $mojangAccounts
*/
class AccountTest extends DbTestCase {
class AccountTest extends TestCase {
use Specify;
public function fixtures() {
return [
'accounts' => [
'class' => AccountFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php',
],
'mojangAccounts' => [
'class' => MojangUsernameFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/mojang-usernames.php',
],
];
}
public function testValidateUsername() {
$this->specify('username required', function() {
$model = new Account(['username' => null]);
expect($model->validate(['username']))->false();
expect($model->getErrors('username'))->equals(['error.username_required']);
});
$this->specify('username should be at least 3 symbols length', function() {
$model = new Account(['username' => 'at']);
expect($model->validate(['username']))->false();
expect($model->getErrors('username'))->equals(['error.username_too_short']);
});
$this->specify('username should be not more than 21 symbols length', function() {
$model = new Account(['username' => 'erickskrauch_erickskrauch']);
expect($model->validate(['username']))->false();
expect($model->getErrors('username'))->equals(['error.username_too_long']);
});
$this->specify('username can contain many cool symbols', function() {
$shouldBeValid = [
'русский_ник', 'русский_ник_на_грани!', 'numbers1132', '*__*-Stars-*__*', '1-_.!?#$%^&*()[]',
'[ESP]Эрик', 'Свят_помидор;', роблена_ў_беларусі:)',
];
foreach($shouldBeValid as $nickname) {
$model = new Account(['username' => $nickname]);
expect($nickname . ' passed validation', $model->validate(['username']))->true();
expect($model->getErrors('username'))->isEmpty();
}
});
$this->specify('username cannot contain some symbols', function() {
$shouldBeInvalid = [
'nick@name', 'spaced nick',
];
foreach($shouldBeInvalid as $nickname) {
$model = new Account(['username' => $nickname]);
expect($nickname . ' fail validation', $model->validate('username'))->false();
expect($model->getErrors('username'))->equals(['error.username_invalid']);
}
});
$this->specify('username should be unique', function() {
$model = new Account(['username' => $this->accounts['admin']['username']]);
expect($model->validate('username'))->false();
expect($model->getErrors('username'))->equals(['error.username_not_available']);
});
}
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->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() {
@ -164,6 +57,10 @@ class AccountTest extends DbTestCase {
}
public function testHasMojangUsernameCollision() {
$this->tester->haveFixtures([
'mojangUsernames' => MojangUsernameFixture::class,
]);
$this->specify('Expect true if collision with current username', function() {
$model = new Account();
$model->username = 'ErickSkrauch';

View File

@ -1,31 +1,35 @@
<?php
namespace codeception\common\unit\models;
use Codeception\Specify;
use common\models\confirmations\ForgotPassword;
use common\models\confirmations\RegistrationConfirmation;
use common\models\confirmations;
use common\models\EmailActivation;
use tests\codeception\common\fixtures\EmailActivationFixture;
use tests\codeception\common\unit\DbTestCase;
use tests\codeception\common\unit\TestCase;
class EmailActivationTest extends DbTestCase {
use Specify;
class EmailActivationTest extends TestCase {
public function fixtures() {
public function _fixtures() {
return [
'emailActivations' => EmailActivationFixture::class,
];
}
public function testInstantiate() {
$this->specify('return valid model type', function() {
expect(EmailActivation::findOne([
'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION,
]))->isInstanceOf(RegistrationConfirmation::class);
expect(EmailActivation::findOne([
'type' => EmailActivation::TYPE_FORGOT_PASSWORD_KEY,
]))->isInstanceOf(ForgotPassword::class);
});
$this->assertInstanceOf(confirmations\RegistrationConfirmation::class, EmailActivation::findOne([
'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION,
]));
$this->assertInstanceOf(confirmations\ForgotPassword::class, EmailActivation::findOne([
'type' => EmailActivation::TYPE_FORGOT_PASSWORD_KEY,
]));
$this->assertInstanceOf(confirmations\CurrentEmailConfirmation::class, EmailActivation::findOne([
'type' => EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION,
]));
$this->assertInstanceOf(confirmations\NewEmailConfirmation::class, EmailActivation::findOne([
'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION,
]));
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace codeception\common\unit\validators;
use common\validators\EmailValidator;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\unit\TestCase;
use yii\base\Model;
class EmailValidatorTest extends TestCase {
/**
* @var EmailValidator
*/
private $validator;
public function _before() {
parent::_before();
$this->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;
}
}

View File

@ -11,25 +11,18 @@ class LanguageValidatorTest extends TestCase {
use ProtectedCaller;
public function testGetFilesNames() {
$this->specify('get list of 2 languages: ru and en', function() {
$model = $this->createModelWithFixturePath();
expect($this->callProtected($model, 'getFilesNames'))->equals(['en', 'ru']);
});
$model = $this->createModelWithFixturePath();
$this->assertEquals(['en', 'ru'], $this->callProtected($model, 'getFilesNames'));
}
public function testValidateValue() {
$this->specify('get null, because language is supported', function() {
$model = $this->createModelWithFixturePath();
expect($this->callProtected($model, 'validateValue', 'ru'))->null();
});
public function testValidateValueSupportedLanguage() {
$model = $this->createModelWithFixturePath();
$this->assertNull($this->callProtected($model, 'validateValue', 'ru'));
}
$this->specify('get error message, because language is unsupported', function() {
$model = $this->createModelWithFixturePath();
expect($this->callProtected($model, 'validateValue', 'by'))->equals([
$model->message,
[],
]);
});
public function testValidateNotSupportedLanguage() {
$model = $this->createModelWithFixturePath();
$this->assertEquals([$model->message, []], $this->callProtected($model, 'validateValue', 'by'));
}
/**

View File

@ -0,0 +1,106 @@
<?php
namespace codeception\common\unit\validators;
use common\validators\UsernameValidator;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\unit\TestCase;
use yii\base\Model;
class UsernameValidatorTest extends TestCase {
/**
* @var UsernameValidator
*/
private $validator;
public function _before() {
parent::_before();
$this->validator = new UsernameValidator();
}
public function testValidateAttributeRequired() {
$model = $this->createModel('');
$this->validator->validateAttribute($model, 'field');
$this->assertEquals(['error.username_required'], $model->getErrors('field'));
$model = $this->createModel('username');
$this->validator->validateAttribute($model, 'field');
$this->assertNotEquals(['error.username_required'], $model->getErrors('field'));
}
public function testValidateAttributeLength() {
$model = $this->createModel('at');
$this->validator->validateAttribute($model, 'field');
$this->assertEquals(['error.username_too_short'], $model->getErrors('field'));
$model = $this->createModel('erickskrauch_erickskrauch');
$this->validator->validateAttribute($model, 'field');
$this->assertEquals(['error.username_too_long'], $model->getErrors('field'));
$model = $this->createModel('username');
$this->validator->validateAttribute($model, 'field');
$this->assertNotEquals(['error.username_too_short'], $model->getErrors('field'));
$this->assertNotEquals(['error.username_too_long'], $model->getErrors('field'));
}
public function testValidateAttributePattern() {
$shouldBeValid = [
'русский_ник', 'русский_ник_на_грани!', 'numbers1132', '*__*-Stars-*__*', '1-_.!$%^&*()[]',
'[ESP]Эрик', 'Свят_помидор;', роблена_ў_беларусі:)',
];
foreach($shouldBeValid as $nickname) {
$model = $this->createModel($nickname);
$this->validator->validateAttribute($model, 'field');
$this->assertNotEquals(['error.username_invalid'], $model->getErrors('field'));
}
$shouldBeInvalid = [
'nick@name', 'spaced nick', 'im#hashed', 'quest?ion'
];
foreach($shouldBeInvalid as $nickname) {
$model = $this->createModel($nickname);
$this->validator->validateAttribute($model, 'field');
$this->assertEquals(['error.username_invalid'], $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->username);
$this->validator->validateAttribute($model, 'field');
$this->assertEquals(['error.username_not_available'], $model->getErrors('field'));
$model = $this->createModel($accountFixture->username);
$this->validator->accountCallback = function() use ($accountFixture) {
return $accountFixture->id;
};
$this->validator->validateAttribute($model, 'field');
$this->assertNotEquals(['error.username_not_available'], $model->getErrors('field'));
$this->validator->accountCallback = null;
$model = $this->createModel('some-unique-username');
$this->validator->validateAttribute($model, 'field');
$this->assertNotEquals(['error.username_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;
}
}

View File

@ -12,42 +12,66 @@ class UuidValidatorTest extends TestCase {
public function testValidateAttribute() {
$this->specify('expected error if passed empty value', function() {
$model = new UuidTestModel();
expect($model->validate())->false();
expect($model->getErrors('attribute'))->equals(['Attribute must be valid uuid']);
$validator = new UuidValidator();
$model = $this->createModel();
$validator->validateAttribute($model, 'attribute');
$this->assertTrue($model->hasErrors());
$this->assertEquals(['Attribute must be valid uuid'], $model->getErrors('attribute'));
});
$this->specify('expected error if passed invalid string', function() {
$model = new UuidTestModel();
$validator = new UuidValidator();
$model = $this->createModel();
$model->attribute = '123456789';
expect($model->validate())->false();
expect($model->getErrors('attribute'))->equals(['Attribute must be valid uuid']);
$validator->validateAttribute($model, 'attribute');
$this->assertTrue($model->hasErrors());
$this->assertEquals(['Attribute must be valid uuid'], $model->getErrors('attribute'));
});
$this->specify('no errors if passed nil uuid and allowNil is set to true', function() {
$validator = new UuidValidator();
$model = $this->createModel();
$model->attribute = '00000000-0000-0000-0000-000000000000';
$validator->validateAttribute($model, 'attribute');
$this->assertFalse($model->hasErrors());
});
$this->specify('no errors if passed nil uuid and allowNil is set to false', function() {
$validator = new UuidValidator();
$validator->allowNil = false;
$model = $this->createModel();
$model->attribute = '00000000-0000-0000-0000-000000000000';
$validator->validateAttribute($model, 'attribute');
$this->assertTrue($model->hasErrors());
$this->assertEquals(['Attribute must be valid uuid'], $model->getErrors('attribute'));
});
$this->specify('no errors if passed valid uuid', function() {
$model = new UuidTestModel();
$validator = new UuidValidator();
$model = $this->createModel();
$model->attribute = Uuid::uuid();
expect($model->validate())->true();
$validator->validateAttribute($model, 'attribute');
$this->assertFalse($model->hasErrors());
});
$this->specify('no errors if passed uuid string without dashes and converted to standart value', function() {
$model = new UuidTestModel();
$validator = new UuidValidator();
$model = $this->createModel();
$originalUuid = Uuid::uuid();
$model->attribute = str_replace('-', '', $originalUuid);
expect($model->validate())->true();
expect($model->attribute)->equals($originalUuid);
$validator->validateAttribute($model, 'attribute');
$this->assertFalse($model->hasErrors());
$this->assertEquals($originalUuid, $model->attribute);
});
}
}
class UuidTestModel extends Model {
public $attribute;
public function rules() {
return [
['attribute', UuidValidator::class],
];
/**
* @return Model
*/
public function createModel() {
return new class extends Model {
public $attribute;
};
}
}

View File

@ -1,6 +1,8 @@
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
class_name: UnitTester
modules:
enabled:
- Yii2:
part: [orm, email, fixtures]
config:
Yii2:
configFile: '../config/console/unit.php'

Some files were not shown because too many files have changed in this diff Show More