Refactor emails models objects, rework related tests

This commit is contained in:
ErickSkrauch 2019-06-16 23:59:19 +03:00
parent 1bf249030f
commit 70d1999d55
24 changed files with 522 additions and 196 deletions

View File

@ -4,14 +4,12 @@ declare(strict_types=1);
namespace common\components\EmailsRenderer; namespace common\components\EmailsRenderer;
use common\components\EmailsRenderer\Request\TemplateRequest; use common\components\EmailsRenderer\Request\TemplateRequest;
use common\emails\RendererInterface;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper; use yii\helpers\ArrayHelper;
use yii\helpers\FileHelper; use yii\helpers\FileHelper;
/**
* @property string $baseDomain
*/
class Component extends \yii\base\Component implements RendererInterface { class Component extends \yii\base\Component implements RendererInterface {
/** /**
@ -20,8 +18,12 @@ class Component extends \yii\base\Component implements RendererInterface {
public $serviceUrl; public $serviceUrl;
/** /**
* @var string базовый путь после хоста. Должен начинаться слешем и заканчиваться без него. * @var string application base domain. Can be omitted for web applications (will be extracted from request)
* Например "/email-images" */
public $baseDomain;
/**
* @var string base path after the host. For example "/emails-images"
*/ */
public $basePath = ''; public $basePath = '';
@ -30,11 +32,6 @@ class Component extends \yii\base\Component implements RendererInterface {
*/ */
private $api; private $api;
/**
* @var string
*/
private $_baseDomain;
public function init(): void { public function init(): void {
parent::init(); parent::init();
@ -42,22 +39,14 @@ class Component extends \yii\base\Component implements RendererInterface {
throw new InvalidConfigException('serviceUrl is required'); throw new InvalidConfigException('serviceUrl is required');
} }
if ($this->_baseDomain === null) { if ($this->baseDomain === null) {
$this->_baseDomain = Yii::$app->urlManager->getHostInfo(); $this->baseDomain = Yii::$app->urlManager->getHostInfo();
if ($this->_baseDomain === null) { if ($this->baseDomain === null) {
throw new InvalidConfigException('Cannot automatically obtain base domain'); 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 $templateName
* @param string $locale * @param string $locale
@ -83,7 +72,7 @@ class Component extends \yii\base\Component implements RendererInterface {
} }
private function buildBasePath(): string { private function buildBasePath(): string {
return FileHelper::normalizePath($this->_baseDomain . '/' . $this->basePath, '/'); return FileHelper::normalizePath($this->baseDomain . '/' . $this->basePath, '/');
} }
} }

View File

@ -19,5 +19,8 @@ return [
'mailer' => [ 'mailer' => [
'useFileTransport' => true, 'useFileTransport' => true,
], ],
'emailsRenderer' => [
'class' => common\tests\_support\EmailsRenderer::class,
],
], ],
]; ];

View File

@ -1,7 +1,7 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace common\components\EmailsRenderer; namespace common\emails;
interface RendererInterface { interface RendererInterface {

View File

@ -3,7 +3,6 @@ declare(strict_types=1);
namespace common\emails; namespace common\emails;
use common\emails\exceptions\CannotSendEmailException;
use Yii; use Yii;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\mail\MailerInterface; use yii\mail\MailerInterface;
@ -12,29 +11,12 @@ use yii\mail\MessageInterface;
abstract class Template { abstract class Template {
/** /**
* @var \yii\swiftmailer\Mailer * @var MailerInterface
*/ */
private $mailer; private $mailer;
/** public function __construct(MailerInterface $mailer) {
* @var string|array $this->mailer = $mailer;
*/
private $to;
/**
* @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;
$this->to = $to;
}
/**
* @return array|string
*/
public function getTo() {
return $this->to;
} }
abstract public function getSubject(): string; abstract public function getSubject(): string;
@ -44,7 +26,7 @@ abstract class Template {
* @throws InvalidConfigException * @throws InvalidConfigException
*/ */
public function getFrom() { public function getFrom() {
$fromEmail = Yii::$app->params['fromEmail']; $fromEmail = Yii::$app->params['fromEmail'] ?? '';
if (!$fromEmail) { if (!$fromEmail) {
throw new InvalidConfigException('Please specify fromEmail app in app params'); throw new InvalidConfigException('Please specify fromEmail app in app params');
} }
@ -56,13 +38,14 @@ abstract class Template {
return []; return [];
} }
public function getMailer(): MailerInterface { /**
return $this->mailer; * @param string|array $to see \yii\mail\MessageInterface::setTo to know the format.
} *
* @throws \common\emails\exceptions\CannotSendEmailException
public function send(): void { */
if (!$this->createMessage()->send()) { public function send($to): void {
throw new CannotSendEmailException('Unable send email.'); if (!$this->createMessage($to)->send()) {
throw new exceptions\CannotSendEmailException();
} }
} }
@ -71,10 +54,14 @@ abstract class Template {
*/ */
abstract protected function getView(); abstract protected function getView();
protected function createMessage(): MessageInterface { final protected function getMailer(): MailerInterface {
return $this->mailer;
}
protected function createMessage($for): MessageInterface {
return $this->getMailer() return $this->getMailer()
->compose($this->getView(), $this->getParams()) ->compose($this->getView(), $this->getParams())
->setTo($this->getTo()) ->setTo($for)
->setFrom($this->getFrom()) ->setFrom($this->getFrom())
->setSubject($this->getSubject()); ->setSubject($this->getSubject());
} }

View File

@ -3,9 +3,8 @@ declare(strict_types=1);
namespace common\emails; namespace common\emails;
use common\components\EmailsRenderer\RendererInterface;
use ErrorException;
use Exception; use Exception;
use yii\mail\MailerInterface;
use yii\mail\MessageInterface; use yii\mail\MessageInterface;
abstract class TemplateWithRenderer extends Template { abstract class TemplateWithRenderer extends Template {
@ -18,59 +17,61 @@ abstract class TemplateWithRenderer extends Template {
/** /**
* @var string * @var string
*/ */
private $locale; private $locale = 'en';
/** public function __construct(MailerInterface $mailer, RendererInterface $renderer) {
* @inheritdoc parent::__construct($mailer);
*/
public function __construct($to, string $locale, RendererInterface $renderer) {
parent::__construct($to);
$this->locale = $locale;
$this->renderer = $renderer; $this->renderer = $renderer;
} }
public function setLocale(string $locale): void {
$this->locale = $locale;
}
public function getLocale(): string { public function getLocale(): string {
return $this->locale; return $this->locale;
} }
public function getRenderer(): RendererInterface {
return $this->renderer;
}
/** /**
* Метод должен возвращать имя шаблона, который должен быть использован. * This method should return the template's name, which will be rendered.
* Имена можно взять в репозитории elyby/email-renderer * List of available templates names can be found at https://github.com/elyby/emails-renderer
* *
* @return string * @return string
*/ */
abstract public function getTemplateName(): string; abstract public function getTemplateName(): string;
final protected function getRenderer(): RendererInterface {
return $this->renderer;
}
final protected function getView() { final protected function getView() {
return $this->getTemplateName(); return $this->getTemplateName();
} }
/** /**
* @param string|array $for
*
* @return MessageInterface * @return MessageInterface
* @throws ErrorException * @throws \common\emails\exceptions\CannotRenderEmailException
*/ */
protected function createMessage(): MessageInterface { protected function createMessage($for): MessageInterface {
return $this->getMailer() return $this->getMailer()
->compose() ->compose()
->setHtmlBody($this->render()) ->setHtmlBody($this->render())
->setTo($this->getTo()) ->setTo($for)
->setFrom($this->getFrom()) ->setFrom($this->getFrom())
->setSubject($this->getSubject()); ->setSubject($this->getSubject());
} }
/** /**
* @return string * @return string
* @throws ErrorException * @throws \common\emails\exceptions\CannotRenderEmailException
*/ */
private function render(): string { private function render(): string {
try { try {
return $this->getRenderer()->render($this->getTemplateName(), $this->getLocale(), $this->getParams()); return $this->getRenderer()->render($this->getTemplateName(), $this->getLocale(), $this->getParams());
} catch (Exception $e) { } catch (Exception $e) {
throw new ErrorException('Unable to render the template', 0, 1, __FILE__, __LINE__, $e); throw new exceptions\CannotRenderEmailException($e);
} }
} }

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace common\emails\exceptions;
use Exception;
use Throwable;
class CannotRenderEmailException extends Exception {
public function __construct(Throwable $previous = null) {
parent::__construct('Unable to render a template', 0, $previous);
}
}

View File

@ -4,7 +4,12 @@ declare(strict_types=1);
namespace common\emails\exceptions; namespace common\emails\exceptions;
use Exception; use Exception;
use Throwable;
class CannotSendEmailException extends Exception { class CannotSendEmailException extends Exception {
public function __construct(Throwable $previous = null) {
parent::__construct('Unable send email', 0, $previous);
}
} }

View File

@ -4,13 +4,16 @@ declare(strict_types=1);
namespace common\emails\templates; namespace common\emails\templates;
use common\emails\Template; use common\emails\Template;
use yii\base\InvalidCallException;
class ChangeEmailConfirmCurrentEmail extends Template { class ChangeEmail extends Template {
/**
* @var string|null
*/
private $key; private $key;
public function __construct($to, string $key) { public function setKey(string $key): void {
parent::__construct($to);
$this->key = $key; $this->key = $key;
} }
@ -19,14 +22,15 @@ class ChangeEmailConfirmCurrentEmail extends Template {
} }
public function getParams(): array { public function getParams(): array {
if ($this->key === null) {
throw new InvalidCallException('You need to set key param first');
}
return [ return [
'key' => $this->key, 'key' => $this->key,
]; ];
} }
/**
* @return string|array
*/
protected function getView() { protected function getView() {
return [ return [
'html' => '@common/emails/views/current-email-confirmation-html', 'html' => '@common/emails/views/current-email-confirmation-html',

View File

@ -4,16 +4,25 @@ declare(strict_types=1);
namespace common\emails\templates; namespace common\emails\templates;
use common\emails\Template; use common\emails\Template;
use yii\base\InvalidCallException;
class ChangeEmailConfirmNewEmail extends Template { class ConfirmNewEmail extends Template {
/**
* @var string|null
*/
private $username; private $username;
/**
* @var string|null
*/
private $key; private $key;
public function __construct($to, string $username, string $key) { public function setUsername(string $username): void {
parent::__construct($to);
$this->username = $username; $this->username = $username;
}
public function setKey(string $key): void {
$this->key = $key; $this->key = $key;
} }
@ -22,15 +31,16 @@ class ChangeEmailConfirmNewEmail extends Template {
} }
public function getParams(): array { public function getParams(): array {
if ($this->username === null || $this->key === null) {
throw new InvalidCallException('You need to set username and key params first');
}
return [ return [
'key' => $this->key,
'username' => $this->username, 'username' => $this->username,
'key' => $this->key,
]; ];
} }
/**
* @return string|array
*/
protected function getView() { protected function getView() {
return [ return [
'html' => '@common/emails/views/new-email-confirmation-html', 'html' => '@common/emails/views/new-email-confirmation-html',

View File

@ -3,20 +3,15 @@ declare(strict_types=1);
namespace common\emails\templates; namespace common\emails\templates;
use common\components\EmailsRenderer\RendererInterface;
use common\emails\TemplateWithRenderer; use common\emails\TemplateWithRenderer;
use yii\base\InvalidCallException;
class ForgotPasswordEmail extends TemplateWithRenderer { class ForgotPasswordEmail extends TemplateWithRenderer {
private $params;
/** /**
* @inheritdoc * @var ForgotPasswordParams|null
*/ */
public function __construct($to, string $locale, ForgotPasswordParams $params, RendererInterface $renderer) { private $params;
parent::__construct($to, $locale, $renderer);
$this->params = $params;
}
public function getSubject(): string { public function getSubject(): string {
return 'Ely.by Account forgot password'; return 'Ely.by Account forgot password';
@ -26,7 +21,15 @@ class ForgotPasswordEmail extends TemplateWithRenderer {
return 'forgotPassword'; return 'forgotPassword';
} }
public function setParams(ForgotPasswordParams $params): void {
$this->params = $params;
}
public function getParams(): array { public function getParams(): array {
if ($this->params === null) {
throw new InvalidCallException('You need to set params first');
}
return [ return [
'username' => $this->params->getUsername(), 'username' => $this->params->getUsername(),
'code' => $this->params->getCode(), 'code' => $this->params->getCode(),

View File

@ -3,20 +3,15 @@ declare(strict_types=1);
namespace common\emails\templates; namespace common\emails\templates;
use common\components\EmailsRenderer\RendererInterface;
use common\emails\TemplateWithRenderer; use common\emails\TemplateWithRenderer;
use yii\base\InvalidCallException;
class RegistrationEmail extends TemplateWithRenderer { class RegistrationEmail extends TemplateWithRenderer {
private $params;
/** /**
* @inheritdoc * @var RegistrationEmailParams|null
*/ */
public function __construct($to, string $locale, RegistrationEmailParams $params, RendererInterface $renderer) { private $params;
parent::__construct($to, $locale, $renderer);
$this->params = $params;
}
public function getSubject(): string { public function getSubject(): string {
return 'Ely.by Account registration'; return 'Ely.by Account registration';
@ -26,7 +21,15 @@ class RegistrationEmail extends TemplateWithRenderer {
return 'register'; return 'register';
} }
public function setParams(RegistrationEmailParams $params): void {
$this->params = $params;
}
public function getParams(): array { public function getParams(): array {
if ($this->params === null) {
throw new InvalidCallException('You need to set params first');
}
return [ return [
'username' => $this->params->getUsername(), 'username' => $this->params->getUsername(),
'code' => $this->params->getCode(), 'code' => $this->params->getCode(),

View File

@ -1,9 +1,10 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace common\tasks; namespace common\tasks;
use common\emails\EmailHelper; use common\emails\EmailHelper;
use common\emails\templates\ChangeEmailConfirmCurrentEmail; use common\emails\templates\ChangeEmail;
use common\models\confirmations\CurrentEmailConfirmation; use common\models\confirmations\CurrentEmailConfirmation;
use Yii; use Yii;
use yii\queue\RetryableJobInterface; use yii\queue\RetryableJobInterface;
@ -25,22 +26,23 @@ class SendCurrentEmailConfirmation implements RetryableJobInterface {
return $result; return $result;
} }
public function getTtr() { public function getTtr(): int {
return 30; return 30;
} }
public function canRetry($attempt, $error) { public function canRetry($attempt, $error): bool {
return true; return true;
} }
/** /**
* @param \yii\queue\Queue $queue * @param \yii\queue\Queue $queue
* @throws \common\emails\exceptions\CannotSendEmailException
*/ */
public function execute($queue) { public function execute($queue) {
Yii::$app->statsd->inc('queue.sendCurrentEmailConfirmation.attempt'); Yii::$app->statsd->inc('queue.sendCurrentEmailConfirmation.attempt');
$to = EmailHelper::buildTo($this->username, $this->email); $template = new ChangeEmail(Yii::$app->mailer);
$template = new ChangeEmailConfirmCurrentEmail($to, $this->code); $template->setKey($this->code);
$template->send(); $template->send(EmailHelper::buildTo($this->username, $this->email));
} }
} }

View File

@ -1,9 +1,10 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace common\tasks; namespace common\tasks;
use common\emails\EmailHelper; use common\emails\EmailHelper;
use common\emails\templates\ChangeEmailConfirmNewEmail; use common\emails\templates\ConfirmNewEmail;
use common\models\confirmations\NewEmailConfirmation; use common\models\confirmations\NewEmailConfirmation;
use Yii; use Yii;
use yii\queue\RetryableJobInterface; use yii\queue\RetryableJobInterface;
@ -25,22 +26,24 @@ class SendNewEmailConfirmation implements RetryableJobInterface {
return $result; return $result;
} }
public function getTtr() { public function getTtr(): int {
return 30; return 30;
} }
public function canRetry($attempt, $error) { public function canRetry($attempt, $error): bool {
return true; return true;
} }
/** /**
* @param \yii\queue\Queue $queue * @param \yii\queue\Queue $queue
* @throws \common\emails\exceptions\CannotSendEmailException
*/ */
public function execute($queue) { public function execute($queue) {
Yii::$app->statsd->inc('queue.sendNewEmailConfirmation.attempt'); Yii::$app->statsd->inc('queue.sendNewEmailConfirmation.attempt');
$to = EmailHelper::buildTo($this->username, $this->email); $template = new ConfirmNewEmail(Yii::$app->mailer);
$template = new ChangeEmailConfirmNewEmail($to, $this->username, $this->code); $template->setKey($this->code);
$template->send(); $template->setUsername($this->username);
$template->send(EmailHelper::buildTo($this->username, $this->email));
} }
} }

View File

@ -1,5 +1,6 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace common\tasks; namespace common\tasks;
use common\emails\EmailHelper; use common\emails\EmailHelper;
@ -34,11 +35,11 @@ class SendPasswordRecoveryEmail implements RetryableJobInterface {
return $result; return $result;
} }
public function getTtr() { public function getTtr(): int {
return 30; return 30;
} }
public function canRetry($attempt, $error) { public function canRetry($attempt, $error): bool {
return true; return true;
} }
@ -48,10 +49,10 @@ class SendPasswordRecoveryEmail implements RetryableJobInterface {
*/ */
public function execute($queue) { public function execute($queue) {
Yii::$app->statsd->inc('queue.sendPasswordRecovery.attempt'); Yii::$app->statsd->inc('queue.sendPasswordRecovery.attempt');
$params = new ForgotPasswordParams($this->username, $this->code, $this->link); $template = new ForgotPasswordEmail(Yii::$app->mailer, Yii::$app->emailsRenderer);
$to = EmailHelper::buildTo($this->username, $this->email); $template->setLocale($this->locale);
$template = new ForgotPasswordEmail($to, $this->locale, $params, Yii::$app->emailsRenderer); $template->setParams(new ForgotPasswordParams($this->username, $this->code, $this->link));
$template->send(); $template->send(EmailHelper::buildTo($this->username, $this->email));
} }
} }

View File

@ -1,5 +1,6 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
namespace common\tasks; namespace common\tasks;
use common\emails\EmailHelper; use common\emails\EmailHelper;
@ -34,11 +35,11 @@ class SendRegistrationEmail implements RetryableJobInterface {
return $result; return $result;
} }
public function getTtr() { public function getTtr(): int {
return 30; return 30;
} }
public function canRetry($attempt, $error) { public function canRetry($attempt, $error): bool {
return true; return true;
} }
@ -48,10 +49,10 @@ class SendRegistrationEmail implements RetryableJobInterface {
*/ */
public function execute($queue) { public function execute($queue) {
Yii::$app->statsd->inc('queue.sendRegistrationEmail.attempt'); Yii::$app->statsd->inc('queue.sendRegistrationEmail.attempt');
$params = new RegistrationEmailParams($this->username, $this->code, $this->link); $template = new RegistrationEmail(Yii::$app->mailer, Yii::$app->emailsRenderer);
$to = EmailHelper::buildTo($this->username, $this->email); $template->setLocale($this->locale);
$template = new RegistrationEmail($to, $this->locale, $params, Yii::$app->emailsRenderer); $template->setParams(new RegistrationEmailParams($this->username, $this->code, $this->link));
$template->send(); $template->send(EmailHelper::buildTo($this->username, $this->email));
} }
} }

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace common\tests\_support;
use common\components\EmailsRenderer\Component;
class EmailsRenderer extends Component {
public function render(string $templateName, string $locale, array $params = []): string {
return 'template';
}
}

View File

@ -1,47 +1,73 @@
<?php <?php
declare(strict_types=1);
namespace common\tests\unit\emails; namespace common\tests\unit\emails;
use common\emails\exceptions\CannotSendEmailException;
use common\emails\Template; use common\emails\Template;
use common\tests\_support\ProtectedCaller;
use common\tests\unit\TestCase; use common\tests\unit\TestCase;
use Yii; use Yii;
use yii\mail\MailerInterface; use yii\mail\MailerInterface;
use yii\mail\MessageInterface; use yii\mail\MessageInterface;
class TemplateTest extends TestCase { class TemplateTest extends TestCase {
use ProtectedCaller;
public function testConstructor() { /**
/** @var Template|\Mockery\MockInterface $template */ * @var Template|\PHPUnit\Framework\MockObject\MockObject $template
$template = mock(Template::class, ['find-me'])->makePartial(); */
$this->assertSame('find-me', $template->getTo()); private $template;
$this->assertInstanceOf(MailerInterface::class, $template->getMailer());
/**
* @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'; 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() { protected function _after() {
/** @var Template|\Mockery\MockInterface $template */ parent::_after();
$template = mock(Template::class)->makePartial(); Yii::$app->params['fromEmail'] = $this->initialFromEmail;
$this->assertSame([], $template->getParams());
} }
public function testCreateMessage() { private function runTestForSend(bool $sendResult) {
Yii::$app->params['fromEmail'] = 'from@ely.by'; $this->template->expects($this->once())->method('getSubject')->willReturn('mock-subject');
/** @var Template|\Mockery\MockInterface $template */ $this->template->expects($this->once())->method('getView')->willReturn('mock-view');
$template = mock(Template::class, [['to@ely.by' => 'To']])->makePartial();
$template->shouldReceive('getSubject')->andReturn('mock-subject'); /** @var MailerInterface|\PHPUnit\Framework\MockObject\MockObject $message */
/** @var MessageInterface $message */ $message = $this->createMock(MessageInterface::class);
$message = $this->callProtected($template, 'createMessage'); $message->expects($this->once())->method('setTo')->with(['to@ely.by' => 'To'])->willReturnSelf();
$this->assertInstanceOf(MessageInterface::class, $message); $message->expects($this->once())->method('setFrom')->with(['find-me' => 'Ely.by Accounts'])->willReturnSelf();
$this->assertSame(['to@ely.by' => 'To'], $message->getTo()); $message->expects($this->once())->method('setSubject')->with('mock-subject')->willReturnSelf();
$this->assertSame(['from@ely.by' => 'Ely.by Accounts'], $message->getFrom()); $message->expects($this->once())->method('send')->willReturn($sendResult);
$this->assertSame('mock-subject', $message->getSubject());
$this->mailer->expects($this->once())->method('compose')->with('mock-view', [])->willReturn($message);
$this->template->send(['to@ely.by' => 'To']);
} }
} }

View File

@ -1,49 +1,103 @@
<?php <?php
declare(strict_types=1);
namespace common\tests\unit\emails; namespace common\tests\unit\emails;
use common\components\EmailsRenderer\Component; use common\emails\exceptions\CannotRenderEmailException;
use common\emails\RendererInterface;
use common\emails\TemplateWithRenderer; use common\emails\TemplateWithRenderer;
use common\tests\_support\ProtectedCaller;
use common\tests\unit\TestCase; use common\tests\unit\TestCase;
use Ely\Email\TemplateBuilder; use Exception;
use Yii;
use yii\mail\MailerInterface; use yii\mail\MailerInterface;
use yii\mail\MessageInterface; use yii\mail\MessageInterface;
class TemplateWithRendererTest extends TestCase { class TemplateWithRendererTest extends TestCase {
use ProtectedCaller;
public function testConstructor() { /**
/** @var TemplateWithRenderer|\Mockery\MockInterface $template */ * @var TemplateWithRenderer|\PHPUnit\Framework\MockObject\MockObject $template
$template = mock(TemplateWithRenderer::class, ['mock-to', 'mock-locale'])->makePartial(); */
$this->assertSame('mock-to', $template->getTo()); private $template;
$this->assertSame('mock-locale', $template->getLocale());
$this->assertInstanceOf(MailerInterface::class, $template->getMailer()); /**
$this->assertInstanceOf(Component::class, $template->getRenderer()); * @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() { public function testSend() {
/** @var TemplateBuilder|\Mockery\MockInterface $templateBuilder */ $this->runTestForSend();
$templateBuilder = mock(TemplateBuilder::class)->makePartial(); }
$templateBuilder->shouldReceive('render')->andReturn('mock-html');
/** @var Component|\Mockery\MockInterface $renderer */ public function testSendWithRenderError() {
$renderer = mock(Component::class)->makePartial(); $renderException = new Exception('find me');
$renderer->shouldReceive('getTemplate')->with('mock-template')->andReturn($templateBuilder); 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 */ return;
$template = mock(TemplateWithRenderer::class, [['to@ely.by' => 'To'], 'mock-locale']); }
$template->makePartial();
$template->shouldReceive('getEmailRenderer')->andReturn($renderer); $this->assertFalse(true, 'no exception was thrown');
$template->shouldReceive('getFrom')->andReturn(['from@ely.by' => 'From']); }
$template->shouldReceive('getSubject')->andReturn('mock-subject');
$template->shouldReceive('getTemplateName')->andReturn('mock-template'); protected function _before() {
/** @var \yii\swiftmailer\Message $message */ parent::_before();
$message = $this->callProtected($template, 'createMessage'); $this->mailer = $this->createMock(MailerInterface::class);
$this->assertInstanceOf(MessageInterface::class, $message); $this->renderer = $this->createMock(RendererInterface::class);
$this->assertSame(['to@ely.by' => 'To'], $message->getTo()); $this->template = $this->getMockForAbstractClass(TemplateWithRenderer::class, [$this->mailer, $this->renderer]);
$this->assertSame(['from@ely.by' => 'From'], $message->getFrom()); $this->initialFromEmail = Yii::$app->params['fromEmail'];
$this->assertSame('mock-subject', $message->getSubject()); Yii::$app->params['fromEmail'] = 'find-me';
$this->assertSame('mock-html', $message->getSwiftMessage()->getBody()); }
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']);
} }
} }

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace common\tests\unit\emails\templates;
use common\emails\templates\ChangeEmail;
use common\tests\unit\TestCase;
use yii\base\InvalidCallException;
use yii\mail\MailerInterface;
class ChangeEmailTest extends TestCase {
/**
* @var ChangeEmail()|\PHPUnit\Framework\MockObject\MockObject
*/
private $template;
public function testParams() {
$this->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);
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace common\tests\unit\emails\templates;
use common\emails\templates\ConfirmNewEmail;
use common\tests\unit\TestCase;
use yii\base\InvalidCallException;
use yii\mail\MailerInterface;
class ConfirmNewEmailTest extends TestCase {
/**
* @var ConfirmNewEmail|\PHPUnit\Framework\MockObject\MockObject
*/
private $template;
public function testParams() {
$this->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);
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace common\tests\unit\emails\templates;
use common\emails\RendererInterface;
use common\emails\templates\ForgotPasswordEmail;
use common\emails\templates\ForgotPasswordParams;
use common\tests\unit\TestCase;
use yii\base\InvalidCallException;
use yii\mail\MailerInterface;
class ForgotPasswordEmailTest extends TestCase {
/**
* @var ForgotPasswordEmail|\PHPUnit\Framework\MockObject\MockObject
*/
private $template;
public function testParams() {
$this->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);
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace common\tests\unit\emails\templates;
use common\emails\RendererInterface;
use common\emails\templates\RegistrationEmail;
use common\emails\templates\RegistrationEmailParams;
use common\tests\unit\TestCase;
use yii\base\InvalidCallException;
use yii\mail\MailerInterface;
class RegistrationEmailTest extends TestCase {
/**
* @var RegistrationEmail()|\PHPUnit\Framework\MockObject\MockObject
*/
private $template;
public function testParams() {
$this->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);
}
}

View File

@ -1,14 +1,23 @@
<?php <?php
declare(strict_types=1);
namespace common\tests\unit\tasks; namespace common\tests\unit\tasks;
use common\emails\RendererInterface;
use common\models\Account; use common\models\Account;
use common\models\confirmations\ForgotPassword; use common\models\confirmations\ForgotPassword;
use common\tasks\SendPasswordRecoveryEmail; use common\tasks\SendPasswordRecoveryEmail;
use common\tests\unit\TestCase; use common\tests\unit\TestCase;
use Yii;
use yii\queue\Queue; use yii\queue\Queue;
class SendPasswordRecoveryEmailTest extends TestCase { class SendPasswordRecoveryEmailTest extends TestCase {
/**
* @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private $renderer;
public function testCreateFromConfirmation() { public function testCreateFromConfirmation() {
$account = new Account(); $account = new Account();
$account->username = 'mock-username'; $account->username = 'mock-username';
@ -21,7 +30,6 @@ class SendPasswordRecoveryEmailTest extends TestCase {
$confirmation->shouldReceive('getAccount')->andReturn($account); $confirmation->shouldReceive('getAccount')->andReturn($account);
$result = SendPasswordRecoveryEmail::createFromConfirmation($confirmation); $result = SendPasswordRecoveryEmail::createFromConfirmation($confirmation);
$this->assertInstanceOf(SendPasswordRecoveryEmail::class, $result);
$this->assertSame('mock-username', $result->username); $this->assertSame('mock-username', $result->username);
$this->assertSame('mock@ely.by', $result->email); $this->assertSame('mock@ely.by', $result->email);
$this->assertSame('ABCDEFG', $result->code); $this->assertSame('ABCDEFG', $result->code);
@ -37,6 +45,12 @@ class SendPasswordRecoveryEmailTest extends TestCase {
$task->link = 'https://account.ely.by/recover-password/ABCDEFG'; $task->link = 'https://account.ely.by/recover-password/ABCDEFG';
$task->locale = 'ru'; $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)); $task->execute(mock(Queue::class));
$this->tester->canSeeEmailIsSent(1); $this->tester->canSeeEmailIsSent(1);
@ -44,10 +58,14 @@ class SendPasswordRecoveryEmailTest extends TestCase {
$email = $this->tester->grabSentEmails()[0]; $email = $this->tester->grabSentEmails()[0];
$this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo()); $this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo());
$this->assertSame('Ely.by Account forgot password', $email->getSubject()); $this->assertSame('Ely.by Account forgot password', $email->getSubject());
$body = $email->getSwiftMessage()->getBody(); $this->assertSame('mock-template', $email->getSwiftMessage()->getBody());
$this->assertContains('Привет, mock-username', $body); }
$this->assertContains('GFEDCBA', $body);
$this->assertContains('https://account.ely.by/recover-password/ABCDEFG', $body); protected function _before() {
parent::_before();
$this->renderer = $this->createMock(RendererInterface::class);
Yii::$app->set('emailsRenderer', $this->renderer);
} }
} }

View File

@ -1,14 +1,23 @@
<?php <?php
declare(strict_types=1);
namespace common\tests\unit\tasks; namespace common\tests\unit\tasks;
use common\emails\RendererInterface;
use common\models\Account; use common\models\Account;
use common\models\confirmations\RegistrationConfirmation; use common\models\confirmations\RegistrationConfirmation;
use common\tasks\SendRegistrationEmail; use common\tasks\SendRegistrationEmail;
use common\tests\unit\TestCase; use common\tests\unit\TestCase;
use Yii;
use yii\queue\Queue; use yii\queue\Queue;
class SendRegistrationEmailTest extends TestCase { class SendRegistrationEmailTest extends TestCase {
/**
* @var RendererInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private $renderer;
public function testCreateFromConfirmation() { public function testCreateFromConfirmation() {
$account = new Account(); $account = new Account();
$account->username = 'mock-username'; $account->username = 'mock-username';
@ -21,7 +30,6 @@ class SendRegistrationEmailTest extends TestCase {
$confirmation->shouldReceive('getAccount')->andReturn($account); $confirmation->shouldReceive('getAccount')->andReturn($account);
$result = SendRegistrationEmail::createFromConfirmation($confirmation); $result = SendRegistrationEmail::createFromConfirmation($confirmation);
$this->assertInstanceOf(SendRegistrationEmail::class, $result);
$this->assertSame('mock-username', $result->username); $this->assertSame('mock-username', $result->username);
$this->assertSame('mock@ely.by', $result->email); $this->assertSame('mock@ely.by', $result->email);
$this->assertSame('ABCDEFG', $result->code); $this->assertSame('ABCDEFG', $result->code);
@ -37,17 +45,27 @@ class SendRegistrationEmailTest extends TestCase {
$task->link = 'https://account.ely.by/activation/ABCDEFG'; $task->link = 'https://account.ely.by/activation/ABCDEFG';
$task->locale = 'ru'; $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); $this->tester->canSeeEmailIsSent(1);
/** @var \yii\swiftmailer\Message $email */ /** @var \yii\swiftmailer\Message $email */
$email = $this->tester->grabSentEmails()[0]; $email = $this->tester->grabSentEmails()[0];
$this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo()); $this->assertSame(['mock@ely.by' => 'mock-username'], $email->getTo());
$this->assertSame('Ely.by Account registration', $email->getSubject()); $this->assertSame('Ely.by Account registration', $email->getSubject());
$body = $email->getSwiftMessage()->getBody(); $this->assertSame('mock-template', $email->getSwiftMessage()->getBody());
$this->assertContains('Привет, mock-username', $body); }
$this->assertContains('GFEDCBA', $body);
$this->assertContains('https://account.ely.by/activation/ABCDEFG', $body); protected function _before() {
parent::_before();
$this->renderer = $this->createMock(RendererInterface::class);
Yii::$app->set('emailsRenderer', $this->renderer);
} }
} }