From 7a74716d4062c3bdcddc758997179f47b6740eda Mon Sep 17 00:00:00 2001 From: Anthonius Munthi Date: Sat, 17 Nov 2018 22:04:12 +0800 Subject: [PATCH] refactored plugin to make it more configurable by modules ref yawik/standard#4 --- src/AssetProviderInterface.php | 18 + src/AssetsInstaller.php | 129 +++--- src/Event/ActivateEvent.php | 52 +++ src/Event/ConfigureEvent.php | 54 +++ src/LogTrait.php | 57 ++- src/PermissionsFixer.php | 113 +++-- src/PermissionsFixerModuleInterface.php | 35 ++ src/Plugin.php | 171 ++++---- test/AssetsInstallerTest.php | 418 +++++++++++------- test/Event/ActivateEventTest.php | 36 ++ test/Event/ConfigureEventTest.php | 26 ++ test/LogTraitTest.php | 196 +++++++++ test/PermissionsFixerTest.php | 158 +++++-- test/PluginTest.php | 434 ++++++++----------- test/TestAssetInstaller.php | 20 + test/TestOutputTrait.php | 46 ++ test/TestPermissionFixer.php | 25 ++ test/TestPlugin.php | 53 +++ test/fixtures/{public1/foo.js => foo/bar.js} | 0 test/fixtures/hello/world.js | 0 test/sandbox/config/config.php | 1 + test/sandbox/src/Module.php | 26 ++ 22 files changed, 1395 insertions(+), 673 deletions(-) create mode 100644 src/AssetProviderInterface.php create mode 100644 src/Event/ActivateEvent.php create mode 100644 src/Event/ConfigureEvent.php create mode 100644 src/PermissionsFixerModuleInterface.php create mode 100644 test/Event/ActivateEventTest.php create mode 100644 test/Event/ConfigureEventTest.php create mode 100644 test/LogTraitTest.php create mode 100644 test/TestAssetInstaller.php create mode 100644 test/TestOutputTrait.php create mode 100644 test/TestPermissionFixer.php create mode 100644 test/TestPlugin.php rename test/fixtures/{public1/foo.js => foo/bar.js} (100%) create mode 100644 test/fixtures/hello/world.js create mode 100644 test/sandbox/src/Module.php diff --git a/src/AssetProviderInterface.php b/src/AssetProviderInterface.php new file mode 100644 index 0000000..d8e8ef4 --- /dev/null +++ b/src/AssetProviderInterface.php @@ -0,0 +1,18 @@ + * @TODO Create more documentation for methods */ -class AssetsInstaller +class AssetsInstaller implements EventSubscriberInterface { use LogTrait; @@ -37,25 +34,45 @@ class AssetsInstaller /** * @var Filesystem */ - private $filesystem; + protected $filesystem; public function __construct() { - // @codeCoverageIgnoreStart - if (!class_exists('Core\\Application')) { - include __DIR__.'/../../../autoload.php'; - } - // @codeCoverageIgnoreEnd - - umask(0000); - $this->filesystem = new Filesystem(); - $this->input = new StringInput(''); - $this->output = new ConsoleOutput(OutputInterface::VERBOSITY_NORMAL); + $this->filesystem = new Filesystem(); } - public function setFilesystem(Filesystem $filesystem) + public static function getSubscribedEvents() { - $this->filesystem = $filesystem; + return [ + Plugin::YAWIK_ACTIVATE_EVENT => 'onActivateEvent', + Plugin::YAWIK_CONFIGURE_EVENT => 'onConfigureEvent' + ]; + } + + public function onConfigureEvent(ConfigureEvent $event) + { + $moduleAssets = array(); + $modules = $event->getModules(); + + foreach ($modules as $module) { + $className = get_class($module); + $moduleName = substr($className, 0, strpos($className, '\\')); + $r = new \ReflectionObject($module); + $file = $r->getFileName(); + $dir = null; + if ($module instanceof AssetProviderInterface) { + $dir = $module->getPublicDir(); + } else { + $testDir = substr($file, 0, stripos($file, 'src'.DIRECTORY_SEPARATOR.'Module.php')).'/public'; + if (is_dir($testDir)) { + $dir = $testDir; + } + } + if (is_dir($dir)) { + $moduleAssets[$moduleName] = realpath($dir); + } + } + $this->install($moduleAssets); } /** @@ -67,14 +84,13 @@ public function setFilesystem(Filesystem $filesystem) * @param array $modules An array of modules * @param string $expectedMethod Expected install method */ - public function install($modules, $expectedMethod = self::METHOD_RELATIVE_SYMLINK) + public function install($modules, $expectedMethod = null) { $publicDir = $this->getModuleAssetDir(); - $loadedModules = $this->scanInstalledModules(); - $modules = array_merge($loadedModules, $modules); $rows = []; $exitCode = 0; $copyUsed = false; + $expectedMethod = is_null($expectedMethod) ? self::METHOD_RELATIVE_SYMLINK:$expectedMethod; foreach ($modules as $name => $originDir) { $targetDir = $publicDir.DIRECTORY_SEPARATOR.$name; @@ -100,10 +116,10 @@ public function install($modules, $expectedMethod = self::METHOD_RELATIVE_SYMLIN } else { $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'WARNING' : '!'), $message, $method); } - } catch (\Exception $e) { // @codeCoverageIgnoreStart + } catch (\Exception $e) { $exitCode = 1; $rows[] = array(sprintf('%s', '\\' === DIRECTORY_SEPARATOR ? 'ERROR' : "\xE2\x9C\x98" /* HEAVY BALLOT X (U+2718) */), $message, $e->getMessage()); - }// @codeCoverageIgnoreEnd + } } // render this output only on cli environment @@ -149,7 +165,7 @@ public function getRootDir() public function renderInstallOutput($copyUsed, $rows, $exitCode) { - $io = new SymfonyStyle($this->input, $this->output); + $io = new SymfonyStyle($this->getInput(), $this->getOutput()); $io->newLine(); $io->section('Yawik Assets Installed!'); @@ -168,61 +184,20 @@ public function renderInstallOutput($copyUsed, $rows, $exitCode) } } - private function scanInstalledModules() - { - // @codeCoverageIgnoreStart - if (is_file($file = __DIR__.'/../../../autoload.php')) { - include $file; - } - // @codeCoverageIgnoreEnd - - /* @var \Zend\ModuleManager\ModuleManager $manager */ - $app = Application::init(); - $manager = $app->getServiceManager()->get('ModuleManager'); - $modules = $manager->getLoadedModules(true); - $moduleAssets = array(); - - foreach ($modules as $module) { - try { - $className = get_class($module); - $moduleName = substr($className, 0, strpos($className, '\\')); - $r = new \ReflectionClass($className); - $file = $r->getFileName(); - $dir = null; - if ($module instanceof AssetProviderInterface) { - $dir = $module->getPublicDir(); - } else { - $testDir = substr($file, 0, stripos($file, 'src'.DIRECTORY_SEPARATOR.'Module.php')).'/public'; - if (is_dir($testDir)) { - $dir = $testDir; - } - } - if (is_dir($dir)) { - $moduleAssets[$moduleName] = realpath($dir); - } - } catch (\Exception $e) { // @codeCoverageIgnore - $this->logError($e->getMessage()); // @codeCoverageIgnore - } // @codeCoverageIgnore - } - - return $moduleAssets; - } - /** * Try to create absolute symlink. * * Falling back to hard copy. */ - private function absoluteSymlinkWithFallback($originDir, $targetDir) + public function absoluteSymlinkWithFallback($originDir, $targetDir) { try { $this->symlink($originDir, $targetDir); $method = self::METHOD_ABSOLUTE_SYMLINK; - } catch (\Exception $e) { // @codeCoverageIgnore + } catch (\Exception $e) { // fall back to copy - $method = $this->hardCopy($originDir, $targetDir); // @codeCoverageIgnore - } // @codeCoverageIgnore - + $method = $this->hardCopy($originDir, $targetDir); + } return $method; } @@ -231,17 +206,14 @@ private function absoluteSymlinkWithFallback($originDir, $targetDir) * * Falling back to absolute symlink and finally hard copy. */ - private function relativeSymlinkWithFallback($originDir, $targetDir) + public function relativeSymlinkWithFallback($originDir, $targetDir) { try { $this->symlink($originDir, $targetDir, true); $method = self::METHOD_RELATIVE_SYMLINK; - } - // @codeCoverageIgnoreStart - catch (\Exception $e) { + } catch (\Exception $e) { $method = $this->absoluteSymlinkWithFallback($originDir, $targetDir); } - // @codeCoverageIgnoreEnd return $method; } @@ -251,14 +223,14 @@ private function relativeSymlinkWithFallback($originDir, $targetDir) * * @throws \Exception if link can not be created */ - private function symlink($originDir, $targetDir, $relative = false) + public function symlink($originDir, $targetDir, $relative = false) { if ($relative) { $this->filesystem->mkdir(dirname($targetDir)); $originDir = $this->filesystem->makePathRelative($originDir, realpath(dirname($targetDir))); } $this->filesystem->symlink($originDir, $targetDir); - // @codeCoverageIgnoreStart + if (!file_exists($targetDir)) { throw new \Exception( sprintf('Symbolic link "%s" was created but appears to be broken.', $targetDir), @@ -266,13 +238,12 @@ private function symlink($originDir, $targetDir, $relative = false) null ); } - // @codeCoverageIgnoreEnd } /** * Copies origin to target. */ - private function hardCopy($originDir, $targetDir) + public function hardCopy($originDir, $targetDir) { $this->filesystem->mkdir($targetDir, 0777); // We use a custom iterator to ignore VCS files diff --git a/src/Event/ActivateEvent.php b/src/Event/ActivateEvent.php new file mode 100644 index 0000000..25468f7 --- /dev/null +++ b/src/Event/ActivateEvent.php @@ -0,0 +1,52 @@ +output = $output; + $this->composer = $composer; + + parent::__construct(Plugin::YAWIK_ACTIVATE_EVENT); + } + + /** + * @return IOInterface + */ + public function getOutput() + { + return $this->output; + } + + /** + * @return Composer + */ + public function getComposer() + { + return $this->composer; + } +} diff --git a/src/Event/ConfigureEvent.php b/src/Event/ConfigureEvent.php new file mode 100644 index 0000000..f9ea772 --- /dev/null +++ b/src/Event/ConfigureEvent.php @@ -0,0 +1,54 @@ +modules = $modules; + $this->options = $options; + parent::__construct(Plugin::YAWIK_CONFIGURE_EVENT); + } + + /** + * @return array + */ + public function getModules() + { + return $this->modules; + } + + /** + * @return CoreOptions + */ + public function getOptions() + { + return $this->options; + } +} diff --git a/src/LogTrait.php b/src/LogTrait.php index 739bfd4..14c6bdf 100644 --- a/src/LogTrait.php +++ b/src/LogTrait.php @@ -14,9 +14,11 @@ use Composer\IO\IOInterface; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; +use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputInterface; +use Yawik\Composer\Event\ActivateEvent; trait LogTrait { @@ -36,20 +38,31 @@ trait LogTrait */ private $logger; + public function onActivateEvent(ActivateEvent $event) + { + $this->setOutputFromComposerIO($event->getOutput()); + } + public function setOutputFromComposerIO(IOInterface $output) { - if (!is_null($output)) { - $level = OutputInterface::VERBOSITY_NORMAL; + $level = OutputInterface::VERBOSITY_NORMAL; - if ($output->isVeryVerbose() || $output->isDebug()) { - $level = OutputInterface::VERBOSITY_VERY_VERBOSE; - } elseif ($output->isVerbose()) { - $level = OutputInterface::VERBOSITY_VERBOSE; - } + if ($output->isVeryVerbose() || $output->isDebug()) { + $level = OutputInterface::VERBOSITY_VERY_VERBOSE; + } elseif ($output->isVerbose()) { + $level = OutputInterface::VERBOSITY_VERBOSE; + } + + $this->getOutput()->setVerbosity($level); + $this->getOutput()->setDecorated($output->isDecorated()); + } - $this->getOutput()->setVerbosity($level); - $this->getOutput()->setDecorated($output->isDecorated()); + public function getInput() + { + if (is_null($this->input)) { + $this->input = new StringInput(''); } + return $this->input; } /** @@ -99,25 +112,26 @@ public function setLogger(LoggerInterface $logger) /** * @param string $message */ - public function logDebug($message) + public function logDebug($message, $context = 'yawik') { - $this->doLog(LogLevel::DEBUG, $message); + $this->doLog(LogLevel::DEBUG, $message, $context); } /** * @param string $message */ - public function logError($message) + public function logError($message, $context = 'yawik') { - $this->doLog(LogLevel::ERROR, $message); + $this->doLog(LogLevel::ERROR, $message, $context); } /** * @param string $message + * @param string $context */ - public function log($message) + public function log($message, $context = 'yawik') { - $this->doLog(LogLevel::INFO, $message); + $this->doLog(LogLevel::INFO, $message, $context); } public function isCli() @@ -125,11 +139,11 @@ public function isCli() return php_sapi_name() === 'cli'; } - public function doLog($level, $message) + public function doLog($level, $message, $context = 'yawik') { $message = str_replace(getcwd().DIRECTORY_SEPARATOR, '', $message); if (is_object($this->logger)) { - $this->logger->log($level, $message); + $this->logger->log($level, $message, [$context]); } if ($this->isCli()) { switch ($level) { @@ -145,16 +159,17 @@ public function doLog($level, $message) $outputLevel = OutputInterface::OUTPUT_NORMAL; break; } - $this->doWrite($message, $outputLevel); + $this->doWrite($message, $outputLevel, $context); } } - public function doWrite($message, $outputLevel = 0) + public function doWrite($message, $outputLevel = 0, $context = 'yawik') { $message = sprintf( - '[yawik] %s', + '[%s] %s', + $context, $message ); - $this->output->writeln($message, $outputLevel); + $this->getOutput()->writeln($message, $outputLevel); } } diff --git a/src/PermissionsFixer.php b/src/PermissionsFixer.php index 5c8df5e..ed8bf85 100644 --- a/src/PermissionsFixer.php +++ b/src/PermissionsFixer.php @@ -9,9 +9,11 @@ namespace Yawik\Composer; +use Composer\EventDispatcher\EventSubscriberInterface; use Core\Application; use Core\Options\ModuleOptions as CoreOptions; use Symfony\Component\Filesystem\Filesystem; +use Yawik\Composer\Event\ConfigureEvent; /** * Class PermissionsFixer @@ -19,81 +21,102 @@ * @author Anthonius Munthi * @since 0.32.0 */ -class PermissionsFixer +class PermissionsFixer implements EventSubscriberInterface { use LogTrait; /** * @var Filesystem */ - private $filesystem; + protected $filesystem; public function __construct() { $this->filesystem = new Filesystem(); } - /** - * @param Filesystem $filesystem - * @return PermissionsFixer - */ - public function setFilesystem($filesystem) + public static function getSubscribedEvents() { - $this->filesystem = $filesystem; - return $this; + return [ + Plugin::YAWIK_ACTIVATE_EVENT => 'onActivateEvent', + Plugin::YAWIK_CONFIGURE_EVENT => 'onConfigureEvent' + ]; } - /** - * - */ - public function fix() + public function onConfigureEvent(ConfigureEvent $event) { - /* @var CoreOptions $options */ - $app = Application::init(); - $options = $app->getServiceManager()->get('Core/Options'); + $modules = $event->getModules(); + $files = []; + $directories = []; - $logDir = $options->getLogDir(); - $cacheDir = $options->getCacheDir(); - $configDir = realpath(Application::getConfigDir()); + foreach ($modules as $module) { + if ($module instanceof PermissionsFixerModuleInterface) { + $modDirLists = $module->getDirectoryPermissionLists(); + $modFileLists = $module->getFilePermissionLists(); + if (is_array($modDirLists)) { + $directories = array_merge($directories, $modDirLists); + } else { + $this->logError(sprintf( + '%s::getDirectoryPermissionList() should return an array.', + get_class($module) + )); + } - $dirs = [ - $configDir.'/autoload', - $cacheDir, - $logDir, - $logDir.'/tracy', - ]; - foreach ($dirs as $dir) { - try { - if (!is_dir($dir)) { - $this->mkdir($dir); + if (is_array($modFileLists)) { + $files = array_merge($files, $modFileLists); + } else { + $this->logError(sprintf( + '%s::getFilePermissionList() should return an array.', + get_class($module) + )); } - $this->chmod($dir); - } catch (\Exception $exception) { - $this->logError($exception->getMessage()); } } - if (!is_file($logFile = $logDir.'/yawik.log')) { - touch($logFile); + foreach ($directories as $directory) { + if (!is_dir($directory)) { + $this->mkdir($directory); + } + $this->chmod($directory); + } + + foreach ($files as $file) { + if (!is_file($file)) { + $this->touch($file); + } + $this->chmod($file, 0666); + } + } + + public function touch($file) + { + try { + $this->filesystem->touch($file); + $this->log('created '.$file.'', 'touch'); + } catch (\Exception $exception) { + $this->logError($exception->getMessage(), 'touch'); } - $this->chmod($logFile, 0666); } - private function chmod($dir, $mode = 0777) + public function chmod($dir, $mode = 0777) { - if (is_dir($dir) || is_file($dir)) { + try { $this->filesystem->chmod($dir, $mode); - $this->log(sprintf( - 'chmod: %s with %s', - $dir, - decoct(fileperms($dir) & 0777) - )); + $fileperms = decoct(@fileperms($dir) & 0777); + $message = sprintf('%s with %s', $dir, $fileperms); + $this->log($message, 'chmod'); + } catch (\Exception $exception) { + $this->logError($exception->getMessage(), 'chmod'); } } - private function mkdir($dir) + public function mkdir($dir, $mode = 0777) { - $this->filesystem->mkdir($dir, 0777); - $this->log(sprintf('mkdir: %s', $dir)); + try { + $this->filesystem->mkdir($dir, $mode); + $this->log(sprintf('%s', $dir), 'mkdir'); + } catch (\Exception $e) { + $this->logError($e->getMessage(), 'mkdir'); + } } } diff --git a/src/PermissionsFixerModuleInterface.php b/src/PermissionsFixerModuleInterface.php new file mode 100644 index 0000000..e1a6a94 --- /dev/null +++ b/src/PermissionsFixerModuleInterface.php @@ -0,0 +1,35 @@ + + * @since 0.32.0 + */ +interface PermissionsFixerModuleInterface +{ + /** + * Lists of files that permissions need to be fixed + * + * @return array A list of files + */ + public function getFilePermissionLists(); + + /** + * Lists of directories that permissions need to be fixed + * + * @return array A list of files + */ + public function getDirectoryPermissionLists(); +} diff --git a/src/Plugin.php b/src/Plugin.php index 2a49623..6a26833 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -14,16 +14,20 @@ use Composer\DependencyResolver\Operation\InstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\DependencyResolver\Operation\UninstallOperation; +use Composer\EventDispatcher\EventDispatcher; use Composer\EventDispatcher\EventSubscriberInterface; use Composer\Installer\PackageEvent; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; use Composer\Plugin\PluginInterface; use Composer\Script\Event as ScriptEvent; +use Core\Application; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Yawik\Composer\Event\ActivateEvent; +use Yawik\Composer\Event\ConfigureEvent; /** * Class Plugin @@ -34,162 +38,161 @@ */ class Plugin implements PluginInterface, EventSubscriberInterface { - const YAWIK_MODULE_TYPE = 'yawik-module'; + const YAWIK_ACTIVATE_EVENT = 'yawik.activate'; - const SCAN_TYPE_INSTALL = 'install'; - const SCAN_TYPE_REMOVE = 'remove'; + const YAWIK_CONFIGURE_EVENT = 'yawik.configure'; + + const YAWIK_MODULE_TYPE = 'yawik-module'; + + const ADD_TYPE_INSTALL = 'install'; + + const ADD_TYPE_REMOVE = 'remove'; + + /** + * @var EventDispatcher + */ + protected $dispatcher; + + /** + * @var Application + */ + protected $application; /** * @var Composer */ - private $composer; + protected $composer; /** * @var IOInterface */ - private $output; + protected $output; /** * An array list of available modules * * @var array */ - private $installed = []; + protected $installed = []; - private $uninstalled = []; - - private $projectPath; + protected $uninstalled = []; /** - * @var AssetsInstaller - */ - private $assetsInstaller; - - /** - * @var PermissionsFixer + * A lists of available installed yawik modules type + * @var array */ - private $permissionsFixer; - - public function __construct($projectPath=null) - { - $this->projectPath = is_null($projectPath) ? getcwd():$projectPath; - } + protected $yawikModules; public function activate(Composer $composer, IOInterface $io) { - $this->composer = $composer; - $this->output = $io; + $dispatcher = $composer->getEventDispatcher(); + $assets = new AssetsInstaller(); + $fixer = new PermissionsFixer(); + + + $event = new ActivateEvent($composer, $io); + $dispatcher->addSubscriber($assets); + $dispatcher->addSubscriber($fixer); + + $dispatcher->dispatch(static::YAWIK_ACTIVATE_EVENT, $event); + $this->composer = $composer; + $this->output = $io; } /** - * Define AssetsInstaller to use - * This very usefull during testing process - * @param AssetsInstaller $installer + * Provide composer event listeners. + * + * @return array */ - public function setAssetsInstaller(AssetsInstaller $installer) + public static function getSubscribedEvents() { - if (!is_null($this->output)) { - $installer->setOutputFromComposerIO($this->output); - } - $this->assetsInstaller = $installer; + return [ + 'post-autoload-dump' => 'onPostAutoloadDump', + 'post-package-install' => 'onPostPackageInstall', + 'post-package-update' => 'onPostPackageUpdate', + 'pre-package-uninstall' => 'onPrePackageUninstall' + ]; } /** - * @return AssetsInstaller + * Get Yawik Application to use + * @return Application|\Zend\Mvc\Application */ - public function getAssetsInstaller() + public function getApplication() { - if (!is_object($this->assetsInstaller)) { - $this->setAssetsInstaller(new AssetsInstaller()); + // @codeCoverageIgnoreStart + if (is_file(__DIR__.'/../../vendor/autoload')) { + include __DIR__.'/../../vendor/autoload'; } + // @codeCoverageIgnoreEnd - return $this->assetsInstaller; - } - - /** - * @return PermissionsFixer - */ - public function getPermissionsFixer() - { - if (is_null($this->permissionsFixer)) { - $this->setPermissionsFixer(new PermissionsFixer()); + if (!$this->application instanceof Application) { + $this->application = Application::init(); } - return $this->permissionsFixer; + return $this->application; } - /** - * @param PermissionsFixer $permissionsFixer - * @return Plugin - */ - public function setPermissionsFixer($permissionsFixer) + public function getInstalledModules() { - if (!is_null($this->output)) { - $permissionsFixer->setOutputFromComposerIO($this->output); - } - $this->permissionsFixer = $permissionsFixer; - return $this; + return $this->installed; } - /** - * Provide composer event listeners. - * - * @return array - */ - public static function getSubscribedEvents() + public function getUninstalledModules() { - return [ - 'post-autoload-dump' => 'onPostAutoloadDump', - 'post-package-install' => 'onPostPackageInstall', - 'post-package-update' => 'onPostPackageUpdate', - 'pre-package-uninstall' => 'onPrePackageUninstall' - ]; + return $this->uninstalled; } public function onPostAutoloadDump() { - if (count($this->uninstalled) > 0) { - $this->getAssetsInstaller()->uninstall($this->uninstalled); - } else { - $this->getAssetsInstaller()->install($this->installed); + $app = $this->getApplication(); + $modules = $app->getServiceManager()->get('ModuleManager')->getLoadedModules(); + $installed = $this->installed; + $coreOptions = $app->getServiceManager()->get('Core/Options'); + + foreach ($installed as $moduleName) { + $className = $moduleName . '\\Module'; + if (class_exists($className, true)) { + $modules[] = new $className; + } } - $this->getPermissionsFixer()->fix(); + + $event = new ConfigureEvent($coreOptions, $modules); + $dispatcher = $this->composer->getEventDispatcher(); + $dispatcher->dispatch(self::YAWIK_CONFIGURE_EVENT, $event); } public function onPostPackageInstall(PackageEvent $event) { $package = $event->getOperation()->getPackage(); - $this->scanModules($package, static::SCAN_TYPE_INSTALL); + $this->addModules($package, static::ADD_TYPE_INSTALL); } public function onPostPackageUpdate(PackageEvent $event) { $package = $event->getOperation()->getTargetPackage(); - $this->scanModules($package, static::SCAN_TYPE_INSTALL); + $this->addModules($package, static::ADD_TYPE_INSTALL); } public function onPrePackageUninstall(PackageEvent $event) { $package = $event->getOperation()->getPackage(); - $this->scanModules($package, static::SCAN_TYPE_REMOVE); + $this->addModules($package, static::ADD_TYPE_REMOVE); } - private function scanModules(PackageInterface $package, $scanType='install') + public function addModules(PackageInterface $package, $scanType='install') { - $installer = $this->composer->getInstallationManager(); - $packagePath = $installer->getInstallPath($package); $type = $package->getType(); - $publicDir = $packagePath.'/public'; $extras = $package->getExtra(); - if (file_exists($publicDir) && $type === static::YAWIK_MODULE_TYPE) { + if ($type === static::YAWIK_MODULE_TYPE) { // we skip undefined zf module definition if (isset($extras['zf']['module'])) { // we register module class name $moduleName = $extras['zf']['module']; - if (self::SCAN_TYPE_INSTALL == $scanType) { - $this->installed[$moduleName] = realpath($publicDir); - } else { + if (self::ADD_TYPE_REMOVE == $scanType) { $this->uninstalled[] = $moduleName; + } else { + $this->installed[] = $moduleName; } } else { $this->output->write('[warning] No module definition for: ' . $package->getName()); diff --git a/test/AssetsInstallerTest.php b/test/AssetsInstallerTest.php index a93b14c..27bf721 100644 --- a/test/AssetsInstallerTest.php +++ b/test/AssetsInstallerTest.php @@ -11,14 +11,17 @@ namespace YawikTest\Composer; -use Prophecy\Argument; -use Psr\Log\LoggerInterface; -use Psr\Log\LogLevel; -use Symfony\Component\Console\Input\StringInput; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Output\StreamOutput; -use Yawik\Composer\AssetsInstaller; +include __DIR__.'/sandbox/src/Module.php'; + +use Composer\Installer; +use Core\Module as CoreModule; use PHPUnit\Framework\TestCase; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; +use Yawik\Composer\AssetProviderInterface; +use Yawik\Composer\AssetsInstaller; +use Yawik\Composer\Event\ConfigureEvent; +use Yawik\Composer\Plugin; /** * Class AssetsInstallerTest @@ -27,207 +30,298 @@ * @author Anthonius Munthi * @since 0.32.0 * @covers \Yawik\Composer\AssetsInstaller - * @covers \Yawik\Composer\LogTrait */ class AssetsInstallerTest extends TestCase { - /** - * @var AssetsInstaller - */ - private $target; - - /** - * @var StreamOutput - */ - private $output; - - /** - * Store the current directory because Yawik config file will chdir into sandbox - * @var string - */ - private static $cwd; + use TestOutputTrait; - public function setUp() + public function testGetSubscribedEvent() { - $output = new StreamOutput(fopen('php://memory', 'w')); - $input = new StringInput('some input'); - - // setup the target - $target = new AssetsInstaller(); - $target->setOutput($output); - $target->setInput($input); - - $this->output = $output; - $this->target = $target; - } - - public static function setUpBeforeClass() - { - static::$cwd = getcwd(); + $this->assertEquals([ + Plugin::YAWIK_ACTIVATE_EVENT => 'onActivateEvent', + Plugin::YAWIK_CONFIGURE_EVENT => 'onConfigureEvent' + ], AssetsInstaller::getSubscribedEvents()); } - public static function tearDownAfterClass() + public function testOnConfigureEvent() { - chdir(static::$cwd); + $mod1 = $this->createMock(AssetProviderInterface::class); + $mod2 = new CoreModule(); + $event = $this->createMock(ConfigureEvent::class); + $mod1->expects($this->once()) + ->method('getPublicDir') + ->willReturn(__DIR__.'/fixtures/foo') + ; + $event->expects($this->once()) + ->method('getModules') + ->willReturn([$mod1,$mod2]) + ; + + $installer = $this->getMockBuilder(AssetsInstaller::class) + ->setMethods(['install']) + ->getMock() + ; + $modulesAssert = function ($modules) { + $this->assertCount(2, $modules); + $this->assertArrayHasKey('Core', $modules); + $this->assertContains('/vendor/yawik/core', $modules['Core']); + return true; + }; + $installer->expects($this->once()) + ->method('install') + ->with($this->callback($modulesAssert)) + ; + + $installer->onConfigureEvent($event); } /** - * Gets the display returned by the last execution of the command. - * - * @param bool $normalize Whether to normalize end of lines to \n or not - * - * @return string The display + * @param string $flag + * @param string $expectedMethod + * @dataProvider getTestInstall */ - public function getDisplay($normalize = false) + public function testInstall($flag, $expectedMethod) { - $output = $this->output; - - rewind($output->getStream()); - - $display = stream_get_contents($output->getStream()); - - if ($normalize) { - $display = str_replace(PHP_EOL, "\n", $display); - } + $modules = [ + 'Foo' => __DIR__.'/fixtures/foo', + 'Hello' => __DIR__.'/fixtures/hello', + ]; - return $display; + $fs = $this->createMock(Filesystem::class); + $fs->expects($this->exactly(2)) + ->method('remove') + ->withConsecutive( + [$this->stringContains('public/modules/Foo')], + [$this->stringContains('public/modules/Hello')] + ) + ; + + $installer = $this->getMockBuilder(TestAssetInstaller::class) + ->setMethods([ + 'relativeSymlinkWithFallback', + 'absoluteSymlinkWithFallback', + 'renderInstallOutput', + 'hardcopy', + ]) + ->getMock() + ; + + $installer->expects($this->exactly(2)) + ->method($expectedMethod) + ->withConsecutive( + [$this->stringContains('fixtures/foo'), $this->stringContains('modules/Foo')], + [$this->stringContains('fixtures/hello'), $this->stringContains('modules/Hello')] + ) + ->willReturn($flag) + ; + $installer->expects($this->once()) + ->method('renderInstallOutput') + ; + $installer->setFilesystem($fs); + $installer->install($modules, $flag); } - public function testDirectoriesScan() + public function getTestInstall() { - $this->assertEquals(__DIR__.'/sandbox/public/modules', $this->target->getModuleAssetDir()); + return [ + // default using relative symlink + [null, 'relativeSymlinkWithFallback'], + [ AssetsInstaller::METHOD_RELATIVE_SYMLINK, 'relativeSymlinkWithFallback'], + [ AssetsInstaller::METHOD_ABSOLUTE_SYMLINK, 'absoluteSymlinkWithFallback'], + [ AssetsInstaller::METHOD_COPY, 'hardcopy'], + ]; } - public function testInstall() + public function testInstallWithException() { - $fixtures = __DIR__.'/fixtures/public1'; $modules = [ - 'Foo' => $fixtures, - 'Hello' => $fixtures, + 'Foo' => __DIR__.'/fixtures/foo', + 'Hello' => __DIR__.'/fixtures/hello', ]; - $moduleDir = $this->target->getModuleAssetDir(); - - $this->target->install($modules, AssetsInstaller::METHOD_ABSOLUTE_SYMLINK); - $display = $this->getDisplay(true); - - $this->assertContains('absolute symlink', $display); - $this->assertDirectoryExists($moduleDir.'/Foo'); - $this->assertDirectoryExists($moduleDir.'/Hello'); - $this->assertFileExists($moduleDir.'/Foo/foo.js'); - $this->assertFileExists($moduleDir.'/Hello/foo.js'); - // should load loaded modules - $this->assertFileExists($moduleDir.'/Core/Gruntfile.js'); + $installer = $this->getMockBuilder(TestAssetInstaller::class) + ->setMethods([ + 'relativeSymlinkWithFallback', + 'absoluteSymlinkWithFallback', + 'renderInstallOutput', + 'hardcopy', + ]) + ->getMock() + ; + $fs = $this->createMock(Filesystem::class); + $fs->expects($this->exactly(2)) + ->method('remove') + ->willThrowException(new \Exception('some error')) + ; + + $installer->expects($this->once()) + ->method('renderInstallOutput') + ->with(false, $this->countOf(2), 1) + ; + + $installer->setFilesystem($fs); + $installer->install($modules); } public function testUninstall() { - $target = $this->target; - $fixtures = __DIR__.'/fixtures/public1'; - $moduleDir = $target->getModuleAssetDir(); - $modules = [ - 'Foo' => $fixtures, - 'Hello' => $fixtures, - ]; - - $this->target->install($modules); - $this->target->uninstall(['Foo']); - $this->assertFileNotExists($moduleDir.'/Foo/foo.js'); - $this->assertFileExists($moduleDir.'/Hello/foo.js'); - - - $this->target->uninstall(['Hello']); - $this->assertFileNotExists($moduleDir.'/Hello/foo.js'); + $targetDir = __DIR__.'/sandbox/public/modules'; + @mkdir($fooDir = $targetDir.'/Foo', 0777, true); + @mkdir($helloDir = $targetDir.'/Hello', 0777, true); + + $installer = $this->getMockBuilder(TestAssetInstaller::class) + ->setMethods(['log']) + ->getMock() + ; + $installer->expects($this->exactly(2)) + ->method('log') + ->withConsecutive( + [$this->stringContains('Foo')], + [$this->stringContains('Hello')] + ); + + $this->assertDirectoryExists($fooDir); + $this->assertDirectoryExists($helloDir); + + $installer->uninstall(['Foo','Hello']); + + $this->assertDirectoryNotExists($fooDir); + $this->assertDirectoryNotExists($helloDir); } - public function testSymlink() + public function testRenderInstallOutput() { - $fixtures = __DIR__.'/fixtures/public1'; - $modules = [ - 'Foo' => $fixtures, - 'Hello' => $fixtures, - ]; - - $moduleDir = $this->target->getModuleAssetDir(); + $installer = new TestAssetInstaller(); + $installer->setOutput($this->getOutput()); - $this->target->install($modules, AssetsInstaller::METHOD_ABSOLUTE_SYMLINK); - $display = $this->getDisplay(true); + $installer->renderInstallOutput(true, [['Core','method','ok']], 0); + $display = $this->getDisplay(); + $this->assertContains('Yawik Assets Installed!', $display); + $this->assertContains('installed via copy', $display); - $this->assertContains('absolute symlink', $display); - $this->assertTrue(is_link($moduleDir.'/Foo')); - $this->assertDirectoryExists($moduleDir.'/Foo'); - $this->assertDirectoryExists($moduleDir.'/Hello'); - $this->assertFileExists($moduleDir.'/Foo/foo.js'); - $this->assertFileExists($moduleDir.'/Hello/foo.js'); + $installer->renderInstallOutput(false, [['Core','method','ok']], 1); + $display = $this->getDisplay(); + $this->assertContains('Some errors occurred while installing assets', $display); } - public function testRelative() + public function testAbsoluteSymlinkWithFallback() { - $fixtures = __DIR__.'/fixtures/public1'; - $modules = [ - 'Foo' => $fixtures, - 'Hello' => $fixtures, - ]; + $installer = $this->getMockBuilder(AssetsInstaller::class) + ->setMethods(['symlink']) + ->getMock() + ; + $installer->expects($this->once()) + ->method('symlink') + ->with('origin', 'target') + ; + $this->assertEquals( + AssetsInstaller::METHOD_ABSOLUTE_SYMLINK, + $installer->absoluteSymlinkWithFallback('origin', 'target') + ); + } - $moduleDir = $this->target->getModuleAssetDir(); + public function testAbsoluteSymlinkWithFallbackError() + { + $installer = $this->getMockBuilder(AssetsInstaller::class) + ->setMethods(['symlink','hardCopy']) + ->getMock() + ; + $installer->expects($this->once()) + ->method('symlink') + ->with('origin', 'target') + ->willThrowException(new \Exception('some error')) + ; + $installer->expects($this->once()) + ->method('hardCopy') + ->with('origin', 'target') + ->willReturn('some value') + ; + $this->assertEquals( + 'some value', + $installer->absoluteSymlinkWithFallback('origin', 'target') + ); + } - $this->target->install($modules, AssetsInstaller::METHOD_RELATIVE_SYMLINK); - $display = $this->getDisplay(true); + public function testRelativeSymlinkWithFallback() + { + $installer = $this->getMockBuilder(AssetsInstaller::class) + ->setMethods(['symlink']) + ->getMock() + ; + $installer->expects($this->once()) + ->method('symlink') + ->with('origin', 'target') + ; + $this->assertEquals( + AssetsInstaller::METHOD_RELATIVE_SYMLINK, + $installer->relativeSymlinkWithFallback('origin', 'target') + ); + } - $this->assertRegExp('/relative symlink/', $display); - $this->assertDirectoryExists($moduleDir.'/Foo'); - $this->assertDirectoryExists($moduleDir.'/Hello'); - $this->assertFileExists($moduleDir.'/Foo/foo.js'); - $this->assertFileExists($moduleDir.'/Hello/foo.js'); + public function testRelativeSymlinkWithFallbackError() + { + $installer = $this->getMockBuilder(AssetsInstaller::class) + ->setMethods(['symlink','absoluteSymlinkWithFallback']) + ->getMock() + ; + $installer->expects($this->once()) + ->method('symlink') + ->with('origin', 'target') + ->willThrowException(new \Exception('some error')) + ; + $installer->expects($this->once()) + ->method('absoluteSymlinkWithFallback') + ->with('origin', 'target') + ->willReturn('some value') + ; + $this->assertEquals( + 'some value', + $installer->relativeSymlinkWithFallback('origin', 'target') + ); } - public function testCopy() + public function testSymlinkShouldThrowWhenLinkIsBroken() { - $fixtures = __DIR__.'/fixtures/public1'; - $modules = [ - 'Foo' => $fixtures, - 'Hello' => $fixtures, - ]; + $filesystem = $this->createMock(Filesystem::class); + + $installer = new TestAssetInstaller(); + $installer->setFilesystem($filesystem); + $this->expectException(\Exception::class); + $this->expectExceptionMessage( + 'Symbolic link "target" was created but appears to be broken.' + ); + $installer->symlink('origin', 'target'); + } - $moduleDir = $this->target->getModuleAssetDir(); + public function testSymlink() + { + $originDir = __DIR__.'/fixtures/foo'; + $targetDir = __DIR__.'/sandbox/public/modules'; + @mkdir($targetDir, 0777, true); - $this->target->install($modules, AssetsInstaller::METHOD_COPY); - $display = $this->getDisplay(true); + //$this->expectException(\Exception::class); + $installer = new TestAssetInstaller(); + $installer->symlink($originDir, $targetDir.'/Foo', true); - $this->assertContains('[NOTE] Some assets were installed via copy', $display); - $this->assertDirectoryExists($moduleDir.'/Foo'); - $this->assertDirectoryExists($moduleDir.'/Hello'); - $this->assertFileExists($moduleDir.'/Foo/foo.js'); - $this->assertFileExists($moduleDir.'/Hello/foo.js'); + $this->assertDirectoryExists($targetDir.'/Foo'); + $this->assertTrue(is_link($targetDir.'/Foo')); } - public function testLog() + public function testHardCopy() { - $log = $this->prophesize(LoggerInterface::class); - $output = $this->prophesize(OutputInterface::class); - $target = $this->target; - - // log expectation - $log->log(LogLevel::DEBUG, 'some debug') - ->shouldBeCalled(); - $log->log(LogLevel::INFO, 'some info') - ->shouldBeCalled(); - $log->log(LogLevel::ERROR, 'some error') - ->shouldBeCalled(); - - // output expectation - $output->writeln(Argument::containingString('some debug'), OutputInterface::VERBOSITY_VERY_VERBOSE) - ->shouldBeCalled(); - $output->writeln(Argument::containingString('some info'), OutputInterface::OUTPUT_NORMAL) - ->shouldBeCalled(); - $output->writeln(Argument::containingString('some error'), OutputInterface::OUTPUT_NORMAL) - ->shouldBeCalled(); - - $target - ->setLogger($log->reveal()) - ->setOutput($output->reveal()); - $target->logDebug('some debug'); - $target->log('some info'); - $target->logError('some error'); + $origin = __DIR__.'/fixtures/foo'; + + $fs = $this->createMock(Filesystem::class); + $fs->expects($this->once()) + ->method('mirror') + ->with($origin, 'target', $this->isInstanceOf(Finder::class)) + ; + $fs->expects($this->once()) + ->method('mkdir') + ->with('target', 0777) + ; + $installer = new TestAssetInstaller(); + $installer->setFilesystem($fs); + $installer->hardCopy($origin, 'target'); } } diff --git a/test/Event/ActivateEventTest.php b/test/Event/ActivateEventTest.php new file mode 100644 index 0000000..7d23b23 --- /dev/null +++ b/test/Event/ActivateEventTest.php @@ -0,0 +1,36 @@ + + * @since 0.32.0 + * @covers \Yawik\Composer\Event\ActivateEvent + */ +class ActivateEventTest extends TestCase +{ + public function testConstructor() + { + $composer = $this->createMock(Composer::class); + $output = $this->createMock(IOInterface::class); + + $event = new ActivateEvent($composer, $output); + $this->assertEquals($composer, $event->getComposer()); + $this->assertEquals($output, $event->getOutput()); + } +} diff --git a/test/Event/ConfigureEventTest.php b/test/Event/ConfigureEventTest.php new file mode 100644 index 0000000..9162606 --- /dev/null +++ b/test/Event/ConfigureEventTest.php @@ -0,0 +1,26 @@ +createMock(CoreOptions::class); + $modules = ['some-module']; + + $event = new ConfigureEvent($options, $modules); + $this->assertEquals($options, $event->getOptions()); + $this->assertEquals($modules, $event->getModules()); + } +} diff --git a/test/LogTraitTest.php b/test/LogTraitTest.php new file mode 100644 index 0000000..ab36dba --- /dev/null +++ b/test/LogTraitTest.php @@ -0,0 +1,196 @@ +prophesize(IOInterface::class); + $composer = $this->prophesize(Composer::class); + + $event = new ActivateEvent($composer->reveal(), $output->reveal()); + $target = $this->getMockBuilder(TestLogTrait::class) + ->setMethods(['setOutputFromComposerIO']) + ->getMock() + ; + $target->expects($this->once()) + ->method('setOutputFromComposerIO') + ->with($output->reveal()) + ; + + $target->onActivateEvent($event); + } + + /** + * Verify verbose for installer output + * @param string $expect + * @param bool $verbose + * @param bool $debug + * @param bool $veryVerbose + * @param bool $decorated + * @dataProvider getTestSetupOutput + */ + public function testSetupOutput( + $expectOutputLevel, + $verbose = false, + $debug = false, + $veryVerbose=false, + $decorated=false + ) { + $output = $this->prophesize(IOInterface::class); + $installerOutput = $this->prophesize(OutputInterface::class); + + $output->isVerbose()->willReturn($verbose); + $output->isDebug()->willReturn($debug); + $output->isVeryVerbose()->willReturn($veryVerbose); + $output->isDecorated()->willReturn($decorated); + + $installerOutput->setVerbosity($expectOutputLevel) + ->shouldBeCalled() + ; + $installerOutput->setDecorated($decorated) + ->shouldBeCalled() + ; + + $target = new TestLogTrait(); + $target->setOutput($installerOutput->reveal()); + $target->setOutputFromComposerIO($output->reveal()); + } + + public function getTestSetupOutput() + { + return[ + [ + OutputInterface::VERBOSITY_VERY_VERBOSE, + false, + false, + true, + true + ], + [ + OutputInterface::VERBOSITY_VERY_VERBOSE, + false, + true, + false, + false + ], + [ + OutputInterface::VERBOSITY_VERBOSE, + true, + false, + false, + false + ], + [ + OutputInterface::VERBOSITY_NORMAL, + false, + false, + false, + false + ] + ]; + } + + public function testDefaultValues() + { + $target = new TestLogTrait(); + $this->assertInstanceOf(InputInterface::class, $target->getInput()); + $this->assertInstanceOf(OutputInterface::class, $target->getOutput()); + + $input = new StringInput('foo'); + $target->setInput($input); + $this->assertEquals($input, $target->getInput()); + } + + /** + * @param string $method + * @param string $expectLevel + * @param string $message + * @param string $context + * @dataProvider getTestLog + */ + public function testLog($method, $expectLevel, $message = 'some message', $context = 'yawik') + { + $target = $this->getMockBuilder(TestLogTrait::class) + ->setMethods(['doLog']) + ->getMock() + ; + $target->expects($this->once()) + ->method('doLog') + ->with($expectLevel, $message, $context) + ; + + call_user_func_array([$target,$method], [$message,$context]); + } + + public function getTestLog() + { + return [ + ['logDebug',LogLevel::DEBUG], + ['logError',LogLevel::ERROR], + ['log',LogLevel::INFO], + ]; + } + + /** + * @param string $expectOutputLevel + * @param string $logLevel + * @param string $message + * @param string $context + * @dataProvider getTestDoLog + */ + public function testDoLog($expectOutputLevel, $logLevel, $message = 'some message', $context = 'yawik') + { + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('log') + ->with($logLevel, $message, [$context]) + ; + + $output = $this->createMock(OutputInterface::class); + $output->expects($this->once()) + ->method('writeln') + ->with($this->stringContains($message), $expectOutputLevel) + ; + + $target = new TestLogTrait(); + $target->setLogger($logger); + $target->setOutput($output); + + $target->doLog($logLevel, $message, $context); + } + + public function getTestDoLog() + { + return [ + [OutputInterface::VERBOSITY_VERY_VERBOSE, LogLevel::DEBUG], + [OutputInterface::OUTPUT_NORMAL, LogLevel::ERROR], + [OutputInterface::OUTPUT_NORMAL, LogLevel::INFO], + ]; + } +} diff --git a/test/PermissionsFixerTest.php b/test/PermissionsFixerTest.php index c663583..0791c57 100644 --- a/test/PermissionsFixerTest.php +++ b/test/PermissionsFixerTest.php @@ -15,8 +15,11 @@ use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Filesystem\Filesystem; +use Yawik\Composer\Event\ConfigureEvent; use Yawik\Composer\PermissionsFixer; use PHPUnit\Framework\TestCase; +use Yawik\Composer\PermissionsFixerModuleInterface; +use Yawik\Composer\Plugin; /** * Class PermissionsFixerTest @@ -25,19 +28,16 @@ * @author Anthonius Munthi * @since 0.32.0 * @covers \Yawik\Composer\PermissionsFixer - * @covers \Yawik\Composer\LogTrait */ class PermissionsFixerTest extends TestCase { - private $output; - - private $input; - /** * @var PermissionsFixer */ private $target; + private $output; + public function setUp() { $output = new StreamOutput(fopen('php://memory', 'w')); @@ -52,50 +52,136 @@ public function setUp() $this->target = $target; } - public function testFixDirPermissions() + public function testGetSubscribedEvent() { - $rootDir = __DIR__.'/sandbox'; + $this->assertEquals([ + Plugin::YAWIK_ACTIVATE_EVENT => 'onActivateEvent', + Plugin::YAWIK_CONFIGURE_EVENT => 'onConfigureEvent' + ], PermissionsFixer::getSubscribedEvents()); + } - $filesystem = $this->getMockBuilder(Filesystem::class) - //->setMethods(['mkdir','chmod']) + public function testOnConfigureEvent() + { + $module1 = $this->createMock(PermissionsFixerModuleInterface::class); + $module2 = $this->createMock(PermissionsFixerModuleInterface::class); + $modError = $this->createMock(PermissionsFixerModuleInterface::class); + $modules = [$module1,$module2]; + $plugin = $this->getMockBuilder(PermissionsFixer::class) + ->setMethods(['touch','chmod','mkdir','logError']) ->getMock() ; - $filesystem->expects($this->exactly(5)) + + foreach ($modules as $index => $module) { + $index = $index+1; + $module->expects($this->once()) + ->method('getDirectoryPermissionLists') + ->willReturn([ + 'public/static/module'.$index + ]) + ; + $module->expects($this->once()) + ->method('getFilePermissionLists') + ->willReturn([ + 'public/module'.$index.'.log' + ]) + ; + } + + $modError->expects($this->once()) + ->method('getDirectoryPermissionLists') + ->willReturn('foo') + ; + $modError->expects($this->once()) + ->method('getFilePermissionLists') + ->willReturn('bar') + ; + $modules[] = $modError; + $event = $this->prophesize(ConfigureEvent::class); + $event->getModules() + ->willReturn($modules) + ->shouldBeCalled() + ; + + $plugin->expects($this->exactly(2)) + ->method('touch') + ->withConsecutive( + ['public/module1.log'], + ['public/module2.log'] + ) + ; + $plugin->expects($this->exactly(2)) + ->method('mkdir') + ->withConsecutive( + ['public/static/module1'], + ['public/static/module2'] + ); + $plugin->expects($this->exactly(4)) ->method('chmod') ->withConsecutive( - [$rootDir.'/config/autoload',0777], - [$rootDir.'/var/cache',0777], - [$rootDir.'/var/log',0777], - [$rootDir.'/var/log/tracy',0777], - [$rootDir.'/var/log/yawik.log',0666] + ['public/static/module1'], + ['public/static/module2'], + ['public/module1.log'], + ['public/module2.log'] ) ; - $logger = $this->prophesize(LoggerInterface::class); - $logger - ->log(LogLevel::INFO, Argument::containingString('config/autoload')) - ->shouldBeCalled() - ; - $logger - ->log(LogLevel::INFO, Argument::containingString('var/cache')) - ->shouldBeCalled() - ; - $logger - ->log(LogLevel::INFO, Argument::containingString('var/log')) - ->shouldBeCalled() + $plugin->expects($this->exactly(2)) + ->method('logError') + ->withConsecutive( + [$this->stringContains('getDirectoryPermissionList()')], + [$this->stringContains('getFilePermissionList()')] + ) ; - $logger - ->log(LogLevel::INFO, Argument::containingString('var/log/tracy')) - ->shouldBeCalled() + + $plugin->onConfigureEvent($event->reveal()); + } + + /** + * @param string $method + * @param array $args + * @param string $expectLog + * @param string $logType + * @dataProvider getTestFilesystemAction + */ + public function testFilesystemAction($method, $args, $expectLog, $logType='log') + { + $plugin = $this->getMockBuilder(TestPermissionFixer::class) + ->setMethods([$logType]) + ->getMock() ; - $logger - ->log(LogLevel::INFO, Argument::containingString('var/log/yawik.log')) - ->shouldBeCalled() + + $fs = $this->createMock(Filesystem::class); + if ('log' == $logType) { + $fs->expects($this->once()) + ->method($method) + ; + } else { + $fs->expects($this->once()) + ->method($method) + ->willThrowException(new \Exception($expectLog)) + ; + } + + $plugin->expects($this->once()) + ->method($logType) + ->with($this->stringContains($expectLog), $method) ; + $plugin->setFilesystem($fs); + call_user_func_array([$plugin,$method], $args); + } + + public function getTestFilesystemAction() + { + return [ + ['mkdir',['some/dir'],'some/dir'], + ['mkdir',['some/dir'],'some error','logError'], + + ['chmod',['some/dir'],'some/dir'], + ['chmod',['some/file',0775],'some error','logError'], - $this->target->setFilesystem($filesystem); - $this->target->setLogger($logger->reveal()); - $this->target->fix(); + ['touch',['some/file'],'some/file'], + ['touch',['some/file'],'some error','logError'], + ]; } } diff --git a/test/PluginTest.php b/test/PluginTest.php index bcd81f9..29ac163 100644 --- a/test/PluginTest.php +++ b/test/PluginTest.php @@ -11,21 +11,26 @@ use Composer\Composer; use Composer\DependencyResolver\Operation\InstallOperation; -use Composer\DependencyResolver\Operation\OperationInterface; -use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; -use Composer\Installer; +use Composer\EventDispatcher\EventDispatcher; use Composer\Installer\InstallationManager; use Composer\Installer\PackageEvent; use Composer\IO\IOInterface; use Composer\Package\PackageInterface; -use org\bovigo\vfs\vfsStream; +use Core\Application; +use Core\Module as CoreModule; +use Core\Options\ModuleOptions; +use Interop\Container\ContainerInterface; use PHPUnit\Framework\TestCase; -use Prophecy\Prophecy\ObjectProphecy; -use Symfony\Component\Console\Output\OutputInterface; +use Prophecy\Argument; use Yawik\Composer\AssetsInstaller; +use Yawik\Composer\Event\ActivateEvent; +use Yawik\Composer\Event\ConfigureEvent; use Yawik\Composer\PermissionsFixer; +use Yawik\Composer\PermissionsFixerModuleInterface; use Yawik\Composer\Plugin; +use Zend\ModuleManager\ModuleManager; +use Zend\Mvc\Application as ZendMvcApplication; /** * Class PluginTest @@ -36,47 +41,21 @@ */ class PluginTest extends TestCase { - /** - * @var ObjectProphecy - */ - private $package; + private $testDir; - /** - * @var ObjectProphecy - */ - private $installManager; + private $packageEvent; - /** - * @var ObjectProphecy - */ private $composer; - /** - * @var ObjectProphecy - */ - private $event; - - /** - * @var ObjectProphecy - */ - private $output; - - /** - * @var ObjectProphecy - */ private $operation; - /** - * @var ObjectProphecy - */ - private $assetsInstaller; + private $installManager; - /** - * @var vfsStream - */ - private $filesystem; + private $output; - private $testDir; + private $package; + + private $dispatcher; public function setUp() { @@ -84,6 +63,7 @@ public function setUp() if (!is_dir($this->testDir)) { mkdir($this->testDir, 0777, true); } + @mkdir($this->testDir.'/public'); } public function testSubscribedEvents() @@ -96,274 +76,236 @@ public function testSubscribedEvents() ], Plugin::getSubscribedEvents()); } - public function testSetInstaller() + private function setupMock($operationType = 'install') { - $plugin = new Plugin(); - $assetsInstaller = new AssetsInstaller(); - $plugin->setAssetsInstaller($assetsInstaller); - $this->assertEquals($assetsInstaller, $plugin->getAssetsInstaller()); - } + $composer = $this->prophesize(Composer::class); + $installManager = $this->prophesize(InstallationManager::class); + $package = $this->prophesize(PackageInterface::class); + $dispatcher = $this->prophesize(EventDispatcher::class); - /** - * Verify verbose for installer output - * @param string $expect - * @param bool $verbose - * @param bool $debug - * @param bool $veryVerbose - * @param bool $decorated - * @dataProvider getTestInstaller - */ - public function testGetInstaller( - $expectOutputLevel, - $verbose = false, - $debug = false, - $veryVerbose=false, - $decorated=false - ) { - $this->initializeMock(); - $output = $this->output; - $composer = $this->composer; - $installerOutput = $this->prophesize(OutputInterface::class); - - $output->isVerbose()->willReturn($verbose); - $output->isDebug()->willReturn($debug); - $output->isVeryVerbose()->willReturn($veryVerbose); - $output->isDecorated()->willReturn($decorated); - - $installerOutput->setVerbosity($expectOutputLevel) - ->shouldBeCalled() + $installManager->getInstallPath($package->reveal()) + ->willReturn($this->testDir) ; - $installerOutput->setDecorated($decorated) - ->shouldBeCalled() + + $composer->getInstallationManager() + ->will([$installManager,'reveal']) + ; + $composer->getEventDispatcher() + ->will([$dispatcher,'reveal']) ; - $plugin = new Plugin(); - $plugin->activate($composer->reveal(), $output->reveal()); + if ($operationType == 'update') { + $operation = $this->prophesize(UpdateOperation::class); + $operation->getTargetPackage() + ->will([$package,'reveal']) + ; + } else { + $operation = $this->prophesize(InstallOperation::class); + $operation + ->getPackage() + ->will([$package,'reveal']) + ; + } - $installer = new AssetsInstaller(); - $installer->setOutput($installerOutput->reveal()); - $plugin->setAssetsInstaller($installer); - $plugin->onPostPackageInstall($this->event->reveal()); - } + $packageEvent = $this->prophesize(PackageEvent::class); + $packageEvent->getOperation() + ->will([$operation,'reveal']) + ; - public function getTestInstaller() - { - return[ - [ - OutputInterface::VERBOSITY_VERY_VERBOSE, - false, - false, - true, - true - ], - [ - OutputInterface::VERBOSITY_VERY_VERBOSE, - false, - true, - false, - false - ], - [ - OutputInterface::VERBOSITY_VERBOSE, - true, - false, - false, - false - ], - [ - OutputInterface::VERBOSITY_NORMAL, - false, - false, - false, - false - ] - ]; + $this->composer = $composer; + $this->output = $this->prophesize(IOInterface::class); + $this->operation = $operation; + $this->installManager = $installManager; + $this->packageEvent = $packageEvent; + $this->package = $package; + $this->dispatcher = $dispatcher; } - public function testOnPostPackageInstall() + private function setupActivateDispatcher($dispatcher) { - $this->initializeMock(); - $event = $this->event; - $package = $this->package; - $package->getType() - ->willReturn('yawik-module') + $eventChecker = function ($event) { + $this->assertInstanceOf(ActivateEvent::class, $event); + $this->assertInstanceOf(IOInterface::class, $event->getOutput()); + $this->assertInstanceOf(Composer::class, $event->getComposer()); + return true; + }; + $dispatcher->addSubscriber(Argument::type(AssetsInstaller::class)) ->shouldBeCalled() ; - $package->getExtra() + $dispatcher->addSubscriber(Argument::type(PermissionsFixer::class)) ->shouldBeCalled() - ->willReturn([ - 'zf' => [ - 'module' => 'SomeModule' - ] - ]) ; - $assetsInstaller = $this->prophesize(AssetsInstaller::class); - $assetsInstaller->install([ - 'SomeModule' => $this->testDir.'/public' - ]) + $dispatcher->dispatch(Plugin::YAWIK_ACTIVATE_EVENT, Argument::that($eventChecker)) ->shouldBeCalled() ; - $fixer = $this->prophesize(PermissionsFixer::class); - $fixer->fix() + } + + public function testGetApplication() + { + $plugin = new Plugin(); + $this->assertInstanceOf(ZendMvcApplication::class, $plugin->getApplication()); + } + + public function testActivate() + { + $dispatcher = $this->prophesize(EventDispatcher::class); + $composer = $this->prophesize(Composer::class); + $io = $this->prophesize(IOInterface::class); + + $this->setupActivateDispatcher($dispatcher); + + $composer->getEventDispatcher() ->shouldBeCalled() + ->willReturn($dispatcher->reveal()) ; - $plugin = new Plugin($this->filesystem); - $plugin->setAssetsInstaller($assetsInstaller->reveal()); - $plugin->setPermissionsFixer($fixer->reveal()); - $plugin->activate($this->composer->reveal(), $this->output->reveal()); - $plugin->onPostPackageInstall($event->reveal()); - $plugin->onPostAutoloadDump(); + + $plugin = new TestPlugin(); + $plugin->activate($composer->reveal(), $io->reveal()); } - public function testOnPostPackageUpdate() + public function testAddInstalledModules() { - $this->initializeMock('update'); - $event = $this->event; - $package = $this->package; + $package = $this->prophesize(PackageInterface::class); $package->getType() ->willReturn('yawik-module') - ->shouldBeCalled() ; $package->getExtra() - ->shouldBeCalled() ->willReturn([ - 'zf' => [ - 'module' => 'SomeModule' - ] - ]) - ; - $assetsInstaller = $this->prophesize(AssetsInstaller::class); - $assetsInstaller->install([ - 'SomeModule' => $this->testDir.'/public' - ]) - ->shouldBeCalled() - ; - $fixer = $this->prophesize(PermissionsFixer::class); - $fixer->fix() - ->shouldBeCalled() - ; + 'zf' => [ + 'module' => 'SomeModule' + ] + ]); - $plugin = new Plugin(); - $plugin->setAssetsInstaller($assetsInstaller->reveal()); - $plugin->setPermissionsFixer($fixer->reveal()); - $plugin->activate($this->composer->reveal(), $this->output->reveal()); - $plugin->onPostPackageUpdate($event->reveal()); - $plugin->onPostAutoloadDump(); - } + $plugin = new TestPlugin(); + $plugin->addModules($package->reveal(), Plugin::ADD_TYPE_INSTALL); + $this->assertContains('SomeModule', $plugin->getInstalledModules()); - public function testOnPrePackageUninstall() - { - $this->initializeMock(); - $event = $this->event; - $package = $this->package; + // test non yawik-module type + $package = $this->prophesize(PackageInterface::class); $package->getType() - ->willReturn('yawik-module') - ->shouldBeCalled() + ->willReturn('some-type') ; $package->getExtra() - ->shouldBeCalled() ->willReturn([ 'zf' => [ 'module' => 'SomeModule' ] - ]) + ]); + $plugin = new TestPlugin(); + $plugin->addModules($package->reveal(), Plugin::ADD_TYPE_INSTALL); + $this->assertNotContains('SomeModule', $plugin->getInstalledModules()); + + // test with no extras definition + $package = $this->prophesize(PackageInterface::class); + $package->getType() + ->willReturn(Plugin::YAWIK_MODULE_TYPE) ; - $assetsInstaller = $this->prophesize(AssetsInstaller::class); - $assetsInstaller->uninstall([ - 'SomeModule' - ]) - ->shouldBeCalled() + $package->getExtra() + ->willReturn([]) ; - $fixer = $this->prophesize(PermissionsFixer::class); - $fixer->fix() + $package->getName()->willReturn('test/some-name'); + $output = $this->prophesize(IOInterface::class); + $output + ->write(Argument::containingString('No module definition for: test/some-name')) ->shouldBeCalled() ; - - $plugin = new Plugin(); - $plugin->setAssetsInstaller($assetsInstaller->reveal()); - $plugin->setPermissionsFixer($fixer->reveal()); - $plugin->activate($this->composer->reveal(), $this->output->reveal()); - $plugin->onPrePackageUninstall($event->reveal()); - $plugin->onPostAutoloadDump(); + $plugin = new TestPlugin(); + $plugin->setOutput($output->reveal()); + $plugin->addModules($package->reveal(), Plugin::ADD_TYPE_INSTALL); + $this->assertNotContains('SomeModule', $plugin->getInstalledModules()); } - public function testOnNonYawikModule() + public function testAddUninstalledModules() { - $this->initializeMock(); - $event = $this->event; - $package = $this->package; + $package = $this->prophesize(PackageInterface::class); $package->getType() - ->willReturn('other-type') - ->shouldBeCalled() + ->willReturn('yawik-module') ; $package->getExtra() - ->shouldBeCalled() ->willReturn([ 'zf' => [ 'module' => 'SomeModule' ] - ]) - ; - $assetsInstaller = $this->prophesize(AssetsInstaller::class); - $assetsInstaller->install([]) - ->shouldBeCalled() - ; - $fixer = $this->prophesize(PermissionsFixer::class); - $fixer->fix() - ->shouldBeCalled() - ; + ]); - $plugin = new Plugin(); - $plugin->setAssetsInstaller($assetsInstaller->reveal()); - $plugin->setPermissionsFixer($fixer->reveal()); - $plugin->activate($this->composer->reveal(), $this->output->reveal()); - $plugin->onPostPackageInstall($event->reveal()); - $plugin->onPostAutoloadDump(); + $plugin = new TestPlugin(); + $plugin->addModules($package->reveal(), Plugin::ADD_TYPE_REMOVE); + $this->assertEmpty($plugin->getInstalledModules()); + $this->assertContains('SomeModule', $plugin->getUninstalledModules()); } - private function initializeMock($operationType = 'install') + /** + * @param string $eventName + * @param string $expectedAddType + * @dataProvider getTestEventHandling + */ + public function testEventHandling($eventName, $expectedAddType) { - $this->package = $this->prophesize(PackageInterface::class); - $installManager = $this->prophesize(InstallationManager::class); - $installManager - ->getInstallPath($this->package->reveal()) - ->willReturn($this->testDir) - ->shouldBeCalled() + $setupType = ($eventName == 'onPostPackageUpdate') ? 'update':'install'; + $this->setupMock($setupType); + + $event = $this->packageEvent; + $package = $this->package; + + $plugin = $this->getMockBuilder(Plugin::class) + ->setMethods(['addModules']) + ->getMock() ; - $composer = $this->prophesize(Composer::class); - $composer->getInstallationManager() - ->will([$installManager,'reveal']) - ->shouldBeCalled() + $plugin->expects($this->once()) + ->method('addModules') + ->with($package->reveal(), $expectedAddType) ; - if ($operationType == 'update') { - $operation = $this->prophesize(UpdateOperation::class); - $operation->getTargetPackage() - ->will([$this->package,'reveal']) - ->shouldBeCalled() - ; - } else { - $operation = $this->prophesize(InstallOperation::class); - $operation - ->getPackage() - ->will([$this->package,'reveal']) - ->shouldBeCalled() - ; - } + call_user_func_array([$plugin,$eventName], [$event->reveal()]); + } - $this->event = $this->prophesize(PackageEvent::class); - $this->event - ->getOperation() - ->will([$operation,'reveal']) + public function getTestEventHandling() + { + return [ + ['onPostPackageInstall',Plugin::ADD_TYPE_INSTALL], + ['onPostPackageUpdate',Plugin::ADD_TYPE_INSTALL], + ['onPrePackageUninstall',Plugin::ADD_TYPE_REMOVE], + ]; + } + + public function testOnPostAutoloadDump() + { + $this->setupMock(); + $container = $this->prophesize(ContainerInterface::class); + $app = $this->prophesize(Application::class); + $manager = $this->prophesize(ModuleManager::class); + $options = $this->prophesize(ModuleOptions::class); + $mod1 = $this->prophesize(PermissionsFixerModuleInterface::class) + ->reveal(); + $modules = [$mod1]; + $dispatcher = $this->dispatcher; + + $app->getServiceManager()->willReturn($container); + $container->get('ModuleManager')->willReturn($manager); + $container->get('Core/Options')->willReturn($options->reveal()); + $manager->getLoadedModules()->willReturn($modules); + + $this->setupActivateDispatcher($dispatcher); + $assertEvent = function ($event) use ($mod1, $options) { + $this->assertInstanceOf(ConfigureEvent::class, $event); + $this->assertEquals($options->reveal(), $event->getOptions()); + + $modules = $event->getModules(); + $this->assertCount(2, $modules); + $this->assertContains($mod1, $modules); + $this->assertInstanceOf(CoreModule::class, $modules[1]); + return true; + }; + $dispatcher->dispatch(Plugin::YAWIK_CONFIGURE_EVENT, Argument::that($assertEvent)) ->shouldBeCalled() ; - $this->output = $this->prophesize(IOInterface::class); - $this->composer = $composer; - $this->operation = $operation; - $this->installManager = $installManager; - @mkdir($this->testDir.'/public'); + $plugin = new TestPlugin(); + $plugin->setInstalledModules(['Core']); + $plugin->setApplication($app->reveal()); + $plugin->activate($this->composer->reveal(), $this->output->reveal()); + $plugin->onPostAutoloadDump(); } } diff --git a/test/TestAssetInstaller.php b/test/TestAssetInstaller.php new file mode 100644 index 0000000..2b90a18 --- /dev/null +++ b/test/TestAssetInstaller.php @@ -0,0 +1,20 @@ +filesystem = $filesystem; + } +} diff --git a/test/TestOutputTrait.php b/test/TestOutputTrait.php new file mode 100644 index 0000000..21d4585 --- /dev/null +++ b/test/TestOutputTrait.php @@ -0,0 +1,46 @@ +output)) { + $this->output = new StreamOutput(fopen('php://memory', 'w')); + } + return $this->output; + } + + /** + * Gets the display returned by the last execution of the command. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = true) + { + $output = $this->output; + + rewind($output->getStream()); + + $display = stream_get_contents($output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } +} diff --git a/test/TestPermissionFixer.php b/test/TestPermissionFixer.php new file mode 100644 index 0000000..24f79c4 --- /dev/null +++ b/test/TestPermissionFixer.php @@ -0,0 +1,25 @@ +filesystem = $filesystem; + return $this; + } +} diff --git a/test/TestPlugin.php b/test/TestPlugin.php new file mode 100644 index 0000000..e3ac1f8 --- /dev/null +++ b/test/TestPlugin.php @@ -0,0 +1,53 @@ +dispatcher = $dispatcher; + } + + /** + * @param Application $application + */ + public function setApplication($application) + { + $this->application = $application; + } + + public function setInstalledModules(array $modules = []) + { + $this->installed = $modules; + } + + public function setUninstalledModules(array $modules = []) + { + $this->uninstalled = $modules; + } + + public function setOutput(IOInterface $output) + { + $this->output = $output; + } +} diff --git a/test/fixtures/public1/foo.js b/test/fixtures/foo/bar.js similarity index 100% rename from test/fixtures/public1/foo.js rename to test/fixtures/foo/bar.js diff --git a/test/fixtures/hello/world.js b/test/fixtures/hello/world.js new file mode 100644 index 0000000..e69de29 diff --git a/test/sandbox/config/config.php b/test/sandbox/config/config.php index 8922df8..d97aaf2 100644 --- a/test/sandbox/config/config.php +++ b/test/sandbox/config/config.php @@ -2,6 +2,7 @@ // chdir in config file so tests environment can chdir to this sandbox chdir(dirname(__DIR__)); +include_once __DIR__.'/../src/Module.php'; return [ 'modules' => [ 'Core', diff --git a/test/sandbox/src/Module.php b/test/sandbox/src/Module.php new file mode 100644 index 0000000..d81c941 --- /dev/null +++ b/test/sandbox/src/Module.php @@ -0,0 +1,26 @@ +