diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..9bd86b4 --- /dev/null +++ b/.gitlab-ci.yml @@ -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 diff --git a/Dockerfile b/Dockerfile index 69be73d..ef80df9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/Dockerfile-dev b/Dockerfile-dev index 0258174..e3e5ebb 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -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"] diff --git a/README.md b/README.md index fb019f3..f96d4ad 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/api/components/User/Component.php b/api/components/User/Component.php index 336c083..a0685d5 100644 --- a/api/components/User/Component.php +++ b/api/components/User/Component.php @@ -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()), ]; } diff --git a/api/components/User/LoginResult.php b/api/components/User/LoginResult.php index 5c7d394..791873e 100644 --- a/api/components/User/LoginResult.php +++ b/api/components/User/LoginResult.php @@ -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(); diff --git a/api/components/User/RenewResult.php b/api/components/User/RenewResult.php index 71c81ca..1e1933f 100644 --- a/api/components/User/RenewResult.php +++ b/api/components/User/RenewResult.php @@ -1,6 +1,8 @@ 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(), ]; } diff --git a/api/config/config-dev.php b/api/config/config-dev.php index b6f714e..e7bc2d3 100644 --- a/api/config/config-dev.php +++ b/api/config/config-dev.php @@ -12,8 +12,5 @@ return [ 'class' => yii\debug\Module::class, 'allowedIPs' => ['*'], ], - 'gii' => [ - 'class' => yii\gii\Module::class, - ], ], ]; diff --git a/api/config/config.php b/api/config/config.php index ff3123d..7092a05 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -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', ], diff --git a/api/controllers/AccountsController.php b/api/controllers/AccountsController.php index 1f12be4..8fca1e6 100644 --- a/api/controllers/AccountsController.php +++ b/api/controllers/AccountsController.php @@ -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(), diff --git a/api/controllers/OauthController.php b/api/controllers/OauthController.php index a2d4fab..4f5db86 100644 --- a/api/controllers/OauthController.php +++ b/api/controllers/OauthController.php @@ -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 diff --git a/api/models/authentication/ConfirmEmailForm.php b/api/models/authentication/ConfirmEmailForm.php index 0c46680..a568308 100644 --- a/api/models/authentication/ConfirmEmailForm.php +++ b/api/models/authentication/ConfirmEmailForm.php @@ -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(); diff --git a/api/models/authentication/LoginForm.php b/api/models/authentication/LoginForm.php index d56e7b6..c1ad5e6 100644 --- a/api/models/authentication/LoginForm.php +++ b/api/models/authentication/LoginForm.php @@ -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(); } diff --git a/api/models/authentication/RecoverPasswordForm.php b/api/models/authentication/RecoverPasswordForm.php index 243638a..867268c 100644 --- a/api/models/authentication/RecoverPasswordForm.php +++ b/api/models/authentication/RecoverPasswordForm.php @@ -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'], ]); } diff --git a/api/models/authentication/RegistrationForm.php b/api/models/authentication/RegistrationForm.php index 41e71ee..c39a249 100644 --- a/api/models/authentication/RegistrationForm.php +++ b/api/models/authentication/RegistrationForm.php @@ -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); + } + } diff --git a/api/models/profile/AcceptRulesForm.php b/api/models/profile/AcceptRulesForm.php index 1e25381..05785f0 100644 --- a/api/models/profile/AcceptRulesForm.php +++ b/api/models/profile/AcceptRulesForm.php @@ -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'); } diff --git a/api/models/profile/ChangeEmail/NewEmailForm.php b/api/models/profile/ChangeEmail/NewEmailForm.php index c87ba6c..9dd8b76 100644 --- a/api/models/profile/ChangeEmail/NewEmailForm.php +++ b/api/models/profile/ChangeEmail/NewEmailForm.php @@ -2,10 +2,10 @@ namespace api\models\profile\ChangeEmail; use api\models\base\KeyConfirmationForm; -use common\helpers\Error as E; use common\models\Account; use common\models\confirmations\NewEmailConfirmation; use common\models\EmailActivation; +use common\validators\EmailValidator; use Yii; use yii\base\ErrorException; use yii\base\Exception; @@ -25,26 +25,14 @@ class NewEmailForm extends KeyConfirmationForm { parent::__construct($config); } - /** - * @return Account - */ - public function getAccount() { - return $this->account; - } - public function rules() { return array_merge(parent::rules(), [ - ['email', 'required', 'message' => E::EMAIL_REQUIRED], - ['email', 'validateEmail'], + ['email', EmailValidator::class], ]); } - public function validateEmail() { - $account = new Account(); - $account->email = $this->email; - if (!$account->validate(['email'])) { - $this->addErrors($account->getErrors()); - } + public function getAccount() : Account { + return $this->account; } public function sendNewEmailConfirmation() { diff --git a/api/models/profile/ChangePasswordForm.php b/api/models/profile/ChangePasswordForm.php index a8edc50..2d4741b 100644 --- a/api/models/profile/ChangePasswordForm.php +++ b/api/models/profile/ChangePasswordForm.php @@ -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], diff --git a/api/models/profile/ChangeUsernameForm.php b/api/models/profile/ChangeUsernameForm.php index ef7f069..fb0a931 100644 --- a/api/models/profile/ChangeUsernameForm.php +++ b/api/models/profile/ChangeUsernameForm.php @@ -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; diff --git a/api/modules/mojang/controllers/ApiController.php b/api/modules/mojang/controllers/ApiController.php index 53967df..1545ee7 100644 --- a/api/modules/mojang/controllers/ApiController.php +++ b/api/modules/mojang/controllers/ApiController.php @@ -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; } } diff --git a/api/modules/session/models/JoinForm.php b/api/modules/session/models/JoinForm.php index e0ac1de..648338e 100644 --- a/api/modules/session/models/JoinForm.php +++ b/api/modules/session/models/JoinForm.php @@ -90,6 +90,7 @@ class JoinForm extends Model { } $validator = new UuidValidator(); + $validator->allowNil = false; $validator->validateAttribute($this, $attribute); if ($this->hasErrors($attribute)) { diff --git a/api/web/index.php b/api/web/index.php index 38ed822..ba41774 100644 --- a/api/web/index.php +++ b/api/web/index.php @@ -1,7 +1,7 @@ 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) { diff --git a/common/components/UserFriendlyRandomKey.php b/common/components/UserFriendlyRandomKey.php index cae9958..ebeb111 100644 --- a/common/components/UserFriendlyRandomKey.php +++ b/common/components/UserFriendlyRandomKey.php @@ -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; diff --git a/common/components/oauth/Storage/Redis/AuthCodeStorage.php b/common/components/oauth/Storage/Redis/AuthCodeStorage.php index 204027d..f3bdbdc 100644 --- a/common/components/oauth/Storage/Redis/AuthCodeStorage.php +++ b/common/components/oauth/Storage/Redis/AuthCodeStorage.php @@ -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: нужно проверить все выданные скоупы на их существование diff --git a/common/components/oauth/Storage/Yii2/AccessTokenStorage.php b/common/components/oauth/Storage/Yii2/AccessTokenStorage.php index c2b757f..03cc89a 100644 --- a/common/components/oauth/Storage/Yii2/AccessTokenStorage.php +++ b/common/components/oauth/Storage/Yii2/AccessTokenStorage.php @@ -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.'); diff --git a/common/components/oauth/Storage/Yii2/SessionStorage.php b/common/components/oauth/Storage/Yii2/SessionStorage.php index 0686977..1542391 100644 --- a/common/components/oauth/Storage/Yii2/SessionStorage.php +++ b/common/components/oauth/Storage/Yii2/SessionStorage.php @@ -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.'); diff --git a/common/components/oauth/Util/KeyAlgorithm/UuidAlgorithm.php b/common/components/oauth/Util/KeyAlgorithm/UuidAlgorithm.php index 9fd5c72..e75580d 100644 --- a/common/components/oauth/Util/KeyAlgorithm/UuidAlgorithm.php +++ b/common/components/oauth/Util/KeyAlgorithm/UuidAlgorithm.php @@ -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 diff --git a/common/helpers/Amqp.php b/common/helpers/Amqp.php index 3b1f321..f0d7243 100644 --- a/common/helpers/Amqp.php +++ b/common/helpers/Amqp.php @@ -2,7 +2,6 @@ namespace common\helpers; use common\components\RabbitMQ\Helper; -use Yii; class Amqp extends Helper { diff --git a/common/mail/layouts/text.php b/common/mail/layouts/text.php index 7087cea..0b07007 100644 --- a/common/mail/layouts/text.php +++ b/common/mail/layouts/text.php @@ -1,9 +1,10 @@ beginPage() ?> beginBody() ?> diff --git a/common/models/Account.php b/common/models/Account.php index 4227e9d..2bc1e93 100644 --- a/common/models/Account.php +++ b/common/models/Account.php @@ -1,10 +1,7 @@ '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 * diff --git a/common/models/OauthAccessToken.php b/common/models/OauthAccessToken.php index 8a2dff5..b253ef2 100644 --- a/common/models/OauthAccessToken.php +++ b/common/models/OauthAccessToken.php @@ -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() { diff --git a/common/models/OauthClient.php b/common/models/OauthClient.php index 8bec648..58b444b 100644 --- a/common/models/OauthClient.php +++ b/common/models/OauthClient.php @@ -1,7 +1,6 @@ getDb()->getSchema()->getRawTableName($this->tableName()), $this->id, 'scopes'); + return new Set(static::getDb()->getSchema()->getRawTableName(static::tableName()), $this->id, 'scopes'); } public function beforeDelete() { diff --git a/common/models/Textures.php b/common/models/Textures.php index 761e227..3bb3333 100644 --- a/common/models/Textures.php +++ b/common/models/Textures.php @@ -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); } } diff --git a/common/validators/EmailValidator.php b/common/validators/EmailValidator.php new file mode 100644 index 0000000..4309352 --- /dev/null +++ b/common/validators/EmailValidator.php @@ -0,0 +1,64 @@ + 'trim']); + + $required = new validators\RequiredValidator(); + $required->message = E::EMAIL_REQUIRED; + + $length = new validators\StringValidator(); + $length->max = 255; + $length->tooLong = E::EMAIL_TOO_LONG; + + $email = new validators\EmailValidator(); + $email->checkDNS = true; + $email->enableIDN = true; + $email->message = E::EMAIL_INVALID; + + $tempmail = new TempmailValidator(); + $tempmail->message = E::EMAIL_IS_TEMPMAIL; + + $unique = new validators\UniqueValidator(); + $unique->message = E::EMAIL_NOT_AVAILABLE; + $unique->targetClass = Account::class; + $unique->targetAttribute = 'email'; + if ($this->accountCallback !== null) { + $unique->filter = function(QueryInterface $query) { + $query->andWhere(['NOT', ['id' => ($this->accountCallback)()]]); + }; + } + + $this->executeValidation($filter, $model, $attribute) && + $this->executeValidation($required, $model, $attribute) && + $this->executeValidation($length, $model, $attribute) && + $this->executeValidation($email, $model, $attribute) && + $this->executeValidation($tempmail, $model, $attribute) && + $this->executeValidation($unique, $model, $attribute); + } + + protected function executeValidation(Validator $validator, Model $model, string $attribute) { + $validator->validateAttribute($model, $attribute); + + return !$model->hasErrors($attribute); + } + +} diff --git a/common/validators/PasswordValidate.php b/common/validators/PasswordValidator.php similarity index 86% rename from common/validators/PasswordValidate.php rename to common/validators/PasswordValidator.php index b4391f7..2b9c047 100644 --- a/common/validators/PasswordValidate.php +++ b/common/validators/PasswordValidator.php @@ -7,7 +7,7 @@ use yii\validators\StringValidator; /** * Класс должен реализовывать в себе все критерии валидации пароля пользователя */ -class PasswordValidate extends StringValidator { +class PasswordValidator extends StringValidator { public $min = 8; diff --git a/common/validators/UsernameValidator.php b/common/validators/UsernameValidator.php new file mode 100644 index 0000000..7f5b76d --- /dev/null +++ b/common/validators/UsernameValidator.php @@ -0,0 +1,59 @@ + '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); + } + +} diff --git a/common/validators/UuidValidator.php b/common/validators/UuidValidator.php index e0ccced..1103bf7 100644 --- a/common/validators/UuidValidator.php +++ b/common/validators/UuidValidator.php @@ -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, []); + } } } diff --git a/composer.json b/composer.json index df059f8..fb6029c 100644 --- a/composer.json +++ b/composer.json @@ -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", diff --git a/console/controllers/AccountQueueController.php b/console/controllers/AccountQueueController.php index 72e7bcb..fe3d910 100644 --- a/console/controllers/AccountQueueController.php +++ b/console/controllers/AccountQueueController.php @@ -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(); + } + } diff --git a/console/controllers/base/AmqpController.php b/console/controllers/AmqpController.php similarity index 94% rename from console/controllers/base/AmqpController.php rename to console/controllers/AmqpController.php index 9828298..3a54ed7 100644 --- a/console/controllers/base/AmqpController.php +++ b/console/controllers/AmqpController.php @@ -1,5 +1,5 @@ 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]); + } + +} diff --git a/console/migrations/m161104_150634_accounts_uuid_index.php b/console/migrations/m161104_150634_accounts_uuid_index.php new file mode 100644 index 0000000..945acf0 --- /dev/null +++ b/console/migrations/m161104_150634_accounts_uuid_index.php @@ -0,0 +1,15 @@ +createIndex('uuid', '{{%accounts}}', 'uuid', true); + } + + public function safeDown() { + $this->dropColumn('{{%accounts}}', 'uuid'); + } + +} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 01589ad..1d59c03 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,7 +1,7 @@ version: '2' services: app: - build: . + image: registry.ely.by/elyby/accounts:latest depends_on: - db - redis diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh index 12ad553..f6723cb 100644 --- a/docker/php/entrypoint.sh +++ b/docker/php/entrypoint.sh @@ -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 "$@" diff --git a/tests/codeception/api/functional.suite.yml b/tests/codeception/api/functional.suite.yml index 4669215..232c933 100644 --- a/tests/codeception/api/functional.suite.yml +++ b/tests/codeception/api/functional.suite.yml @@ -12,6 +12,7 @@ modules: config: Yii2: configFile: '../config/api/functional.php' + cleanup: true Redis: host: testredis port: 6379 diff --git a/tests/codeception/api/functional/AccountsChangeEmailConfirmNewEmailCest.php b/tests/codeception/api/functional/AccountsChangeEmailConfirmNewEmailCest.php index 40fd8eb..0b8016d 100644 --- a/tests/codeception/api/functional/AccountsChangeEmailConfirmNewEmailCest.php +++ b/tests/codeception/api/functional/AccountsChangeEmailConfirmNewEmailCest.php @@ -1,7 +1,6 @@ 'Admin', 'email' => 'admin@ely.by', 'lang' => 'en', - 'shouldChangePassword' => false, 'isActive' => true, 'hasMojangUsernameCollision' => false, 'shouldAcceptRules' => false, diff --git a/tests/codeception/api/functional/RegisterCest.php b/tests/codeception/api/functional/RegisterCest.php index 210afb3..4730d8f 100644 --- a/tests/codeception/api/functional/RegisterCest.php +++ b/tests/codeception/api/functional/RegisterCest.php @@ -1,7 +1,6 @@ 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]); diff --git a/tests/codeception/api/functional/_bootstrap.php b/tests/codeception/api/functional/_bootstrap.php index d9488ac..b3d9bbc 100644 --- a/tests/codeception/api/functional/_bootstrap.php +++ b/tests/codeception/api/functional/_bootstrap.php @@ -1,4 +1 @@ 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(); diff --git a/tests/codeception/api/functional/sessionserver/JoinLegacyCest.php b/tests/codeception/api/functional/sessionserver/JoinLegacyCest.php index 7818870..e82695a 100644 --- a/tests/codeception/api/functional/sessionserver/JoinLegacyCest.php +++ b/tests/codeception/api/functional/sessionserver/JoinLegacyCest.php @@ -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'); diff --git a/tests/codeception/api/unit.suite.yml b/tests/codeception/api/unit.suite.yml index 1ba1eb4..8ce2d31 100644 --- a/tests/codeception/api/unit.suite.yml +++ b/tests/codeception/api/unit.suite.yml @@ -1 +1,8 @@ class_name: UnitTester +modules: + enabled: + - Yii2: + part: [orm, email, fixtures] + config: + Yii2: + configFile: '../config/api/unit.php' diff --git a/tests/codeception/api/unit/DbTestCase.php b/tests/codeception/api/unit/DbTestCase.php deleted file mode 100644 index f42013b..0000000 --- a/tests/codeception/api/unit/DbTestCase.php +++ /dev/null @@ -1,9 +0,0 @@ -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') { diff --git a/tests/codeception/api/unit/filters/ActiveUserRuleTest.php b/tests/codeception/api/unit/filters/ActiveUserRuleTest.php index faa5b20..5a44e92 100644 --- a/tests/codeception/api/unit/filters/ActiveUserRuleTest.php +++ b/tests/codeception/api/unit/filters/ActiveUserRuleTest.php @@ -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; diff --git a/tests/codeception/api/unit/models/AccountIdentityTest.php b/tests/codeception/api/unit/models/AccountIdentityTest.php index b501604..819787d 100644 --- a/tests/codeception/api/unit/models/AccountIdentityTest.php +++ b/tests/codeception/api/unit/models/AccountIdentityTest.php @@ -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); diff --git a/tests/codeception/api/unit/models/FeedbackFormTest.php b/tests/codeception/api/unit/models/FeedbackFormTest.php index 82a5409..b8f76eb 100644 --- a/tests/codeception/api/unit/models/FeedbackFormTest.php +++ b/tests/codeception/api/unit/models/FeedbackFormTest.php @@ -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); } } diff --git a/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php b/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php index 4e62a6c..1fd318b 100644 --- a/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php +++ b/tests/codeception/api/unit/models/authentication/ConfirmEmailFormTest.php @@ -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); - }); - } - } diff --git a/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php b/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php index cd9f568..dc68fb5 100644 --- a/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php +++ b/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php @@ -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 diff --git a/tests/codeception/api/unit/models/authentication/LoginFormTest.php b/tests/codeception/api/unit/models/authentication/LoginFormTest.php index 625b08b..e32f243 100644 --- a/tests/codeception/api/unit/models/authentication/LoginFormTest.php +++ b/tests/codeception/api/unit/models/authentication/LoginFormTest.php @@ -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); diff --git a/tests/codeception/api/unit/models/authentication/LogoutFormTest.php b/tests/codeception/api/unit/models/authentication/LogoutFormTest.php index 6cfbc92..cd0c05a 100644 --- a/tests/codeception/api/unit/models/authentication/LogoutFormTest.php +++ b/tests/codeception/api/unit/models/authentication/LogoutFormTest.php @@ -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() { diff --git a/tests/codeception/api/unit/models/authentication/RecoverPasswordFormTest.php b/tests/codeception/api/unit/models/authentication/RecoverPasswordFormTest.php index 3e5aec7..530ecb1 100644 --- a/tests/codeception/api/unit/models/authentication/RecoverPasswordFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RecoverPasswordFormTest.php @@ -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')); } } diff --git a/tests/codeception/api/unit/models/authentication/RefreshTokenFormTest.php b/tests/codeception/api/unit/models/authentication/RefreshTokenFormTest.php index f12db8a..5aab389 100644 --- a/tests/codeception/api/unit/models/authentication/RefreshTokenFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RefreshTokenFormTest.php @@ -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()); } } diff --git a/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php b/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php index 0a1313f..faa2cc2 100644 --- a/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php @@ -1,53 +1,34 @@ 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'; - } - } diff --git a/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php b/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php index e132ace..e6fe403 100644 --- a/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php @@ -1,50 +1,30 @@ 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 diff --git a/tests/codeception/api/unit/models/base/ApiFormTest.php b/tests/codeception/api/unit/models/base/ApiFormTest.php index c2ea2e4..afac6c1 100644 --- a/tests/codeception/api/unit/models/base/ApiFormTest.php +++ b/tests/codeception/api/unit/models/base/ApiFormTest.php @@ -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'); } } diff --git a/tests/codeception/api/unit/models/base/KeyConfirmationFormTest.php b/tests/codeception/api/unit/models/base/KeyConfirmationFormTest.php index 4a93568..6a06378 100644 --- a/tests/codeception/api/unit/models/base/KeyConfirmationFormTest.php +++ b/tests/codeception/api/unit/models/base/KeyConfirmationFormTest.php @@ -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()); } } diff --git a/tests/codeception/api/unit/models/profile/AcceptRulesFormTest.php b/tests/codeception/api/unit/models/profile/AcceptRulesFormTest.php index 2105a40..591d640 100644 --- a/tests/codeception/api/unit/models/profile/AcceptRulesFormTest.php +++ b/tests/codeception/api/unit/models/profile/AcceptRulesFormTest.php @@ -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); } } diff --git a/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php b/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php index 6ef0dc0..68ab6c4 100644 --- a/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangeEmail/ConfirmNewEmailFormTest.php @@ -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); } } diff --git a/tests/codeception/api/unit/models/profile/ChangeEmail/InitStateFormTest.php b/tests/codeception/api/unit/models/profile/ChangeEmail/InitStateFormTest.php index 8428bcf..e073115 100644 --- a/tests/codeception/api/unit/models/profile/ChangeEmail/InitStateFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangeEmail/InitStateFormTest.php @@ -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(); } } diff --git a/tests/codeception/api/unit/models/profile/ChangeEmail/NewEmailFormTest.php b/tests/codeception/api/unit/models/profile/ChangeEmail/NewEmailFormTest.php index f3679a0..a34c842 100644 --- a/tests/codeception/api/unit/models/profile/ChangeEmail/NewEmailFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangeEmail/NewEmailFormTest.php @@ -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(); } } diff --git a/tests/codeception/api/unit/models/profile/ChangeLanguageFormTest.php b/tests/codeception/api/unit/models/profile/ChangeLanguageFormTest.php index eaa09c2..287eea7 100644 --- a/tests/codeception/api/unit/models/profile/ChangeLanguageFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangeLanguageFormTest.php @@ -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); } } diff --git a/tests/codeception/api/unit/models/profile/ChangePasswordFormTest.php b/tests/codeception/api/unit/models/profile/ChangePasswordFormTest.php index a0a197d..e5d33da 100644 --- a/tests/codeception/api/unit/models/profile/ChangePasswordFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangePasswordFormTest.php @@ -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', diff --git a/tests/codeception/api/unit/models/profile/ChangeUsernameFormTest.php b/tests/codeception/api/unit/models/profile/ChangeUsernameFormTest.php index 71ad483..a41dd75 100644 --- a/tests/codeception/api/unit/models/profile/ChangeUsernameFormTest.php +++ b/tests/codeception/api/unit/models/profile/ChangeUsernameFormTest.php @@ -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']; } } diff --git a/tests/codeception/api/unit/modules/authserver/models/AuthenticationFormTest.php b/tests/codeception/api/unit/modules/authserver/models/AuthenticationFormTest.php index bb3be9e..c7b4c5d 100644 --- a/tests/codeception/api/unit/modules/authserver/models/AuthenticationFormTest.php +++ b/tests/codeception/api/unit/modules/authserver/models/AuthenticationFormTest.php @@ -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)); } diff --git a/tests/codeception/api/unit/traits/AccountFinderTest.php b/tests/codeception/api/unit/traits/AccountFinderTest.php index 2b09d42..dcba39f 100644 --- a/tests/codeception/api/unit/traits/AccountFinderTest.php +++ b/tests/codeception/api/unit/traits/AccountFinderTest.php @@ -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() { diff --git a/tests/codeception/api/unit/traits/ApiNormalizerTest.php b/tests/codeception/api/unit/traits/ApiNormalizerTest.php index 9700be0..5641478 100644 --- a/tests/codeception/api/unit/traits/ApiNormalizerTest.php +++ b/tests/codeception/api/unit/traits/ApiNormalizerTest.php @@ -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); } } diff --git a/tests/codeception/api/unit/validators/EmailActivationKeyValidatorTest.php b/tests/codeception/api/unit/validators/EmailActivationKeyValidatorTest.php index bf7baa5..9089edc 100644 --- a/tests/codeception/api/unit/validators/EmailActivationKeyValidatorTest.php +++ b/tests/codeception/api/unit/validators/EmailActivationKeyValidatorTest.php @@ -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); diff --git a/tests/codeception/api/unit/validators/PasswordRequiredValidatorTest.php b/tests/codeception/api/unit/validators/PasswordRequiredValidatorTest.php index cdbcbc5..f036f14 100644 --- a/tests/codeception/api/unit/validators/PasswordRequiredValidatorTest.php +++ b/tests/codeception/api/unit/validators/PasswordRequiredValidatorTest.php @@ -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; diff --git a/tests/codeception/common/_support/FixtureHelper.php b/tests/codeception/common/_support/FixtureHelper.php index 5d198df..6e5dfd0 100644 --- a/tests/codeception/common/_support/FixtureHelper.php +++ b/tests/codeception/common/_support/FixtureHelper.php @@ -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, ]; } diff --git a/tests/codeception/common/fixtures/MojangUsernameFixture.php b/tests/codeception/common/fixtures/MojangUsernameFixture.php index b27ef0c..c0e53d7 100644 --- a/tests/codeception/common/fixtures/MojangUsernameFixture.php +++ b/tests/codeception/common/fixtures/MojangUsernameFixture.php @@ -8,4 +8,6 @@ class MojangUsernameFixture extends ActiveFixture { public $modelClass = MojangUsername::class; + public $dataFile = '@tests/codeception/common/fixtures/data/mojang-usernames.php'; + } diff --git a/tests/codeception/common/fixtures/OauthClientFixture.php b/tests/codeception/common/fixtures/OauthClientFixture.php index e6dee18..5b0385f 100644 --- a/tests/codeception/common/fixtures/OauthClientFixture.php +++ b/tests/codeception/common/fixtures/OauthClientFixture.php @@ -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, ]; diff --git a/tests/codeception/common/fixtures/OauthSessionFixture.php b/tests/codeception/common/fixtures/OauthSessionFixture.php index 77adb2a..0bbab87 100644 --- a/tests/codeception/common/fixtures/OauthSessionFixture.php +++ b/tests/codeception/common/fixtures/OauthSessionFixture.php @@ -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, diff --git a/tests/codeception/common/unit.suite.yml b/tests/codeception/common/unit.suite.yml index a0582a5..d072b09 100644 --- a/tests/codeception/common/unit.suite.yml +++ b/tests/codeception/common/unit.suite.yml @@ -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' diff --git a/tests/codeception/common/unit/DbTestCase.php b/tests/codeception/common/unit/DbTestCase.php deleted file mode 100644 index 2159a69..0000000 --- a/tests/codeception/common/unit/DbTestCase.php +++ /dev/null @@ -1,11 +0,0 @@ -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() { diff --git a/tests/codeception/common/unit/behaviors/EmailActivationExpirationBehaviorTest.php b/tests/codeception/common/unit/behaviors/EmailActivationExpirationBehaviorTest.php index 5fbd663..5078acb 100644 --- a/tests/codeception/common/unit/behaviors/EmailActivationExpirationBehaviorTest.php +++ b/tests/codeception/common/unit/behaviors/EmailActivationExpirationBehaviorTest.php @@ -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() { diff --git a/tests/codeception/common/unit/components/Mojang/ApiTest.php b/tests/codeception/common/unit/components/Mojang/ApiTest.php new file mode 100644 index 0000000..821bebc --- /dev/null +++ b/tests/codeception/common/unit/components/Mojang/ApiTest.php @@ -0,0 +1,54 @@ +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'); + } + +} diff --git a/tests/codeception/common/unit/models/AccountSessionTest.php b/tests/codeception/common/unit/models/AccountSessionTest.php index 66a8f14..0e98803 100644 --- a/tests/codeception/common/unit/models/AccountSessionTest.php +++ b/tests/codeception/common/unit/models/AccountSessionTest.php @@ -1,35 +1,27 @@ 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'); } } diff --git a/tests/codeception/common/unit/models/AccountTest.php b/tests/codeception/common/unit/models/AccountTest.php index 8bdb07d..afb5de9 100644 --- a/tests/codeception/common/unit/models/AccountTest.php +++ b/tests/codeception/common/unit/models/AccountTest.php @@ -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'; diff --git a/tests/codeception/common/unit/models/EmailActivationTest.php b/tests/codeception/common/unit/models/EmailActivationTest.php index 8880e2d..d436b23 100644 --- a/tests/codeception/common/unit/models/EmailActivationTest.php +++ b/tests/codeception/common/unit/models/EmailActivationTest.php @@ -1,31 +1,35 @@ 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, + ])); } } diff --git a/tests/codeception/common/unit/validators/EmailValidatorTest.php b/tests/codeception/common/unit/validators/EmailValidatorTest.php new file mode 100644 index 0000000..4bf7256 --- /dev/null +++ b/tests/codeception/common/unit/validators/EmailValidatorTest.php @@ -0,0 +1,109 @@ +validator = new EmailValidator(); + } + + public function testValidateAttributeRequired() { + $model = $this->createModel(''); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_required'], $model->getErrors('field')); + + $model = $this->createModel('email'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_required'], $model->getErrors('field')); + } + + public function testValidateAttributeLength() { + $model = $this->createModel( + 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . + 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . + 'emailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemailemail' . + '@gmail.com' // = 256 symbols + ); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_too_long'], $model->getErrors('field')); + + $model = $this->createModel('some-email@gmail.com'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_too_long'], $model->getErrors('field')); + } + + public function testValidateAttributeEmail() { + $model = $this->createModel('non-email'); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_invalid'], $model->getErrors('field')); + + $model = $this->createModel('non-email@etot-domen-ne-suschestrvyet.de'); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_invalid'], $model->getErrors('field')); + + $model = $this->createModel('valid-email@gmail.com'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_invalid'], $model->getErrors('field')); + } + + public function testValidateAttributeTempmail() { + $model = $this->createModel('ibrpycwyjdnt@dropmail.me'); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_is_tempmail'], $model->getErrors('field')); + + $model = $this->createModel('valid-email@gmail.com'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_is_tempmail'], $model->getErrors('field')); + } + + public function testValidateAttributeUnique() { + $this->tester->haveFixtures([ + 'accounts' => AccountFixture::class, + ]); + + /** @var \common\models\Account $accountFixture */ + $accountFixture = $this->tester->grabFixture('accounts', 'admin'); + + $model = $this->createModel($accountFixture->email); + $this->validator->validateAttribute($model, 'field'); + $this->assertEquals(['error.email_not_available'], $model->getErrors('field')); + + $model = $this->createModel($accountFixture->email); + $this->validator->accountCallback = function() use ($accountFixture) { + return $accountFixture->id; + }; + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_not_available'], $model->getErrors('field')); + $this->validator->accountCallback = null; + + $model = $this->createModel('some-unique-email@gmail.com'); + $this->validator->validateAttribute($model, 'field'); + $this->assertNotEquals(['error.email_not_available'], $model->getErrors('field')); + } + + /** + * @param string $fieldValue + * @return Model + */ + private function createModel(string $fieldValue) : Model { + $class = new class extends Model { + public $field; + }; + + $class->field = $fieldValue; + + return $class; + } + +} diff --git a/tests/codeception/common/unit/validators/LanguageValidatorTest.php b/tests/codeception/common/unit/validators/LanguageValidatorTest.php index 5d1b0ea..e40e72c 100644 --- a/tests/codeception/common/unit/validators/LanguageValidatorTest.php +++ b/tests/codeception/common/unit/validators/LanguageValidatorTest.php @@ -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')); } /** diff --git a/tests/codeception/common/unit/validators/UsernameValidatorTest.php b/tests/codeception/common/unit/validators/UsernameValidatorTest.php new file mode 100644 index 0000000..34ed86e --- /dev/null +++ b/tests/codeception/common/unit/validators/UsernameValidatorTest.php @@ -0,0 +1,106 @@ +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; + } + +} diff --git a/tests/codeception/common/unit/validators/UuidValidatorTest.php b/tests/codeception/common/unit/validators/UuidValidatorTest.php index ef90735..d41775e 100644 --- a/tests/codeception/common/unit/validators/UuidValidatorTest.php +++ b/tests/codeception/common/unit/validators/UuidValidatorTest.php @@ -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; + }; } } diff --git a/tests/codeception/console/unit.suite.yml b/tests/codeception/console/unit.suite.yml index a0582a5..3ac6f10 100644 --- a/tests/codeception/console/unit.suite.yml +++ b/tests/codeception/console/unit.suite.yml @@ -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' diff --git a/tests/codeception/console/unit/DbTestCase.php b/tests/codeception/console/unit/DbTestCase.php deleted file mode 100644 index a841790..0000000 --- a/tests/codeception/console/unit/DbTestCase.php +++ /dev/null @@ -1,8 +0,0 @@ - [ - 'class' => AccountFixture::class, - 'dataFile' => '@tests/codeception/common/fixtures/data/accounts.php', - ], - 'mojangUsernames' => [ - 'class' => MojangUsernameFixture::class, - 'dataFile' => '@tests/codeception/common/fixtures/data/mojang-usernames.php', - ], + 'accounts' => AccountFixture::class, + 'mojangUsernames' => MojangUsernameFixture::class, ]; } - public function testRouteUsernameChanged() { - // TODO: пропустить тест, если у нас нету интернета - $controller = new AccountQueueController('account-queue', Yii::$app); - $this->specify('Update last_pulled_at time if username exists', function() use ($controller) { - $accountInfo = $this->accounts['admin']; - $body = new UsernameChanged([ - 'accountId' => $accountInfo['id'], - 'oldUsername' => $accountInfo['username'], - 'newUsername' => 'Notch', - ]); - $controller->routeUsernameChanged($body); - /** @var MojangUsername|null $mojangUsername */ - $mojangUsername = MojangUsername::findOne('Notch'); - expect($mojangUsername)->isInstanceOf(MojangUsername::class); - expect($mojangUsername->last_pulled_at)->greaterThan($this->mojangUsernames['Notch']['last_pulled_at']); - expect($mojangUsername->last_pulled_at)->lessOrEquals(time()); - }); + public function _before() { + parent::_before(); - $this->specify('Add new MojangUsername if don\'t exists', function() use ($controller) { - $accountInfo = $this->accounts['admin']; - $body = new UsernameChanged([ - 'accountId' => $accountInfo['id'], - 'oldUsername' => $accountInfo['username'], - 'newUsername' => 'Chest', - ]); - $controller->routeUsernameChanged($body); - /** @var MojangUsername|null $mojangUsername */ - $mojangUsername = MojangUsername::findOne('Chest'); - expect($mojangUsername)->isInstanceOf(MojangUsername::class); - }); + /** @var AccountQueueController|\PHPUnit_Framework_MockObject_MockObject $controller */ + $controller = $this->getMockBuilder(AccountQueueController::class) + ->setMethods(['createMojangApi']) + ->setConstructorArgs(['account-queue', Yii::$app]) + ->getMock(); - $this->specify('Remove MojangUsername, if now it\'s does\'t exists', function() use ($controller) { - $accountInfo = $this->accounts['admin']; - $username = $this->mojangUsernames['not-exists']['username']; - $body = new UsernameChanged([ - 'accountId' => $accountInfo['id'], - 'oldUsername' => $accountInfo['username'], - 'newUsername' => $username, - ]); - $controller->routeUsernameChanged($body); - /** @var MojangUsername|null $mojangUsername */ - $mojangUsername = MojangUsername::findOne($username); - expect($mojangUsername)->null(); - }); + /** @var Api|\PHPUnit_Framework_MockObject_MockObject $apiMock */ + $apiMock = $this->getMockBuilder(Api::class) + ->setMethods(['usernameToUUID']) + ->getMock(); - $this->specify('Update uuid if username for now owned by other player', function() use ($controller) { - $accountInfo = $this->accounts['admin']; - $mojangInfo = $this->mojangUsernames['uuid-changed']; - $username = $mojangInfo['username']; - $body = new UsernameChanged([ - 'accountId' => $accountInfo['id'], - 'oldUsername' => $accountInfo['username'], - 'newUsername' => $username, - ]); - $controller->routeUsernameChanged($body); - /** @var MojangUsername|null $mojangUsername */ - $mojangUsername = MojangUsername::findOne($username); - expect($mojangUsername)->isInstanceOf(MojangUsername::class); - expect($mojangUsername->uuid)->notEquals($mojangInfo['uuid']); - }); + $apiMock + ->expects($this->any()) + ->method('usernameToUUID') + ->willReturnCallback(function() { + if ($this->expectedResponse === false) { + throw new NoContentException(); + } else { + return $this->expectedResponse; + } + }); + + $controller + ->expects($this->any()) + ->method('createMojangApi') + ->willReturn($apiMock); + + $this->controller = $controller; + } + + public function testRouteUsernameChangedUsernameExists() { + $expectedResponse = new UsernameToUUIDResponse(); + $expectedResponse->id = '069a79f444e94726a5befca90e38aaf5'; + $expectedResponse->name = 'Notch'; + $this->expectedResponse = $expectedResponse; + + /** @var \common\models\Account $accountInfo */ + $accountInfo = $this->tester->grabFixture('accounts', 'admin'); + /** @var MojangUsername $mojangUsernameFixture */ + $mojangUsernameFixture = $this->tester->grabFixture('mojangUsernames', 'Notch'); + $body = new UsernameChanged([ + 'accountId' => $accountInfo->id, + 'oldUsername' => $accountInfo->username, + 'newUsername' => 'Notch', + ]); + $this->controller->routeUsernameChanged($body); + /** @var MojangUsername|null $mojangUsername */ + $mojangUsername = MojangUsername::findOne('Notch'); + $this->assertInstanceOf(MojangUsername::class, $mojangUsername); + $this->assertGreaterThan($mojangUsernameFixture->last_pulled_at, $mojangUsername->last_pulled_at); + $this->assertLessThanOrEqual(time(), $mojangUsername->last_pulled_at); + } + + public function testRouteUsernameChangedUsernameNotExists() { + $expectedResponse = new UsernameToUUIDResponse(); + $expectedResponse->id = '607153852b8c4909811f507ed8ee737f'; + $expectedResponse->name = 'Chest'; + $this->expectedResponse = $expectedResponse; + + /** @var \common\models\Account $accountInfo */ + $accountInfo = $this->tester->grabFixture('accounts', 'admin'); + $body = new UsernameChanged([ + 'accountId' => $accountInfo['id'], + 'oldUsername' => $accountInfo['username'], + 'newUsername' => 'Chest', + ]); + $this->controller->routeUsernameChanged($body); + /** @var MojangUsername|null $mojangUsername */ + $mojangUsername = MojangUsername::findOne('Chest'); + $this->assertInstanceOf(MojangUsername::class, $mojangUsername); + } + + public function testRouteUsernameChangedRemoveIfExistsNoMore() { + $this->expectedResponse = false; + + /** @var \common\models\Account $accountInfo */ + $accountInfo = $this->tester->grabFixture('accounts', 'admin'); + $username = $this->tester->grabFixture('mojangUsernames', 'not-exists')['username']; + $body = new UsernameChanged([ + 'accountId' => $accountInfo['id'], + 'oldUsername' => $accountInfo['username'], + 'newUsername' => $username, + ]); + $this->controller->routeUsernameChanged($body); + /** @var MojangUsername|null $mojangUsername */ + $mojangUsername = MojangUsername::findOne($username); + $this->assertNull($mojangUsername); + } + + public function testRouteUsernameChangedUuidUpdated() { + $expectedResponse = new UsernameToUUIDResponse(); + $expectedResponse->id = 'f498513ce8c84773be26ecfc7ed5185d'; + $expectedResponse->name = 'jeb'; + $this->expectedResponse = $expectedResponse; + + /** @var \common\models\Account $accountInfo */ + $accountInfo = $this->tester->grabFixture('accounts', 'admin'); + /** @var MojangUsername $mojangInfo */ + $mojangInfo = $this->tester->grabFixture('mojangUsernames', 'uuid-changed'); + $username = $mojangInfo['username']; + $body = new UsernameChanged([ + 'accountId' => $accountInfo['id'], + 'oldUsername' => $accountInfo['username'], + 'newUsername' => $username, + ]); + $this->controller->routeUsernameChanged($body); + /** @var MojangUsername|null $mojangUsername */ + $mojangUsername = MojangUsername::findOne($username); + $this->assertInstanceOf(MojangUsername::class, $mojangUsername); + $this->assertNotEquals($mojangInfo->uuid, $mojangUsername->uuid); } } diff --git a/tests/codeception/console/unit/fixtures/data/.gitkeep b/tests/codeception/console/unit/fixtures/data/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index e7ea80c..4c2d96d 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -2,6 +2,7 @@ version: '2' services: testphp: container_name: accountelyby_testphp + image: account_testphp build: context: ../ dockerfile: Dockerfile-dev @@ -10,7 +11,10 @@ services: - testredis - testrabbit volumes: - - ./../:/var/www/html/ + - ./codeception/_output:/var/www/html/tests/codeception/_output + - ./codeception/api/_output:/var/www/html/tests/codeception/api/_output + - ./codeception/common/_output:/var/www/html/tests/codeception/common/_output + - ./codeception/console/_output:/var/www/html/tests/codeception/console/_output environment: - YII_DEBUG=true - YII_ENV=test @@ -20,6 +24,7 @@ services: testdb: container_name: accountelyby_testdb + image: account_testdb build: ./../docker/mariadb environment: MYSQL_ROOT_PASSWORD: ""