silverstripe-framework/tests/php/Control/Email/EmailTest.php
2022-04-14 13:12:59 +12:00

717 lines
26 KiB
PHP

<?php
namespace SilverStripe\Control\Tests\Email;
use DateTime;
use PHPUnit\Framework\MockObject\MockObject;
use SilverStripe\Control\Director;
use SilverStripe\Control\Email\Email;
use SilverStripe\Control\Email\Mailer;
use SilverStripe\Control\Email\SwiftMailer;
use SilverStripe\Control\Tests\Email\EmailTest\EmailSubClass;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Manifest\ModuleResourceLoader;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\TestMailer;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Security\Member;
use SilverStripe\View\SSViewer;
use Swift_Attachment;
use Swift_Mailer;
use Swift_Message;
use Swift_NullTransport;
use Swift_RfcComplianceException;
class EmailTest extends SapphireTest
{
protected function setUp(): void
{
parent::setUp();
Director::config()->set('alternate_base_url', 'http://www.mysite.com/');
}
public function testAddAttachment()
{
$email = new Email();
$email->addAttachment(__DIR__ . '/EmailTest/attachment.txt', null, 'text/plain');
$children = $email->getSwiftMessage()->getChildren();
$this->assertCount(1, $children);
/** @var Swift_Attachment $child */
$child = reset($children);
$this->assertInstanceOf(Swift_Attachment::class, $child);
$this->assertEquals('text/plain', $child->getContentType());
$this->assertEquals('attachment.txt', $child->getFilename());
}
public function testAddAttachmentFromData()
{
$email = new Email();
$email->addAttachmentFromData('foo bar', 'foo.txt', 'text/plain');
$children = $email->getSwiftMessage()->getChildren();
$this->assertCount(1, $children);
/** @var Swift_Attachment $child */
$child = reset($children);
$this->assertInstanceOf(Swift_Attachment::class, $child);
$this->assertEquals('foo bar', $child->getBody());
$this->assertEquals('text/plain', $child->getContentType());
$this->assertEquals('foo.txt', $child->getFilename());
}
/**
* @dataProvider provideValidEmailAddresses
*/
public function testValidEmailAddress($email)
{
$this->assertTrue(Email::is_valid_address($email));
}
/**
* @dataProvider provideInvalidEmailAddresses
*/
public function testInvalidEmailAddress($email)
{
$this->assertFalse(Email::is_valid_address($email));
}
public function provideValidEmailAddresses()
{
return [
['test@example.com', 'test-123@sub.example.com'],
];
}
public function provideInvalidEmailAddresses()
{
return [
['foo.bar@', '@example.com', 'foo@'],
];
}
public function testObfuscate()
{
$emailAddress = 'test-1@example.com';
$direction = Email::obfuscate($emailAddress, 'direction');
$visible = Email::obfuscate($emailAddress, 'visible');
$hex = Email::obfuscate($emailAddress, 'hex');
$this->assertEquals('<span class="codedirection">moc.elpmaxe@1-tset</span>', $direction);
$this->assertEquals('test [dash] 1 [at] example [dot] com', $visible);
$this->assertEquals(
'&#x74;&#x65;&#x73;&#x74;&#x2d;&#x31;&#x40;&#x65;&#x78;&#x61;&#x6d;&#x70;&#x6c;&#x65;&#x2e;&#x63;&#x6f;&#x6d;',
$hex
);
}
public function testSendPlain()
{
$email = $this->makeEmailMock('Test send plain');
// email should not call render if a body is supplied
$email->expects($this->never())->method('renderWith');
$successful = $email->sendPlain();
$this->assertTrue($successful);
$this->assertEmpty($email->getFailedRecipients());
/** @var TestMailer $mailer */
$mailer = Injector::inst()->get(Mailer::class);
$sentMail = $mailer->findEmail('to@example.com');
$this->assertTrue(is_array($sentMail));
$this->assertEquals('to@example.com', $sentMail['To']);
$this->assertEquals('from@example.com', $sentMail['From']);
$this->assertEquals('Test send plain', $sentMail['Subject']);
$this->assertEquals('Body for Test send plain', $sentMail['Content']);
$this->assertCount(1, $sentMail['AttachedFiles']);
$child = reset($sentMail['AttachedFiles']);
$this->assertEquals('text/plain', $child['mimetype']);
$this->assertEquals('attachment.txt', $child['filename']);
$this->assertEquals('Hello, I\'m a text document.', $child['contents']);
}
public function testSend()
{
/** @var Email|MockObject $email */
$email = $this->makeEmailMock('Test send HTML');
// email should not call render if a body is supplied
$email->expects($this->never())->method('renderWith');
$successful = $email->send();
$this->assertTrue($successful);
$this->assertEmpty($email->getFailedRecipients());
/** @var TestMailer $mailer */
$mailer = Injector::inst()->get(Mailer::class);
$sentMail = $mailer->findEmail('to@example.com');
$this->assertTrue(is_array($sentMail));
$this->assertEquals('to@example.com', $sentMail['To']);
$this->assertEquals('from@example.com', $sentMail['From']);
$this->assertEquals('Test send HTML', $sentMail['Subject']);
$this->assertEquals('Body for Test send HTML', $sentMail['Content']);
$this->assertCount(1, $sentMail['AttachedFiles']);
$child = reset($sentMail['AttachedFiles']);
$this->assertEquals('text/plain', $child['mimetype']);
$this->assertEquals('attachment.txt', $child['filename']);
$this->assertEquals('Hello, I\'m a text document.', $child['contents']);
}
public function testRenderedSend()
{
/** @var Email|MockObject $email */
$email = $this->getMockBuilder(Email::class)
->enableProxyingToOriginalMethods()
->getMock();
$email->setFrom('from@example.com');
$email->setTo('to@example.com');
$email->setData([
'EmailContent' => 'test',
]);
$this->assertFalse($email->hasPlainPart());
$this->assertEmpty($email->getBody());
// these seem to fail for some reason :/
//$email->expects($this->once())->method('render');
//$email->expects($this->once())->method('generatePlainPartFromBody');
$email->send();
$this->assertTrue($email->hasPlainPart());
$this->assertNotEmpty($email->getBody());
}
public function testRenderedSendSubclass()
{
// Include dev theme
SSViewer::set_themes([
'silverstripe/framework:/tests/php/Control/Email/EmailTest',
'$default',
]);
/** @var Email|MockObject $email */
$email = $this->getMockBuilder(EmailSubClass::class)
->enableProxyingToOriginalMethods()
->getMock();
$email->setFrom('from@example.com');
$email->setTo('to@example.com');
$email->setData([
'EmailContent' => 'test',
]);
$this->assertFalse($email->hasPlainPart());
$this->assertEmpty($email->getBody());
$email->send();
$this->assertTrue($email->hasPlainPart());
$this->assertNotEmpty($email->getBody());
$this->assertStringContainsString('<h1>Email Sub-class</h1>', $email->getBody());
}
public function testConsturctor()
{
$email = new Email(
'from@example.com',
'to@example.com',
'subject',
'body',
'cc@example.com',
'bcc@example.com',
'bounce@example.com'
);
$this->assertCount(1, $email->getFrom());
$this->assertContains('from@example.com', array_keys($email->getFrom() ?? []));
$this->assertCount(1, $email->getTo());
$this->assertContains('to@example.com', array_keys($email->getTo() ?? []));
$this->assertEquals('subject', $email->getSubject());
$this->assertEquals('body', $email->getBody());
$this->assertCount(1, $email->getCC());
$this->assertContains('cc@example.com', array_keys($email->getCC() ?? []));
$this->assertCount(1, $email->getBCC());
$this->assertContains('bcc@example.com', array_keys($email->getBCC() ?? []));
$this->assertEquals('bounce@example.com', $email->getReturnPath());
}
public function testGetSwiftMessage()
{
$email = new Email(
'from@example.com',
'to@example.com',
'subject',
'body',
'cc@example.com',
'bcc@example.com',
'bounce@example.com'
);
$swiftMessage = $email->getSwiftMessage();
$this->assertInstanceOf(Swift_Message::class, $swiftMessage);
$this->assertCount(1, $swiftMessage->getFrom());
$this->assertContains('from@example.com', array_keys($swiftMessage->getFrom() ?? []));
$this->assertCount(1, $swiftMessage->getTo());
$this->assertContains('to@example.com', array_keys($swiftMessage->getTo() ?? []));
$this->assertEquals('subject', $swiftMessage->getSubject());
$this->assertEquals('body', $swiftMessage->getBody());
$this->assertCount(1, $swiftMessage->getCC());
$this->assertContains('cc@example.com', array_keys($swiftMessage->getCc() ?? []));
$this->assertCount(1, $swiftMessage->getBCC());
$this->assertContains('bcc@example.com', array_keys($swiftMessage->getBcc() ?? []));
$this->assertEquals('bounce@example.com', $swiftMessage->getReturnPath());
}
public function testSetSwiftMessage()
{
Email::config()->update('admin_email', 'admin@example.com');
DBDatetime::set_mock_now('2017-01-01 07:00:00');
$email = new Email();
$swiftMessage = new Swift_Message();
$email->setSwiftMessage($swiftMessage);
$dateTime = new DateTime();
$dateTime->setTimestamp(DBDatetime::now()->getTimestamp());
$email->getSwiftMessage()->setDate($dateTime);
$this->assertCount(1, $email->getFrom());
$this->assertContains('admin@example.com', array_keys($swiftMessage->getFrom() ?? []));
$this->assertEquals(strtotime('2017-01-01 07:00:00'), $swiftMessage->getDate()->getTimestamp());
$this->assertEquals($swiftMessage, $email->getSwiftMessage());
// check from field is retained
$swiftMessage = new Swift_Message();
$swiftMessage->setFrom('from@example.com');
$email->setSwiftMessage($swiftMessage);
$this->assertCount(1, $email->getFrom());
$this->assertContains('from@example.com', array_keys($email->getFrom() ?? []));
}
public function testAdminEmailApplied()
{
Email::config()->update('admin_email', 'admin@example.com');
$email = new Email();
$this->assertCount(1, $email->getFrom());
$this->assertContains('admin@example.com', array_keys($email->getFrom() ?? []));
}
public function testGetFrom()
{
$email = new Email('from@example.com');
$this->assertCount(1, $email->getFrom());
$this->assertContains('from@example.com', array_keys($email->getFrom() ?? []));
}
public function testSetFrom()
{
$email = new Email('from@example.com');
$this->assertCount(1, $email->getFrom());
$this->assertContains('from@example.com', array_keys($email->getFrom() ?? []));
$email->setFrom('new-from@example.com');
$this->assertCount(1, $email->getFrom());
$this->assertContains('new-from@example.com', array_keys($email->getFrom() ?? []));
}
public function testAddFrom()
{
$email = new Email('from@example.com');
$this->assertCount(1, $email->getFrom());
$this->assertContains('from@example.com', array_keys($email->getFrom() ?? []));
$email->addFrom('new-from@example.com');
$this->assertCount(2, $email->getFrom());
$this->assertContains('from@example.com', array_keys($email->getFrom() ?? []));
$this->assertContains('new-from@example.com', array_keys($email->getFrom() ?? []));
}
public function testSetGetSender()
{
$email = new Email();
$this->assertEmpty($email->getSender());
$email->setSender('sender@example.com', 'Silver Stripe');
$this->assertEquals(['sender@example.com' => 'Silver Stripe'], $email->getSender());
}
public function testSetGetReturnPath()
{
$email = new Email();
$this->assertEmpty($email->getReturnPath());
$email->setReturnPath('return@example.com');
$this->assertEquals('return@example.com', $email->getReturnPath());
}
public function testSetGetTo()
{
$email = new Email('from@example.com', 'to@example.com');
$this->assertCount(1, $email->getTo());
$this->assertContains('to@example.com', array_keys($email->getTo() ?? []));
$email->setTo('new-to@example.com', 'Silver Stripe');
$this->assertEquals(['new-to@example.com' => 'Silver Stripe'], $email->getTo());
}
public function testAddTo()
{
$email = new Email('from@example.com', 'to@example.com');
$this->assertCount(1, $email->getTo());
$this->assertContains('to@example.com', array_keys($email->getTo() ?? []));
$email->addTo('new-to@example.com');
$this->assertCount(2, $email->getTo());
$this->assertContains('to@example.com', array_keys($email->getTo() ?? []));
$this->assertContains('new-to@example.com', array_keys($email->getTo() ?? []));
}
public function testSetGetCC()
{
$email = new Email('from@example.com', 'to@example.com', 'subject', 'body', 'cc@example.com');
$this->assertCount(1, $email->getCC());
$this->assertContains('cc@example.com', array_keys($email->getCC() ?? []));
$email->setCC('new-cc@example.com', 'Silver Stripe');
$this->assertEquals(['new-cc@example.com' => 'Silver Stripe'], $email->getCC());
}
public function testAddCC()
{
$email = new Email('from@example.com', 'to@example.com', 'subject', 'body', 'cc@example.com');
$this->assertCount(1, $email->getCC());
$this->assertContains('cc@example.com', array_keys($email->getCC() ?? []));
$email->addCC('new-cc@example.com', 'Silver Stripe');
$this->assertCount(2, $email->getCC());
$this->assertContains('cc@example.com', array_keys($email->getCC() ?? []));
$this->assertContains('new-cc@example.com', array_keys($email->getCC() ?? []));
}
public function testSetGetBCC()
{
$email = new Email(
'from@example.com',
'to@example.com',
'subject',
'body',
'cc@example.com',
'bcc@example.com'
);
$this->assertCount(1, $email->getBCC());
$this->assertContains('bcc@example.com', array_keys($email->getBCC() ?? []));
$email->setBCC('new-bcc@example.com', 'Silver Stripe');
$this->assertEquals(['new-bcc@example.com' => 'Silver Stripe'], $email->getBCC());
}
public function testAddBCC()
{
$email = new Email(
'from@example.com',
'to@example.com',
'subject',
'body',
'cc@example.com',
'bcc@example.com'
);
$this->assertCount(1, $email->getBCC());
$this->assertContains('bcc@example.com', array_keys($email->getBCC() ?? []));
$email->addBCC('new-bcc@example.com', 'Silver Stripe');
$this->assertCount(2, $email->getBCC());
$this->assertContains('bcc@example.com', array_keys($email->getBCC() ?? []));
$this->assertContains('new-bcc@example.com', array_keys($email->getBCC() ?? []));
}
public function testReplyTo()
{
$email = new Email();
$this->assertEmpty($email->getReplyTo());
$email->setReplyTo('reply-to@example.com', 'Silver Stripe');
$this->assertEquals(['reply-to@example.com' => 'Silver Stripe'], $email->getReplyTo());
$email->addReplyTo('new-reply-to@example.com');
$this->assertCount(2, $email->getReplyTo());
$this->assertContains('reply-to@example.com', array_keys($email->getReplyTo() ?? []));
$this->assertContains('new-reply-to@example.com', array_keys($email->getReplyTo() ?? []));
}
public function testSubject()
{
$email = new Email('from@example.com', 'to@example.com', 'subject');
$this->assertEquals('subject', $email->getSubject());
$email->setSubject('new subject');
$this->assertEquals('new subject', $email->getSubject());
}
public function testPriority()
{
$email = new Email();
$this->assertEquals(3, $email->getPriority());
$email->setPriority(5);
$this->assertEquals(5, $email->getPriority());
}
public function testData()
{
$email = new Email();
$this->assertEmpty($email->getData());
$email->setData([
'Title' => 'My Title',
]);
$this->assertCount(1, $email->getData());
$this->assertEquals(['Title' => 'My Title'], $email->getData());
$email->addData('Content', 'My content');
$this->assertCount(2, $email->getData());
$this->assertEquals([
'Title' => 'My Title',
'Content' => 'My content',
], $email->getData());
$email->removeData('Title');
$this->assertEquals(['Content' => 'My content'], $email->getData());
}
public function testDataWithViewableData()
{
$member = new Member();
$member->FirstName = 'First Name';
$email = new Email();
$this->assertEmpty($email->getData());
$email->setData($member);
$this->assertEquals($member, $email->getData());
$email->addData('Test', 'Test value');
$this->assertEquals('Test value', $email->getData()->Test);
$email->removeData('Test');
$this->assertNull($email->getData()->Test);
}
public function testBody()
{
$email = new Email();
$this->assertEmpty($email->getBody());
$email->setBody('<h1>Title</h1>');
$this->assertEquals('<h1>Title</h1>', $email->getBody());
}
public function testHTMLTemplate()
{
// Include dev theme
SSViewer::set_themes([
'silverstripe/framework:/tests/php/Control/Email/EmailTest',
'$default',
]);
// Find template on disk
$emailTemplate = ModuleResourceLoader::singleton()->resolveResource(
'silverstripe/framework:templates/SilverStripe/Control/Email/Email.ss'
);
$subClassTemplate = ModuleResourceLoader::singleton()->resolveResource(
'silverstripe/framework:tests/php/Control/Email/EmailTest/templates/'
. str_replace('\\', '/', EmailSubClass::class)
. '.ss'
);
$this->assertTrue($emailTemplate->exists());
$this->assertTrue($subClassTemplate->exists());
// Check template is auto-found
$email = new Email();
$this->assertEquals($emailTemplate->getPath(), $email->getHTMLTemplate());
$email->setHTMLTemplate('MyTemplate');
$this->assertEquals('MyTemplate', $email->getHTMLTemplate());
// Check subclass template is found
$email2 = new EmailSubClass();
$this->assertEquals($subClassTemplate->getPath(), $email2->getHTMLTemplate());
$email->setHTMLTemplate('MyTemplate');
$this->assertEquals('MyTemplate', $email->getHTMLTemplate());
}
public function testPlainTemplate()
{
$email = new Email();
$this->assertEmpty($email->getPlainTemplate());
$email->setPlainTemplate('MyTemplate');
$this->assertEquals('MyTemplate', $email->getPlainTemplate());
}
public function testGetFailedRecipients()
{
$mailer = new SwiftMailer();
/** @var Swift_NullTransport|MockObject $transport */
$transport = $this->getMockBuilder(Swift_NullTransport::class)->getMock();
$transport->expects($this->once())
->method('send')
->willThrowException(new Swift_RfcComplianceException('Bad email'));
$mailer->setSwiftMailer(new Swift_Mailer($transport));
$email = new Email();
$email->setTo('to@example.com');
$email->setFrom('from@example.com');
$mailer->send($email);
$this->assertCount(1, $email->getFailedRecipients());
}
public function testIsEmail()
{
$this->assertTrue((new Email)->IsEmail());
}
public function testRenderAgain()
{
$email = new Email();
$email->setData([
'EmailContent' => 'my content',
]);
$email->render();
$this->assertStringContainsString('my content', $email->getBody());
$children = $email->getSwiftMessage()->getChildren();
$this->assertCount(1, $children);
$plainPart = reset($children);
$this->assertEquals('my content', $plainPart->getBody());
// ensure repeat renders don't add multiple plain parts
$email->render();
$this->assertCount(1, $email->getSwiftMessage()->getChildren());
}
public function testRerender()
{
$email = new Email();
$email->setData([
'EmailContent' => 'my content',
]);
$email->render();
$this->assertStringContainsString('my content', $email->getBody());
$children = $email->getSwiftMessage()->getChildren();
$this->assertCount(1, $children);
$plainPart = reset($children);
$this->assertEquals('my content', $plainPart->getBody());
// Ensure setting data causes a rerender
$email->setData([
'EmailContent' => 'your content'
]);
$email->render();
$this->assertStringContainsString('your content', $email->getBody());
// Ensure removing data causes a rerender
$email->removeData('EmailContent');
$email->render();
$this->assertStringNotContainsString('your content', $email->getBody());
// Ensure adding data causes a rerender
$email->addData([
'EmailContent' => 'their content'
]);
$email->render();
$this->assertStringContainsString('their content', $email->getBody());
}
public function testRenderPlainOnly()
{
$email = new Email();
$email->setData([
'EmailContent' => 'test content',
]);
$email->render(true);
$this->assertEquals('text/plain', $email->getSwiftMessage()->getContentType());
$this->assertEmpty($email->getSwiftMessage()->getChildren());
}
public function testHasPlainPart()
{
$email = new Email();
$email->setData([
'EmailContent' => 'test',
]);
//emails are assumed to be HTML by default
$this->assertFalse($email->hasPlainPart());
//make sure plain attachments aren't picked up as a plain part
$email->addAttachmentFromData('data', 'attachent.txt', 'text/plain');
$this->assertFalse($email->hasPlainPart());
$email->getSwiftMessage()->addPart('plain', 'text/plain');
$this->assertTrue($email->hasPlainPart());
}
public function testGeneratePlainPartFromBody()
{
$email = new Email();
$email->setBody('<h1>Test</h1>');
$this->assertEmpty($email->getSwiftMessage()->getChildren());
$email->generatePlainPartFromBody();
$children = $email->getSwiftMessage()->getChildren();
$this->assertCount(1, $children);
$plainPart = reset($children);
$this->assertStringContainsString('Test', $plainPart->getBody());
$this->assertStringNotContainsString('<h1>Test</h1>', $plainPart->getBody());
}
public function testMultipleEmailSends()
{
$email = new Email();
$email->setData([
'EmailContent' => 'Test',
]);
$this->assertEmpty($email->getBody());
$this->assertEmpty($email->getSwiftMessage()->getChildren());
$email->send();
$this->assertStringContainsString('Test', $email->getBody());
$this->assertCount(1, $email->getSwiftMessage()->getChildren());
$children = $email->getSwiftMessage()->getChildren();
/** @var \Swift_MimePart $plainPart */
$plainPart = reset($children);
$this->assertStringContainsString('Test', $plainPart->getBody());
//send again
$email->send();
$this->assertStringContainsString('Test', $email->getBody());
$this->assertCount(1, $email->getSwiftMessage()->getChildren());
$children = $email->getSwiftMessage()->getChildren();
/** @var \Swift_MimePart $plainPart */
$plainPart = reset($children);
$this->assertStringContainsString('Test', $plainPart->getBody());
}
public function testGetDefaultFrom()
{
$email = new Email();
$class = new \ReflectionClass(Email::class);
$method = $class->getMethod('getDefaultFrom');
$method->setAccessible(true);
// default to no-reply@mydomain.com if admin_email config not set
Email::config()->set('admin_email', null);
$this->assertSame('no-reply@www.mysite.com', $method->invokeArgs($email, []));
// default to no-reply@mydomain.com if admin_email config is misconfigured
Email::config()->set('admin_email', 123);
$this->assertSame('no-reply@www.mysite.com', $method->invokeArgs($email, []));
// use admin_email config string syntax
Email::config()->set('admin_email', 'myadmin@somewhere.com');
$this->assertSame('myadmin@somewhere.com', $method->invokeArgs($email, []));
$this->assertTrue(true);
// use admin_email config array syntax
Email::config()->set('admin_email', ['anotheradmin@somewhere.com' => 'Admin-email']);
$this->assertSame('anotheradmin@somewhere.com', $method->invokeArgs($email, []));
$this->assertTrue(true);
}
/**
* @return MockObject|Email
*/
protected function makeEmailMock($subject)
{
/** @var Email|MockObject $email */
$email = $this->getMockBuilder(Email::class)
->enableProxyingToOriginalMethods()
->getMock();
$email->setFrom('from@example.com');
$email->setTo('to@example.com');
$email->setSubject($subject);
$email->setBody("Body for {$subject}");
$email->setCC('cc@example.com');
$email->setBCC('bcc@example.com');
$email->addAttachment(__DIR__ . '/EmailTest/attachment.txt', null, 'text/plain');
return $email;
}
}