From 0696942ad2756e05abc56ab3f102786adc689445 Mon Sep 17 00:00:00 2001 From: efalcy <stl@gresille.org> Date: Thu, 12 Jun 2014 18:56:30 +0200 Subject: [PATCH] dev #1278 autoupdate cosmogramme script : migrate in opac (add controller and tag) --- .../DatabaseMigrationController.php | 34 ++++ .../scripts/database-migration/migrate.phtml | 4 + console.php | 15 +- cosmogramme/php/upgrade_db.php | 106 +------------ library/Class/Migration/Patchs.php | 147 ++++++++++++++++++ library/Class/Migration/ScriptPatchs.php | 70 +++++++++ library/Class/Systeme/Include.php | 41 +++++ library/Class/Testing/FileSystem.php | 2 +- library/ZendAfi/View/Helper/TagMigration.php | 99 ++++++++++++ tests/library/Class/MigrationTest.php | 141 +++++++++++++++++ 10 files changed, 553 insertions(+), 106 deletions(-) create mode 100644 application/modules/opac/controllers/DatabaseMigrationController.php create mode 100644 application/modules/opac/views/scripts/database-migration/migrate.phtml create mode 100644 library/Class/Migration/Patchs.php create mode 100644 library/Class/Migration/ScriptPatchs.php create mode 100644 library/Class/Systeme/Include.php create mode 100644 library/ZendAfi/View/Helper/TagMigration.php create mode 100644 tests/library/Class/MigrationTest.php diff --git a/application/modules/opac/controllers/DatabaseMigrationController.php b/application/modules/opac/controllers/DatabaseMigrationController.php new file mode 100644 index 00000000000..c3caa268a08 --- /dev/null +++ b/application/modules/opac/controllers/DatabaseMigrationController.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved. + * + * AFI-OPAC 2.0 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). + * + * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +class DatabaseMigrationController extends Zend_Controller_Action { + public function init() { +// $this->getHelper('ViewRenderer')->setNoRender(); + } + + public function migrateAction() { + xdebug_break(); + $this->view->reprise=$this->_request->getParam('reprise',0); + $this->view->skip_php=$this->_request->getParam('skip_php',0); + } +} +?> \ No newline at end of file diff --git a/application/modules/opac/views/scripts/database-migration/migrate.phtml b/application/modules/opac/views/scripts/database-migration/migrate.phtml new file mode 100644 index 00000000000..4562336cd6f --- /dev/null +++ b/application/modules/opac/views/scripts/database-migration/migrate.phtml @@ -0,0 +1,4 @@ +<?php +$this->tagMigration($this->reprise); + +?> diff --git a/console.php b/console.php index a16e66b8bed..7bbaad74b75 100644 --- a/console.php +++ b/console.php @@ -19,6 +19,19 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -require('includes.php'); +$base_path = realpath(dirname(__FILE__)); +set_include_path($base_path . '/library' . PATH_SEPARATOR . + $base_path . '/library/storm/src' . PATH_SEPARATOR . + $base_path . '/library/storm/zf/library' . PATH_SEPARATOR . + get_include_path()); + +define("BASE_URL", "/"); + +include_once "local.php"; +include_once "fonctions/fonctions.php"; +require_once "Zend/Loader.php"; +require_once "library/startup.php"; + setupOpac(); + ?> \ No newline at end of file diff --git a/cosmogramme/php/upgrade_db.php b/cosmogramme/php/upgrade_db.php index b1a00dccf6d..c097913038f 100644 --- a/cosmogramme/php/upgrade_db.php +++ b/cosmogramme/php/upgrade_db.php @@ -7,109 +7,7 @@ require_once 'classes/classe_cosmopaths.php'; $cosmo_path = new CosmoPaths(); define('BASE_URL', $cosmo_path->getBaseUrl()); -require_once($basePath.'/../storm_init.php'); - -$sql = Zend_Registry::get('sql'); -$patch_level = Class_CosmoVar::find("patch_level"); - -function versionForFile($file) { - return (int)substr($file, -7, 3); -} - -function extensionForFile($file) { - return substr($file, -4); -} - -// Recup des patches a executer -$path = realpath(dirname(__FILE__)).'/../sql/patch/'; -$handle = @opendir($path); -if (!$handle) - echo('Impossible d\'ouvrir le dossier : '.$path); - -$scripts = []; -while (false !== ($fic = readdir($handle))) { - if (!in_array(extensionForFile($fic), ['.sql', '.php'])) - continue; - $numero = versionForFile($fic); - if ($numero > $patch_level->getValeur()) - $scripts[] = $path . $fic; -} - -sort($scripts); -closedir($handle); - -foreach($scripts as $script) { - $num_patch = versionForFile($script); - echo('Execution patch n° ' . $num_patch ); - - switch(extensionForFile($script)) { - case '.php': - runPhpUpgrade($script);break; - case '.sql': - runSqlUpgrade($script, $sql);break; - } - - // Ecrire le patch dans la base - $patch_level->setValeur($num_patch)->save(); -} -echo('Mise à niveau de la base effectuée avec succès'); -exit; - -function runSqlUpgrade($script, $sql) { - $num_instruction = 0; - $data = file($script); - - $instructions = []; - $contents = implode(' ', $data); - if (false !== strpos($contents, 'CREATE FUNCTION')) - $instructions[] = $contents; - else { - $instruction = ''; - foreach($data as $ligne) { - $ligne = trim($ligne); - if (!$ligne or substr($ligne,0,2)=="--") continue; - - $instruction.=$ligne.' '; - if(substr($ligne,-1)!=";") - continue; - - $instructions[] = $instruction; - $instruction = ''; - } - } - - - foreach($instructions as $instruction) { - echo($instruction."\n"); - flush(); - // Executer - $num_instruction++; - - $sql->execute($instruction); - - } -} - -function runPhpUpgrade($script) { - - set_error_handler( - function($no, $message, $file, $line, $context) { - echo('Erreur :'.$message - . " \nfile : " . $file - . " \nline : " . $line - ); - die(255); - }); - - try { - include $script; - } catch (Exception $e) { - echo("Code : ".$e->getCode()."\n"); - echo("Erreur :".$e->getMessage()); - - exit; - } -} - +require_once($basePath.'/../storm_init.php'); + (new Class_Migration_ScriptPatchs())->run(); ?> \ No newline at end of file diff --git a/library/Class/Migration/Patchs.php b/library/Class/Migration/Patchs.php new file mode 100644 index 00000000000..48680a97d9e --- /dev/null +++ b/library/Class/Migration/Patchs.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved. + * + * AFI-OPAC 2.0 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). + * + * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +class Class_Migration_Patchs { + use Trait_StaticFileSystem; + const PATCH_PATH="./cosmogramme/sql/patch"; + public $force=false; + function extensionForFile($file) { + return substr($file, -4); + } + + function setForce($force) { + $this->force=$force; + return $this; + } + + + function versionForFile($filename) { + return (int)preg_replace('/.*\/patch_(.*)\.(php|sql)/', '\1', $filename); + } + + public function getLastPatchNumber() { + $ids=[]; + foreach (self::getFileSystem()->glob(self::PATCH_PATH."/patch_*") as $filename) { + $ids[]=$this->versionForFile($filename); + } + if (empty($ids)) + return 0; + rsort($ids); + return $ids[0]; + } + + public function getMatchingPatchFiles($patch_level) { + $scripts=[]; + foreach (self::getFileSystem()->glob(self::PATCH_PATH."/patch_*") as $filename) { + if (!in_array($this->extensionForFile($filename), ['.sql', '.php'])) + continue; + $numero = $this->versionForFile($filename); + if ($numero > (int)$patch_level) + $scripts[] = $filename; + } + sort($scripts); + return $scripts; + } + + + public function acceptVisitor($visitor) { + $basePath = dirname(realpath(__FILE__)); + + $patch_level = Class_CosmoVar::find("patch_level"); + $visitor->visitHeader($patch_level,$this->getLastPatchNumber()); +// Recup des patches a executer + $ok=true; + foreach($this->getMatchingPatchFiles($patch_level->getValeur()) as $script) { + $num_patch = $this->versionForFile($script); + echo('Execution patch n° ' . $num_patch ); + + switch($this->extensionForFile($script)) { + case '.php': + $this->runPhpUpgrade($script,$visitor);break; + case '.sql': + $ok=$this->runSqlUpgrade($script, $visitor);break; + } + if ($ok) $patch_level->setValeur($num_patch)->save(); + else return; + } + echo('Mise à niveau de la base effectuée avec succès'); + + } + + + function runPhpUpgrade($script,$visitor) { + if ($visitor->visitIsForceUpgrade()) + return ; + $visitor->visitPhpUpgrade($script); + } + + + function runSqlUpgrade($script,$visitor) { + xdebug_break(); + $num_instruction = 0; + $data = self::getFileSystem()->file($script); + + $instructions = []; + $contents = implode(' ', $data); + if (false !== strpos($contents, 'CREATE FUNCTION')) + $instructions[] = $contents; + else { + $instruction = ''; + foreach($data as $ligne) { + $ligne = trim($ligne); + if (!$ligne or substr($ligne,0,2)=="--") continue; + + $instruction.=$ligne.' '; + if(substr($ligne,-1)!=";") + continue; + + $instructions[] = $instruction; + $instruction = ''; + } + } + + + foreach($instructions as $instruction) { + flush(); + // Executer + $num_instruction++; + + if ($visitor->visitSqlSkip()) + continue; + try { + $sql = Zend_Registry::get('sql'); + $sql->execute($instruction); + + } catch(Exception $e) { + $visitor->visitSqlException($e,$num_instruction); + if (!$this->force) + return false; + } + } + + + return true; + } + + +} +?> \ No newline at end of file diff --git a/library/Class/Migration/ScriptPatchs.php b/library/Class/Migration/ScriptPatchs.php new file mode 100644 index 00000000000..bc2e3e0cdb1 --- /dev/null +++ b/library/Class/Migration/ScriptPatchs.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved. + * + * AFI-OPAC 2.0 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). + * + * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +class Class_Migration_ScriptPatchs { + + public function run($force=false) { + $this->migration=(new Class_Migration_Patchs()); + $this->migration->setForce($force)->acceptVisitor($this); + } + + public function visitPhpUpgrade($script) { + set_error_handler( + function($no, $message, $file, $line, $context) { + echo('Erreur :'.$message + . " \nfile : " . $file + . " \nline : " . $line + ); + die(255); + }); + + try { + Class_Systeme_Include::getInstance()->include_file($script); + } catch (Exception $e) { + echo("Code : ".$e->getCode()."\n"); + echo("Erreur :".$e->getMessage()); + + exit; + } + + } + + public function visitHeader($patch_level) { + } + + public function visitDisplayInstruction($instruction) { + echo($instruction."\n"); + } + + public function visitSqlSkip() { + return false; + } + public function visitSqlException($e,$num_instruction) { + echo("Code : ".$e->getCode()."\n"); + echo("Erreur :".$e->getMessage()); + } + + public function visitIsForceUpgrade() { + return $this->force; + } +} +?> \ No newline at end of file diff --git a/library/Class/Systeme/Include.php b/library/Class/Systeme/Include.php new file mode 100644 index 00000000000..45d1f81d171 --- /dev/null +++ b/library/Class/Systeme/Include.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved. + * + * AFI-OPAC 2.0 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). + * + * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +class Class_Systeme_Include { + protected static $_instance; + + public static function setInstance($instance) { + static::$_instance = $instance; + } + + + public static function getInstance() { + if (!isset(static::$_instance)) + static::$_instance = new self(); + return static::$_instance; + } + + + public function include_file($file_path) { + include $file_path; + } +} + +?> diff --git a/library/Class/Testing/FileSystem.php b/library/Class/Testing/FileSystem.php index 2db688b67ef..f62030d7550 100644 --- a/library/Class/Testing/FileSystem.php +++ b/library/Class/Testing/FileSystem.php @@ -23,7 +23,7 @@ class Class_Testing_FileSystem { protected $_known_functions = ['rmdir', 'unlink', 'fopen', 'fseek', 'fgets', 'filesize', 'fclose', 'ftell', 'fread', 'feof', 'getcwd', 'file_exists', 'scandir', 'is_dir', - 'opendir', 'readdir', 'closedir', 'mkdir']; + 'opendir', 'readdir', 'closedir', 'mkdir','glob','file']; public function __call($name, $args) { if (!in_array($name, $this->_known_functions)) diff --git a/library/ZendAfi/View/Helper/TagMigration.php b/library/ZendAfi/View/Helper/TagMigration.php new file mode 100644 index 00000000000..1c9028cc385 --- /dev/null +++ b/library/ZendAfi/View/Helper/TagMigration.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved. + * + * AFI-OPAC 2.0 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). + * + * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +class ZendAfi_View_Helper_TagMigration extends Zend_View_Helper_HtmlElement { + protected $reprise; + protected $skip_php; + public function tagMigration($reprise=false,$skip_php=false) { + $this->reprise=$reprise; + $this->skip_php=$skip_php; + (new Class_Migration_Patchs())->acceptVisitor($this); + + } + public function visitHeader($patch_level,$last_patch) { + print '<h1>Mise à niveau de la base de données</h1>'; + print('<h3>Niveau de patch : ' . $patch_level->getValeur() . ' / ' . $last_patch . '</h3>'); + } + + + public function visitPhpUpgrade($script) { + set_error_handler( + function($no, $message, $file, $line, $context) { + print('<h3 class="erreur">Erreur PHP</h3>'); + print('<div class="erreur_sql">'); + print("<b>Code : </b>".$no.BR); + print('<b>Erreur : </b>'.$message + . ' file : ' . $file + . ' line : ' . $line + .BR.BR); + print($this->view->tagAnchor($this->view->url(['skip_php' => 1]), "Ignorer l'erreur et continuer")); + + print('</div>'); + die(255); + }); + + try { + include $script; + } catch (Exception $e) { + print('<h3 class="erreur">Erreur PHP</h3>'); + print('<div class="erreur_sql">'); + print("<b>Code : </b>".$e->getCode().BR); + print('<b>Erreur : </b>'.$e->getMessage().BR.BR); + print($this->view->tagAnchor($this->view->url(['skip_php' => 1]), "Ignorer l'erreur et continuer")); + + print('</div>'); + exit; + } + + } + + public function visitSqlSkip() { + if (isset($this->reprise) and ($this->reprise > 0) and ($this->reprise >= $num_instruction)) + return true; + return false; + } + + public function visitDisplayInstruction($instruction) { + print($instruction . BR); + } + + public function visitSqlException($e,$num_instruction) { + + print('<h3 class="erreur">Erreur SQL</h3>'); + print('<div class="erreur_sql">'); + print("<b>Code : </b>".$e->getCode().BR); + print('<b>Erreur : </b>'.$e->getMessage().BR.BR); + print($this->view->tagAnchor($this->view->url(['reprise' => $num_instruction]), "Ignorer l'erreur et continuer")); + print('</div>'); + + } + + + public function visitIsForceUpgrade() { + if (!$this->skip_php) + return false; + $this->skip_php=0; + return true; + } + } +} +?> \ No newline at end of file diff --git a/tests/library/Class/MigrationTest.php b/tests/library/Class/MigrationTest.php new file mode 100644 index 00000000000..ddec1d99256 --- /dev/null +++ b/tests/library/Class/MigrationTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright (c) 2012-2014, Agence Française Informatique (AFI). All rights reserved. + * + * AFI-OPAC 2.0 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). + * + * AFI-OPAC 2.0 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 AFI-OPAC 2.0; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +class Class_MigrationPatchsWithEmptyDirTest extends Storm_Test_ModelTestCase { + public function setup() { + $this->migration = new Class_Migration_Patchs(); + $this->fixture('Class_CosmoVar', ['id' => 1, + 'clef' => 'patch_level', + 'valeur' => 10 ] ); + Class_Migration_Patchs::setFileSystem(Storm_Test_ObjectWrapper::mock() + ->whenCalled('glob') + ->answers([] + )); + + } + + + + /** @test */ + public function lastPatchNumberShouldReturn0() { + $this->assertEquals('0',$this->migration->getLastPatchNumber()); + } +} + + +class Class_MigrationPatchsTest extends Storm_Test_ModelTestCase { + public function setup() { + $this->migration = new Class_Migration_Patchs(); + $this->fixture('Class_CosmoVar', ['id' => 1, + 'clef' => 'patch_level', + 'valeur' => 10 ] ); + Class_Migration_Patchs::setFileSystem(Storm_Test_ObjectWrapper::mock() + ->whenCalled('glob') + ->answers(['./cosmogramme/sql/patch/patch_03.sql', + './cosmogramme/sql/patch/patch_13.sql', + './cosmogramme/sql/patch/patch_10.sql', + './cosmogramme/sql/patch/patch_14.php', + './cosmogramme/sql/patch/patch_5.sql' + ] + )); + + } + + + + /** @test */ + public function lastPatchNumberShouldReturn14() { + $this->assertEquals('14',$this->migration->getLastPatchNumber()); + } + + /** @test */ + public function runPatchDirectoryShouldReturnScripts() { + $this->assertEquals(['./cosmogramme/sql/patch/patch_13.sql', + './cosmogramme/sql/patch/patch_14.php'], + $this->migration->getMatchingPatchFiles(10)); + } + + + +} + +class Class_MigrationScriptPatchsTest extends Storm_Test_ModelTestCase { + + public function setup() { + $this->migration = new Class_Migration_ScriptPatchs(); + $this->fixture('Class_CosmoVar', [ + 'id' => 'patch_level', + 'valeur' => 10 ] ); + Class_Migration_Patchs::setFileSystem(Storm_Test_ObjectWrapper::mock() + ->whenCalled('glob') + ->answers(['./cosmogramme/sql/patch/patch_03.sql', + './cosmogramme/sql/patch/patch_13.sql', + './cosmogramme/sql/patch/patch_10.sql', + './cosmogramme/sql/patch/patch_14.php', + './cosmogramme/sql/patch/patch_12.sql', + './cosmogramme/sql/patch/patch_5.sql' + ]) + ->whenCalled('file') + ->with('./cosmogramme/sql/patch/patch_14.sql') + ->answers([ 'update working;']) + ->whenCalled('file') + ->with('./cosmogramme/sql/patch/patch_13.sql') + ->answers(['update not working;', + '-- commented update;', + 'update working;']) + ->whenCalled('file') + ->with('./cosmogramme/sql/patch/patch_14.sql') + ->answers([ 'update working;']) + ); + $this->_mock_system_include = Storm_Test_ObjectWrapper::mock() + ->whenCalled('include_file') + ->answers(true); + + $mock_sql = Storm_Test_ObjectWrapper::mock(); + $mock_sql->whenCalled('execute') + ->with('update not working; ') + ->answers($this->throwException(new Exception('database error'))) + ->whenCalled('execute') + ->with('update working; ') + ->answers(true); + + Zend_Registry::set('sql', $mock_sql); + + Class_Systeme_Include::setInstance($this->_mock_system_include); + } + + /** @test */ + public function runPatchShouldUpgradePatchLevelTo12() { + (new Class_Migration_ScriptPatchs())->run(); + $this->assertEquals(12,Class_CosmoVar::find('patch_level')->getValeur()); + } + + + /** @test */ + public function runForcePatchShouldUpgradePatchLevelTo14() { + (new Class_Migration_ScriptPatchs())->run(true); + $this->assertEquals(14,Class_CosmoVar::find('patch_level')->getValeur()); + } + + + +} +?> \ No newline at end of file -- GitLab