From 4d2583e536a89a22093f0326ec7149d611ce933b Mon Sep 17 00:00:00 2001 From: Dan Cryer Date: Mon, 14 Jul 2014 16:02:36 +0100 Subject: [PATCH] More service layer functionality, builds now. Also some extra tests for projects and build models. --- PHPCI/Controller/BuildController.php | 23 ++-- PHPCI/Controller/ProjectController.php | 27 ++-- PHPCI/Model/Base/BuildBase.php | 157 +++++++++++++++++++++++ PHPCI/Model/Build.php | 28 ++-- PHPCI/Service/BuildService.php | 112 ++++++++++++++++ PHPCI/Store/Base/BuildStoreBase.php | 32 +++++ Tests/PHPCI/Model/BuildTest.php | 84 ++++++++++++ Tests/PHPCI/Model/ProjectTest.php | 33 ++++- Tests/PHPCI/Service/BuildServiceTest.php | 148 +++++++++++++++++++++ 9 files changed, 602 insertions(+), 42 deletions(-) create mode 100644 PHPCI/Service/BuildService.php create mode 100644 Tests/PHPCI/Model/BuildTest.php create mode 100644 Tests/PHPCI/Service/BuildServiceTest.php diff --git a/PHPCI/Controller/BuildController.php b/PHPCI/Controller/BuildController.php index 145236d5..600c8508 100644 --- a/PHPCI/Controller/BuildController.php +++ b/PHPCI/Controller/BuildController.php @@ -13,6 +13,7 @@ use b8; use b8\Exception\HttpException\NotFoundException; use PHPCI\BuildFactory; use PHPCI\Model\Build; +use PHPCI\Service\BuildService; /** * Build Controller - Allows users to run and view builds. @@ -26,10 +27,16 @@ class BuildController extends \PHPCI\Controller * @var \PHPCI\Store\BuildStore */ protected $buildStore; + + /** + * @var \PHPCI\Service\BuildService + */ + protected $buildService; public function init() { - $this->buildStore = b8\Store\Factory::getStore('Build'); + $this->buildStore = b8\Store\Factory::getStore('Build'); + $this->buildService = new BuildService($this->buildStore); } /** @@ -123,17 +130,7 @@ class BuildController extends \PHPCI\Controller throw new NotFoundException('Build with ID: ' . $buildId . ' does not exist.'); } - $build = new Build(); - $build->setProjectId($copy->getProjectId()); - $build->setCommitId($copy->getCommitId()); - $build->setStatus(Build::STATUS_NEW); - $build->setBranch($copy->getBranch()); - $build->setCreated(new \DateTime()); - $build->setCommitterEmail($copy->getCommitterEmail()); - $build->setCommitMessage($copy->getCommitMessage()); - $build->setExtra(json_encode($copy->getExtra())); - - $build = $this->buildStore->save($build); + $build = $this->buildService->createDuplicateBuild($copy); header('Location: '.PHPCI_URL.'build/view/' . $build->getId()); exit; @@ -154,7 +151,7 @@ class BuildController extends \PHPCI\Controller throw new NotFoundException('Build with ID: ' . $buildId . ' does not exist.'); } - $this->buildStore->delete($build); + $this->buildService->delete($build); header('Location: '.PHPCI_URL.'project/view/' . $build->getProjectId()); exit; diff --git a/PHPCI/Controller/ProjectController.php b/PHPCI/Controller/ProjectController.php index 7683e49e..5add67a8 100644 --- a/PHPCI/Controller/ProjectController.php +++ b/PHPCI/Controller/ProjectController.php @@ -20,6 +20,7 @@ use PHPCI\Helper\Github; use PHPCI\Helper\SshKey; use PHPCI\Model\Build; use PHPCI\Model\Project; +use PHPCI\Service\BuildService; use PHPCI\Service\ProjectService; /** @@ -30,11 +31,6 @@ use PHPCI\Service\ProjectService; */ class ProjectController extends \PHPCI\Controller { - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - /** * @var \PHPCI\Store\ProjectStore */ @@ -45,11 +41,22 @@ class ProjectController extends \PHPCI\Controller */ protected $projectService; + /** + * @var \PHPCI\Store\BuildStore + */ + protected $buildStore; + + /** + * @var \PHPCI\Service\BuildService + */ + protected $buildService; + public function init() { $this->buildStore = Store\Factory::getStore('Build'); $this->projectStore = Store\Factory::getStore('Project'); $this->projectService = new ProjectService($this->projectStore); + $this->buildService = new BuildService($this->buildStore); } /** @@ -95,15 +102,7 @@ class ProjectController extends \PHPCI\Controller throw new NotFoundException('Project with id: ' . $projectId . ' not found'); } - $build = new Build(); - $build->setProjectId($projectId); - $build->setCommitId('Manual'); - $build->setStatus(Build::STATUS_NEW); - $build->setBranch($project->getBranch()); - $build->setCreated(new \DateTime()); - $build->setCommitterEmail($_SESSION['user']->getEmail()); - - $build = $this->buildStore->save($build); + $build = $this->buildService->createBuild($project, null, null, $_SESSION['user']->getEmail()); header('Location: '.PHPCI_URL.'build/view/' . $build->getId()); exit; diff --git a/PHPCI/Model/Base/BuildBase.php b/PHPCI/Model/Base/BuildBase.php index 97b427b5..648bb18c 100644 --- a/PHPCI/Model/Base/BuildBase.php +++ b/PHPCI/Model/Base/BuildBase.php @@ -45,6 +45,8 @@ class BuildBase extends Model 'committer_email' => null, 'commit_message' => null, 'extra' => null, + 'parent_id' => null, + 'engine' => null, ); /** @@ -64,9 +66,12 @@ class BuildBase extends Model 'committer_email' => 'getCommitterEmail', 'commit_message' => 'getCommitMessage', 'extra' => 'getExtra', + 'parent_id' => 'getParentId', + 'engine' => 'getEngine', // Foreign key getters: 'Project' => 'getProject', + 'Parent' => 'getParent', ); /** @@ -86,9 +91,12 @@ class BuildBase extends Model 'committer_email' => 'setCommitterEmail', 'commit_message' => 'setCommitMessage', 'extra' => 'setExtra', + 'parent_id' => 'setParentId', + 'engine' => 'setEngine', // Foreign key setters: 'Project' => 'setProject', + 'Parent' => 'setParent', ); /** @@ -159,6 +167,18 @@ class BuildBase extends Model 'nullable' => true, 'default' => null, ), + 'parent_id' => array( + 'type' => 'int', + 'length' => 11, + 'nullable' => true, + 'default' => null, + ), + 'engine' => array( + 'type' => 'varchar', + 'length' => 50, + 'nullable' => true, + 'default' => null, + ), ); /** @@ -168,6 +188,7 @@ class BuildBase extends Model 'PRIMARY' => array('unique' => true, 'columns' => 'id'), 'project_id' => array('columns' => 'project_id'), 'idx_status' => array('columns' => 'status'), + 'parent_id' => array('columns' => 'parent_id'), ); /** @@ -181,6 +202,13 @@ class BuildBase extends Model 'table' => 'project', 'col' => 'id' ), + 'build_ibfk_2' => array( + 'local_col' => 'parent_id', + 'update' => 'CASCADE', + 'delete' => 'CASCADE', + 'table' => 'build', + 'col' => 'id' + ), ); /** @@ -339,6 +367,30 @@ class BuildBase extends Model return $rtn; } + /** + * Get the value of ParentId / parent_id. + * + * @return int + */ + public function getParentId() + { + $rtn = $this->data['parent_id']; + + return $rtn; + } + + /** + * Get the value of Engine / engine. + * + * @return string + */ + public function getEngine() + { + $rtn = $this->data['engine']; + + return $rtn; + } + /** * Set the value of Id / id. * @@ -563,6 +615,42 @@ class BuildBase extends Model $this->_setModified('extra'); } + /** + * Set the value of ParentId / parent_id. + * + * @param $value int + */ + public function setParentId($value) + { + $this->_validateInt('ParentId', $value); + + if ($this->data['parent_id'] === $value) { + return; + } + + $this->data['parent_id'] = $value; + + $this->_setModified('parent_id'); + } + + /** + * Set the value of Engine / engine. + * + * @param $value string + */ + public function setEngine($value) + { + $this->_validateString('Engine', $value); + + if ($this->data['engine'] === $value) { + return; + } + + $this->data['engine'] = $value; + + $this->_setModified('engine'); + } + /** * Get the Project model for this Build by Id. * @@ -620,6 +708,75 @@ class BuildBase extends Model return $this->setProjectId($value->getId()); } + /** + * Get the Build model for this Build by Id. + * + * @uses \PHPCI\Store\BuildStore::getById() + * @uses \PHPCI\Model\Build + * @return \PHPCI\Model\Build + */ + public function getParent() + { + $key = $this->getParentId(); + + if (empty($key)) { + return null; + } + + $cacheKey = 'Cache.Build.' . $key; + $rtn = $this->cache->get($cacheKey, null); + + if (empty($rtn)) { + $rtn = Factory::getStore('Build', 'PHPCI')->getById($key); + $this->cache->set($cacheKey, $rtn); + } + + return $rtn; + } + + /** + * Set Parent - Accepts an ID, an array representing a Build or a Build model. + * + * @param $value mixed + */ + public function setParent($value) + { + // Is this an instance of Build? + if ($value instanceof \PHPCI\Model\Build) { + return $this->setParentObject($value); + } + + // Is this an array representing a Build item? + if (is_array($value) && !empty($value['id'])) { + return $this->setParentId($value['id']); + } + + // Is this a scalar value representing the ID of this foreign key? + return $this->setParentId($value); + } + + /** + * Set Parent - Accepts a Build model. + * + * @param $value \PHPCI\Model\Build + */ + public function setParentObject(\PHPCI\Model\Build $value) + { + return $this->setParentId($value->getId()); + } + + /** + * Get Build models by ParentId for this Build. + * + * @uses \PHPCI\Store\BuildStore::getByParentId() + * @uses \PHPCI\Model\Build + * @return \PHPCI\Model\Build[] + */ + public function getParentBuilds() + { + return Factory::getStore('Build', 'PHPCI')->getByParentId($this->getId()); + } + /** * Get BuildMeta models by BuildId for this Build. * diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php index 4ca3cf9e..0286b3b8 100644 --- a/PHPCI/Model/Build.php +++ b/PHPCI/Model/Build.php @@ -38,15 +38,6 @@ class Build extends BuildBase return '#'; } - /** - * @return string - */ - public function getProjectTitle() - { - $project = $this->getProject(); - return $project ? $project->getTitle() : ""; - } - /** * Get link to branch from another source (i.e. Github) */ @@ -55,6 +46,11 @@ class Build extends BuildBase return '#'; } + public function getFileLinkTemplate() + { + return null; + } + /** * Send status updates to any relevant third parties (i.e. Github) */ @@ -63,6 +59,15 @@ class Build extends BuildBase return; } + /** + * @return string + */ + public function getProjectTitle() + { + $project = $this->getProject(); + return $project ? $project->getTitle() : ""; + } + /** * Store build metadata */ @@ -160,11 +165,6 @@ class Build extends BuildBase return $config; } - public function getFileLinkTemplate() - { - return null; - } - public function getExtra($key = null) { $data = json_decode($this->data['extra'], true); diff --git a/PHPCI/Service/BuildService.php b/PHPCI/Service/BuildService.php new file mode 100644 index 00000000..99f06ec2 --- /dev/null +++ b/PHPCI/Service/BuildService.php @@ -0,0 +1,112 @@ +buildStore = $buildStore; + } + + /** + * @param Project $project + * @param string|null $commitId + * @param string|null $branch + * @param string|null $committerEmail + * @param string|null $commitMessage + * @param string|null $extra + * @return \PHPCI\Model\Build + */ + public function createBuild( + Project $project, + $commitId = null, + $branch = null, + $committerEmail = null, + $commitMessage = null, + $extra = null + ) + { + $build = new Build(); + $build->setCreated(new \DateTime()); + $build->setProject($project); + $build->setStatus(0); + + if (!is_null($commitId)) { + $build->setCommitId($commitId); + } else { + $build->setCommitId('Manual'); + } + + if (!is_null($branch)) { + $build->setBranch($branch); + } else { + $build->setBranch($project->getBranch()); + } + + if (!is_null($committerEmail)) { + $build->setCommitterEmail($committerEmail); + } + + if (!is_null($commitMessage)) { + $build->setCommitMessage($commitMessage); + } + + if (!is_null($extra)) { + $build->setExtra(json_encode($extra)); + } + + return $this->buildStore->save($build); + } + + /** + * @param Build $copyFrom + * @return \PHPCI\Model\Build + */ + public function createDuplicateBuild(Build $copyFrom) + { + $data = $copyFrom->getDataArray(); + + // Clean up unwanted properties from the original build: + unset($data['id']); + unset($data['status']); + unset($data['log']); + unset($data['started']); + unset($data['finished']); + + $build = new Build(); + $build->setValues($data); + $build->setCreated(new \DateTime()); + + return $this->buildStore->save($build); + } + + /** + * Delete a given build. + * @param Build $build + * @return bool + */ + public function deleteBuild(Build $build) + { + return $this->buildStore->delete($build); + } +} diff --git a/PHPCI/Store/Base/BuildStoreBase.php b/PHPCI/Store/Base/BuildStoreBase.php index b67d5f73..ff21155a 100644 --- a/PHPCI/Store/Base/BuildStoreBase.php +++ b/PHPCI/Store/Base/BuildStoreBase.php @@ -107,4 +107,36 @@ class BuildStoreBase extends Store return array('items' => array(), 'count' => 0); } } + + public function getByParentId($value, $limit = null, $useConnection = 'read') + { + if (is_null($value)) { + throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + $add = ''; + + if ($limit) { + $add .= ' LIMIT ' . $limit; + } + + $count = null; + + $query = 'SELECT * FROM `build` WHERE `parent_id` = :parent_id' . $add; + $stmt = Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':parent_id', $value); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new Build($item); + }; + $rtn = array_map($map, $res); + + return array('items' => $rtn, 'count' => $count); + } else { + return array('items' => array(), 'count' => 0); + } + } } diff --git a/Tests/PHPCI/Model/BuildTest.php b/Tests/PHPCI/Model/BuildTest.php new file mode 100644 index 00000000..b8ee3432 --- /dev/null +++ b/Tests/PHPCI/Model/BuildTest.php @@ -0,0 +1,84 @@ + + */ +class BuildTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestIsAValidModel() + { + $build = new Build(); + $this->assertTrue($build instanceof \b8\Model); + $this->assertTrue($build instanceof Model); + $this->assertTrue($build instanceof Model\Base\BuildBase); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestBaseBuildDefaults() + { + $build = new Build(); + $this->assertEquals('#', $build->getCommitLink()); + $this->assertEquals('#', $build->getBranchLink()); + $this->assertEquals(null, $build->getFileLinkTemplate()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestIsSuccessful() + { + $build = new Build(); + $build->setStatus(Build::STATUS_NEW); + $this->assertFalse($build->isSuccessful()); + + $build->setStatus(Build::STATUS_RUNNING); + $this->assertFalse($build->isSuccessful()); + + $build->setStatus(Build::STATUS_FAILED); + $this->assertFalse($build->isSuccessful()); + + $build->setStatus(Build::STATUS_SUCCESS); + $this->assertTrue($build->isSuccessful()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestBuildExtra() + { + $info = array( + 'item1' => 'Item One', + 'item2' => 2, + ); + + $build = new Build(); + $build->setExtra(json_encode($info)); + + $this->assertEquals('Item One', $build->getExtra('item1')); + $this->assertEquals(2, $build->getExtra('item2')); + $this->assertNull($build->getExtra('item3')); + $this->assertEquals($info, $build->getExtra()); + } +} diff --git a/Tests/PHPCI/Model/ProjectTest.php b/Tests/PHPCI/Model/ProjectTest.php index 367eaa77..95cf933c 100644 --- a/Tests/PHPCI/Model/ProjectTest.php +++ b/Tests/PHPCI/Model/ProjectTest.php @@ -10,9 +10,10 @@ namespace PHPCI\Model\Tests; use PHPCI\Model\Project; +use PHPCI\Model; /** - * Unit tests for the ProjectService class. + * Unit tests for the Project model class. * @author Dan Cryer */ class ProjectTest extends \PHPUnit_Framework_TestCase @@ -21,6 +22,17 @@ class ProjectTest extends \PHPUnit_Framework_TestCase { } + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestIsAValidModel() + { + $project = new Project(); + $this->assertTrue($project instanceof \b8\Model); + $this->assertTrue($project instanceof Model); + $this->assertTrue($project instanceof Model\Base\ProjectBase); + } + /** * @covers PHPUnit::execute */ @@ -75,4 +87,23 @@ class ProjectTest extends \PHPUnit_Framework_TestCase $this->assertEquals('default', $project->getBranch()); } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_TestProjectAccessInformation() + { + $info = array( + 'item1' => 'Item One', + 'item2' => 2, + ); + + $project = new Project(); + $project->setAccessInformation(serialize($info)); + + $this->assertEquals('Item One', $project->getAccessInformation('item1')); + $this->assertEquals(2, $project->getAccessInformation('item2')); + $this->assertNull($project->getAccessInformation('item3')); + $this->assertEquals($info, $project->getAccessInformation()); + } } diff --git a/Tests/PHPCI/Service/BuildServiceTest.php b/Tests/PHPCI/Service/BuildServiceTest.php new file mode 100644 index 00000000..9b1097a3 --- /dev/null +++ b/Tests/PHPCI/Service/BuildServiceTest.php @@ -0,0 +1,148 @@ + + */ +class BuildServiceTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @var BuildService $testedService + */ + protected $testedService; + + /** + * @var \ $mockBuildStore + */ + protected $mockBuildStore; + + public function setUp() + { + $this->mockBuildStore = $this->getMock('PHPCI\Store\BuildStore'); + $this->mockBuildStore->expects($this->any()) + ->method('save') + ->will($this->returnArgument(0)); + + $this->testedService = new BuildService($this->mockBuildStore); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateBasicBuild() + { + $project = new Project(); + $project->setType('github'); + $project->setId(101); + + $returnValue = $this->testedService->createBuild($project); + + $this->assertEquals(101, $returnValue->getProjectId()); + $this->assertEquals(Build::STATUS_NEW, $returnValue->getStatus()); + $this->assertNull($returnValue->getStarted()); + $this->assertNull($returnValue->getFinished()); + $this->assertNull($returnValue->getLog()); + $this->assertNull($returnValue->getCommitMessage()); + $this->assertNull($returnValue->getCommitterEmail()); + $this->assertNull($returnValue->getExtra()); + $this->assertEquals('master', $returnValue->getBranch()); + $this->assertInstanceOf('DateTime', $returnValue->getCreated()); + $this->assertEquals('Manual', $returnValue->getCommitId()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateBuildWithOptions() + { + $project = new Project(); + $project->setType('hg'); + $project->setId(101); + + $returnValue = $this->testedService->createBuild($project, '123', 'testbranch', 'test@example.com', 'test'); + + $this->assertEquals('testbranch', $returnValue->getBranch()); + $this->assertEquals('123', $returnValue->getCommitId()); + $this->assertEquals('test', $returnValue->getCommitMessage()); + $this->assertEquals('test@example.com', $returnValue->getCommitterEmail()); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateBuildWithExtra() + { + $project = new Project(); + $project->setType('bitbucket'); + $project->setId(101); + + $returnValue = $this->testedService->createBuild($project, null, null, null, null, array('item1' => 1001)); + + $this->assertEquals(1001, $returnValue->getExtra('item1')); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_CreateDuplicateBuild() + { + $build = new Build(); + $build->setId(1); + $build->setProjectId(101); + $build->setCommitId('abcde'); + $build->setStatus(Build::STATUS_FAILED); + $build->setLog('Test'); + $build->setBranch('example_branch'); + $build->setStarted(new \DateTime()); + $build->setFinished(new \DateTime()); + $build->setCommitMessage('test'); + $build->setCommitterEmail('test@example.com'); + $build->setExtra(json_encode(array('item1' => 1001))); + + $returnValue = $this->testedService->createDuplicateBuild($build); + + $this->assertNotEquals($build->getId(), $returnValue->getId()); + $this->assertEquals($build->getProjectId(), $returnValue->getProjectId()); + $this->assertEquals($build->getCommitId(), $returnValue->getCommitId()); + $this->assertNotEquals($build->getStatus(), $returnValue->getStatus()); + $this->assertEquals(Build::STATUS_NEW, $returnValue->getStatus()); + $this->assertNull($returnValue->getLog()); + $this->assertEquals($build->getBranch(), $returnValue->getBranch()); + $this->assertNotEquals($build->getCreated(), $returnValue->getCreated()); + $this->assertNull($returnValue->getStarted()); + $this->assertNull($returnValue->getFinished()); + $this->assertEquals('test', $returnValue->getCommitMessage()); + $this->assertEquals('test@example.com', $returnValue->getCommitterEmail()); + $this->assertEquals($build->getExtra('item1'), $returnValue->getExtra('item1')); + } + + /** + * @covers PHPUnit::execute + */ + public function testExecute_DeleteBuild() + { + $store = $this->getMock('PHPCI\Store\BuildStore'); + $store->expects($this->once()) + ->method('delete') + ->will($this->returnValue(true)); + + $service = new BuildService($store); + $build = new Build(); + + $this->assertEquals(true, $service->deleteBuild($build)); + } +}