diff --git a/Mage/Command/AbstractCommand.php b/Mage/Command/AbstractCommand.php index 3ed5a9d..f69b0ef 100644 --- a/Mage/Command/AbstractCommand.php +++ b/Mage/Command/AbstractCommand.php @@ -31,7 +31,7 @@ abstract class AbstractCommand * @return integer exit code * @throws \Exception */ - public abstract function run(); + abstract public function run(); /** * Sets the Loaded Configuration. diff --git a/Mage/Command/BuiltIn/CompileCommand.php b/Mage/Command/BuiltIn/CompileCommand.php index 9cd94b6..ab92fd2 100644 --- a/Mage/Command/BuiltIn/CompileCommand.php +++ b/Mage/Command/BuiltIn/CompileCommand.php @@ -21,20 +21,43 @@ use Mage\Compiler; */ class CompileCommand extends AbstractCommand { + /** + * @var Compiler + */ + private $compiler; + + public function __construct(Compiler $compiler = null) + { + if ($compiler === null) { + $compiler = new Compiler(); + } + + $this->compiler = $compiler; + } + /** * @see \Mage\Compile::compile() */ public function run() { if (ini_get('phar.readonly')) { - Console::output('The php.ini variable phar.readonly must be Off.', 1, 2); + Console::output( + 'The php.ini variable phar.readonly' + . ' must be Off.', + 1, + 2 + ); + return 200; } - $compiler = new Compiler; - $compiler->compile(); + $this->compiler->compile(); - Console::output('mage.phar compiled successfully', 0, 2); + Console::output( + 'mage.phar compiled successfully', + 0, + 2 + ); return 0; } diff --git a/Mage/Command/BuiltIn/ListCommand.php b/Mage/Command/BuiltIn/ListCommand.php index 0d27862..30714b2 100644 --- a/Mage/Command/BuiltIn/ListCommand.php +++ b/Mage/Command/BuiltIn/ListCommand.php @@ -40,7 +40,7 @@ class ListCommand extends AbstractCommand $exitCode = $this->listEnvironments(); break; - default; + default: throw new Exception('The Type of Elements to List is needed.'); break; } diff --git a/Mage/Command/BuiltIn/LockCommand.php b/Mage/Command/BuiltIn/LockCommand.php index 6c2a59e..e39001d 100644 --- a/Mage/Command/BuiltIn/LockCommand.php +++ b/Mage/Command/BuiltIn/LockCommand.php @@ -35,14 +35,26 @@ class LockCommand extends AbstractCommand implements RequiresEnvironment $reason = Console::readInput(); $lockmsg = PHP_EOL; - if ($name) $lockmsg .= 'Locked by ' . $name . ' '; - if ($email) $lockmsg .= '(' . $email . ')'; - if ($reason) $lockmsg .= PHP_EOL . $reason . PHP_EOL; + if ($name) { + $lockmsg .= 'Locked by ' . $name . ' '; + } + if ($email) { + $lockmsg .= '(' . $email . ')'; + } + if ($reason) { + $lockmsg .= PHP_EOL . $reason . PHP_EOL; + } $lockFile = getcwd() . '/.mage/' . $this->getConfig()->getEnvironment() . '.lock'; file_put_contents($lockFile, 'Locked environment at date: ' . date('Y-m-d H:i:s') . $lockmsg); - Console::output('Locked deployment to ' . $this->getConfig()->getEnvironment() . ' environment', 1, 2); + Console::output( + 'Locked deployment to ' + . $this->getConfig()->getEnvironment() + . ' environment', + 1, + 2 + ); return 0; } diff --git a/Mage/Command/BuiltIn/UnlockCommand.php b/Mage/Command/BuiltIn/UnlockCommand.php index 462349c..1666e67 100644 --- a/Mage/Command/BuiltIn/UnlockCommand.php +++ b/Mage/Command/BuiltIn/UnlockCommand.php @@ -19,8 +19,7 @@ use Mage\Console; * * @author Andrés Montañez */ -class UnlockCommand - extends AbstractCommand implements RequiresEnvironment +class UnlockCommand extends AbstractCommand implements RequiresEnvironment { /** * Unlocks an Environment @@ -33,7 +32,12 @@ class UnlockCommand @unlink($lockFile); } - Console::output('Unlocked deployment to ' . $this->getConfig()->getEnvironment() . ' environment', 1, 2); + Console::output( + 'Unlocked deployment to ' + . $this->getConfig()->getEnvironment() . ' environment', + 1, + 2 + ); return 0; } diff --git a/composer.json b/composer.json index 6f15d6c..a8b9d75 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ }, "require-dev": { "phpunit/phpunit": "4.3.5", - "satooshi/php-coveralls": ">=0.6.1" + "satooshi/php-coveralls": ">=0.6.1", + "malkusch/php-mock": "dev-php-5.3" }, "autoload": { "psr-4": { @@ -19,6 +20,11 @@ "Command\\": [".mage/tasks", "../../../.mage/commands"] } }, + "autoload-dev": { + "psr-4": { + "MageTest\\": "./tests/MageTest" + } + }, "config": { "bin-dir": "bin" }, diff --git a/composer.lock b/composer.lock index 312d535..7d89680 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "d82ccc62c52f99a0819bea9b247d4f86", + "hash": "caa09089c7b57461ed42e97a4449f2c6", "packages": [], "packages-dev": [ { @@ -153,6 +153,54 @@ ], "time": "2014-08-11 04:32:36" }, + { + "name": "malkusch/php-mock", + "version": "dev-php-5.3", + "source": { + "type": "git", + "url": "https://github.com/malkusch/php-mock.git", + "reference": "37b301b4b479601232f3919920451c6e777c3264" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/malkusch/php-mock/zipball/37b301b4b479601232f3919920451c6e777c3264", + "reference": "37b301b4b479601232f3919920451c6e777c3264", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": ">=4" + }, + "type": "library", + "autoload": { + "psr-4": { + "malkusch\\phpmock\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL-2.0" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Mock non deterministic built-in PHP functions (e.g. time() or rand())", + "homepage": "https://github.com/malkusch/php-mock", + "keywords": [ + "function", + "mock", + "stub", + "test" + ], + "time": "2014-12-01 18:01:18" + }, { "name": "phpunit/php-code-coverage", "version": "2.0.13", @@ -1208,7 +1256,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "malkusch/php-mock": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/tests/MageTest/Command/AbstractCommandTest.php b/tests/MageTest/Command/AbstractCommandTest.php new file mode 100644 index 0000000..fed7df5 --- /dev/null +++ b/tests/MageTest/Command/AbstractCommandTest.php @@ -0,0 +1,47 @@ + + * @coversDefaultClass Mage\Command\AbstractCommand + */ +class AbstractCommandTest extends BaseTest +{ + /** + * @var AbstractCommand|PHPUnit_Framework_MockObject_MockObject + */ + private $abstractCommand; + + /** + * @before + */ + public function before() + { + $this->abstractCommand = $this->getMockForAbstractClass('Mage\Command\AbstractCommand'); + } + + /** + * @covers ::setConfig + */ + public function testSetConfig() + { + $configMock = $this->getMock('Mage\Config'); + $this->doTestSetter($this->abstractCommand, 'config', $configMock); + } + + /** + * @covers ::getConfig + */ + public function testGetConfig() + { + $configMock = $this->getMock('Mage\Config'); + $this->doTestGetter($this->abstractCommand, 'config', $configMock); + } +} diff --git a/tests/MageTest/Command/BuiltIn/CompileCommandTest.php b/tests/MageTest/Command/BuiltIn/CompileCommandTest.php new file mode 100644 index 0000000..44b4486 --- /dev/null +++ b/tests/MageTest/Command/BuiltIn/CompileCommandTest.php @@ -0,0 +1,108 @@ +iniGetValue = new FixedValueFunction(); + $mockBuilder = new MockBuilder(); + $iniGetMock = $mockBuilder->setNamespace('Mage\Command\BuiltIn') + ->setName("ini_get") + ->setCallableProvider($this->iniGetValue) + ->build(); + $iniGetMock->disable(); + $iniGetMock->enable(); + + $this->setUpConsoleStatics(); + } + + /** + * @covers ::__construct + */ + public function testConstruct() + { + $compilerMock = $this->getMock('Mage\Compiler'); + $compileCommand = new CompileCommand($compilerMock); + + $compilerProperty = $this->getPropertyValue($compileCommand, 'compiler'); + + $this->assertInstanceOf('Mage\Compiler', $compilerProperty); + $this->assertSame($compilerMock, $compilerProperty); + } + + /** + * @covers ::__construct + */ + public function testConstructWithNoParams() + { + $compileCommand = new CompileCommand(); + $compilerProperty = $this->getPropertyValue($compileCommand, 'compiler'); + + $this->assertInstanceOf('Mage\Compiler', $compilerProperty); + } + + /** + * @covers ::__construct + * @covers ::run + */ + public function testRun() + { + $expectedOutput = "mage.phar compiled successfully\n\n"; + $expectedExitCode = 0; + $this->expectOutputString($expectedOutput); + + $this->iniGetValue->setValue(false); + + $compilerMock = $this->getMock('Mage\Compiler'); + $compilerMock->expects($this->once()) + ->method('compile'); + $compileCommand = new CompileCommand($compilerMock); + + $actualExitCode = $compileCommand->run(); + + $this->assertEquals($expectedExitCode, $actualExitCode); + } + + /** + * @covers ::__construct + * @covers ::run + */ + public function testRunWhenPharReadonlyEnabled() + { + $expectedOutput = "\tThe php.ini variable phar.readonly must be Off.\n\n"; + $expectedExitCode = 200; + $this->expectOutputString($expectedOutput); + $this->iniGetValue->setValue(true); + + $compilerMock = $this->getMock('Mage\Compiler'); + $compileCommand = new CompileCommand($compilerMock); + $actualExitCode = $compileCommand->run(); + + $this->assertEquals($expectedExitCode, $actualExitCode); + } +} diff --git a/tests/MageTest/Command/BuiltIn/ListCommandTest.php b/tests/MageTest/Command/BuiltIn/ListCommandTest.php new file mode 100644 index 0000000..4d193e1 --- /dev/null +++ b/tests/MageTest/Command/BuiltIn/ListCommandTest.php @@ -0,0 +1,141 @@ +listCommand = new ListCommand(); + + $this->scandirValueObj = new FixedValueFunction(); + $mockBuilder = new MockBuilder(); + $scandirMock = $mockBuilder->setNamespace('Mage\Command\BuiltIn') + ->setName("scandir") + ->setCallableProvider($this->scandirValueObj) + ->build(); + $scandirMock->disable(); + $scandirMock->enable(); + + $this->setUpConsoleStatics(); + } + + public function listEnvironmentsProvider() + { + return array( + 'normal' => array( + 'environmentFiles' => array( + 'rc.yml', + 'production.yml', + 'local.yml' + ), + 'expectedOutput' => "\tThese are your configured environments:\n" + . "\t\t* local\n" + . "\t\t* production\n" + . "\t\t* rc\n" + . "\t\n", + 'expectedExitCode' => 0 + ), + 'with_missing_yml_files' => array( + 'environmentFiles' => array( + 'rc', + 'production.yml' + ), + 'expectedOutput' => "\tThese are your configured environments:\n" + . "\t\t* production\n" + . "\t\n", + 'expectedExitCode' => 0 + ), + 'with_no_yml_configs' => array( + 'environmentFiles' => array( + 'rc.ini', + 'production.txt' + ), + 'expectedOutput' => "\tYou don't have any environment configured.\n\n", + 'expectedExitCode' => 220 + ), + 'with_no_configs' => array( + 'environmentFiles' => array(), + 'expectedOutput' => "\tYou don't have any environment configured.\n\n", + 'expectedExitCode' => 220 + ) + ); + } + + /** + * @covers ::run + * @covers ::listEnvironments + * @dataProvider listEnvironmentsProvider + */ + public function testListEnvironment($environmentFiles, $expectedOutput, $expectedExitCode) + { + $this->expectOutputString($expectedOutput); + + $this->scandirValueObj->setValue($environmentFiles); + $this->mockInputArgument('environments'); + + $actualExitCode = $this->listCommand->run(); + $this->assertEquals($expectedExitCode, $actualExitCode); + } + + /** + * @covers ::run + */ + public function testRunWithInvalidCommand() + { + $expectedOutput = "\tThe Type of Elements to List is needed.\n\n"; + $this->expectOutputString($expectedOutput); + + $this->mockInputArgument('abc'); + + $expectedExitCode = 221; + $actualExitCode = $this->listCommand->run(); + $this->assertEquals($expectedExitCode, $actualExitCode); + } + + /** + * Stub Config::getArgument to return desired value + * + * @param String $argumentValue Input argument + */ + private function mockInputArgument($argumentValue) + { + $configMock = $this->getMock('Mage\Config'); + $configMock->expects($this->once()) + ->method('getArgument') + ->with(1) + ->willReturn($argumentValue); + + $this->listCommand->setConfig($configMock); + } +} diff --git a/tests/MageTest/Command/BuiltIn/LockCommandTest.php b/tests/MageTest/Command/BuiltIn/LockCommandTest.php new file mode 100644 index 0000000..e56b63f --- /dev/null +++ b/tests/MageTest/Command/BuiltIn/LockCommandTest.php @@ -0,0 +1,205 @@ +lockCommand = new LockCommand(); + + $mockBuilder = new MockBuilder(); + $fopenMock = $mockBuilder + ->setName('fopen') + ->setNamespace('Mage') + ->setFunction(function () { + return 'a'; + }) + ->build(); + + $this->fgetsValue = new FixedValueFunction(); + $fgetsMock = $mockBuilder + ->setNamespace('Mage') + ->setName('fgets') + ->setFunction( + function () { + switch (LockCommandTest::$fgetsCount) { + case 0: + LockCommandTest::$fgetsCount++; + return LockCommandTest::$mockName; + case 1: + LockCommandTest::$fgetsCount++; + return LockCommandTest::$mockEmail; + case 2: + LockCommandTest::$fgetsCount++; + return LockCommandTest::$mockDesc; + default: + throw new \Exception('"fgets" count limit exceed'); + } + } + ) + ->build(); + $getCwdMock = $mockBuilder + ->setNamespace('Mage\Command\Builtin') + ->setName('getcwd') + ->setFunction( + function () { + return ''; + } + ) + ->build(); + $fileGetContentsMock = $mockBuilder + ->setNamespace('Mage\Command\Builtin') + ->setName('file_put_contents') + ->setFunction( + function ($file, $contents) { + LockCommandTest::$filePutContentsFile = $file; + LockCommandTest::$filePutContentsResult = $contents; + } + ) + ->build(); + + $dateMock = $mockBuilder + ->setNamespace('Mage\Command\BuiltIn') + ->setName('date') + ->setFunction( + function () { + return '2015-01-01 12:00:00'; + } + ) + ->build(); + + $fopenMock->disable(); + $fgetsMock->disable(); + $getCwdMock->disable(); + $fileGetContentsMock->disable(); + $dateMock->disable(); + + $fopenMock->enable(); + $fgetsMock->enable(); + $getCwdMock->enable(); + $fileGetContentsMock->enable(); + $dateMock->enable(); + + $this->setUpConsoleStatics(); + } + + public function lockCommandProvider() + { + return array( + 'normal' => array( + 'name' => 'John Smith', + 'email' => 'john.smith@example.com', + 'description' => "There's a critical bug here!", + 'expectedLockFileContents' => "Locked environment at date: 2015-01-01 12:00:00\n" + . "Locked by John Smith (john.smith@example.com)\n" + . "There's a critical bug here!\n", + ), + 'with_no_name' => array( + 'name' => '', + 'email' => 'john.smith@example.com', + 'description' => "There's a critical bug here!", + 'expectedLockFileContents' => "Locked environment at date: 2015-01-01 12:00:00\n" + . "(john.smith@example.com)\n" + . "There's a critical bug here!\n", + ), + 'with_no_email' => array( + 'name' => 'John Smith', + 'email' => '', + 'description' => "There's a critical bug here!", + 'expectedLockFileContents' => "Locked environment at date: 2015-01-01 12:00:00\n" + . "Locked by John Smith \n" + . "There's a critical bug here!\n", + ), + 'with_no_name_nor_email' => array( + 'name' => '', + 'email' => '', + 'description' => "There's a critical bug here!", + 'expectedLockFileContents' => "Locked environment at date: 2015-01-01 12:00:00\n" + . "\n" + . "There's a critical bug here!\n", + ), + 'with_no_desciption' => array( + 'name' => 'John Smith', + 'email' => 'john.smith@example.com', + 'description' => '', + 'expectedLockFileContents' => "Locked environment at date: 2015-01-01 12:00:00\n" + . "Locked by John Smith (john.smith@example.com)" + ), + ); + } + + /** + * @covers ::run + * @dataProvider lockCommandProvider + */ + public function testRun($name, $email, $description, $expectedLockFileContents) + { + $expectedOutput = "Your name (enter to leave blank): " + . "Your email (enter to leave blank): " + . "Reason of lock (enter to leave blank): " + . "\tLocked deployment to production environment\n\n"; + $this->expectOutputString($expectedOutput); + $expectedLockFilePath = '/.mage/production.lock'; + $expectedExitCode = 0; + + self::$mockName = $name; + self::$mockEmail = $email; + self::$mockDesc = $description; + + $configMock = $this->getMock('Mage\Config'); + $configMock->expects($this->atLeastOnce()) + ->method('getEnvironment') + ->willReturn('production'); + $this->lockCommand->setConfig($configMock); + + $actualExitCode = $this->lockCommand->run(); + + $this->assertEquals($expectedExitCode, $actualExitCode); + $this->assertEquals($expectedLockFileContents, self::$filePutContentsResult); + $this->assertEquals($expectedLockFilePath, self::$filePutContentsFile); + } +} diff --git a/tests/MageTest/Command/BuiltIn/UnlockCommandTest.php b/tests/MageTest/Command/BuiltIn/UnlockCommandTest.php new file mode 100644 index 0000000..1732811 --- /dev/null +++ b/tests/MageTest/Command/BuiltIn/UnlockCommandTest.php @@ -0,0 +1,119 @@ + array( + 'file_exists' => true, + ), + 'file_not_exists' => array( + 'file_exsits' => false + ) + ); + } + + /** + * @before + */ + public function before() + { + $this->unlockCommand = new UnlockCommand(); + + self::$isUnlinkCalled = false; + self::$fileExistsResult = false; + self::$isFileExists = false; + + $mockBuilder = new MockBuilder(); + $fileExistsMock = $mockBuilder + ->setName('file_exists') + ->setNamespace('Mage\Command\BuiltIn') + ->setFunction( + function ($filePath) { + UnlockCommandTest::$fileExistsResult = $filePath; + return UnlockCommandTest::$isFileExists; + } + ) + ->build(); + $unlinkMock = $mockBuilder + ->setName('unlink') + ->setNamespace('Mage\Command\BuiltIn') + ->setFunction( + function () { + UnlockCommandTest::$isUnlinkCalled = true; + } + ) + ->build(); + $getCwdMock = $mockBuilder + ->setNamespace('Mage\Command\BuiltIn') + ->setName('getcwd') + ->setFunction( + function () { + return ''; + } + ) + ->build(); + + $fileExistsMock->disable(); + $unlinkMock->disable(); + $getCwdMock->disable(); + + $fileExistsMock->enable(); + $unlinkMock->enable(); + $getCwdMock->enable(); + + $configMock = $this->getMock('Mage\Config'); + $configMock->expects($this->atLeastOnce()) + ->method('getEnvironment') + ->willReturn('production'); + $this->unlockCommand->setConfig($configMock); + + $this->setUpConsoleStatics(); + } + + /** + * @covers ::run + * @dataProvider runProvider + */ + public function testRun($fileExists) + { + $expectedOutput = "\tUnlocked deployment to production environment\n\n"; + $this->expectOutputString($expectedOutput); + $expectedLockFilePath = '/.mage/production.lock'; + + self::$isFileExists = $fileExists; + + $actualExitCode = $this->unlockCommand->run(); + $expectedExitCode = 0; + + $this->assertEquals(self::$isUnlinkCalled, $fileExists); + $this->assertEquals($expectedExitCode, $actualExitCode); + $this->assertEquals($expectedLockFilePath, self::$fileExistsResult); + } +} diff --git a/tests/MageTest/TestHelper/BaseTest.php b/tests/MageTest/TestHelper/BaseTest.php new file mode 100644 index 0000000..d8db4dc --- /dev/null +++ b/tests/MageTest/TestHelper/BaseTest.php @@ -0,0 +1,123 @@ + + */ +abstract class BaseTest extends \PHPUnit_Framework_TestCase +{ + /** + * Returns value of non-public property from given class + * + * @param string|object $object Object instance or class name + * @param string $propertyName Class' or object's property name + * @return mixed + */ + final protected function getPropertyValue($object, $propertyName) + { + $configProperty = new \ReflectionProperty($object, $propertyName); + $configProperty->setAccessible(true); + + return $configProperty->getValue($object); + } + + /** + * Sets value to given property and given object + * + * @param object $object Object instance + * @param string $propertyName Property name + * @param mixed $value Value to set + */ + final protected function setPropertyValue($object, $propertyName, $value) + { + $configProperty = new \ReflectionProperty($object, $propertyName); + $configProperty->setAccessible(true); + $configProperty->setValue($object, $value); + } + + /** + * Disable logging to log file and turn off colors + * + * @before + */ + protected function setUpConsoleStatics() + { + $consoleReflection = new \ReflectionClass('Mage\Console'); + $logEnableProperty = $consoleReflection->getProperty('logEnabled'); + $logEnableProperty->setAccessible(true); + $logEnableProperty->setValue(false); + + $configMock = $this->getMock('Mage\Config'); + $configMock->expects($this->any()) + ->method('getParameter') + ->with('no-color') + ->willReturn(true); + + $configProperty = $consoleReflection->getProperty('config'); + $configProperty->setAccessible(true); + $configProperty->setValue($configMock); + } + + /** + * Tests getter of given object for given property name and example value + * + * @param object $object Object instance + * @param string $propertyName Property name + * @param mixed $propertyValue Value to set + */ + final protected function doTestGetter($object, $propertyName, $propertyValue) + { + $this->setPropertyValue($object, $propertyName, $propertyValue); + $getterName = $this->getGetterName($propertyName); + + $actual = $object->$getterName(); + + $this->assertSame($propertyValue, $actual); + } + + /** + * Tests setter of given object for given property name and example value + * + * @param object $object Object instance + * @param string $propertyName Property name + * @param mixed $propertyValue Value to set + */ + final protected function doTestSetter($object, $propertyName, $propertyValue) + { + $setterName = $this->getSetterName($propertyName); + $object->$setterName($propertyValue); + + $actual = $this->getPropertyValue($object, $propertyName); + $this->assertSame($propertyValue, $actual); + } + + /** + * Returns the conventional getter name for given property name + * + * @param string $propertyName Property name + * @return string Getter method name + */ + private function getGetterName($propertyName) + { + return 'get' . ucfirst($propertyName); + } + + /** + * Returns the conventional setter name for given property name + * + * @param string $propertyName Property name + * @return string Getter method name + */ + private function getSetterName($propertyName) + { + return 'set' . ucfirst($propertyName); + } +}