diff --git a/api/components/ReCaptcha/Validator.php b/api/components/ReCaptcha/Validator.php index 1dfac33..a30b919 100644 --- a/api/components/ReCaptcha/Validator.php +++ b/api/components/ReCaptcha/Validator.php @@ -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(), + ], + ]); } } diff --git a/api/config/config.php b/api/config/config.php index c00e318..d288abc 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -61,6 +61,9 @@ return [ ], 'request' => [ 'baseUrl' => '/api', + 'parsers' => [ + '*' => api\request\RequestParser::class, + ], ], 'urlManager' => [ 'enablePrettyUrl' => true, diff --git a/api/mails/new-email-confirmation-text.php b/api/mails/new-email-confirmation-text.php deleted file mode 100644 index f516746..0000000 --- a/api/mails/new-email-confirmation-text.php +++ /dev/null @@ -1,12 +0,0 @@ - - -This E-mail was specified as new for account username ?>. To confirm this E-mail, pass code below into form on site. - -Code: - -// P.S. yes, this is E-mail is not designed yet :) diff --git a/api/models/FeedbackForm.php b/api/models/FeedbackForm.php index 9feffa2..1c2ce12 100644 --- a/api/models/FeedbackForm.php +++ b/api/models/FeedbackForm.php @@ -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, ]); diff --git a/api/models/authentication/ForgotPasswordForm.php b/api/models/authentication/ForgotPasswordForm.php index 051decb..9e4d355 100644 --- a/api/models/authentication/ForgotPasswordForm.php +++ b/api/models/authentication/ForgotPasswordForm.php @@ -1,26 +1,30 @@ 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; } diff --git a/api/models/authentication/RegistrationForm.php b/api/models/authentication/RegistrationForm.php index c39a249..40fb293 100644 --- a/api/models/authentication/RegistrationForm.php +++ b/api/models/authentication/RegistrationForm.php @@ -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 или ник, после замечают это и пытаются вновь diff --git a/api/models/authentication/RepeatAccountActivationForm.php b/api/models/authentication/RepeatAccountActivationForm.php index 6041f33..a687052 100644 --- a/api/models/authentication/RepeatAccountActivationForm.php +++ b/api/models/authentication/RepeatAccountActivationForm.php @@ -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) { diff --git a/api/models/profile/ChangeEmail/InitStateForm.php b/api/models/profile/ChangeEmail/InitStateForm.php index fd14bba..217415f 100644 --- a/api/models/profile/ChangeEmail/InitStateForm.php +++ b/api/models/profile/ChangeEmail/InitStateForm.php @@ -1,6 +1,7 @@ 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. diff --git a/api/models/profile/ChangeEmail/NewEmailForm.php b/api/models/profile/ChangeEmail/NewEmailForm.php index 9f05948..ba67dfc 100644 --- a/api/models/profile/ChangeEmail/NewEmailForm.php +++ b/api/models/profile/ChangeEmail/NewEmailForm.php @@ -1,6 +1,7 @@ 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); diff --git a/api/modules/authserver/controllers/AuthenticationController.php b/api/modules/authserver/controllers/AuthenticationController.php index 017549b..650928d 100644 --- a/api/modules/authserver/controllers/AuthenticationController.php +++ b/api/modules/authserver/controllers/AuthenticationController.php @@ -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 diff --git a/api/modules/authserver/models/AuthenticationForm.php b/api/modules/authserver/models/AuthenticationForm.php index 14dd601..2a8b5dc 100644 --- a/api/modules/authserver/models/AuthenticationForm.php +++ b/api/modules/authserver/models/AuthenticationForm.php @@ -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; diff --git a/api/modules/authserver/models/Form.php b/api/modules/authserver/models/Form.php deleted file mode 100644 index 02006d8..0000000 --- a/api/modules/authserver/models/Form.php +++ /dev/null @@ -1,27 +0,0 @@ -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); - } - -} diff --git a/api/modules/authserver/models/InvalidateForm.php b/api/modules/authserver/models/InvalidateForm.php index 7d0d7d0..a81e0ef 100644 --- a/api/modules/authserver/models/InvalidateForm.php +++ b/api/modules/authserver/models/InvalidateForm.php @@ -1,10 +1,11 @@ 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); diff --git a/api/modules/session/controllers/SessionController.php b/api/modules/session/controllers/SessionController.php index 384d2f6..046b7ee 100644 --- a/api/modules/session/controllers/SessionController.php +++ b/api/modules/session/controllers/SessionController.php @@ -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(); diff --git a/api/request/RequestParser.php b/api/request/RequestParser.php new file mode 100644 index 0000000..614a5fe --- /dev/null +++ b/api/request/RequestParser.php @@ -0,0 +1,43 @@ +throwException = false; + $result = $parser->parse($rawBody, $contentType); + if (!empty($result)) { + return $result; + } + + mb_parse_str($rawBody, $bodyParams); + if (!empty($bodyParams)) { + return $bodyParams; + } + + return []; + } + +} diff --git a/common/config/config.php b/common/config/config.php index 629b7a5..a73712a 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -1,6 +1,6 @@ '1.1.12', + 'version' => '1.1.13', 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'components' => [ 'cache' => [ diff --git a/common/emails/EmailHelper.php b/common/emails/EmailHelper.php new file mode 100644 index 0000000..2d40cb3 --- /dev/null +++ b/common/emails/EmailHelper.php @@ -0,0 +1,56 @@ +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]; + } + +} diff --git a/common/emails/Template.php b/common/emails/Template.php new file mode 100644 index 0000000..6637421 --- /dev/null +++ b/common/emails/Template.php @@ -0,0 +1,79 @@ + 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()); + } + +} diff --git a/common/emails/TemplateWithRenderer.php b/common/emails/TemplateWithRenderer.php new file mode 100644 index 0000000..33b40f1 --- /dev/null +++ b/common/emails/TemplateWithRenderer.php @@ -0,0 +1,66 @@ +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(); + } + +} diff --git a/common/emails/exceptions/CannotSendEmailException.php b/common/emails/exceptions/CannotSendEmailException.php new file mode 100644 index 0000000..f893d22 --- /dev/null +++ b/common/emails/exceptions/CannotSendEmailException.php @@ -0,0 +1,8 @@ +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, + ]; + } + +} diff --git a/common/emails/templates/ChangeEmailConfirmNewEmail.php b/common/emails/templates/ChangeEmailConfirmNewEmail.php new file mode 100644 index 0000000..507b830 --- /dev/null +++ b/common/emails/templates/ChangeEmailConfirmNewEmail.php @@ -0,0 +1,39 @@ +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, + ]; + } + +} diff --git a/common/emails/templates/ForgotPasswordEmail.php b/common/emails/templates/ForgotPasswordEmail.php new file mode 100644 index 0000000..38def4d --- /dev/null +++ b/common/emails/templates/ForgotPasswordEmail.php @@ -0,0 +1,34 @@ +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(), + ]; + } + +} diff --git a/common/emails/templates/ForgotPasswordParams.php b/common/emails/templates/ForgotPasswordParams.php new file mode 100644 index 0000000..1a5de39 --- /dev/null +++ b/common/emails/templates/ForgotPasswordParams.php @@ -0,0 +1,30 @@ +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; + } + +} diff --git a/common/emails/templates/RegistrationEmail.php b/common/emails/templates/RegistrationEmail.php new file mode 100644 index 0000000..bf85617 --- /dev/null +++ b/common/emails/templates/RegistrationEmail.php @@ -0,0 +1,34 @@ +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(), + ]; + } + +} diff --git a/common/emails/templates/RegistrationEmailParams.php b/common/emails/templates/RegistrationEmailParams.php new file mode 100644 index 0000000..ea3a994 --- /dev/null +++ b/common/emails/templates/RegistrationEmailParams.php @@ -0,0 +1,30 @@ +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; + } + +} diff --git a/api/mails/current-email-confirmation-html.php b/common/emails/views/current-email-confirmation-html.php similarity index 100% rename from api/mails/current-email-confirmation-html.php rename to common/emails/views/current-email-confirmation-html.php diff --git a/api/mails/current-email-confirmation-text.php b/common/emails/views/current-email-confirmation-text.php similarity index 100% rename from api/mails/current-email-confirmation-text.php rename to common/emails/views/current-email-confirmation-text.php diff --git a/api/mails/feedback.php b/common/emails/views/feedback.php similarity index 100% rename from api/mails/feedback.php rename to common/emails/views/feedback.php diff --git a/api/mails/new-email-confirmation-html.php b/common/emails/views/new-email-confirmation-html.php similarity index 53% rename from api/mails/new-email-confirmation-html.php rename to common/emails/views/new-email-confirmation-html.php index 33b9a04..e1adab5 100644 --- a/api/mails/new-email-confirmation-html.php +++ b/common/emails/views/new-email-confirmation-html.php @@ -1,12 +1,12 @@

- This E-mail was specified as new for account username ?>. To confirm this E-mail, pass code + This E-mail was specified as new for account . To confirm this E-mail, pass code below into form on site.

Code:

diff --git a/common/emails/views/new-email-confirmation-text.php b/common/emails/views/new-email-confirmation-text.php new file mode 100644 index 0000000..100dd49 --- /dev/null +++ b/common/emails/views/new-email-confirmation-text.php @@ -0,0 +1,12 @@ + + +This E-mail was specified as new for account . To confirm this E-mail, pass code below into form on site. + +Code: + +// P.S. yes, this is E-mail is not designed yet :) diff --git a/composer.json b/composer.json index 3faccdc..4f9d1c4 100644 --- a/composer.json +++ b/composer.json @@ -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": { diff --git a/tests/codeception/api/functional.suite.yml b/tests/codeception/api/functional.suite.yml index 713312d..3dd11ab 100644 --- a/tests/codeception/api/functional.suite.yml +++ b/tests/codeception/api/functional.suite.yml @@ -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' diff --git a/tests/codeception/api/functional/authserver/AuthorizationCest.php b/tests/codeception/api/functional/authserver/AuthorizationCest.php index 7148e9a..626adb6 100644 --- a/tests/codeception/api/functional/authserver/AuthorizationCest.php +++ b/tests/codeception/api/functional/authserver/AuthorizationCest.php @@ -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([ diff --git a/tests/codeception/api/functional/mojang/UsernamesToUuidsCest.php b/tests/codeception/api/functional/mojang/UsernamesToUuidsCest.php index caa42dd..6c871c5 100644 --- a/tests/codeception/api/functional/mojang/UsernamesToUuidsCest.php +++ b/tests/codeception/api/functional/mojang/UsernamesToUuidsCest.php @@ -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(); diff --git a/tests/codeception/api/unit.suite.yml b/tests/codeception/api/unit.suite.yml index 3d31363..ddf9713 100644 --- a/tests/codeception/api/unit.suite.yml +++ b/tests/codeception/api/unit.suite.yml @@ -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 diff --git a/tests/codeception/api/unit/TestCase.php b/tests/codeception/api/unit/TestCase.php index 1d1cf88..91b79ad 100644 --- a/tests/codeception/api/unit/TestCase.php +++ b/tests/codeception/api/unit/TestCase.php @@ -1,6 +1,8 @@ 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(); } } diff --git a/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php b/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php index c97723f..4ae4cc9 100644 --- a/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php +++ b/tests/codeception/api/unit/models/authentication/ForgotPasswordFormTest.php @@ -1,17 +1,29 @@ 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); } /** diff --git a/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php b/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php index faa2cc2..d3e7c57 100644 --- a/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RegistrationFormTest.php @@ -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']) diff --git a/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php b/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php index e6fe403..23acf22 100644 --- a/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php +++ b/tests/codeception/api/unit/models/authentication/RepeatAccountActivationFormTest.php @@ -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; } diff --git a/tests/codeception/api/unit/request/RequestParserTest.php b/tests/codeception/api/unit/request/RequestParserTest.php new file mode 100644 index 0000000..8dbb3e0 --- /dev/null +++ b/tests/codeception/api/unit/request/RequestParserTest.php @@ -0,0 +1,20 @@ + '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', '')); + } + +} diff --git a/tests/codeception/common/_support/Mockery.php b/tests/codeception/common/_support/Mockery.php new file mode 100644 index 0000000..125441d --- /dev/null +++ b/tests/codeception/common/_support/Mockery.php @@ -0,0 +1,31 @@ +assert_mocks) { + \Mockery::close(); + } else { + \Mockery::getContainer()->mockery_close(); + \Mockery::resetContainer(); + } + } + + public function _failed(TestInterface $test, $fail) { + $this->assert_mocks = false; + } + +} diff --git a/tests/codeception/common/_support/amqp/Helper.php b/tests/codeception/common/_support/amqp/Helper.php index 7c6cd1b..8e2c08c 100644 --- a/tests/codeception/common/_support/amqp/Helper.php +++ b/tests/codeception/common/_support/amqp/Helper.php @@ -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 ); } diff --git a/tests/codeception/common/_support/amqp/TestComponent.php b/tests/codeception/common/_support/amqp/TestComponent.php index 9b8e159..f305e7d 100644 --- a/tests/codeception/common/_support/amqp/TestComponent.php +++ b/tests/codeception/common/_support/amqp/TestComponent.php @@ -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; } } diff --git a/tests/codeception/common/unit.suite.yml b/tests/codeception/common/unit.suite.yml index 98fb59d..74f9d6a 100644 --- a/tests/codeception/common/unit.suite.yml +++ b/tests/codeception/common/unit.suite.yml @@ -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 diff --git a/tests/codeception/common/unit/TestCase.php b/tests/codeception/common/unit/TestCase.php index ef1be1f..b3c1f9b 100644 --- a/tests/codeception/common/unit/TestCase.php +++ b/tests/codeception/common/unit/TestCase.php @@ -1,6 +1,8 @@ makePartial(); + $account->username = 'mock-username'; + $account->email = 'mock@ely.by'; + $this->assertEquals(['mock@ely.by' => 'mock-username'], EmailHelper::buildTo($account)); + } + +} diff --git a/tests/codeception/common/unit/emails/TemplateTest.php b/tests/codeception/common/unit/emails/TemplateTest.php new file mode 100644 index 0000000..d796b32 --- /dev/null +++ b/tests/codeception/common/unit/emails/TemplateTest.php @@ -0,0 +1,47 @@ +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()); + } + +} diff --git a/tests/codeception/common/unit/emails/TemplateWithRendererTest.php b/tests/codeception/common/unit/emails/TemplateWithRendererTest.php new file mode 100644 index 0000000..83f715c --- /dev/null +++ b/tests/codeception/common/unit/emails/TemplateWithRendererTest.php @@ -0,0 +1,49 @@ +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()); + } + +} diff --git a/tests/codeception/config/api/config.php b/tests/codeception/config/api/config.php index eb1ccbb..c6ea4c5 100644 --- a/tests/codeception/config/api/config.php +++ b/tests/codeception/config/api/config.php @@ -1,4 +1,7 @@ [ '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; + } + }; + }, + ], + ], ]; diff --git a/tests/codeception/console/unit.suite.yml b/tests/codeception/console/unit.suite.yml index bdcb10b..110e551 100644 --- a/tests/codeception/console/unit.suite.yml +++ b/tests/codeception/console/unit.suite.yml @@ -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 diff --git a/tests/codeception/console/unit/TestCase.php b/tests/codeception/console/unit/TestCase.php index 7bd53f4..650812b 100644 --- a/tests/codeception/console/unit/TestCase.php +++ b/tests/codeception/console/unit/TestCase.php @@ -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(); + } + }