Merge branch 'develop'

This commit is contained in:
ErickSkrauch 2017-06-11 21:27:51 +03:00
commit d3219ac7b1
57 changed files with 942 additions and 369 deletions

View File

@ -2,14 +2,20 @@
namespace api\components\ReCaptcha;
use common\helpers\Error as E;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
use Psr\Http\Message\ResponseInterface;
use Yii;
use yii\base\Exception;
use yii\base\InvalidConfigException;
use yii\di\Instance;
class Validator extends \yii\validators\Validator {
const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
private const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';
private const REPEAT_LIMIT = 3;
private const REPEAT_TIMEOUT = 1;
public $skipOnEmpty = false;
@ -17,15 +23,21 @@ class Validator extends \yii\validators\Validator {
public $requiredMessage = E::CAPTCHA_REQUIRED;
/**
* @var Component|string
*/
public $component = 'reCaptcha';
private $client;
public function __construct(ClientInterface $client, array $config = []) {
parent::__construct($config);
$this->client = $client;
}
public function init() {
parent::init();
if ($this->getComponent() === null) {
throw new InvalidConfigException('Required "reCaptcha" component as instance of ' . Component::class . '.');
}
$this->when = function() {
return !YII_ENV_TEST;
};
$this->component = Instance::ensure($this->component, Component::class);
}
/**
@ -36,31 +48,47 @@ class Validator extends \yii\validators\Validator {
return [$this->requiredMessage, []];
}
$response = $this->createClient()->post(self::SITE_VERIFY_URL, [
'form_params' => [
'secret' => $this->getComponent()->secret,
'response' => $value,
'remoteip' => Yii::$app->getRequest()->getUserIP(),
],
]);
$data = json_decode($response->getBody(), true);
$repeats = 0;
do {
$isSuccess = true;
try {
$response = $this->performRequest($value);
} catch (ConnectException | ServerException $e) {
if (++$repeats >= self::REPEAT_LIMIT) {
throw $e;
}
$isSuccess = false;
sleep(self::REPEAT_TIMEOUT);
}
} while (!$isSuccess);
/** @noinspection PhpUndefinedVariableInspection */
$data = json_decode($response->getBody(), true);
if (!isset($data['success'])) {
throw new Exception('Invalid recaptcha verify response.');
}
return $data['success'] ? null : [$this->message, []];
if (!$data['success']) {
return [$this->message, []];
}
return null;
}
/**
* @return Component
* @param string $value
* @throws \GuzzleHttp\Exception\GuzzleException
* @return ResponseInterface
*/
protected function getComponent() {
return Yii::$app->reCaptcha;
}
protected function createClient() {
return new GuzzleClient();
protected function performRequest(string $value): ResponseInterface {
return $this->client->request('POST', self::SITE_VERIFY_URL, [
'form_params' => [
'secret' => $this->component->secret,
'response' => $value,
'remoteip' => Yii::$app->getRequest()->getUserIP(),
],
]);
}
}

View File

@ -61,6 +61,9 @@ return [
],
'request' => [
'baseUrl' => '/api',
'parsers' => [
'*' => api\request\RequestParser::class,
],
],
'urlManager' => [
'enablePrettyUrl' => true,

View File

@ -1,12 +0,0 @@
<?php
/**
* @var \common\models\Account $account
* @var string $key
*/
?>
This E-mail was specified as new for account <?= $account->username ?>. To confirm this E-mail, pass code below into form on site.
Code: <?= $key ?>
// P.S. yes, this is E-mail is not designed yet :)

View File

@ -42,7 +42,7 @@ class FeedbackForm extends ApiForm {
$account = $this->getAccount();
/** @var \yii\swiftmailer\Message $message */
$message = $mailer->compose('@app/mails/feedback', [
$message = $mailer->compose('@common/emails/views/feedback', [
'model' => $this,
'account' => $account,
]);

View File

@ -1,26 +1,30 @@
<?php
namespace api\models\authentication;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\base\ApiForm;
use api\validators\TotpValidator;
use common\emails\EmailHelper;
use common\helpers\Error as E;
use api\traits\AccountFinder;
use common\components\UserFriendlyRandomKey;
use common\models\Account;
use common\models\confirmations\ForgotPassword;
use common\models\EmailActivation;
use Yii;
use yii\base\ErrorException;
use yii\base\InvalidConfigException;
class ForgotPasswordForm extends ApiForm {
use AccountFinder;
public $captcha;
public $login;
public $token;
public function rules() {
return [
['captcha', ReCaptchaValidator::class],
['login', 'required', 'message' => E::LOGIN_REQUIRED],
['login', 'validateLogin'],
['token', 'required', 'when' => function(self $model) {
@ -92,41 +96,11 @@ class ForgotPasswordForm extends ApiForm {
throw new ErrorException('Cannot create email activation for forgot password form');
}
$this->sendMail($emailActivation);
EmailHelper::forgotPassword($emailActivation);
return true;
}
public function sendMail(EmailActivation $emailActivation) {
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$fromEmail = Yii::$app->params['fromEmail'];
if (!$fromEmail) {
throw new InvalidConfigException('Please specify fromEmail app in app params');
}
$account = $emailActivation->account;
$htmlBody = Yii::$app->emailRenderer->getTemplate('forgotPassword')
->setLocale($account->lang)
->setParams([
'username' => $account->username,
'code' => $emailActivation->key,
'link' => Yii::$app->request->getHostInfo() . '/recover-password/' . $emailActivation->key,
])
->render();
/** @var \yii\swiftmailer\Message $message */
$message = $mailer->compose()
->setHtmlBody($htmlBody)
->setTo([$account->email => $account->username])
->setFrom([$fromEmail => 'Ely.by Accounts'])
->setSubject('Ely.by Account forgot password');
if (!$message->send()) {
throw new ErrorException('Unable send email with activation code.');
}
}
public function getLogin() {
return $this->login;
}

View File

@ -2,12 +2,12 @@
namespace api\models\authentication;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use common\emails\EmailHelper;
use api\models\base\ApiForm;
use common\helpers\Error as E;
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;
@ -17,7 +17,6 @@ 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;
@ -103,7 +102,7 @@ class RegistrationForm extends ApiForm {
throw new ErrorException('Cannot save username history record');
}
$this->sendMail($emailActivation, $account);
EmailHelper::registration($emailActivation);
$transaction->commit();
} catch (Exception $e) {
@ -114,37 +113,6 @@ class RegistrationForm extends ApiForm {
return $account;
}
// TODO: подумать, чтобы вынести этот метод в какую-то отдельную конструкцию, т.к. используется и внутри NewAccountActivationForm
public function sendMail(EmailActivation $emailActivation, Account $account) {
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$fromEmail = Yii::$app->params['fromEmail'];
if (!$fromEmail) {
throw new InvalidConfigException('Please specify fromEmail app in app params');
}
$htmlBody = Yii::$app->emailRenderer->getTemplate('register')
->setLocale($account->lang)
->setParams([
'username' => $account->username,
'code' => $emailActivation->key,
'link' => Yii::$app->request->getHostInfo() . '/activation/' . $emailActivation->key,
])
->render();
/** @var \yii\swiftmailer\Message $message */
$message = $mailer->compose()
->setHtmlBody($htmlBody)
->setTo([$account->email => $account->username])
->setFrom([$fromEmail => 'Ely.by Accounts'])
->setSubject('Ely.by Account registration');
if (!$message->send()) {
throw new ErrorException('Unable send email with activation code.');
}
}
/**
* Метод проверяет, можно ли занять указанный при регистрации ник или e-mail. Так случается,
* что пользователи вводят неправильный e-mail или ник, после замечают это и пытаются вновь

View File

@ -2,6 +2,7 @@
namespace api\models\authentication;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use common\emails\EmailHelper;
use api\models\base\ApiForm;
use common\helpers\Error as E;
use common\components\UserFriendlyRandomKey;
@ -72,8 +73,7 @@ class RepeatAccountActivationForm extends ApiForm {
throw new ErrorException('Unable save email-activation model.');
}
$regForm = new RegistrationForm();
$regForm->sendMail($activation, $account);
EmailHelper::registration($activation);
$transaction->commit();
} catch (ErrorException $e) {

View File

@ -1,6 +1,7 @@
<?php
namespace api\models\profile\ChangeEmail;
use common\emails\EmailHelper;
use api\models\base\ApiForm;
use api\validators\PasswordRequiredValidator;
use common\helpers\Error as E;
@ -10,7 +11,6 @@ use common\models\EmailActivation;
use Yii;
use yii\base\ErrorException;
use yii\base\Exception;
use yii\base\InvalidConfigException;
class InitStateForm extends ApiForm {
@ -55,7 +55,8 @@ class InitStateForm extends ApiForm {
try {
$this->removeOldCode();
$activation = $this->createCode();
$this->sendCode($activation);
EmailHelper::changeEmailConfirmCurrent($activation);
$transaction->commit();
} catch (Exception $e) {
@ -93,29 +94,6 @@ class InitStateForm extends ApiForm {
$emailActivation->delete();
}
public function sendCode(EmailActivation $code) {
$mailer = Yii::$app->mailer;
$fromEmail = Yii::$app->params['fromEmail'];
if (!$fromEmail) {
throw new InvalidConfigException('Please specify fromEmail app in app params');
}
$acceptor = $code->account;
$message = $mailer->compose([
'html' => '@app/mails/current-email-confirmation-html',
'text' => '@app/mails/current-email-confirmation-text',
], [
'key' => $code->key,
])
->setTo([$acceptor->email => $acceptor->username])
->setFrom([$fromEmail => 'Ely.by Accounts'])
->setSubject('Ely.by Account change E-mail confirmation');
if (!$message->send()) {
throw new ErrorException('Unable send email with activation code.');
}
}
/**
* Возвращает E-mail активацию, которая использовалась внутри процесса для перехода на следующий шаг.
* Метод предназначен для проверки, не слишком ли часто отправляются письма о смене E-mail.

View File

@ -1,6 +1,7 @@
<?php
namespace api\models\profile\ChangeEmail;
use common\emails\EmailHelper;
use api\models\base\ApiForm;
use api\validators\EmailActivationKeyValidator;
use common\models\Account;
@ -9,7 +10,6 @@ use common\models\EmailActivation;
use common\validators\EmailValidator;
use Yii;
use yii\base\ErrorException;
use yii\base\InvalidConfigException;
class NewEmailForm extends ApiForm {
@ -45,7 +45,8 @@ class NewEmailForm extends ApiForm {
$previousActivation->delete();
$activation = $this->createCode();
$this->sendCode($activation);
EmailHelper::changeEmailConfirmNew($activation);
$transaction->commit();
@ -67,32 +68,6 @@ class NewEmailForm extends ApiForm {
return $emailActivation;
}
public function sendCode(EmailActivation $code) {
/** @var \yii\swiftmailer\Mailer $mailer */
$mailer = Yii::$app->mailer;
$fromEmail = Yii::$app->params['fromEmail'];
if (!$fromEmail) {
throw new InvalidConfigException('Please specify fromEmail app in app params');
}
$acceptor = $code->account;
/** @var \yii\swiftmailer\Message $message */
$message = $mailer->compose([
'html' => '@app/mails/new-email-confirmation-html',
'text' => '@app/mails/new-email-confirmation-text',
], [
'key' => $code->key,
'account' => $acceptor,
])
->setTo([$this->email => $acceptor->username])
->setFrom([$fromEmail => 'Ely.by Accounts'])
->setSubject('Ely.by Account new E-mail confirmation');
if (!$message->send()) {
throw new ErrorException('Unable send email with activation code.');
}
}
public function __construct(Account $account, array $config = []) {
$this->account = $account;
parent::__construct($config);

View File

@ -3,6 +3,7 @@ namespace api\modules\authserver\controllers;
use api\controllers\Controller;
use api\modules\authserver\models;
use Yii;
class AuthenticationController extends Controller {
@ -25,21 +26,21 @@ class AuthenticationController extends Controller {
public function actionAuthenticate() {
$model = new models\AuthenticationForm();
$model->loadByPost();
$model->load(Yii::$app->request->post());
return $model->authenticate()->getResponseData(true);
}
public function actionRefresh() {
$model = new models\RefreshTokenForm();
$model->loadByPost();
$model->load(Yii::$app->request->post());
return $model->refresh()->getResponseData(false);
}
public function actionValidate() {
$model = new models\ValidateForm();
$model->loadByPost();
$model->load(Yii::$app->request->post());
$model->validateToken();
// В случае успеха ожидается пустой ответ. В случае ошибки же бросается исключение,
// которое обработает ErrorHandler
@ -47,7 +48,7 @@ class AuthenticationController extends Controller {
public function actionSignout() {
$model = new models\SignoutForm();
$model->loadByPost();
$model->load(Yii::$app->request->post());
$model->signout();
// В случае успеха ожидается пустой ответ. В случае ошибки же бросается исключение,
// которое обработает ErrorHandler
@ -55,7 +56,7 @@ class AuthenticationController extends Controller {
public function actionInvalidate() {
$model = new models\InvalidateForm();
$model->loadByPost();
$model->load(Yii::$app->request->post());
$model->invalidateToken();
// В случае успеха ожидается пустой ответ. В случае ошибки же бросается исключение,
// которое обработает ErrorHandler

View File

@ -2,6 +2,7 @@
namespace api\modules\authserver\models;
use api\models\authentication\LoginForm;
use api\models\base\ApiForm;
use api\modules\authserver\exceptions\ForbiddenOperationException;
use api\modules\authserver\Module as Authserver;
use api\modules\authserver\validators\RequiredValidator;
@ -9,7 +10,7 @@ use common\helpers\Error as E;
use common\models\Account;
use common\models\MinecraftAccessKey;
class AuthenticationForm extends Form {
class AuthenticationForm extends ApiForm {
public $username;
public $password;

View File

@ -1,27 +0,0 @@
<?php
namespace api\modules\authserver\models;
use Yii;
use yii\base\Model;
abstract class Form extends Model {
public function formName() {
return '';
}
public function loadByGet() {
return $this->load(Yii::$app->request->get());
}
public function loadByPost() {
$data = Yii::$app->request->post();
if (empty($data)) {
// TODO: помнится у Yii2 есть механизм парсинга данных входящего запроса. Лучше будет сделать это там
$data = json_decode(Yii::$app->request->getRawBody(), true);
}
return $this->load($data);
}
}

View File

@ -1,10 +1,11 @@
<?php
namespace api\modules\authserver\models;
use api\models\base\ApiForm;
use api\modules\authserver\validators\RequiredValidator;
use common\models\MinecraftAccessKey;
class InvalidateForm extends Form {
class InvalidateForm extends ApiForm {
public $accessToken;
public $clientToken;

View File

@ -1,12 +1,13 @@
<?php
namespace api\modules\authserver\models;
use api\models\base\ApiForm;
use api\modules\authserver\exceptions\ForbiddenOperationException;
use api\modules\authserver\validators\RequiredValidator;
use common\models\Account;
use common\models\MinecraftAccessKey;
class RefreshTokenForm extends Form {
class RefreshTokenForm extends ApiForm {
public $accessToken;
public $clientToken;

View File

@ -2,13 +2,14 @@
namespace api\modules\authserver\models;
use api\models\authentication\LoginForm;
use api\models\base\ApiForm;
use api\modules\authserver\exceptions\ForbiddenOperationException;
use api\modules\authserver\validators\RequiredValidator;
use common\helpers\Error as E;
use common\models\MinecraftAccessKey;
use Yii;
class SignoutForm extends Form {
class SignoutForm extends ApiForm {
public $username;
public $password;

View File

@ -1,11 +1,12 @@
<?php
namespace api\modules\authserver\models;
use api\models\base\ApiForm;
use api\modules\authserver\exceptions\ForbiddenOperationException;
use api\modules\authserver\validators\RequiredValidator;
use common\models\MinecraftAccessKey;
class ValidateForm extends Form {
class ValidateForm extends ApiForm {
public $accessToken;

View File

@ -86,10 +86,7 @@ class ApiController extends Controller {
public function actionUuidsByUsernames() {
$usernames = Yii::$app->request->post();
if (empty($usernames)) {
$usernames = json_decode(Yii::$app->request->getRawBody());
if (empty($usernames)) {
return $this->illegalArgumentResponse('Passed array of profile names is an invalid JSON string.');
}
return $this->illegalArgumentResponse('Passed array of profile names is an invalid JSON string.');
}
$usernames = array_unique($usernames);

View File

@ -35,11 +35,6 @@ class SessionController extends ApiController {
Yii::$app->response->format = Response::FORMAT_JSON;
$data = Yii::$app->request->post();
if (empty($data)) {
// TODO: помнится у Yii2 есть механизм парсинга данных входящего запроса. Лучше будет сделать это там
$data = json_decode(Yii::$app->request->getRawBody(), true);
}
$protocol = new ModernJoin($data['accessToken'] ?? '', $data['selectedProfile'] ?? '', $data['serverId'] ?? '');
$joinForm = new JoinForm($protocol);
$joinForm->join();

View File

@ -0,0 +1,43 @@
<?php
namespace api\request;
use Yii;
use yii\web\JsonParser;
use yii\web\RequestParserInterface;
/**
* Т.к. Yii2 не предоставляет возможности сделать fallback для неспаршенного
* request, нужно полностью реимплементировать логику парсинга запроса.
*
* Код взят из \yii\web\Request::getBodyParams() и вывернут таким образом,
* чтобы по нисходящей пытаться спарсить запрос:
* - сначала проверяем, если PHP справился сам, то возвращаем его значение
* - дальше пробуем спарсить JSON, который закодирован в теле
* - если не вышло, то предположим, что это PUT, DELETE или иной другой запрос,
* который PHP автоматически не осиливает спарсить, так что пытаемся его спарсить
* самостоятельно
*/
class RequestParser implements RequestParserInterface {
public function parse($rawBody, $contentType) {
if (!empty($_POST)) {
return $_POST;
}
/** @var JsonParser $parser */
$parser = Yii::createObject(JsonParser::class);
$parser->throwException = false;
$result = $parser->parse($rawBody, $contentType);
if (!empty($result)) {
return $result;
}
mb_parse_str($rawBody, $bodyParams);
if (!empty($bodyParams)) {
return $bodyParams;
}
return [];
}
}

View File

@ -1,6 +1,6 @@
<?php
return [
'version' => '1.1.12',
'version' => '1.1.13',
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'components' => [
'cache' => [

View File

@ -0,0 +1,56 @@
<?php
namespace common\emails;
use common\emails\templates\ChangeEmailConfirmCurrentEmail;
use common\emails\templates\ChangeEmailConfirmNewEmail;
use common\emails\templates\ForgotPasswordEmail;
use common\emails\templates\ForgotPasswordParams;
use common\emails\templates\RegistrationEmail;
use common\emails\templates\RegistrationEmailParams;
use common\models\Account;
use common\models\confirmations\CurrentEmailConfirmation;
use common\models\confirmations\ForgotPassword;
use common\models\confirmations\NewEmailConfirmation;
use common\models\confirmations\RegistrationConfirmation;
use Yii;
class EmailHelper {
public static function registration(RegistrationConfirmation $emailActivation): void {
$account = $emailActivation->account;
$locale = $account->lang;
$params = new RegistrationEmailParams(
$account->username,
$emailActivation->key,
Yii::$app->request->getHostInfo() . '/activation/' . $emailActivation->key
);
(new RegistrationEmail(self::buildTo($account), $locale, $params))->send();
}
public static function forgotPassword(ForgotPassword $emailActivation): void {
$account = $emailActivation->account;
$locale = $account->lang;
$params = new ForgotPasswordParams(
$account->username,
$emailActivation->key,
Yii::$app->request->getHostInfo() . '/recover-password/' . $emailActivation->key
);
(new ForgotPasswordEmail(self::buildTo($account), $locale, $params))->send();
}
public static function changeEmailConfirmCurrent(CurrentEmailConfirmation $emailActivation): void {
(new ChangeEmailConfirmCurrentEmail(self::buildTo($emailActivation->account), $emailActivation->key))->send();
}
public static function changeEmailConfirmNew(NewEmailConfirmation $emailActivation): void {
$account = $emailActivation->account;
(new ChangeEmailConfirmNewEmail(self::buildTo($account), $account->username, $emailActivation->key))->send();
}
public static function buildTo(Account $account): array {
return [$account->email => $account->username];
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace common\emails;
use common\emails\exceptions\CannotSendEmailException;
use Yii;
use yii\base\InvalidConfigException;
use yii\mail\MailerInterface;
use yii\mail\MessageInterface;
abstract class Template {
/**
* @var \yii\swiftmailer\Mailer
*/
private $mailer;
/**
* @var string|array
*/
private $to;
/**
* @param string|array $to получатель письма. Задаётся как Email или как массив [email => name]
*/
public function __construct($to) {
$this->mailer = Yii::$app->mailer;
$this->to = $to;
}
/**
* @return array|string
*/
public function getTo() {
return $this->to;
}
abstract public function getSubject(): string;
/**
* @return array|string
* @throws InvalidConfigException
*/
public function getFrom() {
$fromEmail = Yii::$app->params['fromEmail'];
if (!$fromEmail) {
throw new InvalidConfigException('Please specify fromEmail app in app params');
}
return [$fromEmail => 'Ely.by Accounts'];
}
public function getParams(): array {
return [];
}
public function getMailer(): MailerInterface {
return $this->mailer;
}
public function send(): void {
if (!$this->createMessage()->send()) {
throw new CannotSendEmailException('Unable send email.');
}
}
/**
* @return string|array
*/
abstract protected function getView();
protected function createMessage(): MessageInterface {
return $this->getMailer()
->compose($this->getView(), $this->getParams())
->setTo($this->getTo())
->setFrom($this->getFrom())
->setSubject($this->getSubject());
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace common\emails;
use common\components\EmailRenderer;
use Yii;
use yii\mail\MessageInterface;
abstract class TemplateWithRenderer extends Template {
/**
* @var EmailRenderer
*/
private $emailRenderer;
/**
* @var string
*/
private $locale;
/**
* @inheritdoc
*/
public function __construct($to, string $locale) {
parent::__construct($to);
$this->emailRenderer = Yii::$app->emailRenderer;
$this->locale = $locale;
}
public function getLocale(): string {
return $this->locale;
}
public function getEmailRenderer(): EmailRenderer {
return $this->emailRenderer;
}
/**
* Метод должен возвращать имя шаблона, который должен быть использован.
* Имена можно взять в репозитории elyby/email-renderer
*
* @return string
*/
abstract public function getTemplateName(): string;
protected final function getView() {
return $this->getTemplateName();
}
protected function createMessage(): MessageInterface {
return $this->getMailer()
->compose()
->setHtmlBody($this->render())
->setTo($this->getTo())
->setFrom($this->getFrom())
->setSubject($this->getSubject());
}
private function render(): string {
return $this->getEmailRenderer()
->getTemplate($this->getTemplateName())
->setLocale($this->getLocale())
->setParams($this->getParams())
->render();
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace common\emails\exceptions;
use Exception;
class CannotSendEmailException extends Exception {
}

View File

@ -0,0 +1,35 @@
<?php
namespace common\emails\templates;
use common\emails\Template;
class ChangeEmailConfirmCurrentEmail extends Template {
private $key;
public function __construct($to, string $key) {
parent::__construct($to);
$this->key = $key;
}
public function getSubject(): string {
return 'Ely.by Account change E-mail confirmation';
}
/**
* @return string|array
*/
protected function getView() {
return [
'html' => '@common/emails/views/current-email-confirmation-html',
'text' => '@common/emails/views/current-email-confirmation-text',
];
}
public function getParams(): array {
return [
'key' => $this->key,
];
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace common\emails\templates;
use common\emails\Template;
class ChangeEmailConfirmNewEmail extends Template {
private $username;
private $key;
public function __construct($to, string $username, string $key) {
parent::__construct($to);
$this->username = $username;
$this->key = $key;
}
public function getSubject(): string {
return 'Ely.by Account new E-mail confirmation';
}
/**
* @return string|array
*/
protected function getView() {
return [
'html' => '@common/emails/views/new-email-confirmation-html',
'text' => '@common/emails/views/new-email-confirmation-text',
];
}
public function getParams(): array {
return [
'key' => $this->key,
'username' => $this->username,
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace common\emails\templates;
use common\emails\TemplateWithRenderer;
class ForgotPasswordEmail extends TemplateWithRenderer {
private $params;
/**
* @inheritdoc
*/
public function __construct($to, string $locale, ForgotPasswordParams $params) {
TemplateWithRenderer::__construct($to, $locale);
$this->params = $params;
}
public function getSubject(): string {
return 'Ely.by Account forgot password';
}
public function getTemplateName(): string {
return 'forgotPassword';
}
public function getParams(): array {
return [
'username' => $this->params->getUsername(),
'code' => $this->params->getCode(),
'link' => $this->params->getLink(),
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace common\emails\templates;
class ForgotPasswordParams {
private $username;
private $code;
private $link;
public function __construct(string $username, string $code, string $link) {
$this->username = $username;
$this->code = $code;
$this->link = $link;
}
public function getUsername(): string {
return $this->username;
}
public function getCode(): string {
return $this->code;
}
public function getLink(): string {
return $this->link;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace common\emails\templates;
use common\emails\TemplateWithRenderer;
class RegistrationEmail extends TemplateWithRenderer {
private $params;
/**
* @inheritdoc
*/
public function __construct($to, string $locale, RegistrationEmailParams $params) {
TemplateWithRenderer::__construct($to, $locale);
$this->params = $params;
}
public function getSubject(): string {
return 'Ely.by Account registration';
}
public function getTemplateName(): string {
return 'register';
}
public function getParams(): array {
return [
'username' => $this->params->getUsername(),
'code' => $this->params->getCode(),
'link' => $this->params->getLink(),
];
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace common\emails\templates;
class RegistrationEmailParams {
private $username;
private $code;
private $link;
public function __construct(string $username, string $code, string $link) {
$this->username = $username;
$this->code = $code;
$this->link = $link;
}
public function getUsername(): string {
return $this->username;
}
public function getCode(): string {
return $this->code;
}
public function getLink(): string {
return $this->link;
}
}

View File

@ -1,12 +1,12 @@
<?php
/**
* @var \common\models\Account $account
* @var string $username
* @var string $key
*/
?>
<p>
This E-mail was specified as new for account <?= $account->username ?>. To confirm this E-mail, pass code
This E-mail was specified as new for account <?= $username ?>. To confirm this E-mail, pass code
below into form on site.
</p>
<p>Code: <?= $key ?></p>

View File

@ -0,0 +1,12 @@
<?php
/**
* @var string $username
* @var string $key
*/
?>
This E-mail was specified as new for account <?= $username ?>. To confirm this E-mail, pass code below into form on site.
Code: <?= $key ?>
// P.S. yes, this is E-mail is not designed yet :)

View File

@ -25,7 +25,7 @@
"ely/yii2-tempmail-validator": "~1.0.0",
"emarref/jwt": "~1.0.3",
"ely/amqp-controller": "dev-master#d7f8cdbc66c45e477c9c7d5d509bc0c1b11fd3ec",
"ely/email-renderer": "dev-master#d66ec82d2ebae59986ad5e009ae0c951d1cfcccb",
"ely/email-renderer": "dev-master#ecc044a00c87059be54bfbd0ed3b516383e3dcd5",
"predis/predis": "^1.0",
"mito/yii2-sentry": "^1.0",
"minime/annotations": "~3.0",
@ -37,10 +37,13 @@
"yiisoft/yii2-debug": "*",
"yiisoft/yii2-faker": "*",
"flow/jsonpath": "^0.3.1",
"codeception/codeception": "~2.2.4",
"phpunit/phpunit": "^5.7",
"codeception/codeception": "~2.3",
"codeception/specify": "*",
"codeception/verify": "*",
"phploc/phploc": "^3.0.1"
"phploc/phploc": "^3.0.1",
"mockery/mockery": "1.0.0-alpha1",
"php-mock/php-mock-mockery": "dev-mockery-1.0.0#03956ed4b34ae25bc20a0677500f4f4b416f976c"
},
"config": {
"process-timeout": 1800
@ -61,6 +64,10 @@
{
"type": "git",
"url": "git@gitlab.ely.by:elyby/oauth2-server.git"
},
{
"type": "git",
"url": "git@github.com:erickskrauch/php-mock-mockery.git"
}
],
"scripts": {

View File

@ -12,8 +12,9 @@ modules:
config:
Yii2:
configFile: '../config/api/functional.php'
cleanup: false
cleanup: true
Redis:
host: "%REDIS_HOST%"
port: 6379
database: 0
cleanupBefore: 'test'

View File

@ -38,6 +38,17 @@ class AuthorizationCest {
$this->testSuccessResponse($I);
}
public function byNamePassedViaPOSTBody(FunctionalTester $I) {
$I->wantTo('authenticate by username and password sent via post body');
$this->route->authenticate(json_encode([
'username' => 'admin',
'password' => 'password_0',
'clientToken' => Uuid::uuid4()->toString(),
]));
$this->testSuccessResponse($I);
}
public function byEmailWithEnabledTwoFactorAuth(FunctionalTester $I) {
$I->wantTo('get valid error by authenticate account with enabled two factor auth');
$this->route->authenticate([

View File

@ -98,17 +98,6 @@ class UsernamesToUuidsCest {
]);
}
public function passWrongPostBody(FunctionalTester $I) {
$I->wantTo('get specific response when pass invalid json string');
$this->route->uuidsByUsernames('wrong-json');
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'error' => 'IllegalArgumentException',
'errorMessage' => 'Passed array of profile names is an invalid JSON string.',
]);
}
private function validateFewValidUsernames(FunctionalTester $I) {
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();

View File

@ -4,7 +4,8 @@ modules:
- Yii2:
part: [orm, email, fixtures]
- tests\codeception\common\_support\amqp\Helper
- tests\codeception\common\_support\Mockery
config:
Yii2:
configFile: '../config/api/unit.php'
cleanup: false
cleanup: true

View File

@ -1,6 +1,8 @@
<?php
namespace tests\codeception\api\unit;
use Mockery;
class TestCase extends \Codeception\Test\Unit {
/**
@ -19,4 +21,9 @@ class TestCase extends \Codeception\Test\Unit {
return [];
}
protected function tearDown() {
parent::tearDown();
Mockery::close();
}
}

View File

@ -2,64 +2,73 @@
namespace codeception\api\unit\components\ReCaptcha;
use api\components\ReCaptcha\Validator;
use Codeception\Specify;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Psr7\Response;
use phpmock\mockery\PHPMockery;
use ReflectionClass;
use tests\codeception\api\unit\TestCase;
class ValidatorTest extends TestCase {
use Specify;
public function testValidateValue() {
$this->specify('Get error.captcha_required, if passed empty value', function() {
$validator = new Validator();
expect($validator->validate('', $error))->false();
expect($error)->equals('error.captcha_required');
});
$this->specify('Get error.captcha_invalid, if passed wrong value', function() {
/** @var \PHPUnit_Framework_MockObject_MockObject|Validator $validator */
$validator = $this->getMockBuilder(Validator::class)
->setMethods(['createClient'])
->getMock();
$validator->expects($this->once())
->method('createClient')
->will($this->returnValue($this->createMockGuzzleClient([
'success' => false,
'error-codes' => [
'invalid-input-response', // The response parameter is invalid or malformed.
],
])));
expect($validator->validate('12341234', $error))->false();
expect($error)->equals('error.captcha_invalid');
});
$this->specify('Get error.captcha_invalid, if passed wrong value', function() {
/** @var \PHPUnit_Framework_MockObject_MockObject|Validator $validator */
$validator = $this->getMockBuilder(Validator::class)
->setMethods(['createClient'])
->getMock();
$validator->expects($this->once())
->method('createClient')
->will($this->returnValue($this->createMockGuzzleClient(['success' => true])));
expect($validator->validate('12341234', $error))->true();
expect($error)->null();
});
public function testValidateEmptyValue() {
$validator = new Validator(mock(ClientInterface::class));
$this->assertFalse($validator->validate('', $error));
$this->assertEquals('error.captcha_required', $error, 'Get error.captcha_required, if passed empty value');
}
private function createMockGuzzleClient(array $response) {
$mock = new MockHandler([
new Response(200, [], json_encode($response)),
]);
$handler = HandlerStack::create($mock);
public function testValidateInvalidValue() {
$mockClient = mock(ClientInterface::class);
$mockClient->shouldReceive('request')->andReturn(new Response(200, [], json_encode([
'success' => false,
'error-codes' => [
'invalid-input-response', // The response parameter is invalid or malformed.
],
])));
return new Client(['handler' => $handler]);
$validator = new Validator($mockClient);
$this->assertFalse($validator->validate('12341234', $error));
$this->assertEquals('error.captcha_invalid', $error, 'Get error.captcha_invalid, if passed wrong value');
}
public function testValidateWithNetworkTroubles() {
$mockClient = mock(ClientInterface::class);
$mockClient->shouldReceive('request')->andThrow(mock(ConnectException::class))->once();
$mockClient->shouldReceive('request')->andReturn(new Response(200, [], json_encode([
'success' => true,
'error-codes' => [
'invalid-input-response', // The response parameter is invalid or malformed.
],
])))->once();
PHPMockery::mock($this->getClassNamespace(Validator::class), 'sleep')->once();
$validator = new Validator($mockClient);
$this->assertTrue($validator->validate('12341234', $error));
$this->assertNull($error);
}
public function testValidateWithHugeNetworkTroubles() {
$mockClient = mock(ClientInterface::class);
$mockClient->shouldReceive('request')->andThrow(mock(ConnectException::class))->times(3);
PHPMockery::mock($this->getClassNamespace(Validator::class), 'sleep')->times(2);
$validator = new Validator($mockClient);
$this->expectException(ConnectException::class);
$validator->validate('12341234', $error);
}
public function testValidateValidValue() {
$mockClient = mock(ClientInterface::class);
$mockClient->shouldReceive('request')->andReturn(new Response(200, [], json_encode([
'success' => true,
])));
$validator = new Validator($mockClient);
$this->assertTrue($validator->validate('12341234', $error));
$this->assertNull($error);
}
private function getClassNamespace(string $className): string {
return (new ReflectionClass($className))->getNamespaceName();
}
}

View File

@ -1,17 +1,29 @@
<?php
namespace codeception\api\unit\models\authentication;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\authentication\ForgotPasswordForm;
use Codeception\Specify;
use common\models\EmailActivation;
use GuzzleHttp\ClientInterface;
use OTPHP\TOTP;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use Yii;
class ForgotPasswordFormTest extends TestCase {
use Specify;
public function setUp() {
parent::setUp();
Yii::$container->set(ReCaptchaValidator::class, new class(mock(ClientInterface::class)) extends ReCaptchaValidator {
public function validateValue($value) {
return null;
}
});
}
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
@ -20,17 +32,13 @@ class ForgotPasswordFormTest extends TestCase {
}
public function testValidateLogin() {
$this->specify('error.login_not_exist if login is invalid', function() {
$model = new ForgotPasswordForm(['login' => 'unexist']);
$model->validateLogin('login');
expect($model->getErrors('login'))->equals(['error.login_not_exist']);
});
$model = new ForgotPasswordForm(['login' => 'unexist']);
$model->validateLogin('login');
$this->assertEquals(['error.login_not_exist'], $model->getErrors('login'), 'error.login_not_exist if login is invalid');
$this->specify('empty errors if login is exists', function() {
$model = new ForgotPasswordForm(['login' => $this->tester->grabFixture('accounts', 'admin')['username']]);
$model->validateLogin('login');
expect($model->getErrors('login'))->isEmpty();
});
$model = new ForgotPasswordForm(['login' => $this->tester->grabFixture('accounts', 'admin')['username']]);
$model->validateLogin('login');
$this->assertEmpty($model->getErrors('login'), 'empty errors if login is exists');
}
public function testValidateTotpToken() {
@ -49,77 +57,66 @@ class ForgotPasswordFormTest extends TestCase {
}
public function testValidateActivity() {
$this->specify('error.account_not_activated if account is not confirmed', function() {
$model = new ForgotPasswordForm([
'login' => $this->tester->grabFixture('accounts', 'not-activated-account')['username'],
]);
$model->validateActivity('login');
expect($model->getErrors('login'))->equals(['error.account_not_activated']);
});
$model = new ForgotPasswordForm([
'login' => $this->tester->grabFixture('accounts', 'not-activated-account')['username'],
]);
$model->validateActivity('login');
$this->assertEquals(['error.account_not_activated'], $model->getErrors('login'), 'expected error if account is not confirmed');
$this->specify('empty errors if login is exists', function() {
$model = new ForgotPasswordForm([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
]);
$model->validateLogin('login');
expect($model->getErrors('login'))->isEmpty();
});
$model = new ForgotPasswordForm([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
]);
$model->validateLogin('login');
$this->assertEmpty($model->getErrors('login'), 'empty errors if login is exists');
}
public function testValidateFrequency() {
$this->specify('error.account_not_activated if recently was message', function() {
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => $this->tester->grabFixture('emailActivations', 'freshPasswordRecovery')['key'],
]);
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => $this->tester->grabFixture('emailActivations', 'freshPasswordRecovery')['key'],
]);
$model->validateFrequency('login');
$this->assertEquals(['error.recently_sent_message'], $model->getErrors('login'), 'error.account_not_activated if recently was message');
$model->validateFrequency('login');
expect($model->getErrors('login'))->equals(['error.recently_sent_message']);
});
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => $this->tester->grabFixture('emailActivations', 'oldPasswordRecovery')['key'],
]);
$model->validateFrequency('login');
$this->assertEmpty($model->getErrors('login'), 'empty errors if email was sent a long time ago');
$this->specify('empty errors if email was sent a long time ago', function() {
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => $this->tester->grabFixture('emailActivations', 'oldPasswordRecovery')['key'],
]);
$model->validateFrequency('login');
expect($model->getErrors('login'))->isEmpty();
});
$this->specify('empty errors if previous confirmation model not founded', function() {
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => 'invalid-key',
]);
$model->validateFrequency('login');
expect($model->getErrors('login'))->isEmpty();
});
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => 'invalid-key',
]);
$model->validateFrequency('login');
$this->assertEmpty($model->getErrors('login'), 'empty errors if previous confirmation model not founded');
}
public function testForgotPassword() {
$this->specify('successfully send message with restore password key', function() {
$model = new ForgotPasswordForm(['login' => $this->tester->grabFixture('accounts', 'admin')['username']]);
expect($model->forgotPassword())->true();
expect($model->getEmailActivation())->notNull();
$this->tester->canSeeEmailIsSent(1);
});
$model = new ForgotPasswordForm(['login' => $this->tester->grabFixture('accounts', 'admin')['username']]);
$this->assertTrue($model->forgotPassword(), 'form should be successfully processed');
$activation = $model->getEmailActivation();
$this->assertInstanceOf(EmailActivation::class, $activation, 'getEmailActivation should return valid object instance');
$this->tester->canSeeEmailIsSent(1);
/** @var \yii\swiftmailer\Message $email */
$email = $this->tester->grabSentEmails()[0];
$body = $email->getSwiftMessage()->getBody();
$this->assertContains($activation->key, $body);
$this->assertContains('/recover-password/' . $activation->key, $body);
}
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' => $fixture['username'],
]);
$callTime = time();
expect($model->forgotPassword())->true();
$emailActivation = $model->getEmailActivation();
expect($emailActivation)->notNull();
expect($emailActivation->created_at)->greaterOrEquals($callTime);
$this->tester->canSeeEmailIsSent(1);
});
$fixture = $this->tester->grabFixture('accounts', 'account-with-expired-forgot-password-message');
$model = new ForgotPasswordForm([
'login' => $fixture['username'],
]);
$callTime = time();
$this->assertTrue($model->forgotPassword(), 'form should be successfully processed');
$emailActivation = $model->getEmailActivation();
$this->assertInstanceOf(EmailActivation::class, $emailActivation);
$this->assertGreaterThanOrEqual($callTime, $emailActivation->created_at);
$this->tester->canSeeEmailIsSent(1);
}
/**

View File

@ -7,6 +7,7 @@ use Codeception\Specify;
use common\models\Account;
use common\models\EmailActivation;
use common\models\UsernameHistory;
use GuzzleHttp\ClientInterface;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use Yii;
@ -19,7 +20,7 @@ class RegistrationFormTest extends TestCase {
public function setUp() {
parent::setUp();
$this->mockRequest();
Yii::$container->set(ReCaptchaValidator::class, new class extends ReCaptchaValidator {
Yii::$container->set(ReCaptchaValidator::class, new class(mock(ClientInterface::class)) extends ReCaptchaValidator {
public function validateValue($value) {
return null;
}
@ -96,20 +97,27 @@ class RegistrationFormTest extends TestCase {
'username' => 'some_username',
'email' => 'some_email@example.com',
])->exists(), 'user model exists in database');
$this->assertTrue(EmailActivation::find()->andWhere([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION,
])->exists(), 'email activation code exists in database');
/** @var EmailActivation $activation */
$activation = EmailActivation::find()
->andWhere([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION,
])
->one();
$this->assertInstanceOf(EmailActivation::class, $activation, 'email activation code exists in database');
$this->assertTrue(UsernameHistory::find()->andWhere([
'username' => $account->username,
'account_id' => $account->id,
'applied_in' => $account->created_at,
])->exists(), 'username history record exists in database');
$this->tester->canSeeEmailIsSent(1);
/** @var \yii\swiftmailer\Message $email */
$email = $this->tester->grabSentEmails()[0];
$body = $email->getSwiftMessage()->getBody();
$this->assertContains($activation->key, $body);
$this->assertContains('/activation/' . $activation->key, $body);
}
// TODO: там в самой форме есть метод sendMail(), который рано или поздно должен переехать. К нему нужны будут тоже тесты
private function mockRequest($ip = '88.225.20.236') {
$request = $this->getMockBuilder(Request::class)
->setMethods(['getUserIP'])

View File

@ -5,6 +5,7 @@ use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\authentication\RepeatAccountActivationForm;
use Codeception\Specify;
use common\models\EmailActivation;
use GuzzleHttp\ClientInterface;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
@ -15,7 +16,7 @@ class RepeatAccountActivationFormTest extends TestCase {
public function setUp() {
parent::setUp();
Yii::$container->set(ReCaptchaValidator::class, new class extends ReCaptchaValidator {
Yii::$container->set(ReCaptchaValidator::class, new class(mock(ClientInterface::class)) extends ReCaptchaValidator {
public function validateValue($value) {
return null;
}

View File

@ -0,0 +1,20 @@
<?php
namespace tests\codeception\api\unit\request;
use api\request\RequestParser;
use tests\codeception\api\unit\TestCase;
class RequestParserTest extends TestCase {
public function testParse() {
$parser = new RequestParser();
$_POST = ['from' => 'post'];
$this->assertEquals(['from' => 'post'], $parser->parse('from=post', ''));
$this->assertEquals(['from' => 'post'], $parser->parse('', ''));
$_POST = [];
$this->assertEquals(['from' => 'json'], $parser->parse('{"from":"json"}', ''));
$this->assertEquals(['from' => 'body'], $parser->parse('from=body', ''));
$this->assertEquals(['onlykey' => ''], $parser->parse('onlykey', ''));
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace tests\codeception\common\_support;
use Codeception\Module;
use Codeception\TestInterface;
class Mockery extends Module {
/**
* @var bool Run mockery expectations after test or not
*/
private $assert_mocks = true;
public function _before(TestInterface $test) {
\Mockery::globalHelpers();
}
public function _after(TestInterface $test) {
if ($this->assert_mocks) {
\Mockery::close();
} else {
\Mockery::getContainer()->mockery_close();
\Mockery::resetContainer();
}
}
public function _failed(TestInterface $test, $fail) {
$this->assert_mocks = false;
}
}

View File

@ -28,12 +28,9 @@ class Helper extends Module {
return;
}
// TODO: заменить на assertCount() после релиза Codeception 2.2.7
// https://github.com/Codeception/Codeception/pull/3802
/** @noinspection PhpUnitTestsInspection */
$this->assertEquals(
$this->assertCount(
$num,
count($this->grabSentAmqpMessages($exchange)),
$this->grabSentAmqpMessages($exchange),
'number of created messages is equal to ' . $num
);
}

View File

@ -43,16 +43,16 @@ class TestComponent extends Component {
public function getSentMessages(string $exchangeName = null) : array {
if ($exchangeName !== null) {
return $this->sentMessages[$exchangeName] ?? [];
} else {
$messages = [];
foreach($this->sentMessages as $exchangeGroup) {
foreach ($exchangeGroup as $message) {
$messages[] = $message;
}
}
return $messages;
}
$messages = [];
foreach($this->sentMessages as $exchangeGroup) {
foreach ($exchangeGroup as $message) {
$messages[] = $message;
}
}
return $messages;
}
}

View File

@ -3,7 +3,8 @@ modules:
enabled:
- Yii2:
part: [orm, email, fixtures]
- tests\codeception\common\_support\Mockery
config:
Yii2:
configFile: '../config/common/unit.php'
cleanup: false
cleanup: true

View File

@ -1,6 +1,8 @@
<?php
namespace tests\codeception\common\unit;
use Mockery;
class TestCase extends \Codeception\Test\Unit {
/**
@ -19,4 +21,9 @@ class TestCase extends \Codeception\Test\Unit {
return [];
}
protected function tearDown() {
parent::tearDown();
Mockery::close();
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace tests\codeception\common\unit\emails;
use common\emails\EmailHelper;
use common\models\Account;
use tests\codeception\common\unit\TestCase;
class EmailHelperTest extends TestCase {
public function testBuildTo() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class)->makePartial();
$account->username = 'mock-username';
$account->email = 'mock@ely.by';
$this->assertEquals(['mock@ely.by' => 'mock-username'], EmailHelper::buildTo($account));
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace tests\codeception\common\unit\emails;
use common\emails\Template;
use tests\codeception\common\_support\ProtectedCaller;
use tests\codeception\common\unit\TestCase;
use Yii;
use yii\mail\MailerInterface;
use yii\mail\MessageInterface;
class TemplateTest extends TestCase {
use ProtectedCaller;
public function testConstructor() {
/** @var Template|\Mockery\MockInterface $template */
$template = mock(Template::class, ['find-me'])->makePartial();
$this->assertEquals('find-me', $template->getTo());
$this->assertInstanceOf(MailerInterface::class, $template->getMailer());
}
public function testGetFrom() {
Yii::$app->params['fromEmail'] = 'find-me';
/** @var Template|\Mockery\MockInterface $template */
$template = mock(Template::class)->makePartial();
$this->assertEquals(['find-me' => 'Ely.by Accounts'], $template->getFrom());
}
public function testGetParams() {
/** @var Template|\Mockery\MockInterface $template */
$template = mock(Template::class)->makePartial();
$this->assertEquals([], $template->getParams());
}
public function testCreateMessage() {
Yii::$app->params['fromEmail'] = 'from@ely.by';
/** @var Template|\Mockery\MockInterface $template */
$template = mock(Template::class, [['to@ely.by' => 'To']])->makePartial();
$template->shouldReceive('getSubject')->andReturn('mock-subject');
/** @var MessageInterface $message */
$message = $this->callProtected($template, 'createMessage');
$this->assertInstanceOf(MessageInterface::class, $message);
$this->assertEquals(['to@ely.by' => 'To'], $message->getTo());
$this->assertEquals(['from@ely.by' => 'Ely.by Accounts'], $message->getFrom());
$this->assertEquals('mock-subject', $message->getSubject());
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace tests\codeception\common\unit\emails;
use common\components\EmailRenderer;
use common\emails\TemplateWithRenderer;
use Ely\Email\TemplateBuilder;
use tests\codeception\common\_support\ProtectedCaller;
use tests\codeception\common\unit\TestCase;
use yii\mail\MailerInterface;
use yii\mail\MessageInterface;
class TemplateWithRendererTest extends TestCase {
use ProtectedCaller;
public function testConstructor() {
/** @var TemplateWithRenderer|\Mockery\MockInterface $template */
$template = mock(TemplateWithRenderer::class, ['mock-to', 'mock-locale'])->makePartial();
$this->assertEquals('mock-to', $template->getTo());
$this->assertEquals('mock-locale', $template->getLocale());
$this->assertInstanceOf(MailerInterface::class, $template->getMailer());
$this->assertInstanceOf(EmailRenderer::class, $template->getEmailRenderer());
}
public function testCreateMessage() {
/** @var TemplateBuilder|\Mockery\MockInterface $templateBuilder */
$templateBuilder = mock(TemplateBuilder::class)->makePartial();
$templateBuilder->shouldReceive('render')->andReturn('mock-html');
/** @var EmailRenderer|\Mockery\MockInterface $renderer */
$renderer = mock(EmailRenderer::class)->makePartial();
$renderer->shouldReceive('getTemplate')->with('mock-template')->andReturn($templateBuilder);
/** @var TemplateWithRenderer|\Mockery\MockInterface $template */
$template = mock(TemplateWithRenderer::class, [['to@ely.by' => 'To'], 'mock-locale']);
$template->makePartial();
$template->shouldReceive('getEmailRenderer')->andReturn($renderer);
$template->shouldReceive('getFrom')->andReturn(['from@ely.by' => 'From']);
$template->shouldReceive('getSubject')->andReturn('mock-subject');
$template->shouldReceive('getTemplateName')->andReturn('mock-template');
/** @var \yii\swiftmailer\Message $message */
$message = $this->callProtected($template, 'createMessage');
$this->assertInstanceOf(MessageInterface::class, $message);
$this->assertEquals(['to@ely.by' => 'To'], $message->getTo());
$this->assertEquals(['from@ely.by' => 'From'], $message->getFrom());
$this->assertEquals('mock-subject', $message->getSubject());
$this->assertEquals('mock-html', $message->getSwiftMessage()->getBody());
}
}

View File

@ -1,4 +1,7 @@
<?php
use api\components\ReCaptcha\Validator;
use GuzzleHttp\Client;
return [
'components' => [
'user' => [
@ -17,4 +20,15 @@ return [
'params' => [
'authserverHost' => 'authserver.ely.by',
],
'container' => [
'definitions' => [
Validator::class => function() {
return new class(new Client()) extends Validator {
protected function validateValue($value) {
return null;
}
};
},
],
],
];

View File

@ -3,7 +3,8 @@ modules:
enabled:
- Yii2:
part: [orm, email, fixtures]
- tests\codeception\common\_support\Mockery
config:
Yii2:
configFile: '../config/console/unit.php'
cleanup: false
cleanup: true

View File

@ -2,6 +2,7 @@
namespace tests\codeception\console\unit;
use Codeception\Test\Unit;
use Mockery;
class TestCase extends Unit {
@ -21,4 +22,9 @@ class TestCase extends Unit {
return [];
}
protected function tearDown() {
parent::tearDown();
Mockery::close();
}
}