From 7b14b92a10989aa5de143b758131617614f0aede Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Thu, 6 Jun 2019 02:14:15 +0300 Subject: [PATCH 1/4] Integrate new renderer via docker-compose configuration and nginx [skip ci] --- docker-compose.dev.yml | 4 ++++ docker-compose.prod.yml | 5 +++++ docker/nginx/account.ely.by.conf.template | 11 ++++++----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a705544..890854a 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -9,6 +9,7 @@ services: build_env: dev depends_on: - app + - emails-renderer env_file: .env volumes: - ./:/var/www/html @@ -62,6 +63,9 @@ services: - ./:/var/www/html env_file: .env + emails-renderer: + image: elyby/emails-renderer:dev + db: build: context: . diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 019f5be..9d1a6a0 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -11,6 +11,7 @@ services: restart: always depends_on: - app + - emails-renderer env_file: .env volumes: - ./frontend:/var/www/html/frontend @@ -53,6 +54,10 @@ services: env_file: .env logging: *default-logging + emails-renderer: + image: elyby/emails-renderer:dev + logging: *default-logging + db: image: registry.ely.by/elyby/accounts-mariadb:latest restart: always diff --git a/docker/nginx/account.ely.by.conf.template b/docker/nginx/account.ely.by.conf.template index 91a72e4..be254c3 100644 --- a/docker/nginx/account.ely.by.conf.template +++ b/docker/nginx/account.ely.by.conf.template @@ -41,13 +41,14 @@ server { try_files $uri /index.html =404; } - location /api { - try_files $uri /api/index.php$is_args$args; + location /images/emails/assets { + proxy_pass http://emails-renderer:3000/assets/; + expires $cache_duration; + access_log off; } - location /images/emails/assets { - alias '${root_path}/vendor/ely/email-renderer/dist/assets'; - access_log off; + location /api { + try_files $uri /api/index.php$is_args$args; } location ~* \.php$ { From 1bf249030fdf0883a31e02aecf95f753b6231c16 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Fri, 7 Jun 2019 02:16:13 +0300 Subject: [PATCH 2/4] Removed ely/email-renderer package and implemented new emails renderer client [skip ci] --- .env-dist | 1 + autocompletion.php | 2 +- common/components/EmailRenderer.php | 71 --------------- common/components/EmailsRenderer/Api.php | 59 ++++++++++++ .../components/EmailsRenderer/Component.php | 89 +++++++++++++++++++ .../EmailsRenderer/RendererInterface.php | 10 +++ .../Request/TemplateRequest.php | 41 +++++++++ common/config/config.php | 5 +- common/emails/EmailHelper.php | 2 + common/emails/Template.php | 5 +- common/emails/TemplateWithRenderer.php | 37 +++++--- .../exceptions/CannotSendEmailException.php | 2 + .../ChangeEmailConfirmCurrentEmail.php | 2 + .../templates/ChangeEmailConfirmNewEmail.php | 2 + .../emails/templates/ForgotPasswordEmail.php | 7 +- .../emails/templates/ForgotPasswordParams.php | 2 + common/emails/templates/RegistrationEmail.php | 7 +- .../templates/RegistrationEmailParams.php | 2 + common/tasks/SendPasswordRecoveryEmail.php | 2 +- common/tasks/SendRegistrationEmail.php | 2 +- .../unit/emails/TemplateWithRendererTest.php | 8 +- composer.json | 5 -- composer.lock | 45 +--------- 23 files changed, 262 insertions(+), 146 deletions(-) delete mode 100644 common/components/EmailRenderer.php create mode 100644 common/components/EmailsRenderer/Api.php create mode 100644 common/components/EmailsRenderer/Component.php create mode 100644 common/components/EmailsRenderer/RendererInterface.php create mode 100644 common/components/EmailsRenderer/Request/TemplateRequest.php diff --git a/.env-dist b/.env-dist index 8357fea..24b206c 100644 --- a/.env-dist +++ b/.env-dist @@ -3,6 +3,7 @@ YII_DEBUG=true YII_ENV=dev DOMAIN=https://account.ely.by +EMAILS_RENDERER_HOST=http://emails-renderer:3000 ## Параметры, отвечающие за безопасность JWT_USER_SECRET= diff --git a/autocompletion.php b/autocompletion.php index a70d827..ba53026 100644 --- a/autocompletion.php +++ b/autocompletion.php @@ -20,7 +20,7 @@ class Yii extends \yii\BaseYii { * @property \yii\swiftmailer\Mailer $mailer * @property \yii\redis\Connection $redis * @property \GuzzleHttp\Client $guzzle - * @property \common\components\EmailRenderer $emailRenderer + * @property \common\components\EmailsRenderer\Component $emailsRenderer * @property \mito\sentry\Component $sentry * @property \api\components\OAuth2\Component $oauth * @property \common\components\StatsD $statsd diff --git a/common/components/EmailRenderer.php b/common/components/EmailRenderer.php deleted file mode 100644 index 615906e..0000000 --- a/common/components/EmailRenderer.php +++ /dev/null @@ -1,71 +0,0 @@ -_baseDomain === null) { - $this->_baseDomain = Yii::$app->urlManager->getHostInfo(); - if ($this->_baseDomain === null) { - throw new InvalidConfigException('Cannot automatically obtain base domain'); - } - } - - $this->renderer = new Renderer($this->buildBasePath()); - } - - public function setBaseDomain(string $baseDomain) { - $this->_baseDomain = $baseDomain; - $this->renderer->setBaseDomain($this->buildBasePath()); - } - - public function getBaseDomain(): string { - return $this->_baseDomain; - } - - /** - * @param string $templateName - * @return TemplateBuilder - */ - public function getTemplate(string $templateName): TemplateBuilder { - return $this->renderer->getTemplate($templateName); - } - - /** - * @param TemplateBuilder $template - * @throws \Ely\Email\RendererException - * @return string - */ - public function render(TemplateBuilder $template): string { - return $this->renderer->render($template); - } - - private function buildBasePath(): string { - return $this->_baseDomain . $this->basePath; - } - -} diff --git a/common/components/EmailsRenderer/Api.php b/common/components/EmailsRenderer/Api.php new file mode 100644 index 0000000..8259f8b --- /dev/null +++ b/common/components/EmailsRenderer/Api.php @@ -0,0 +1,59 @@ +baseUrl = $baseUrl; + } + + public function setClient(ClientInterface $client): void { + $this->client = $client; + } + + /** + * @param \common\components\EmailsRenderer\Request\TemplateRequest $request + * + * @return string + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function getTemplate(Request\TemplateRequest $request): string { + return $this->getClient() + ->request('GET', "/templates/{$request->getLocale()}/{$request->getName()}", [ + 'query' => $request->getParams(), + ]) + ->getBody() + ->getContents(); + } + + /** + * @return ClientInterface + */ + protected function getClient(): ClientInterface { + if ($this->client === null) { + $this->client = $this->createDefaultClient(); + } + + return $this->client; + } + + private function createDefaultClient(): ClientInterface { + return new GuzzleClient([ + 'timeout' => 5, + 'base_uri' => $this->baseUrl, + ]); + } + +} diff --git a/common/components/EmailsRenderer/Component.php b/common/components/EmailsRenderer/Component.php new file mode 100644 index 0000000..f7e6dbe --- /dev/null +++ b/common/components/EmailsRenderer/Component.php @@ -0,0 +1,89 @@ +serviceUrl === null) { + throw new InvalidConfigException('serviceUrl is required'); + } + + if ($this->_baseDomain === null) { + $this->_baseDomain = Yii::$app->urlManager->getHostInfo(); + if ($this->_baseDomain === null) { + throw new InvalidConfigException('Cannot automatically obtain base domain'); + } + } + } + + public function setBaseDomain(string $baseDomain): void { + $this->_baseDomain = $baseDomain; + } + + public function getBaseDomain(): string { + return $this->_baseDomain; + } + + /** + * @param string $templateName + * @param string $locale + * @param array $params + * + * @return string + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function render(string $templateName, string $locale, array $params = []): string { + $request = new TemplateRequest($templateName, $locale, ArrayHelper::merge($params, [ + 'assetsHost' => $this->buildBasePath(), + ])); + + return $this->getApi()->getTemplate($request); + } + + private function getApi(): Api { + if ($this->api === null) { + $this->api = new Api($this->serviceUrl); + } + + return $this->api; + } + + private function buildBasePath(): string { + return FileHelper::normalizePath($this->_baseDomain . '/' . $this->basePath, '/'); + } + +} diff --git a/common/components/EmailsRenderer/RendererInterface.php b/common/components/EmailsRenderer/RendererInterface.php new file mode 100644 index 0000000..ad4804e --- /dev/null +++ b/common/components/EmailsRenderer/RendererInterface.php @@ -0,0 +1,10 @@ +name = $name; + $this->locale = $locale; + $this->params = $params; + } + + public function getName(): string { + return $this->name; + } + + public function getLocale(): string { + return $this->locale; + } + + public function getParams(): array { + return $this->params; + } + +} diff --git a/common/config/config.php b/common/config/config.php index 5cd970d..d6cd125 100644 --- a/common/config/config.php +++ b/common/config/config.php @@ -89,8 +89,9 @@ return [ 'guzzle' => [ 'class' => GuzzleHttp\Client::class, ], - 'emailRenderer' => [ - 'class' => common\components\EmailRenderer::class, + 'emailsRenderer' => [ + 'class' => common\components\EmailsRenderer\Component::class, + 'serviceUrl' => getenv('EMAILS_RENDERER_HOST'), 'basePath' => '/images/emails', ], 'oauth' => [ diff --git a/common/emails/EmailHelper.php b/common/emails/EmailHelper.php index 3b0d5e3..708b7b2 100644 --- a/common/emails/EmailHelper.php +++ b/common/emails/EmailHelper.php @@ -1,4 +1,6 @@ name] + * @param string|array $to message receiver. Can be passed as string (pure email) + * or as an array [email => user's name] */ public function __construct($to) { $this->mailer = Yii::$app->mailer; diff --git a/common/emails/TemplateWithRenderer.php b/common/emails/TemplateWithRenderer.php index adfef8c..851c3d4 100644 --- a/common/emails/TemplateWithRenderer.php +++ b/common/emails/TemplateWithRenderer.php @@ -1,16 +1,19 @@ emailRenderer = Yii::$app->emailRenderer; $this->locale = $locale; + $this->renderer = $renderer; } public function getLocale(): string { return $this->locale; } - public function getEmailRenderer(): EmailRenderer { - return $this->emailRenderer; + public function getRenderer(): RendererInterface { + return $this->renderer; } /** @@ -46,6 +49,10 @@ abstract class TemplateWithRenderer extends Template { return $this->getTemplateName(); } + /** + * @return MessageInterface + * @throws ErrorException + */ protected function createMessage(): MessageInterface { return $this->getMailer() ->compose() @@ -55,12 +62,16 @@ abstract class TemplateWithRenderer extends Template { ->setSubject($this->getSubject()); } + /** + * @return string + * @throws ErrorException + */ private function render(): string { - return $this->getEmailRenderer() - ->getTemplate($this->getTemplateName()) - ->setLocale($this->getLocale()) - ->setParams($this->getParams()) - ->render(); + try { + return $this->getRenderer()->render($this->getTemplateName(), $this->getLocale(), $this->getParams()); + } catch (Exception $e) { + throw new ErrorException('Unable to render the template', 0, 1, __FILE__, __LINE__, $e); + } } } diff --git a/common/emails/exceptions/CannotSendEmailException.php b/common/emails/exceptions/CannotSendEmailException.php index f893d22..2b9420a 100644 --- a/common/emails/exceptions/CannotSendEmailException.php +++ b/common/emails/exceptions/CannotSendEmailException.php @@ -1,4 +1,6 @@ params = $params; } diff --git a/common/emails/templates/ForgotPasswordParams.php b/common/emails/templates/ForgotPasswordParams.php index 1a5de39..f78ad7f 100644 --- a/common/emails/templates/ForgotPasswordParams.php +++ b/common/emails/templates/ForgotPasswordParams.php @@ -1,4 +1,6 @@ params = $params; } diff --git a/common/emails/templates/RegistrationEmailParams.php b/common/emails/templates/RegistrationEmailParams.php index ea3a994..1044c75 100644 --- a/common/emails/templates/RegistrationEmailParams.php +++ b/common/emails/templates/RegistrationEmailParams.php @@ -1,4 +1,6 @@ statsd->inc('queue.sendPasswordRecovery.attempt'); $params = new ForgotPasswordParams($this->username, $this->code, $this->link); $to = EmailHelper::buildTo($this->username, $this->email); - $template = new ForgotPasswordEmail($to, $this->locale, $params); + $template = new ForgotPasswordEmail($to, $this->locale, $params, Yii::$app->emailsRenderer); $template->send(); } diff --git a/common/tasks/SendRegistrationEmail.php b/common/tasks/SendRegistrationEmail.php index a960e96..2a5e4fd 100644 --- a/common/tasks/SendRegistrationEmail.php +++ b/common/tasks/SendRegistrationEmail.php @@ -50,7 +50,7 @@ class SendRegistrationEmail implements RetryableJobInterface { Yii::$app->statsd->inc('queue.sendRegistrationEmail.attempt'); $params = new RegistrationEmailParams($this->username, $this->code, $this->link); $to = EmailHelper::buildTo($this->username, $this->email); - $template = new RegistrationEmail($to, $this->locale, $params); + $template = new RegistrationEmail($to, $this->locale, $params, Yii::$app->emailsRenderer); $template->send(); } diff --git a/common/tests/unit/emails/TemplateWithRendererTest.php b/common/tests/unit/emails/TemplateWithRendererTest.php index 08e2aa5..3144871 100644 --- a/common/tests/unit/emails/TemplateWithRendererTest.php +++ b/common/tests/unit/emails/TemplateWithRendererTest.php @@ -1,7 +1,7 @@ assertSame('mock-to', $template->getTo()); $this->assertSame('mock-locale', $template->getLocale()); $this->assertInstanceOf(MailerInterface::class, $template->getMailer()); - $this->assertInstanceOf(EmailRenderer::class, $template->getEmailRenderer()); + $this->assertInstanceOf(Component::class, $template->getRenderer()); } public function testCreateMessage() { @@ -26,8 +26,8 @@ class TemplateWithRendererTest extends TestCase { $templateBuilder = mock(TemplateBuilder::class)->makePartial(); $templateBuilder->shouldReceive('render')->andReturn('mock-html'); - /** @var EmailRenderer|\Mockery\MockInterface $renderer */ - $renderer = mock(EmailRenderer::class)->makePartial(); + /** @var Component|\Mockery\MockInterface $renderer */ + $renderer = mock(Component::class)->makePartial(); $renderer->shouldReceive('getTemplate')->with('mock-template')->andReturn($templateBuilder); /** @var TemplateWithRenderer|\Mockery\MockInterface $template */ diff --git a/composer.json b/composer.json index cb444a3..f39482e 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,6 @@ "ext-simplexml": "*", "bacon/bacon-qr-code": "^1.0", "domnikl/statsd": "^2.6", - "ely/email-renderer": "dev-master", "ely/mojang-api": "^0.2.0", "ely/yii2-tempmail-validator": "^2.0", "emarref/jwt": "~1.0.3", @@ -52,10 +51,6 @@ { "type": "composer", "url": "https://asset-packagist.org" - }, - { - "type": "git", - "url": "https://gitlab+deploy-token-1:FDGgmcnLdykcsyJJ_8Uv@gitlab.ely.by/elyby/email-renderer.git" } ], "config": { diff --git a/composer.lock b/composer.lock index 6139b73..d6056bc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "68086c7aa86b976c390b02cc53b182f9", + "content-hash": "752322170e3433ba42cb90c1dd6122a2", "packages": [ { "name": "bacon/bacon-qr-code", @@ -166,7 +166,7 @@ "version": "3.27.0", "source": { "type": "git", - "url": "git@github.com:getsentry/raven-js-bower.git", + "url": "https://github.com/getsentry/raven-js-bower.git", "reference": "c8b3a6040be6928e2f57fa5eec4d7afc31750235" }, "dist": { @@ -606,46 +606,6 @@ ], "time": "2018-12-04T22:38:24+00:00" }, - { - "name": "ely/email-renderer", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://gitlab+deploy-token-1:FDGgmcnLdykcsyJJ_8Uv@gitlab.ely.by/elyby/email-renderer.git", - "reference": "3b06c19b39a298e6cb00f711b5fed40c315cd95e" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Ely\\Email\\": "src-php/" - } - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ely.by team", - "email": "team@ely.by" - }, - { - "name": "ErickSkrauch", - "email": "erickskrauch@ely.by" - }, - { - "name": "SleepWalker", - "email": "dev@udf.su" - } - ], - "homepage": "http://ely.by", - "keywords": [ - "" - ], - "time": "2019-05-13T21:56:45+00:00" - }, { "name": "ely/mojang-api", "version": "0.2.0", @@ -6180,7 +6140,6 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "ely/email-renderer": 20, "roave/security-advisories": 20 }, "prefer-stable": false, From 70d1999d5537d6d8800f9bd463f283106d0cff9e Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 16 Jun 2019 23:59:19 +0300 Subject: [PATCH 3/4] Refactor emails models objects, rework related tests --- .../components/EmailsRenderer/Component.php | 33 ++--- common/config/config-test.php | 3 + .../RendererInterface.php | 2 +- common/emails/Template.php | 49 +++----- common/emails/TemplateWithRenderer.php | 41 +++--- .../exceptions/CannotRenderEmailException.php | 15 +++ .../exceptions/CannotSendEmailException.php | 5 + ...onfirmCurrentEmail.php => ChangeEmail.php} | 16 ++- ...onfirmNewEmail.php => ConfirmNewEmail.php} | 24 ++-- .../emails/templates/ForgotPasswordEmail.php | 19 +-- common/emails/templates/RegistrationEmail.php | 19 +-- common/tasks/SendCurrentEmailConfirmation.php | 14 ++- common/tasks/SendNewEmailConfirmation.php | 15 ++- common/tasks/SendPasswordRecoveryEmail.php | 13 +- common/tasks/SendRegistrationEmail.php | 13 +- common/tests/_support/EmailsRenderer.php | 14 +++ common/tests/unit/emails/TemplateTest.php | 78 ++++++++---- .../unit/emails/TemplateWithRendererTest.php | 118 +++++++++++++----- .../unit/emails/templates/ChangeEmailTest.php | 36 ++++++ .../emails/templates/ConfirmNewEmailTest.php | 49 ++++++++ .../templates/ForgotPasswordEmailTest.php | 42 +++++++ .../templates/RegistrationEmailTest.php | 42 +++++++ .../tasks/SendPasswordRecoveryEmailTest.php | 28 ++++- .../unit/tasks/SendRegistrationEmailTest.php | 30 ++++- 24 files changed, 522 insertions(+), 196 deletions(-) rename common/{components/EmailsRenderer => emails}/RendererInterface.php (78%) create mode 100644 common/emails/exceptions/CannotRenderEmailException.php rename common/emails/templates/{ChangeEmailConfirmCurrentEmail.php => ChangeEmail.php} (69%) rename common/emails/templates/{ChangeEmailConfirmNewEmail.php => ConfirmNewEmail.php} (62%) create mode 100644 common/tests/_support/EmailsRenderer.php create mode 100644 common/tests/unit/emails/templates/ChangeEmailTest.php create mode 100644 common/tests/unit/emails/templates/ConfirmNewEmailTest.php create mode 100644 common/tests/unit/emails/templates/ForgotPasswordEmailTest.php create mode 100644 common/tests/unit/emails/templates/RegistrationEmailTest.php diff --git a/common/components/EmailsRenderer/Component.php b/common/components/EmailsRenderer/Component.php index f7e6dbe..8adc39f 100644 --- a/common/components/EmailsRenderer/Component.php +++ b/common/components/EmailsRenderer/Component.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace common\components\EmailsRenderer; use common\components\EmailsRenderer\Request\TemplateRequest; +use common\emails\RendererInterface; use Yii; use yii\base\InvalidConfigException; use yii\helpers\ArrayHelper; use yii\helpers\FileHelper; -/** - * @property string $baseDomain - */ class Component extends \yii\base\Component implements RendererInterface { /** @@ -20,8 +18,12 @@ class Component extends \yii\base\Component implements RendererInterface { public $serviceUrl; /** - * @var string базовый путь после хоста. Должен начинаться слешем и заканчиваться без него. - * Например "/email-images" + * @var string application base domain. Can be omitted for web applications (will be extracted from request) + */ + public $baseDomain; + + /** + * @var string base path after the host. For example "/emails-images" */ public $basePath = ''; @@ -30,11 +32,6 @@ class Component extends \yii\base\Component implements RendererInterface { */ private $api; - /** - * @var string - */ - private $_baseDomain; - public function init(): void { parent::init(); @@ -42,22 +39,14 @@ class Component extends \yii\base\Component implements RendererInterface { throw new InvalidConfigException('serviceUrl is required'); } - if ($this->_baseDomain === null) { - $this->_baseDomain = Yii::$app->urlManager->getHostInfo(); - if ($this->_baseDomain === null) { + if ($this->baseDomain === null) { + $this->baseDomain = Yii::$app->urlManager->getHostInfo(); + if ($this->baseDomain === null) { throw new InvalidConfigException('Cannot automatically obtain base domain'); } } } - public function setBaseDomain(string $baseDomain): void { - $this->_baseDomain = $baseDomain; - } - - public function getBaseDomain(): string { - return $this->_baseDomain; - } - /** * @param string $templateName * @param string $locale @@ -83,7 +72,7 @@ class Component extends \yii\base\Component implements RendererInterface { } private function buildBasePath(): string { - return FileHelper::normalizePath($this->_baseDomain . '/' . $this->basePath, '/'); + return FileHelper::normalizePath($this->baseDomain . '/' . $this->basePath, '/'); } } diff --git a/common/config/config-test.php b/common/config/config-test.php index 091aba4..2cffd5e 100644 --- a/common/config/config-test.php +++ b/common/config/config-test.php @@ -19,5 +19,8 @@ return [ 'mailer' => [ 'useFileTransport' => true, ], + 'emailsRenderer' => [ + 'class' => common\tests\_support\EmailsRenderer::class, + ], ], ]; diff --git a/common/components/EmailsRenderer/RendererInterface.php b/common/emails/RendererInterface.php similarity index 78% rename from common/components/EmailsRenderer/RendererInterface.php rename to common/emails/RendererInterface.php index ad4804e..1d8f101 100644 --- a/common/components/EmailsRenderer/RendererInterface.php +++ b/common/emails/RendererInterface.php @@ -1,7 +1,7 @@ user's name] - */ - public function __construct($to) { - $this->mailer = Yii::$app->mailer; - $this->to = $to; - } - - /** - * @return array|string - */ - public function getTo() { - return $this->to; + public function __construct(MailerInterface $mailer) { + $this->mailer = $mailer; } abstract public function getSubject(): string; @@ -44,7 +26,7 @@ abstract class Template { * @throws InvalidConfigException */ public function getFrom() { - $fromEmail = Yii::$app->params['fromEmail']; + $fromEmail = Yii::$app->params['fromEmail'] ?? ''; if (!$fromEmail) { throw new InvalidConfigException('Please specify fromEmail app in app params'); } @@ -56,13 +38,14 @@ abstract class Template { return []; } - public function getMailer(): MailerInterface { - return $this->mailer; - } - - public function send(): void { - if (!$this->createMessage()->send()) { - throw new CannotSendEmailException('Unable send email.'); + /** + * @param string|array $to see \yii\mail\MessageInterface::setTo to know the format. + * + * @throws \common\emails\exceptions\CannotSendEmailException + */ + public function send($to): void { + if (!$this->createMessage($to)->send()) { + throw new exceptions\CannotSendEmailException(); } } @@ -71,10 +54,14 @@ abstract class Template { */ abstract protected function getView(); - protected function createMessage(): MessageInterface { + final protected function getMailer(): MailerInterface { + return $this->mailer; + } + + protected function createMessage($for): MessageInterface { return $this->getMailer() ->compose($this->getView(), $this->getParams()) - ->setTo($this->getTo()) + ->setTo($for) ->setFrom($this->getFrom()) ->setSubject($this->getSubject()); } diff --git a/common/emails/TemplateWithRenderer.php b/common/emails/TemplateWithRenderer.php index 851c3d4..8e912d6 100644 --- a/common/emails/TemplateWithRenderer.php +++ b/common/emails/TemplateWithRenderer.php @@ -3,9 +3,8 @@ declare(strict_types=1); namespace common\emails; -use common\components\EmailsRenderer\RendererInterface; -use ErrorException; use Exception; +use yii\mail\MailerInterface; use yii\mail\MessageInterface; abstract class TemplateWithRenderer extends Template { @@ -18,59 +17,61 @@ abstract class TemplateWithRenderer extends Template { /** * @var string */ - private $locale; + private $locale = 'en'; - /** - * @inheritdoc - */ - public function __construct($to, string $locale, RendererInterface $renderer) { - parent::__construct($to); - $this->locale = $locale; + public function __construct(MailerInterface $mailer, RendererInterface $renderer) { + parent::__construct($mailer); $this->renderer = $renderer; } + public function setLocale(string $locale): void { + $this->locale = $locale; + } + public function getLocale(): string { return $this->locale; } - public function getRenderer(): RendererInterface { - return $this->renderer; - } - /** - * Метод должен возвращать имя шаблона, который должен быть использован. - * Имена можно взять в репозитории elyby/email-renderer + * This method should return the template's name, which will be rendered. + * List of available templates names can be found at https://github.com/elyby/emails-renderer * * @return string */ abstract public function getTemplateName(): string; + final protected function getRenderer(): RendererInterface { + return $this->renderer; + } + final protected function getView() { return $this->getTemplateName(); } /** + * @param string|array $for + * * @return MessageInterface - * @throws ErrorException + * @throws \common\emails\exceptions\CannotRenderEmailException */ - protected function createMessage(): MessageInterface { + protected function createMessage($for): MessageInterface { return $this->getMailer() ->compose() ->setHtmlBody($this->render()) - ->setTo($this->getTo()) + ->setTo($for) ->setFrom($this->getFrom()) ->setSubject($this->getSubject()); } /** * @return string - * @throws ErrorException + * @throws \common\emails\exceptions\CannotRenderEmailException */ private function render(): string { try { return $this->getRenderer()->render($this->getTemplateName(), $this->getLocale(), $this->getParams()); } catch (Exception $e) { - throw new ErrorException('Unable to render the template', 0, 1, __FILE__, __LINE__, $e); + throw new exceptions\CannotRenderEmailException($e); } } diff --git a/common/emails/exceptions/CannotRenderEmailException.php b/common/emails/exceptions/CannotRenderEmailException.php new file mode 100644 index 0000000..456e313 --- /dev/null +++ b/common/emails/exceptions/CannotRenderEmailException.php @@ -0,0 +1,15 @@ +key = $key; } @@ -19,14 +22,15 @@ class ChangeEmailConfirmCurrentEmail extends Template { } public function getParams(): array { + if ($this->key === null) { + throw new InvalidCallException('You need to set key param first'); + } + return [ 'key' => $this->key, ]; } - /** - * @return string|array - */ protected function getView() { return [ 'html' => '@common/emails/views/current-email-confirmation-html', diff --git a/common/emails/templates/ChangeEmailConfirmNewEmail.php b/common/emails/templates/ConfirmNewEmail.php similarity index 62% rename from common/emails/templates/ChangeEmailConfirmNewEmail.php rename to common/emails/templates/ConfirmNewEmail.php index ac32c10..de19655 100644 --- a/common/emails/templates/ChangeEmailConfirmNewEmail.php +++ b/common/emails/templates/ConfirmNewEmail.php @@ -4,16 +4,25 @@ declare(strict_types=1); namespace common\emails\templates; use common\emails\Template; +use yii\base\InvalidCallException; -class ChangeEmailConfirmNewEmail extends Template { +class ConfirmNewEmail extends Template { + /** + * @var string|null + */ private $username; + /** + * @var string|null + */ private $key; - public function __construct($to, string $username, string $key) { - parent::__construct($to); + public function setUsername(string $username): void { $this->username = $username; + } + + public function setKey(string $key): void { $this->key = $key; } @@ -22,15 +31,16 @@ class ChangeEmailConfirmNewEmail extends Template { } public function getParams(): array { + if ($this->username === null || $this->key === null) { + throw new InvalidCallException('You need to set username and key params first'); + } + return [ - 'key' => $this->key, 'username' => $this->username, + 'key' => $this->key, ]; } - /** - * @return string|array - */ protected function getView() { return [ 'html' => '@common/emails/views/new-email-confirmation-html', diff --git a/common/emails/templates/ForgotPasswordEmail.php b/common/emails/templates/ForgotPasswordEmail.php index 51c9e4e..ce50b4c 100644 --- a/common/emails/templates/ForgotPasswordEmail.php +++ b/common/emails/templates/ForgotPasswordEmail.php @@ -3,20 +3,15 @@ declare(strict_types=1); namespace common\emails\templates; -use common\components\EmailsRenderer\RendererInterface; use common\emails\TemplateWithRenderer; +use yii\base\InvalidCallException; class ForgotPasswordEmail extends TemplateWithRenderer { - private $params; - /** - * @inheritdoc + * @var ForgotPasswordParams|null */ - public function __construct($to, string $locale, ForgotPasswordParams $params, RendererInterface $renderer) { - parent::__construct($to, $locale, $renderer); - $this->params = $params; - } + private $params; public function getSubject(): string { return 'Ely.by Account forgot password'; @@ -26,7 +21,15 @@ class ForgotPasswordEmail extends TemplateWithRenderer { return 'forgotPassword'; } + public function setParams(ForgotPasswordParams $params): void { + $this->params = $params; + } + public function getParams(): array { + if ($this->params === null) { + throw new InvalidCallException('You need to set params first'); + } + return [ 'username' => $this->params->getUsername(), 'code' => $this->params->getCode(), diff --git a/common/emails/templates/RegistrationEmail.php b/common/emails/templates/RegistrationEmail.php index 8cf0893..ca065b8 100644 --- a/common/emails/templates/RegistrationEmail.php +++ b/common/emails/templates/RegistrationEmail.php @@ -3,20 +3,15 @@ declare(strict_types=1); namespace common\emails\templates; -use common\components\EmailsRenderer\RendererInterface; use common\emails\TemplateWithRenderer; +use yii\base\InvalidCallException; class RegistrationEmail extends TemplateWithRenderer { - private $params; - /** - * @inheritdoc + * @var RegistrationEmailParams|null */ - public function __construct($to, string $locale, RegistrationEmailParams $params, RendererInterface $renderer) { - parent::__construct($to, $locale, $renderer); - $this->params = $params; - } + private $params; public function getSubject(): string { return 'Ely.by Account registration'; @@ -26,7 +21,15 @@ class RegistrationEmail extends TemplateWithRenderer { return 'register'; } + public function setParams(RegistrationEmailParams $params): void { + $this->params = $params; + } + public function getParams(): array { + if ($this->params === null) { + throw new InvalidCallException('You need to set params first'); + } + return [ 'username' => $this->params->getUsername(), 'code' => $this->params->getCode(), diff --git a/common/tasks/SendCurrentEmailConfirmation.php b/common/tasks/SendCurrentEmailConfirmation.php index fe0285e..7396c8e 100644 --- a/common/tasks/SendCurrentEmailConfirmation.php +++ b/common/tasks/SendCurrentEmailConfirmation.php @@ -1,9 +1,10 @@ statsd->inc('queue.sendCurrentEmailConfirmation.attempt'); - $to = EmailHelper::buildTo($this->username, $this->email); - $template = new ChangeEmailConfirmCurrentEmail($to, $this->code); - $template->send(); + $template = new ChangeEmail(Yii::$app->mailer); + $template->setKey($this->code); + $template->send(EmailHelper::buildTo($this->username, $this->email)); } } diff --git a/common/tasks/SendNewEmailConfirmation.php b/common/tasks/SendNewEmailConfirmation.php index da034ff..0f7fcd4 100644 --- a/common/tasks/SendNewEmailConfirmation.php +++ b/common/tasks/SendNewEmailConfirmation.php @@ -1,9 +1,10 @@ statsd->inc('queue.sendNewEmailConfirmation.attempt'); - $to = EmailHelper::buildTo($this->username, $this->email); - $template = new ChangeEmailConfirmNewEmail($to, $this->username, $this->code); - $template->send(); + $template = new ConfirmNewEmail(Yii::$app->mailer); + $template->setKey($this->code); + $template->setUsername($this->username); + $template->send(EmailHelper::buildTo($this->username, $this->email)); } } diff --git a/common/tasks/SendPasswordRecoveryEmail.php b/common/tasks/SendPasswordRecoveryEmail.php index 41689ea..d9a90c4 100644 --- a/common/tasks/SendPasswordRecoveryEmail.php +++ b/common/tasks/SendPasswordRecoveryEmail.php @@ -1,5 +1,6 @@ statsd->inc('queue.sendPasswordRecovery.attempt'); - $params = new ForgotPasswordParams($this->username, $this->code, $this->link); - $to = EmailHelper::buildTo($this->username, $this->email); - $template = new ForgotPasswordEmail($to, $this->locale, $params, Yii::$app->emailsRenderer); - $template->send(); + $template = new ForgotPasswordEmail(Yii::$app->mailer, Yii::$app->emailsRenderer); + $template->setLocale($this->locale); + $template->setParams(new ForgotPasswordParams($this->username, $this->code, $this->link)); + $template->send(EmailHelper::buildTo($this->username, $this->email)); } } diff --git a/common/tasks/SendRegistrationEmail.php b/common/tasks/SendRegistrationEmail.php index 2a5e4fd..2380c80 100644 --- a/common/tasks/SendRegistrationEmail.php +++ b/common/tasks/SendRegistrationEmail.php @@ -1,5 +1,6 @@ statsd->inc('queue.sendRegistrationEmail.attempt'); - $params = new RegistrationEmailParams($this->username, $this->code, $this->link); - $to = EmailHelper::buildTo($this->username, $this->email); - $template = new RegistrationEmail($to, $this->locale, $params, Yii::$app->emailsRenderer); - $template->send(); + $template = new RegistrationEmail(Yii::$app->mailer, Yii::$app->emailsRenderer); + $template->setLocale($this->locale); + $template->setParams(new RegistrationEmailParams($this->username, $this->code, $this->link)); + $template->send(EmailHelper::buildTo($this->username, $this->email)); } } diff --git a/common/tests/_support/EmailsRenderer.php b/common/tests/_support/EmailsRenderer.php new file mode 100644 index 0000000..cf9670a --- /dev/null +++ b/common/tests/_support/EmailsRenderer.php @@ -0,0 +1,14 @@ +makePartial(); - $this->assertSame('find-me', $template->getTo()); - $this->assertInstanceOf(MailerInterface::class, $template->getMailer()); + /** + * @var Template|\PHPUnit\Framework\MockObject\MockObject $template + */ + private $template; + + /** + * @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $mailer; + + /** + * @var string + */ + private $initialFromEmail; + + public function testGetters() { + $this->assertSame(['find-me' => 'Ely.by Accounts'], $this->template->getFrom()); + $this->assertSame([], $this->template->getParams()); } - public function testGetFrom() { + public function testSend() { + $this->runTestForSend(true); + } + + public function testNotSend() { + $this->expectException(CannotSendEmailException::class); + $this->runTestForSend(false); + } + + protected function _before() { + parent::_before(); + $this->mailer = $this->createMock(MailerInterface::class); + $this->template = $this->getMockForAbstractClass(Template::class, [$this->mailer]); + $this->initialFromEmail = Yii::$app->params['fromEmail']; Yii::$app->params['fromEmail'] = 'find-me'; - /** @var Template|\Mockery\MockInterface $template */ - $template = mock(Template::class)->makePartial(); - $this->assertSame(['find-me' => 'Ely.by Accounts'], $template->getFrom()); } - public function testGetParams() { - /** @var Template|\Mockery\MockInterface $template */ - $template = mock(Template::class)->makePartial(); - $this->assertSame([], $template->getParams()); + protected function _after() { + parent::_after(); + Yii::$app->params['fromEmail'] = $this->initialFromEmail; } - 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->assertSame(['to@ely.by' => 'To'], $message->getTo()); - $this->assertSame(['from@ely.by' => 'Ely.by Accounts'], $message->getFrom()); - $this->assertSame('mock-subject', $message->getSubject()); + private function runTestForSend(bool $sendResult) { + $this->template->expects($this->once())->method('getSubject')->willReturn('mock-subject'); + $this->template->expects($this->once())->method('getView')->willReturn('mock-view'); + + /** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $message */ + $message = $this->createMock(MessageInterface::class); + $message->expects($this->once())->method('setTo')->with(['to@ely.by' => 'To'])->willReturnSelf(); + $message->expects($this->once())->method('setFrom')->with(['find-me' => 'Ely.by Accounts'])->willReturnSelf(); + $message->expects($this->once())->method('setSubject')->with('mock-subject')->willReturnSelf(); + $message->expects($this->once())->method('send')->willReturn($sendResult); + + $this->mailer->expects($this->once())->method('compose')->with('mock-view', [])->willReturn($message); + + $this->template->send(['to@ely.by' => 'To']); } } diff --git a/common/tests/unit/emails/TemplateWithRendererTest.php b/common/tests/unit/emails/TemplateWithRendererTest.php index 3144871..acb923f 100644 --- a/common/tests/unit/emails/TemplateWithRendererTest.php +++ b/common/tests/unit/emails/TemplateWithRendererTest.php @@ -1,49 +1,103 @@ makePartial(); - $this->assertSame('mock-to', $template->getTo()); - $this->assertSame('mock-locale', $template->getLocale()); - $this->assertInstanceOf(MailerInterface::class, $template->getMailer()); - $this->assertInstanceOf(Component::class, $template->getRenderer()); + /** + * @var TemplateWithRenderer|\PHPUnit\Framework\MockObject\MockObject $template + */ + private $template; + + /** + * @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $mailer; + + /** + * @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $renderer; + + /** + * @var string + */ + private $initialFromEmail; + + public function testGetLocale() { + $this->assertSame('en', $this->template->getLocale()); + $this->template->setLocale('find me'); + $this->assertSame('find me', $this->template->getLocale()); } - public function testCreateMessage() { - /** @var TemplateBuilder|\Mockery\MockInterface $templateBuilder */ - $templateBuilder = mock(TemplateBuilder::class)->makePartial(); - $templateBuilder->shouldReceive('render')->andReturn('mock-html'); + public function testSend() { + $this->runTestForSend(); + } - /** @var Component|\Mockery\MockInterface $renderer */ - $renderer = mock(Component::class)->makePartial(); - $renderer->shouldReceive('getTemplate')->with('mock-template')->andReturn($templateBuilder); + public function testSendWithRenderError() { + $renderException = new Exception('find me'); + try { + $this->runTestForSend($renderException); + } catch (CannotRenderEmailException $e) { + // Catch exception manually to assert the previous exception + $this->assertSame('Unable to render a template', $e->getMessage()); + $this->assertSame($renderException, $e->getPrevious()); - /** @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->assertSame(['to@ely.by' => 'To'], $message->getTo()); - $this->assertSame(['from@ely.by' => 'From'], $message->getFrom()); - $this->assertSame('mock-subject', $message->getSubject()); - $this->assertSame('mock-html', $message->getSwiftMessage()->getBody()); + return; + } + + $this->assertFalse(true, 'no exception was thrown'); + } + + protected function _before() { + parent::_before(); + $this->mailer = $this->createMock(MailerInterface::class); + $this->renderer = $this->createMock(RendererInterface::class); + $this->template = $this->getMockForAbstractClass(TemplateWithRenderer::class, [$this->mailer, $this->renderer]); + $this->initialFromEmail = Yii::$app->params['fromEmail']; + Yii::$app->params['fromEmail'] = 'find-me'; + } + + protected function _after() { + parent::_after(); + Yii::$app->params['fromEmail'] = $this->initialFromEmail; + } + + private function runTestForSend($renderException = null) { + $renderMethodExpectation = $this->renderer->expects($this->once())->method('render')->with('mock-template', 'mock-locale', []); + if ($renderException === null) { + $renderMethodExpectation->willReturn('mock-template-contents'); + $times = [$this, 'once']; + } else { + $renderMethodExpectation->willThrowException($renderException); + $times = [$this, 'any']; + } + + $this->template->expects($times())->method('getSubject')->willReturn('mock-subject'); + $this->template->expects($times())->method('getTemplateName')->willReturn('mock-template'); + + /** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $message */ + $message = $this->createMock(MessageInterface::class); + $message->expects($times())->method('setTo')->with(['to@ely.by' => 'To'])->willReturnSelf(); + $message->expects($times())->method('setHtmlBody')->with('mock-template-contents')->willReturnSelf(); + $message->expects($times())->method('setFrom')->with(['find-me' => 'Ely.by Accounts'])->willReturnSelf(); + $message->expects($times())->method('setSubject')->with('mock-subject')->willReturnSelf(); + $message->expects($times())->method('send')->willReturn(true); + + $this->mailer->expects($times())->method('compose')->willReturn($message); + + $this->template->setLocale('mock-locale'); + $this->template->send(['to@ely.by' => 'To']); } } diff --git a/common/tests/unit/emails/templates/ChangeEmailTest.php b/common/tests/unit/emails/templates/ChangeEmailTest.php new file mode 100644 index 0000000..e9a1724 --- /dev/null +++ b/common/tests/unit/emails/templates/ChangeEmailTest.php @@ -0,0 +1,36 @@ +template->setKey('mock-key'); + $params = $this->template->getParams(); + $this->assertSame('mock-key', $params['key']); + } + + public function testInvalidCallOfParams() { + $this->expectException(InvalidCallException::class); + $this->template->getParams(); + } + + protected function _before() { + parent::_before(); + /** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $mailer */ + $mailer = $this->createMock(MailerInterface::class); + $this->template = new ChangeEmail($mailer); + } + +} diff --git a/common/tests/unit/emails/templates/ConfirmNewEmailTest.php b/common/tests/unit/emails/templates/ConfirmNewEmailTest.php new file mode 100644 index 0000000..266b72d --- /dev/null +++ b/common/tests/unit/emails/templates/ConfirmNewEmailTest.php @@ -0,0 +1,49 @@ +template->setUsername('mock-username'); + $this->template->setKey('mock-key'); + $params = $this->template->getParams(); + $this->assertSame('mock-username', $params['username']); + $this->assertSame('mock-key', $params['key']); + } + + /** + * @dataProvider getInvalidCallsCases + */ + public function testInvalidCallOfParams(?string $username, ?string $key) { + $this->expectException(InvalidCallException::class); + $username !== null && $this->template->setUsername($username); + $key !== null && $this->template->setKey($key); + $this->template->getParams(); + } + + public function getInvalidCallsCases() { + yield [null, null]; + yield ['value', null]; + yield [null, 'value']; + } + + protected function _before() { + parent::_before(); + /** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $mailer */ + $mailer = $this->createMock(MailerInterface::class); + $this->template = new ConfirmNewEmail($mailer); + } + +} diff --git a/common/tests/unit/emails/templates/ForgotPasswordEmailTest.php b/common/tests/unit/emails/templates/ForgotPasswordEmailTest.php new file mode 100644 index 0000000..f8c61d5 --- /dev/null +++ b/common/tests/unit/emails/templates/ForgotPasswordEmailTest.php @@ -0,0 +1,42 @@ +template->setParams(new ForgotPasswordParams('mock-username', 'mock-code', 'mock-link')); + $params = $this->template->getParams(); + $this->assertSame('mock-username', $params['username']); + $this->assertSame('mock-code', $params['code']); + $this->assertSame('mock-link', $params['link']); + } + + public function testInvalidCallOfParams() { + $this->expectException(InvalidCallException::class); + $this->template->getParams(); + } + + protected function _before() { + parent::_before(); + /** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $mailer */ + $mailer = $this->createMock(MailerInterface::class); + /** @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject $renderer */ + $renderer = $this->createMock(RendererInterface::class); + $this->template = new ForgotPasswordEmail($mailer, $renderer); + } + +} diff --git a/common/tests/unit/emails/templates/RegistrationEmailTest.php b/common/tests/unit/emails/templates/RegistrationEmailTest.php new file mode 100644 index 0000000..f2f36c9 --- /dev/null +++ b/common/tests/unit/emails/templates/RegistrationEmailTest.php @@ -0,0 +1,42 @@ +template->setParams(new RegistrationEmailParams('mock-username', 'mock-code', 'mock-link')); + $params = $this->template->getParams(); + $this->assertSame('mock-username', $params['username']); + $this->assertSame('mock-code', $params['code']); + $this->assertSame('mock-link', $params['link']); + } + + public function testInvalidCallOfParams() { + $this->expectException(InvalidCallException::class); + $this->template->getParams(); + } + + protected function _before() { + parent::_before(); + /** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $mailer */ + $mailer = $this->createMock(MailerInterface::class); + /** @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject $renderer */ + $renderer = $this->createMock(RendererInterface::class); + $this->template = new RegistrationEmail($mailer, $renderer); + } + +} diff --git a/common/tests/unit/tasks/SendPasswordRecoveryEmailTest.php b/common/tests/unit/tasks/SendPasswordRecoveryEmailTest.php index 0b86507..0112df0 100644 --- a/common/tests/unit/tasks/SendPasswordRecoveryEmailTest.php +++ b/common/tests/unit/tasks/SendPasswordRecoveryEmailTest.php @@ -1,14 +1,23 @@ username = 'mock-username'; @@ -21,7 +30,6 @@ class SendPasswordRecoveryEmailTest extends TestCase { $confirmation->shouldReceive('getAccount')->andReturn($account); $result = SendPasswordRecoveryEmail::createFromConfirmation($confirmation); - $this->assertInstanceOf(SendPasswordRecoveryEmail::class, $result); $this->assertSame('mock-username', $result->username); $this->assertSame('mock@ely.by', $result->email); $this->assertSame('ABCDEFG', $result->code); @@ -37,6 +45,12 @@ class SendPasswordRecoveryEmailTest extends TestCase { $task->link = 'https://account.ely.by/recover-password/ABCDEFG'; $task->locale = 'ru'; + $this->renderer->expects($this->once())->method('render')->with('forgotPassword', 'ru', [ + 'username' => 'mock-username', + 'code' => 'GFEDCBA', + 'link' => 'https://account.ely.by/recover-password/ABCDEFG', + ])->willReturn('mock-template'); + $task->execute(mock(Queue::class)); $this->tester->canSeeEmailIsSent(1); @@ -44,10 +58,14 @@ class SendPasswordRecoveryEmailTest extends TestCase { $email = $this->tester->grabSentEmails()[0]; $this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo()); $this->assertSame('Ely.by Account forgot password', $email->getSubject()); - $body = $email->getSwiftMessage()->getBody(); - $this->assertContains('Привет, mock-username', $body); - $this->assertContains('GFEDCBA', $body); - $this->assertContains('https://account.ely.by/recover-password/ABCDEFG', $body); + $this->assertSame('mock-template', $email->getSwiftMessage()->getBody()); + } + + protected function _before() { + parent::_before(); + + $this->renderer = $this->createMock(RendererInterface::class); + Yii::$app->set('emailsRenderer', $this->renderer); } } diff --git a/common/tests/unit/tasks/SendRegistrationEmailTest.php b/common/tests/unit/tasks/SendRegistrationEmailTest.php index 229001a..1cc69d4 100644 --- a/common/tests/unit/tasks/SendRegistrationEmailTest.php +++ b/common/tests/unit/tasks/SendRegistrationEmailTest.php @@ -1,14 +1,23 @@ username = 'mock-username'; @@ -21,7 +30,6 @@ class SendRegistrationEmailTest extends TestCase { $confirmation->shouldReceive('getAccount')->andReturn($account); $result = SendRegistrationEmail::createFromConfirmation($confirmation); - $this->assertInstanceOf(SendRegistrationEmail::class, $result); $this->assertSame('mock-username', $result->username); $this->assertSame('mock@ely.by', $result->email); $this->assertSame('ABCDEFG', $result->code); @@ -37,17 +45,27 @@ class SendRegistrationEmailTest extends TestCase { $task->link = 'https://account.ely.by/activation/ABCDEFG'; $task->locale = 'ru'; - $task->execute(mock(Queue::class)); + $this->renderer->expects($this->once())->method('render')->with('register', 'ru', [ + 'username' => 'mock-username', + 'code' => 'GFEDCBA', + 'link' => 'https://account.ely.by/activation/ABCDEFG', + ])->willReturn('mock-template'); + + $task->execute($this->createMock(Queue::class)); $this->tester->canSeeEmailIsSent(1); /** @var \yii\swiftmailer\Message $email */ $email = $this->tester->grabSentEmails()[0]; $this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo()); $this->assertSame('Ely.by Account registration', $email->getSubject()); - $body = $email->getSwiftMessage()->getBody(); - $this->assertContains('Привет, mock-username', $body); - $this->assertContains('GFEDCBA', $body); - $this->assertContains('https://account.ely.by/activation/ABCDEFG', $body); + $this->assertSame('mock-template', $email->getSwiftMessage()->getBody()); + } + + protected function _before() { + parent::_before(); + + $this->renderer = $this->createMock(RendererInterface::class); + Yii::$app->set('emailsRenderer', $this->renderer); } } From d5cb0f304cfb7d0ca35aabc9fdfa21d06950e288 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Tue, 18 Jun 2019 01:19:07 +0300 Subject: [PATCH 4/4] Add tests for emails renderer api client and Yii2 wrapper component --- api/codeception.dist.yml | 1 - .../components/EmailsRenderer/Component.php | 5 +- .../components/EmailsRenderer/ApiTest.php | 58 +++++++++++++++++++ .../EmailsRenderer/ComponentTest.php | 53 +++++++++++++++++ 4 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 common/tests/unit/components/EmailsRenderer/ApiTest.php create mode 100644 common/tests/unit/components/EmailsRenderer/ComponentTest.php diff --git a/api/codeception.dist.yml b/api/codeception.dist.yml index 527e806..4f785ce 100644 --- a/api/codeception.dist.yml +++ b/api/codeception.dist.yml @@ -20,7 +20,6 @@ coverage: - config/* - runtime/* - tests/* - - web/* - codeception.dist.yml - codeception.yml c3url: 'http://localhost/api/web/index.php' diff --git a/common/components/EmailsRenderer/Component.php b/common/components/EmailsRenderer/Component.php index 8adc39f..ceffa55 100644 --- a/common/components/EmailsRenderer/Component.php +++ b/common/components/EmailsRenderer/Component.php @@ -8,7 +8,6 @@ use common\emails\RendererInterface; use Yii; use yii\base\InvalidConfigException; use yii\helpers\ArrayHelper; -use yii\helpers\FileHelper; class Component extends \yii\base\Component implements RendererInterface { @@ -63,7 +62,7 @@ class Component extends \yii\base\Component implements RendererInterface { return $this->getApi()->getTemplate($request); } - private function getApi(): Api { + protected function getApi(): Api { if ($this->api === null) { $this->api = new Api($this->serviceUrl); } @@ -72,7 +71,7 @@ class Component extends \yii\base\Component implements RendererInterface { } private function buildBasePath(): string { - return FileHelper::normalizePath($this->baseDomain . '/' . $this->basePath, '/'); + return trim($this->baseDomain, '/') . '/' . trim($this->basePath, '/'); } } diff --git a/common/tests/unit/components/EmailsRenderer/ApiTest.php b/common/tests/unit/components/EmailsRenderer/ApiTest.php new file mode 100644 index 0000000..a5d439d --- /dev/null +++ b/common/tests/unit/components/EmailsRenderer/ApiTest.php @@ -0,0 +1,58 @@ +mockHandler = new MockHandler(); + $handlerStack = HandlerStack::create($this->mockHandler); + $this->history = []; + $handlerStack->push(Middleware::history($this->history), 'history'); + $client = new Client([ + 'handler' => $handlerStack, + 'base_uri' => 'http://emails-renderer', + ]); + $this->api = new Api('http://emails-renderer'); + $this->api->setClient($client); + } + + public function testGetTemplate() { + $this->mockHandler->append(new Response(200, [], 'mock-response')); + + $request = new TemplateRequest('mock-name', 'mock-locale', ['find-me' => 'please']); + $this->assertSame('mock-response', $this->api->getTemplate($request)); + + /** @var \Psr\Http\Message\RequestInterface $request */ + ['request' => $request] = $this->history[0]; + $this->assertSame('http://emails-renderer/templates/mock-locale/mock-name?find-me=please', (string)$request->getUri()); + } + +} diff --git a/common/tests/unit/components/EmailsRenderer/ComponentTest.php b/common/tests/unit/components/EmailsRenderer/ComponentTest.php new file mode 100644 index 0000000..ea95708 --- /dev/null +++ b/common/tests/unit/components/EmailsRenderer/ComponentTest.php @@ -0,0 +1,53 @@ +api = $this->createMock(Api::class); + $componentParams = [ + 'api' => $this->api, + 'serviceUrl' => 'http://emails-renderer', + 'basePath' => '/images/emails-templates', + ]; + $this->component = new class($componentParams) extends Component { + public $api; + + protected function getApi(): Api { + return $this->api; + } + }; + } + + public function testRender() { + $expectedRequest = new TemplateRequest('mock-name', 'mock-locale', [ + 'find-me' => 'please', + 'assetsHost' => 'http://localhost/images/emails-templates', + ]); + + $this->api->expects($this->once())->method('getTemplate')->with($expectedRequest)->willReturn('mock-template'); + + $result = $this->component->render('mock-name', 'mock-locale', ['find-me' => 'please']); + $this->assertSame('mock-template', $result); + } + +}