diff --git a/PHPCI/Builder.php b/PHPCI/Builder.php
index a7b60bc1..f943a092 100644
--- a/PHPCI/Builder.php
+++ b/PHPCI/Builder.php
@@ -116,6 +116,16 @@ class Builder
return isset($this->config[$key]) ? $this->config[$key] : null;
}
+ /**
+ * Access a variable from the config.yml
+ * @param $key
+ * @return mixed
+ */
+ public function getSystemConfig($key)
+ {
+ return \b8\Registry::getInstance()->get($key);
+ }
+
/**
* Access the build.
* @param Build
@@ -125,6 +135,22 @@ class Builder
return $this->build;
}
+ /**
+ * @return string The title of the project being built.
+ */
+ public function getBuildProjectTitle() {
+ return $this->getBuild()->getProject()->getTitle();
+ }
+
+ /**
+ * Indicates if the build has passed or failed.
+ * @return bool
+ */
+ public function getSuccessStatus()
+ {
+ return $this->success;
+ }
+
/**
* Run the active build.
*/
diff --git a/PHPCI/Command/InstallCommand.php b/PHPCI/Command/InstallCommand.php
index 35459aec..ad9cbeb7 100644
--- a/PHPCI/Command/InstallCommand.php
+++ b/PHPCI/Command/InstallCommand.php
@@ -48,6 +48,13 @@ class InstallCommand extends Command
$conf['phpci']['github']['id'] = $this->ask('(Optional) Github Application ID: ', true);
$conf['phpci']['github']['secret'] = $this->ask('(Optional) Github Application Secret: ', true);
+ $conf['phpci']['email_settings']['smtp_address'] = $this->ask('(Optional) Smtp server address: ', true);
+ $conf['phpci']['email_settings']['smtp_port'] = $this->ask('(Optional) Smtp port: ', true);
+ $conf['phpci']['email_settings']['smtp_username'] = $this->ask('(Optional) Smtp Username: ', true);
+ $conf['phpci']['email_settings']['smtp_password'] = $this->ask('(Optional) Smtp Password: ', true);
+ $conf['phpci']['email_settings']['from_address'] = $this->ask('(Optional) Email address to send from: ', true);
+ $conf['phpci']['email_settings']['default_mailto_address'] = $this->ask('(Optional) Default address to email notifications to: ', true);
+
$dbUser = $conf['b8']['database']['username'];
$dbPass = $conf['b8']['database']['password'];
$dbHost = $conf['b8']['database']['servers']['write'];
diff --git a/PHPCI/Plugin/Email.php b/PHPCI/Plugin/Email.php
new file mode 100644
index 00000000..ae7ced05
--- /dev/null
+++ b/PHPCI/Plugin/Email.php
@@ -0,0 +1,191 @@
+
+* @package PHPCI
+* @subpackage Plugins
+*/
+class Email implements \PHPCI\Plugin
+{
+
+ /**
+ * @var \PHPCI\Builder
+ */
+ protected $phpci;
+
+ /**
+ * @var array
+ */
+ protected $options;
+
+ /**
+ * @var array
+ */
+ protected $emailConfig;
+
+ /**
+ * @var \Swift_Mailer
+ */
+ protected $mailer;
+
+ public function __construct(\PHPCI\Builder $phpci,
+ array $options = array(),
+ \Swift_Mailer $mailer = null)
+ {
+ $phpCiSettings = $phpci->getSystemConfig('phpci');
+ $this->phpci = $phpci;
+ $this->options = $options;
+ $this->emailConfig = isset($phpCiSettings['email_settings']) ? $phpCiSettings['email_settings'] : array();
+
+ // Either a mailer will have been passed in or we load from the
+ // config.
+ if ($mailer === null) {
+ $this->loadSwiftMailerFromConfig();
+ }
+ else {
+ $this->mailer = $mailer;
+ }
+ }
+
+ /**
+ * Connects to MySQL and runs a specified set of queries.
+ */
+ public function execute()
+ {
+ $addresses = $this->getEmailAddresses();
+
+ // Without some email addresses in the yml file then we
+ // can't do anything.
+ if (count($addresses) == 0) {
+ return false;
+ }
+
+ $sendFailures = array();
+
+ $subjectTemplate = "PHPCI - %s - %s";
+ $projectName = $this->phpci->getBuildProjectTitle();
+ $logText = $this->phpci->getBuild()->getLog();
+
+ if($this->phpci->getSuccessStatus()) {
+ $sendFailures = $this->sendSeparateEmails(
+ $addresses,
+ sprintf($subjectTemplate, $projectName, "Passing Build"),
+ sprintf("Log Output:
%s", $logText) + ); + } + else { + $sendFailures = $this->sendSeparateEmails( + $addresses, + sprintf($subjectTemplate, $projectName, "Failing Build"), + sprintf("Log Output:
%s", $logText) + ); + } + + // This is a success if we've not failed to send anything. + $this->phpci->log(sprintf( + "%d emails sent", + (count($addresses) - count($sendFailures))) + ); + $this->phpci->log(sprintf( + "%d emails failed to send", + count($sendFailures)) + ); + return (count($sendFailures) == 0); + } + + /** + * @param array|string $toAddresses Array or single address to send to + * @param string $subject Email subject + * @param string $body Email body + * @return array Array of failed addresses + */ + public function sendEmail($toAddresses, $subject, $body) + { + $message = \Swift_Message::newInstance($subject) + ->setFrom($this->getMailConfig('from_address')) + ->setTo($toAddresses) + ->setBody($body) + ->setContentType("text/html"); + $failedAddresses = array(); + $this->mailer->send($message, $failedAddresses); + + return $failedAddresses; + } + + public function sendSeparateEmails(array $toAddresses, $subject, $body) + { + $failures = array(); + foreach($toAddresses as $address) { + $newFailures = $this->sendEmail($address, $subject, $body); + foreach($newFailures as $failure) { + $failures[] = $failure; + } + } + return $failures; + } + + protected function loadSwiftMailerFromConfig() + { + /** @var \Swift_SmtpTransport $transport */ + $transport = \Swift_SmtpTransport::newInstance( + $this->getMailConfig('smtp_address'), + $this->getMailConfig('smtp_port') + ); + $transport->setUsername($this->getMailConfig('smtp_username')); + $transport->setPassword($this->getMailConfig('smtp_password')); + + $this->mailer = \Swift_Mailer::newInstance($transport); + } + + protected function getMailConfig($configName) + { + if (isset($this->emailConfig[$configName]) + && $this->emailConfig[$configName] != "") + { + return $this->emailConfig[$configName]; + } + // Check defaults + else { + switch($configName) { + case 'smtp_address': + return "localhost"; + case 'default_mailto_address': + return null; + case 'smtp_port': + return '25'; + case 'from_address': + return "notifications-ci@phptesting.org"; + default: + return ""; + } + } + } + + protected function getEmailAddresses() + { + $addresses = array(); + + if (isset($this->options['addresses'])) { + foreach ($this->options['addresses'] as $address) { + $addresses[] = $address; + } + } + + if (isset($this->options['default_mailto_address'])) { + $addresses[] = $this->options['default_mailto_address']; + return $addresses; + } + return $addresses; + } +} \ No newline at end of file diff --git a/Tests/PHPCI/Plugin/Email.php b/Tests/PHPCI/Plugin/Email.php new file mode 100644 index 00000000..8000da7e --- /dev/null +++ b/Tests/PHPCI/Plugin/Email.php @@ -0,0 +1,256 @@ +mockBuild = $this->getMock( + '\PHPCI\Model\Build', + array('getLog'), + array(), + "mockBuild", + false + ); + + $this->mockBuild->expects($this->any()) + ->method('getLog') + ->will($this->returnValue("Build Log")); + + $this->mockCiBuilder = $this->getMock( + '\PHPCI\Builder', + array('getSystemConfig', + 'getBuildProjectTitle', + 'getBuild', + 'log'), + array(), + "mockBuilder", + false + ); + + $this->mockCiBuilder->buildPath = "/"; + + $this->mockCiBuilder->expects($this->any()) + ->method('getSystemConfig') + ->with('phpci') + ->will($this->returnValue(array( + 'email_settings' => array( + 'from_address' => "test-from-address@example.com" + ) + ))); + $this->mockCiBuilder->expects($this->any()) + ->method('getBuildProjectTitle') + ->will($this->returnValue('Test-Project')); + $this->mockCiBuilder->expects($this->any()) + ->method('getBuild') + ->will($this->returnValue($this->mockBuild)); + + $this->mockMailer = $this->getMock( + '\Swift_Mailer', + array('send'), + array(), + "mockMailer", + false + ); + + $this->loadEmailPluginWithOptions(); + } + + protected function loadEmailPluginWithOptions($arrOptions = array()) + { + $this->testedEmailPlugin = new EmailPlugin( + $this->mockCiBuilder, + $arrOptions, + $this->mockMailer + ); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_ReturnsFalseWithoutArgs() + { + $returnValue = $this->testedEmailPlugin->execute(); + // As no addresses will have been mailed as non are configured. + $expectedReturn = false; + + $this->assertEquals($expectedReturn, $returnValue); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_BuildsBasicEmails() + { + $this->loadEmailPluginWithOptions(array( + 'addresses' => array('test-receiver@example.com') + )); + + /** @var \Swift_Message $actualMail */ + $actualMail = null; + $this->catchMailPassedToSend($actualMail); + + $returnValue = $this->testedEmailPlugin->execute(); + $expectedReturn = true; + + $this->assertSystemMail( + 'test-receiver@example.com', + 'test-from-address@example.com', + "Log Output:
Build Log", + "PHPCI - Test-Project - Passing Build", + $actualMail + ); + + $this->assertEquals($expectedReturn, $returnValue); + + + } + + /** + * @covers PHPUnit::sendEmail + */ + public function testSendEmail_CallsMailerSend() + { + $this->mockMailer->expects($this->once()) + ->method('send'); + $this->testedEmailPlugin->sendEmail("test@email.com", "hello", "body"); + } + + /** + * @covers PHPUnit::sendEmail + */ + public function testSendEmail_BuildsAMessageObject() + { + $subject = "Test mail"; + $body = "Message Body"; + $toAddress = "test@example.com"; + + $this->mockMailer->expects($this->once()) + ->method('send') + ->with($this->isInstanceOf('\Swift_Message'), $this->anything()); + $this->testedEmailPlugin->sendEmail($toAddress, $subject, $body); + } + + /** + * @covers PHPUnit::sendEmail + */ + public function testSendEmail_BuildsExpectedMessage() + { + $subject = "Test mail"; + $body = "Message Body"; + $toAddress = "test@example.com"; + $expectedMessage = \Swift_Message::newInstance($subject) + ->setFrom('test-from-address@example.com') + ->setTo($toAddress) + ->setBody($body); + + /** @var \Swift_Message $actualMail */ + $actualMail = null; + $this->catchMailPassedToSend($actualMail); + + $this->testedEmailPlugin->sendEmail($toAddress, $subject, $body); + + $this->assertSystemMail( + $toAddress, + 'test-from-address@example.com', + $body, + $subject, + $actualMail + ); + } + + /** + * @param \Swift_Message $actualMail passed by ref and populated with + * the message object the mock mailer + * receives. + */ + protected function catchMailPassedToSend(&$actualMail) + { + $this->mockMailer->expects($this->once()) + ->method('send') + ->will( + $this->returnCallback( + function ($passedMail) use (&$actualMail) { + $actualMail = $passedMail; + return array(); + } + ) + ); + } + + /** + * Asserts that the actual mail object is populated as expected. + * + * @param string $expectedToAddress + * @param $expectedFromAddress + * @param string $expectedBody + * @param string $expectedSubject + * @param \Swift_Message $actualMail + */ + protected function assertSystemMail($expectedToAddress, + $expectedFromAddress, + $expectedBody, + $expectedSubject, + $actualMail) + { + if (! ($actualMail instanceof \Swift_Message)) { + $type = is_object($actualMail) ? get_class($actualMail) : gettype($actualMail); + throw new \Exception("Expected Swift_Message got " . $type); + } + $this->assertEquals( + array($expectedFromAddress => null), + $actualMail->getFrom() + ); + + $this->assertEquals( + array($expectedToAddress => null), + $actualMail->getTo() + ); + + $this->assertEquals( + $expectedBody, + $actualMail->getBody() + ); + + $this->assertEquals( + $expectedSubject, + $actualMail->getSubject() + ); + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 7b8c37b3..a4424830 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "phpspec/phpspec" : "2.*", "symfony/yaml" : "2.2.x-dev", "symfony/console" : "2.2.*", - "fabpot/php-cs-fixer" : "0.3.*@dev" + "fabpot/php-cs-fixer" : "0.3.*@dev", + "swiftmailer/swiftmailer" : "v5.0.0" } }