Merge branch 'master' of git://github.com/Block8/PHPCI into feature/summaryview

This commit is contained in:
Gabriel Baker 2013-06-09 20:15:58 +01:00
commit d02e5d86d0
10 changed files with 538 additions and 10 deletions

View file

@ -10,6 +10,7 @@
namespace PHPCI;
use b8;
use b8\Registry;
use b8\Http\Response\RedirectResponse;
use b8\View;
@ -33,8 +34,11 @@ class Application extends b8\Application
$sessionAction = ($this->controllerName == 'Session' && in_array($this->action, array('login', 'logout')));
$externalAction = in_array($this->controllerName, array('Bitbucket', 'Github', 'BuildStatus'));
$skipValidation = ($externalAction || $sessionAction);
if($skipValidation || $this->validateSession()) {
if ( !empty($_SESSION['user']) ) {
Registry::getInstance()->set('user', $_SESSION['user']);
}
parent::handleRequest();
}
@ -43,7 +47,7 @@ class Application extends b8\Application
$view->content = $this->response->getContent();
$this->response->setContent($view->render());
}
return $this->response;
}

View file

@ -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.
*/

View file

@ -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'];

View file

@ -86,10 +86,10 @@ class BuildController extends \PHPCI\Controller
*/
public function delete($buildId)
{
if (!Registry::getInstance()->get('user')->getIsAdmin()) {
if (empty($_SESSION['user']) || !$_SESSION['user']->getIsAdmin()) {
throw new \Exception('You do not have permission to do that.');
}
$build = $this->_buildStore->getById($buildId);
$this->_buildStore->delete($build);

191
PHPCI/Plugin/Email.php Normal file
View file

@ -0,0 +1,191 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Plugin;
/**
* Email Plugin - Provides simple email capability to PHPCI.
* @author Steve Brazier <meadsteve@gmail.com>
* @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: <br><pre>%s</pre>", $logText)
);
}
else {
$sendFailures = $this->sendSeparateEmails(
$addresses,
sprintf($subjectTemplate, $projectName, "Failing Build"),
sprintf("Log Output: <br><pre>%s</pre>", $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;
}
}

View file

@ -11,21 +11,26 @@ switch($build->getStatus())
{
case 0:
$cls = 'info';
$subcls = 'info';
$status = 'Pending';
break;
case 1:
$cls = 'warning';
$subcls = 'warning';
$status = 'Running';
break;
case 2:
$cls = 'success';
$subcls = 'success';
$status = 'Success';
break;
case 3:
$cls = 'error';
$subcls = 'important';
$status = 'Failed';
break;
}
@ -35,7 +40,31 @@ switch($build->getStatus())
<td><a href="<?= PHPCI_URL ?>project/view/<?php print $build->getProjectId(); ?>"><?php print $build->getProject()->getTitle(); ?></a></td>
<td><a href="<?php print $build->getCommitLink(); ?>"><?php print $build->getCommitId(); ?></a></td>
<td><a href="<?php print $build->getBranchLink(); ?>"><?php print $build->getBranch(); ?></a></td>
<td><?php print $status; ?></td>
<td>
<?php
$plugins = json_decode($build->getPlugins(), true);
if ( !is_array($plugins) ) {
$plugins = array();
}
if ( 0 === count($plugins) ) {
?>
<span class='label label-<?= $subcls ?>'>
<?= $status ?>
</span>
<?php
}
?>
<?php
foreach($plugins as $plugin => $pluginstatus):
$subcls = $pluginstatus?'label label-success':'label label-important';
?>
<span class='<?= $subcls ?>'>
<?= ucwords(str_replace('_', ' ', $plugin)) ?>
</span>
<?php endforeach; ?>
<br style='clear:both;' />
</td>
<td>
<div class="btn-group">
<a class="btn" href="<?= PHPCI_URL ?>build/view/<?php print $build->getId(); ?>">View</a>

View file

@ -61,14 +61,14 @@
$pages = ceil($total / 10);
$pages = $pages == 0 ? 1 : $pages;
print '<li class="'.($page == 1 ? 'disabled' : '').'"><a href="<?= PHPCI_URL ?>project/view/'.$project->getId().'?p='.($page == 1 ? '1' : $page - 1).'">&laquo;</a></li>';
print '<li class="'.($page == 1 ? 'disabled' : '').'"><a href="' . PHPCI_URL . 'project/view/'.$project->getId().'?p='.($page == 1 ? '1' : $page - 1).'">&laquo;</a></li>';
for($i = 1; $i <= $pages; $i++)
{
print '<li><a href="<?= PHPCI_URL ?>project/view/' . $project->getId() . '?p=' . $i . '">' . $i . '</a></li>';
print '<li><a href="' . PHPCI_URL . 'project/view/' . $project->getId() . '?p=' . $i . '">' . $i . '</a></li>';
}
print '<li class="'.($page == $pages ? 'disabled' : '').'"><a href="<?= PHPCI_URL ?>project/view/'.$project->getId().'?p='.($page == $pages ? $pages : $page + 1).'">&raquo;</a></li>';
print '<li class="'.($page == $pages ? 'disabled' : '').'"><a href="' . PHPCI_URL . 'project/view/'.$project->getId().'?p='.($page == $pages ? $pages : $page + 1).'">&raquo;</a></li>';
print '</ul></div>';

View file

@ -0,0 +1,256 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Plugin\Tests;
use PHPCI\Plugin\Email as EmailPlugin;
define('PHPCI_BIN_DIR', "FAKEPHPCIBIN");
/**
* Unit test for the PHPUnit plugin.
* @author meadsteve
*/
class EmailTest extends \PHPUnit_Framework_TestCase
{
/**
* @var EmailPlugin $testedPhpUnit
*/
protected $testedEmailPlugin;
/**
* @var \PHPUnit_Framework_MockObject_MockObject $mockCiBuilder
*/
protected $mockCiBuilder;
/**
* @var \PHPUnit_Framework_MockObject_MockObject $mockMailer
*/
protected $mockMailer;
/**
* @var \PHPUnit_Framework_MockObject_MockObject $mockMailer
*/
protected $mockBuild;
public function setUp()
{
$this->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: <br><pre>Build Log</pre>",
"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()
);
}
}

View file

@ -14,6 +14,20 @@ body
padding: 10px;
}
td .label { margin-right: 5px; }
.success-message {
background-color: #4F8A10;
}
.error-message {
background-color: #FF4747;
}
#latest-builds td {
vertical-align: middle;
}
.widget-title, .modal-header, .table th, div.dataTables_wrapper .ui-widget-header, .ui-dialog .ui-dialog-titlebar {
background-color: #efefef;
background-image: -webkit-gradient(linear, 0 0%, 0 100%, from(#fdfdfd), to(#eaeaea));
@ -61,7 +75,7 @@ body
background: url('/assets/img/icon-build-running.png') no-repeat top left;
}
h3
h3
{
border-bottom: 1px solid #f0f0f0;
margin-top: 0;

View file

@ -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"
}
}