From 5dd473cd53cbf2704af244d4db8ead8456d5708c Mon Sep 17 00:00:00 2001 From: Arthur Suzuki <arthur.suzuki@biblibre.com> Date: Mon, 9 Aug 2021 17:26:35 +0200 Subject: [PATCH] hotline#139455: fix install skin button --- VERSIONS_HOTLINE/139455 | 1 + .../admin/controllers/IndexController.php | 7 + library/Class/Profil/SkinUpdateReader.php | 370 +++++++++++++++--- library/Class/TableDescription/CloneSkins.php | 34 ++ .../Class/TableDescription/UpdateSkins.php | 28 ++ .../ZendAfi/View/Helper/Admin/UpdateSkins.php | 72 ++-- library/startup.php | 2 +- scripts/update_skins.php | 5 +- .../IndexControllerUpdateSkinTest.php | 154 ++++++-- .../Class/Profil/SkinUpdateReaderTest.php | 246 +++++++++++- 10 files changed, 783 insertions(+), 136 deletions(-) create mode 100644 VERSIONS_HOTLINE/139455 create mode 100644 library/Class/TableDescription/CloneSkins.php create mode 100644 library/Class/TableDescription/UpdateSkins.php diff --git a/VERSIONS_HOTLINE/139455 b/VERSIONS_HOTLINE/139455 new file mode 100644 index 00000000000..444fc37ec34 --- /dev/null +++ b/VERSIONS_HOTLINE/139455 @@ -0,0 +1 @@ + - ticket #139455 : Administration : Correction de la mécanique d'ajout d'un thème. \ No newline at end of file diff --git a/application/modules/admin/controllers/IndexController.php b/application/modules/admin/controllers/IndexController.php index b1718abf6bc..6c739bbff7f 100644 --- a/application/modules/admin/controllers/IndexController.php +++ b/application/modules/admin/controllers/IndexController.php @@ -169,4 +169,11 @@ class Admin_IndexController extends ZendAfi_Controller_Action { $this->_helper->notify($message); $this->_redirect('admin/index/update-skin'); } + + + public function cloneSkinCancelAction() { + $this->getHelper('ViewRenderer')->setNoRender(); + $this->_helper->notify((new Class_Profil_SkinUpdateReader)->cancelClone($this->_getParam('id'))); + $this->_redirect('admin/index/update-skin'); + } } \ No newline at end of file diff --git a/library/Class/Profil/SkinUpdateReader.php b/library/Class/Profil/SkinUpdateReader.php index 7a5a32479a6..e2f6ec6c920 100644 --- a/library/Class/Profil/SkinUpdateReader.php +++ b/library/Class/Profil/SkinUpdateReader.php @@ -21,21 +21,44 @@ class Class_Profil_SkinUpdateReader { - use Trait_StaticFileWriter, + use + Trait_StaticFileWriter, Trait_Translator, Trait_TimeSource, Trait_StaticCommand; - public static $skins_update_log = 'skins_update_log.json', + protected static $_realtime; + + public static + $skins_update_log = 'skins_update_log.json', $command_pull = 'git reset --hard HEAD && git pull --rebase', + $skins_clone_log = 'skins_clone_log.json', $command_clone = 'git clone'; - public static function getLogPath() { + /** @category testing */ + public static function setRealtime($value) { + static::$_realtime = (bool)$value; + } + + + protected function _isRealtime() { + return isset(static::$_realtime) + ? static::$_realtime + : GIT_REALTIME; + } + + + public static function getUpdateLogPath() { return PATH_TEMP . static::$skins_update_log; } + public static function getCloneLogPath() { + return PATH_TEMP . static::$skins_clone_log; + } + + public static function getSkinsPath($folder) { return ROOT_PATH . (new Class_Profil())->getExtraPath() . $folder; } @@ -44,70 +67,126 @@ class Class_Profil_SkinUpdateReader { public function askGitPull() { $data = []; foreach($this->getUpdatableSkins() as $skin) - $data[$skin->getLabel()] = GIT_REALTIME - ? $this->_runGitPullIn($skin->getLabel()) - : ['Status' => $this->_('En attente depuis le %s', static::getCurrentDateTime())]; + $data[$skin] = $this->_askOneGitPull($skin); - return $this->_writeInLog($data, true); + return $this->_writeInUpdateLog($data, !$this->_isRealtime()); } - public function askGitClone($skin) { - if (GIT_REALTIME) - return $this->_runGitClone($skin); + protected function _askOneGitPull($label) { + $status = $this->_isRealtime() + ? $this->_runGitPullIn($label)->getMessage() + : $this->_('En attente depuis le %s', static::getCurrentDateTime()); - $data[$skin] = ['Status' => $this->_('En attente depuis le %s', static::getCurrentDateTime()), - 'url' => GIT_SKINS . '/' . $skin]; - return $this->_writeInLog($data, true); + return ['Status' => $status]; } public function runGitPull() { - if(!$this->_shouldRun()) - return $this->_('Aucune demande d\'exécution de la commande "%s" trouvée dans le fichier "%s"', static::$command_pull, static::$skins_update_log); + if (!$this->_shouldRunUpdate()) + return $this->_('Aucune demande d\'exécution de la commande "%s" trouvée dans le fichier "%s"', + static::$command_pull, static::$skins_update_log); $data = []; - foreach($this->getUpdatableSkins() as $skin) { - $data[$skin->getLabel()] = ['Status' => $this->_runGitPullIn($skin->getLabel())]; - } + foreach($this->getUpdatableSkins() as $skin) + $data[$skin] = ['Status' => $this->_runGitPullIn($skin)->getMessage()]; + + return $this->_writeInUpdateLog($data, false); + } + - $data = ($clone_data = $this->runGitClone()) - ? array_merge( $data , $clone_data) - : array_merge( $data , []); + protected function _runGitPullIn($folder) { + $commands = ['cd ./' . SKINS . '/' . $folder . ' 2>&1', + static::$command_pull . ' 2>&1', + 'cd ../../ 2>&1']; - return $this->_writeInLog($data, false); + return $this->_runAndLogCommands($commands); + } + + + public function askGitClone($label) { + $url = GIT_SKINS . '/' . $label . '.git'; + + if ($this->_isRealtime()) + return $this->_runRealtimeClone($label, $url); + + if (!$datas = $this->getCloneJson()) + $datas = []; + + $datas[$label] = ['status' => $this->_('En attente depuis le %s', static::getCurrentDateTime()), + 'url' => $url]; + + return $this->_writeInCloneLog($datas, true); + } + + + protected function _runRealtimeClone($label, $url) { + $clone_result = $this->_runGitCloneFrom($url); + $message = $clone_result->getMessage(); + if (!$clone_result->isSuccess()) + return $message; + + $update_datas = $this->getUpdateJson(); + $update_datas[$label] = ['Status' => $message]; + $this->_writeInUpdateLog($update_datas, null); + + return $message; } public function runGitClone() { - $data = []; - foreach ($this->getJson() as $skin) { - if (!is_array($skin)) - continue; + if (!$this->_shouldRunClone()) + return $this->_('Aucune demande d\'exécution de la commande "%s" trouvée dans le fichier "%s"', + static::$command_clone, static::$skins_clone_log); + + $clones_datas = $this->getCloneJson(); + $state = new Class_Profil_SkinUpdateReader_DataState($clones_datas, + $this->getUpdateJson()); + + foreach ($clones_datas as $label => $data) + $this->_runOneGitClone($label, $data, $state); + + if ($state->shouldWriteUpdate()) + $this->_writeInUpdateLog($state->getUpdate(), null); - if (!in_array('url',array_keys($skin))) - continue; + return $this->_writeInCloneLog($state->getClones(), false); + } + + + protected function _runOneGitClone($label, $data, $state) { + if (!is_array($data) || !isset($data['url'])) + return; - $label = end(explode('/', $skin['url'])); - $data[$label] = ['Status' => $this->_runGitClone($skin['url'])]; - } + $clone_result = $this->_runGitCloneFrom($data['url']); + $message = $clone_result->getMessage(); - return empty($data) - ? false - : $this->_writeInLog($data, false); + $clone_result->isSuccess() + ? $state->cloneBecomesUpdatable($label, $message) + : $state->setCloneMessage($label, $message); } - public function getJson() { - return json_decode($this->getFileWriter()->getContents(static::getLogPath()), true); + public function cancelClone($label) { + if (!$label || 'should_run' == $label) + return $this->_('Impossible d\'annuler l\'ajout d\'un thème inconnu'); + + $clones_datas = $this->getCloneJson(); + unset($clones_datas[$label]); + $this->_writeInCloneLog($clones_datas, null); + return $this->_('Ajout du thème "%s" annulé', $label); + } + + + public function getUpdateJson() { + return json_decode($this->getFileWriter()->getContents(static::getUpdateLogPath()), true); } public function getStatus($folder) { - if(!$folder || !($json = $this->getJson())) + if (!$folder || !($json = $this->getUpdateJson())) return ''; - if(!isset($json[$folder])) + if (!isset($json[$folder])) return ''; return $json[$folder]['Status']; @@ -115,11 +194,43 @@ class Class_Profil_SkinUpdateReader { public function getUpdatableSkins() { - $skins = []; - foreach((new Class_Profil())->getUpdatableSkins() as $skin_label) - $skins[] = (new Class_Entity())->updateAttributes(['Label' => $skin_label, - 'Status' => $this->getStatus($skin_label)]); - return $skins; + return (new Class_Profil)->getUpdatableSkins(); + } + + + public function getUpdatableSkinsCollection() { + $skins = array_map(function($label) + { + return new Class_Profil_SkinUpdateReader_Skin($label, + $this->getStatus($label)); + }, + $this->getUpdatableSkins()); + + return new Storm_Collection($skins); + } + + + public function getWaitingClones() { + if (!$datas = $this->getCloneJson()) + return []; + + $clones = array_map(function($data, $label) + { + return is_array($data) + ? new Class_Profil_SkinUpdateReader_Clone($label, + $data['status'], + $data['url']) + : null; + }, + $datas, + array_keys($datas)); + + return array_filter($clones); + } + + + public function getCloneJson() { + return json_decode($this->getFileWriter()->getContents(static::getCloneLogPath()), true); } @@ -129,39 +240,174 @@ class Class_Profil_SkinUpdateReader { $runner->exec($command); $output = '<br>' . implode('<br>', $runner->getOutput()); + $is_success = 0 === $runner->getReturnVar(); + $message = $is_success + ? static::getCurrentDateTime() . $output + : $this->_('La commande %s a échoué : %s', $command, $output); + + return new Class_Profil_SkinUpdateReader_ExecResult($is_success, $message); + } - if(0 !== $runner->getReturnVar()) - return $this->_('La commande %s a échoué : %s', $command, $output); - return static::getCurrentDateTime() . $output; + protected function _runGitCloneFrom($url) { + $commands = ['cd ' . SKINS . ' 2>&1', + static::$command_clone . ' ' . $url . ' 2>&1', + 'cd ' . ROOT_PATH . ' 2>&1']; + return $this->_runAndLogCommands($commands); } - protected function _runGitPullIn($folder) { - $commands = ['cd ./' . SKINS . '/' . $folder . ' 2>&1', - static::$command_pull . ' 2>&1', - 'cd ../../ 2>&1']; + protected function _writeInUpdateLog($data, $should_run) { + return $this->_writeInLog($data, static::getUpdateLogPath(), $should_run); + } - return $this->_runAndLogCommands($commands); + + protected function _writeInCloneLog($data, $should_run) { + return $this->_writeInLog($data, static::getCloneLogPath(), $should_run); } - protected function _runGitClone($url) { - $commands = ['cd ' . SKINS . ' 2>&1', - static::$command_clone . ' ' . $url . ' 2>&1', - 'cd ' . ROOT_PATH . ' 2>&1']; - return $this->_runAndLogCommands($commands); + protected function _writeInLog($data, $log_path, $should_run) { + if (is_bool($should_run)) + $data['should_run'] = $should_run; + + return static::getFileWriter()->putContents($log_path, json_encode($data)); + } + + + protected function _shouldRunUpdate() { + $json = $this->getUpdateJson(); + return isset($json['should_run']) && $json['should_run']; + } + + + protected function _shouldRunClone() { + $json = $this->getCloneJson(); + return isset($json['should_run']) && $json['should_run']; + } +} + + + + +class Class_Profil_SkinUpdateReader_DataState { + protected + $_should_write_update = false, + $_clones = [], + $_update = []; + + public function __construct($clones, $update) { + $this->_clones = $clones; + $this->_update = $update; + } + + + public function cloneBecomesUpdatable($label, $message) { + $this->_update[$label] = ['Status' => $message]; + unset($this->_clones[$label]); + $this->_should_write_update = true; + return $this; + } + + + public function setCloneMessage($label, $message) { + if (isset($this->_clones[$label])) + $this->_clones[$label]['status'] = $message; + + return $this; + } + + + public function shouldWriteUpdate() { + return $this->_should_write_update; + } + + + public function getUpdate() { + return $this->_update; + } + + + public function getClones() { + return $this->_clones; + } +} + + + + +class Class_Profil_SkinUpdateReader_ExecResult { + protected $_is_success, $_message; + + public function __construct($is_success, $message) { + $this->_is_success = $is_success; + $this->_message = $message; + } + + + public function isSuccess() { + return $this->_is_success; + } + + + public function getMessage() { + return $this->_message; + } +} + + + + +class Class_Profil_SkinUpdateReader_Skin { + use Trait_GetterByAttributeName; + + protected + $_label, + $_status; + + public function __construct($label, $status) { + $this->_label = $label; + $this->_status = $status; + } + + + public function getLabel() { + return $this->_label; + } + + + public function getStatus() { + return $this->_status; + } +} + + + + +class Class_Profil_SkinUpdateReader_Clone { + use Trait_GetterByAttributeName; + + protected + $_skin, + $_url; + + public function __construct($label, $status, $url) { + $this->_skin = new Class_Profil_SkinUpdateReader_Skin($label, $status); + $this->_url = $url; + } + + + public function getLabel() { + return $this->_skin->getLabel(); } - protected function _writeInLog($data, $should_run) { - $data['should_run'] = $should_run; - return static::getFileWriter()->putContents(static::getLogPath(), json_encode($data)); + public function getStatus() { + return $this->_skin->getStatus(); } - protected function _shouldRun() { - return $this->getJson()['should_run']; + public function getUrl() { + return $this->_url; } } -?> \ No newline at end of file diff --git a/library/Class/TableDescription/CloneSkins.php b/library/Class/TableDescription/CloneSkins.php new file mode 100644 index 00000000000..13b637a29e9 --- /dev/null +++ b/library/Class/TableDescription/CloneSkins.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved. + * + * BOKEH is free software; you can redistribute it and/or modify + * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by + * the Free Software Foundation. + * + * There are special exceptions to the terms and conditions of the AGPL as it + * is applied to this software (see README file). + * + * BOKEH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE + * along with BOKEH; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +class Class_TableDescription_CloneSkins extends Class_TableDescription_UpdateSkins { + public function init() { + parent::init(); + $this->addColumn($this->_('Url'), 'url') + ->addRowAction(['url' => ['action' => 'clone-skin-cancel', + 'id' => '%s'], + 'id' => function($model) { return $model->getLabel(); }, + 'icon' => 'delete', + 'label' => $this->_('Annuler'), + 'anchorOptions' => ['onclick' => 'return confirm(\'Êtes-vous sûr ?\')']]); + } +} diff --git a/library/Class/TableDescription/UpdateSkins.php b/library/Class/TableDescription/UpdateSkins.php new file mode 100644 index 00000000000..7c7835be2cc --- /dev/null +++ b/library/Class/TableDescription/UpdateSkins.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved. + * + * BOKEH is free software; you can redistribute it and/or modify + * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by + * the Free Software Foundation. + * + * There are special exceptions to the terms and conditions of the AGPL as it + * is applied to this software (see README file). + * + * BOKEH is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE + * along with BOKEH; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +class Class_TableDescription_UpdateSkins extends Class_TableDescription { + public function init() { + $this->addColumn($this->_('Thème'), 'label') + ->addColumn($this->_('Statut'), 'status'); + } +} diff --git a/library/ZendAfi/View/Helper/Admin/UpdateSkins.php b/library/ZendAfi/View/Helper/Admin/UpdateSkins.php index a6a1c1b84fb..04a6c9183c9 100644 --- a/library/ZendAfi/View/Helper/Admin/UpdateSkins.php +++ b/library/ZendAfi/View/Helper/Admin/UpdateSkins.php @@ -21,49 +21,57 @@ class ZendAfi_View_Helper_Admin_UpdateSkins extends ZendAfi_View_Helper_BaseHelper { + protected $_reader; public function Admin_UpdateSkins($reader) { - $skins = $reader->getUpdatableSkins(); - $html = $this->_tag('p', $this->_('Cette fonctionnalité nécessite la mise en place d\'une tâche planifiée sur le serveur d\'hébergement pour fonctionner.')); + if (!$this->_reader = $reader) + return ''; - if (Class_Users::isCurrentUserSuperAdmin()) - $html .= $this->view->renderForm((new ZendAfi_Form) - ->addElement('text', - 'url', - ['label' => $this->_('Nom du thème à installer'), - 'size' => 50, - 'required' => true, - 'allowEmpty' => false]) - ->setAction('/admin/index/update-skin/git/clone')); + return $this->_tag('p', + $this->_('Cette fonctionnalité nécessite la mise en place d\'une tâche planifiée sur le serveur d\'hébergement pour fonctionner.')) + . $this->_renderExisting() + . $this->_renderAdd() + ; + } - $html .= $this->view->button((new Class_Button) - ->setText($this->_('Demander la mise à jour')) - ->setUrl($this->view->url(['git' => 'pull'])) - ->setImage($this->view->tagImg(Class_Admin_Skin::current() - ->getIconUrl('buttons', - 'generate')))); - $lis = ''; - foreach($skins as $skin) - $lis .= $this->_tag('li', $this->_renderSkin($skin)); + protected function _renderExisting() { + return + $this->_tag('h2', + $this->_('Thèmes installés') + . $this->view->button((new Class_Button) + ->setText($this->_('Demander la mise à jour')) + ->setUrl($this->view->url(['git' => 'pull'])) + ->setImage($this->view->tagImg(Class_Admin_Skin::current() + ->getIconUrl('buttons', 'generate'))))) - $html .= $this->_tag('ul', $lis); - return $html; + . $this->view->renderTable(new Class_TableDescription_UpdateSkins('skins'), + $this->_reader->getUpdatableSkinsCollection()); } - protected function _renderSkin($skin) { - $header = $this->_tag('h2', $this->_('Thème %s', $skin->getLabel())); - $update_time = ($update_time = $skin->getStatus()) - ? $update_time - : $this->_('inconnu'); + protected function _renderAdd() { + if (!Class_Users::isCurrentUserSuperAdmin()) + return ''; + + $group_url = 'https://git.afi-sa.net/opac-skins'; + + return + $this->_tag('h2', $this->_('Ajouter un thème')) + . $this->_tag('p', $this->_('Limité aux dépôts présents dans %s', + $this->_tagAnchor($group_url, $group_url))) - $definitions = $this->_tag('dt', $this->_('Statut : ')) - . $this->_tag('dd', $update_time); + . $this->view->renderForm((new ZendAfi_Form) + ->addElement('text', + 'url', + ['label' => $this->_('Nom du thème à ajouter'), + 'size' => 50, + 'required' => true, + 'allowEmpty' => false]) + ->setAction('/admin/index/update-skin/git/clone')) - $content = $this->_tag('dl', $definitions); - return $header . $content; + . $this->view->renderTable(new Class_TableDescription_CloneSkins('waiting-clones'), + $this->_reader->getWaitingClones()); } } -?> \ No newline at end of file diff --git a/library/startup.php b/library/startup.php index 5e7781548a2..ce18e681e85 100644 --- a/library/startup.php +++ b/library/startup.php @@ -101,7 +101,7 @@ class Bokeh_Engine { defineConstant('SKINS', 'skins'); defineConstant('GIT_REALTIME', false); - defineConstant('GIT_SKINS', 'git://git.afi-sa.net/opac-skins'); + defineConstant('GIT_SKINS', 'git@git.afi-sa.net:opac-skins'); defineConstant('PATH_TEMP', ROOT_PATH . 'temp/'); diff --git a/scripts/update_skins.php b/scripts/update_skins.php index f2a73112a98..5ca6cb701de 100644 --- a/scripts/update_skins.php +++ b/scripts/update_skins.php @@ -1,4 +1,5 @@ <?php require('console.php'); -return (new Class_Profil_SkinUpdateReader())->runGitPull(); -?> \ No newline at end of file +$updater = new Class_Profil_SkinUpdateReader; +$updater->runGitPull(); +$updater->runGitClone(); diff --git a/tests/application/modules/admin/controllers/IndexControllerUpdateSkinTest.php b/tests/application/modules/admin/controllers/IndexControllerUpdateSkinTest.php index 990e4b02578..a443297099f 100644 --- a/tests/application/modules/admin/controllers/IndexControllerUpdateSkinTest.php +++ b/tests/application/modules/admin/controllers/IndexControllerUpdateSkinTest.php @@ -1,6 +1,6 @@ <?php /** - * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved. + * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved. * * BOKEH is free software; you can redistribute it and/or modify * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by @@ -21,7 +21,12 @@ abstract class IndexControllerUpdateSkinTestCase extends Admin_AbstractControllerTestCase { - protected $_file_writer; + protected + $_file_writer, + $_update_log_content, + $_update_log_data, + $_clone_log_content, + $_clone_log_data; public function setUp() { parent::setUp(); @@ -37,17 +42,57 @@ abstract class IndexControllerUpdateSkinTestCase extends Admin_AbstractControlle $this->_file_writer = $this->mock(); Class_Profil_SkinUpdateReader::setFileWriter($this->_file_writer); + $this->_update_log_content = json_encode(['Valence' => ['Status' => '25/04/2016 15:01:37']]); + $this->_clone_log_content = json_encode(['my-super-skin' => ['status' => 'En attente depuis longtemps...', + 'url' => 'git@git.afi-sa.net:opac-skins/my-super-skin.git'], + 'should_run' => false]); + $this->_file_writer ->whenCalled('getContents') - ->with(Class_Profil_SkinUpdateReader::getLogPath()) - ->answers(json_encode(['Valence' => ['Status' => '25/04/2016 15:01:37']])) + ->willDo(function($path) + { + if ($this->_updateLogPath() == $path) + return $this->_update_log_content; + + if ($this->_cloneLogPath() == $path) + return $this->_clone_log_content; + + return ''; + }) ->whenCalled('putContents') - ->with(Class_Profil_SkinUpdateReader::getLogPath(), - '{"Valence":{"Status":"En attente depuis le 2016-05-02 12:30:00"},"should_run":true}') - ->answers(true) + ->willDo(function($path, $contents) + { + if ($this->_updateLogPath() == $path) { + $this->_update_log_content = $contents; + return true; + } + + if ($this->_cloneLogPath() == $path) { + $this->_clone_log_content = $contents; + return true; + } + + return false; + }); + } + + + protected function _updateLogPath() { + return Class_Profil_SkinUpdateReader::getUpdateLogPath(); + } + - ->beStrict(); + protected function _cloneLogPath() { + return Class_Profil_SkinUpdateReader::getCloneLogPath(); + } + + + public function tearDown() { + Class_Profil_Skin::setFileSystem(null); + Class_Profil_SkinUpdateReader::setFileWriter(null); + Class_Profil_SkinUpdateReader::setTimeSource(null); + parent::tearDown(); } } @@ -69,13 +114,13 @@ class IndexControllerUpdateSkinDispatchTest extends IndexControllerUpdateSkinTes /** @test */ public function listShouldContainsSkinValence() { - $this->assertXPathContentContains('//ul/li/h2', 'Thème Valence'); + $this->assertXPathContentContains('//table[@id="skins"]//td', 'Valence'); } /** @test */ public function valenceShouldhaveBeenUpdatedOn25Slash04slash2016() { - $this->assertXPathContentContains('//ul/li/dl/dd', '25/04/2016 15:01:37'); + $this->assertXPathContentContains('//table[@id="skins"]//td', '25/04/2016 15:01:37'); } @@ -89,7 +134,43 @@ class IndexControllerUpdateSkinDispatchTest extends IndexControllerUpdateSkinTes /** @test */ public function formInstallSkinShouldBePresent() { $this->assertXPathContentContains('//form[@action="/admin/index/update-skin/git/clone"]', - 'Nom du thème à installer'); + 'Nom du thème à ajouter'); + } + + + /** @test */ + public function waitingCloneMySuperSkinShouldBePresent() { + $this->assertXPathContentContains('//table[@id="waiting-clones"]//td', + 'my-super-skin'); + } + + + /** @test */ + public function linkToCancelWaitingMySuperSkinCloneShouldBePresent() { + $this->assertXPath('//table[@id="waiting-clones"]//a[contains(@href, "/clone-skin-cancel/id/my-super-skin")]'); + } +} + + + + +class IndexControllerUpdateSkinCancelCloneTest extends IndexControllerUpdateSkinTestCase { + public function setUp() { + parent::setUp(); + $this->dispatch('admin/index/clone-skin-cancel/id/my-super-skin'); + $this->_clone_log_data = json_decode($this->_clone_log_content, true); + } + + + /** @test */ + public function messageShouldBeAjoutAnnule() { + $this->assertFlashMessengerContentContains('Ajout du thème "my-super-skin" annulé'); + } + + + /** @test */ + public function mySuperSkinShouldNotBeInCloneData() { + $this->assertFalse(isset($this->_clone_log_data['my-super-skin'])); } } @@ -100,12 +181,26 @@ class IndexControllerUpdateSkinDispatchTest extends IndexControllerUpdateSkinTes class IndexControllerUpdateSkinGitPullTest extends IndexControllerUpdateSkinTestCase { public function setUp() { parent::setUp(); - $this->dispatch('admin/index/update-skin/git/pull', true); + $this->dispatch('admin/index/update-skin/git/pull'); + $this->_update_log_data = json_decode($this->_update_log_content, true); + } + + + /** @test */ + public function logShouldBeMarkedAsRunnable() { + $this->assertTrue($this->_update_log_data['should_run']); + } + + + /** @test */ + public function valenceShouldBeWaiting() { + $this->assertEquals('En attente depuis le 2016-05-02 12:30:00', + $this->_update_log_data['Valence']['Status']); } /** @test */ - public function askSendShouldBeDisplay() { + public function updateAskedMessageShouldBeDisplay() { $this->assertFlashMessengerContentContains('La demande de mise à jour a été envoyée au serveur'); } } @@ -113,22 +208,35 @@ class IndexControllerUpdateSkinGitPullTest extends IndexControllerUpdateSkinTest -class IndexControllerInstallSkinGitCloneTest extends IndexControllerUpdateSkinTestCase { +class IndexControllerUpdateSkinGitCloneTest extends IndexControllerUpdateSkinTestCase { public function setUp() { parent::setUp(); - $this->_file_writer - ->whenCalled('putContents') - ->with(Class_Profil_SkinUpdateReader::getLogPath(), - '{"Valence":{"Status":"En attente depuis le 2016-05-02 12:30:00","url":"git:\/\/git.afi-sa.net\/opac-skins\/Valence"},"should_run":true}') - ->answers(true) - ->beStrict(); + $this->dispatch('admin/index/update-skin/git/clone/url/my-super-skin'); + $this->_clone_log_data = json_decode($this->_clone_log_content, true); + } + + + /** @test */ + public function mySuperSkinShouldBeWaitingClone() { + $this->assertTrue(isset($this->_clone_log_data['my-super-skin'])); + } - $this->dispatch('admin/index/update-skin/git/clone/url/Valence'); + + /** @test */ + public function mySuperSkinUrlShouldBeInOpacSkins() { + $this->assertEquals('git@git.afi-sa.net:opac-skins/my-super-skin.git', + $this->_clone_log_data['my-super-skin']['url']); } /** @test */ - public function askInstallShouldBeDisplay() { + public function waitingClonesShouldBeRunnable() { + $this->assertTrue($this->_clone_log_data['should_run']); + } + + + /** @test */ + public function installAskedMessageShouldBeDisplay() { $this->assertFlashMessengerContentContains('La demande d\'installation a été envoyée au serveur'); } -} \ No newline at end of file +} diff --git a/tests/library/Class/Profil/SkinUpdateReaderTest.php b/tests/library/Class/Profil/SkinUpdateReaderTest.php index dfd0520c43e..b95db2f96c2 100644 --- a/tests/library/Class/Profil/SkinUpdateReaderTest.php +++ b/tests/library/Class/Profil/SkinUpdateReaderTest.php @@ -1,6 +1,6 @@ <?php /** - * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved. + * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved. * * BOKEH is free software; you can redistribute it and/or modify * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by @@ -19,9 +19,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -class Class_SkinUpdateReaderTest extends ModelTestCase { - protected $_reader; +abstract class Class_SkinUpdateReaderTestCase extends ModelTestCase { + protected + $_reader, + $_file_writer, + $_update_log_content, + $_clone_log_content; public function setUp() { parent::setUp(); @@ -31,8 +34,8 @@ class Class_SkinUpdateReaderTest extends ModelTestCase { Class_Profil_Skin::setFileSystem($file_system); - $file_writer = $this->mock(); - Class_Profil_SkinUpdateReader::setFileWriter($file_writer); + $this->_file_writer = $this->mock(); + Class_Profil_SkinUpdateReader::setFileWriter($this->_file_writer); $time_source = new TimeSourceForTest('2016-05-02 12:30:00'); Class_Profil_SkinUpdateReader::setTimeSource($time_source); @@ -42,7 +45,16 @@ class Class_SkinUpdateReaderTest extends ModelTestCase { $command ->whenCalled('exec') - ->with('cd ./skins/Valence 2>&1 && git reset --hard HEAD && git pull --rebase 2>&1 && cd ../../ 2>&1') + ->with('cd ./skins/Valence 2>&1' + . ' && git reset --hard HEAD' + . ' && git pull --rebase 2>&1' + . ' && cd ../../ 2>&1') + ->answers('') + + ->whenCalled('exec') + ->with('cd skins 2>&1' + . ' && git clone git@git.afi-sa.net:opac-skins/Iep38.git 2>&1' + . ' && cd ' . ROOT_PATH . ' 2>&1') ->answers('') ->whenCalled('getOutput') @@ -53,26 +65,228 @@ class Class_SkinUpdateReaderTest extends ModelTestCase { ->beStrict(); - $file_writer + $this->_update_log_content = json_encode(['Valence' => ['Status' => 'En attente depuis le 2016-05-02 12:30:00'], + 'should_run' => true]); + + $this->_clone_log_content = json_encode(['Iep38' => ['Status' => 'En attente depuis le 2016-05-02 12:30:00', + 'url' => 'git@git.afi-sa.net:opac-skins/Iep38.git'], + 'should_run' => true]); + + $this->_file_writer ->whenCalled('getContents') - ->with(Class_Profil_SkinUpdateReader::getLogPath()) - ->answers(json_encode(['Valence' => ['Status' => '2016-05-02 12:30:00'], - 'should_run' => true])) + ->willDo(function($path) + { + if ($this->_updateLogPath() == $path) + return $this->_update_log_content; + + if ($this->_cloneLogPath() == $path) + return $this->_clone_log_content; + + return ''; + }) ->whenCalled('putContents') - ->with(Class_Profil_SkinUpdateReader::getLogPath(), - '{"Valence":{"Status":"2016-05-02 12:30:00<br>"},"should_run":false}') - ->answers(true) + ->willDo(function($path, $contents) + { + if ($this->_updateLogPath() == $path) { + $this->_update_log_content = $contents; + return true; + } - ->beStrict(); + if ($this->_cloneLogPath() == $path) { + $this->_clone_log_content = $contents; + return true; + } + + return false; + }); $this->_reader = new Class_Profil_SkinUpdateReader(); + } + + + protected function _updateLogPath() { + return Class_Profil_SkinUpdateReader::getUpdateLogPath(); + } + + + protected function _cloneLogPath() { + return Class_Profil_SkinUpdateReader::getCloneLogPath(); + } + + + public function tearDown() { + Class_Profil_Skin::setFileSystem(null); + Class_Profil_SkinUpdateReader::setFileWriter(null); + Class_Profil_SkinUpdateReader::setTimeSource(null); + Class_Profil_SkinUpdateReader::setCommand(null); + Class_Profil_SkinUpdateReader::setRealtime(null); + + parent::tearDown(); + } +} + + + + +class Class_SkinUpdateReaderRunPullTest extends Class_SkinUpdateReaderTestCase { + public function setUp() { + parent::setUp(); $this->_reader->runGitPull(); } /** @test */ public function valenceShouldHaveBeenUpdated() { - $this->assertEquals('2016-05-02 12:30:00', $this->_reader->getStatus('Valence')); + $this->assertEquals('2016-05-02 12:30:00<br>', $this->_reader->getStatus('Valence')); + } + + + /** @test */ + public function iep38ShouldNotHaveBeenCloned() { + $this->assertEquals('', $this->_reader->getStatus('Iep38')); + } +} + + + + +class Class_SkinUpdateReaderAskRealtimePullTest extends Class_SkinUpdateReaderTestCase { + public function setUp() { + parent::setUp(); + Class_Profil_SkinUpdateReader::setRealtime(true); + $this->_reader->askGitPull(); + } + + + /** @test */ + public function valenceShouldHaveBeenUpdated() { + $this->assertEquals('2016-05-02 12:30:00<br>', $this->_reader->getStatus('Valence')); + } + + + /** @test */ + public function iep38ShouldNotHaveBeenCloned() { + $this->assertEquals('', $this->_reader->getStatus('Iep38')); + } +} + + + + +class Class_SkinUpdateReaderRunCloneTest extends Class_SkinUpdateReaderTestCase { + public function setUp() { + parent::setUp(); + $this->_reader->runGitClone(); + } + + + /** @test */ + public function valenceShouldNotHaveBeenUpdated() { + $this->assertEquals('En attente depuis le 2016-05-02 12:30:00', + $this->_reader->getStatus('Valence')); + } + + + /** @test */ + public function iep38ShouldHaveBeenCloned() { + $this->assertEquals('2016-05-02 12:30:00<br>', + $this->_reader->getStatus('Iep38')); + } + + + /** @test */ + public function iep38ShouldNotBeWaitingClone() { + $this->assertNotContains('Iep38', $this->_clone_log_content); + } +} + + + + +class Class_SkinUpdateReaderRunCloneWithErrorTest extends Class_SkinUpdateReaderTestCase { + public function setUp() { + parent::setUp(); + $command = $this->mock(); + Class_Profil_SkinUpdateReader::setCommand($command); + + $command + ->whenCalled('exec') + ->with('cd skins 2>&1' + . ' && git clone git@git.afi-sa.net:opac-skins/Iep38.git 2>&1' + . ' && cd ' . ROOT_PATH . ' 2>&1') + ->answers('') + + ->whenCalled('getOutput') + ->answers(['error: git: bad trip']) + + ->whenCalled('getReturnVar') + ->answers(99) + + ->beStrict(); + + $this->_reader->runGitClone(); + } + + + /** @test */ + public function valenceShouldNotHaveBeenUpdated() { + $this->assertEquals('En attente depuis le 2016-05-02 12:30:00', + $this->_reader->getStatus('Valence')); + } + + + /** @test */ + public function iep38ShouldNotHaveBeenCloned() { + $this->assertEquals('', $this->_reader->getStatus('Iep38')); + } + + + /** @test */ + public function iep38ErrorShouldBeInWaitingCloneStatus() { + $this->assertContains('error: git: bad trip', $this->_clone_log_content); + } +} + + + + +class Class_SkinUpdateReaderAskRealtimeCloneTest extends Class_SkinUpdateReaderTestCase { + public function setUp() { + parent::setUp(); + Class_Profil_SkinUpdateReader::setRealtime(true); + $datas = ['Valence' => ['Status' => 'En attente depuis le 2016-05-02 12:30:00'], + 'should_run' => true]; + $this->_clone_log_content = json_encode($datas); + + $this->_reader->askGitClone('Iep38'); + } + + + /** @test */ + public function valenceShouldNotHaveBeenUpdated() { + $this->assertEquals('En attente depuis le 2016-05-02 12:30:00', + $this->_reader->getStatus('Valence')); + } + + + /** @test */ + public function updateShouldStillBeRunnable() { + $this->assertTrue(json_decode($this->_update_log_content, true)['should_run']); + } + + + /** @test */ + public function cloneIep38CommandShouldHaveBeenExecuted() { + $this->assertContains('git clone git@git.afi-sa.net:opac-skins/Iep38.git', + Class_Profil_SkinUpdateReader::getCommand() + ->getFirstAttributeForLastCallOn('exec')); + } + + + /** @test */ + public function iep38ShouldHaveUpdatableStatus() { + $this->assertEquals('2016-05-02 12:30:00<br>', + $this->_reader->getStatus('Iep38')); } } -- GitLab