From f569157368b0208eafe4a59c2722a3e0383d677b Mon Sep 17 00:00:00 2001 From: Daniel Thee Roperto Date: Fri, 16 Sep 2016 17:26:48 +1000 Subject: [PATCH 1/2] Issue #27 - CLI implemented. Added full test coverage to CLI classes. --- auth.php | 7 +- classes/cli/clibase.php | 133 +++++++++++++++++ classes/cli/cliexception.php | 30 ++++ classes/cli/create.php | 208 +++++++++++++++++++++++++++ classes/cli/finish.php | 106 ++++++++++++++ classes/cli/waitforit.php | 178 +++++++++++++++++++++++ cli/create.php | 48 +++++++ cli/finish.php | 39 +++++ cli/waitforit.php | 39 +++++ lang/en/auth_outage.php | 31 ++++ renderer.php | 28 ++-- tests/cli/cli_testcase.php | 61 ++++++++ tests/cli/create_test.php | 270 +++++++++++++++++++++++++++++++++++ tests/cli/finish_test.php | 64 +++++++++ tests/cli/waitforit_test.php | 171 ++++++++++++++++++++++ tests/phpunit.xml | 31 ++++ 16 files changed, 1429 insertions(+), 15 deletions(-) create mode 100644 classes/cli/clibase.php create mode 100644 classes/cli/cliexception.php create mode 100644 classes/cli/create.php create mode 100644 classes/cli/finish.php create mode 100644 classes/cli/waitforit.php create mode 100644 cli/create.php create mode 100644 cli/finish.php create mode 100644 cli/waitforit.php create mode 100644 tests/cli/cli_testcase.php create mode 100644 tests/cli/create_test.php create mode 100644 tests/cli/finish_test.php create mode 100644 tests/cli/waitforit_test.php create mode 100644 tests/phpunit.xml diff --git a/auth.php b/auth.php index 6a12f9c..b04abe6 100644 --- a/auth.php +++ b/auth.php @@ -24,17 +24,14 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ -if (!defined('MOODLE_INTERNAL')) { - die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. -} +defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/authlib.php'); /** * Class auth_plugin_outage */ -class auth_plugin_outage extends auth_plugin_base -{ +class auth_plugin_outage extends auth_plugin_base { /** * Constructor. */ diff --git a/classes/cli/clibase.php b/classes/cli/clibase.php new file mode 100644 index 0000000..d0eb62c --- /dev/null +++ b/classes/cli/clibase.php @@ -0,0 +1,133 @@ +. + +namespace auth_outage\cli; + +use core\session\manager; +use InvalidArgumentException; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->libdir . '/clilib.php'); + +/** + * Outage CLI base class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class clibase { + /** + * @var array Options passed as parameters to the CLI. + */ + protected $options; + + /** + * @var int The reference time to use when creating an outage. + */ + protected $time; + + /** + * clibase constructor. + * @param array|null $options The parameters to use or null to read from the command line. + * @throws cliexception + */ + public function __construct(array $options = null) { + $this->becomeadmin(); + + if (is_null($options)) { + // Using Moodle CLI API to read the parameters. + list($options, $unrecognized) = cli_get_params($this->generateoptions(), $this->generateshortcuts()); + if ($unrecognized) { + $unrecognized = implode("\n ", $unrecognized); + throw new cliexception(get_string('cliunknowoption', 'admin', $unrecognized)); + } + } else { + // If not using Moodle CLI API to read parameters, ensure all keys exist. + $default = $this->generateoptions(); + foreach ($options as $k => $v) { + if (!array_key_exists($k, $default)) { + throw new cliexception(get_string('cliunknowoption', 'admin', $k)); + } + $default[$k] = $v; + } + $options = $default; + } + + $this->options = $options; + $this->time = time(); + } + + /** + * Sets the reference time for creating outages. + * @param int $time Timestamp for the reference time. + */ + public function set_referencetime($time) { + if (!is_int($time) || ($time <= 0)) { + throw new InvalidArgumentException('$time must be a positive int.'); + } + $this->time = $time; + } + + /** + * Generates all options (parameters) available for the CLI command. + * @return array Options. + */ + public abstract function generateoptions(); + + /** + * Generate all short forms for the available options. + * @return array Short form options. + */ + public abstract function generateshortcuts(); + + /** + * Executes the CLI script. + */ + public abstract function execute(); + + /** + * Change session to admin user. + */ + protected function becomeadmin() { + global $DB; + $user = $DB->get_record('user', array('id' => 2)); + unset($user->description); + unset($user->access); + unset($user->preference); + manager::init_empty_session(); + manager::set_user($user); + } + + /** + * Outputs a help message. + * @param string $cliname Name of CLI used in the language file. + */ + protected function showhelp($cliname) { + $options = $this->generateoptions(); + $shorts = array_flip($this->generateshortcuts()); + + printf("%s\n\n", get_string('cli' . $cliname . 'help', 'auth_outage')); + foreach (array_keys($options) as $long) { + $text = get_string('cli' . $cliname . 'param' . $long, 'auth_outage'); + $short = isset($shorts[$long]) ? ('-' . $shorts[$long] . ',') : ''; + $long = '--' . $long; + printf(" %-4s %-20s %s\n", $short, $long, $text); + } + } +} diff --git a/classes/cli/cliexception.php b/classes/cli/cliexception.php new file mode 100644 index 0000000..2ae4b1f --- /dev/null +++ b/classes/cli/cliexception.php @@ -0,0 +1,30 @@ +. + +namespace auth_outage\cli; + +use Exception; + +/** + * Exception executing CLI. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cliexception extends Exception { +} diff --git a/classes/cli/create.php b/classes/cli/create.php new file mode 100644 index 0000000..0275c07 --- /dev/null +++ b/classes/cli/create.php @@ -0,0 +1,208 @@ +. + +namespace auth_outage\cli; + +use auth_outage\models\outage; +use auth_outage\outagedb; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage CLI to create outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class create extends clibase { + /** + * @var array Defaults to use if given option is null. + */ + private $defaults; + + /** + * Generates all options (parameters) available for the CLI command. + * @return array Options. + */ + public function generateoptions() { + // Do not provide some defaults, if cloning an outage we need to know which parameters were provided. + $options = [ + 'help' => false, + 'clone' => null, + 'warn' => null, + 'start' => null, + 'duration' => null, + 'title' => null, + 'description' => null, + 'onlyid' => false, + 'block' => false, + ]; + return $options; + } + + /** + * Generate all short forms for the available options. + * @return array Short form options. + */ + public function generateshortcuts() { + return [ + 'b' => 'block', + 'c' => 'clone', + 'd' => 'duration', + 'e' => 'description', + 'h' => 'help', + 's' => 'start', + 't' => 'title', + 'w' => 'warn', + ]; + } + + /** + * Sets the default values for options. + * @param array $defaults Defaults. + */ + public function set_defaults(array $defaults) { + $this->defaults = $defaults; + } + + /** + * Executes the CLI. + */ + public function execute() { + // Help always overrides any other parameter. + if ($this->options['help']) { + $this->showhelp('create'); + return; + } + + // If not help mode, 'start' is required and cannot use default. + if (is_null($this->options['start'])) { + throw new cliexception(get_string('clierrormissingparamaters', 'auth_outage')); + } + + // If cloning, set defaults to outage being cloned. + if (!is_null($this->options['clone'])) { + $this->clonedefaults(); + } + + // Merge provided parameters with defaults then create outage. + $options = $this->mergeoptions(); + $id = $this->createoutage($options); + + if ($options['block']) { + $block = new waitforit(['outageid' => $id]); + $block->execute(); + } + } + + /** + * Merges provided options with defaults, checking and converting types as needed. + * @return array Parameters to use. + * @throws cliexception + */ + private function mergeoptions() { + $options = $this->options; + // Merge with defaults. + if (!is_null($this->defaults)) { + foreach ($options as $k => $v) { + if (is_null($v) && array_key_exists($k, $this->defaults)) { + $options[$k] = $this->defaults[$k]; + } + } + } + + return $this->mergeoptions_checkparameters($options); + } + + /** + * Creates an outages based on the provided options. + * @param array $options Options used to create the outage. + * @return int Id of the new outage. + */ + private function createoutage(array $options) { + // We need to become an admin to avoid permission problems. + $this->becomeadmin(); + + // Create the outage. + $start = $this->time + ($options['start'] * 60); + $outage = new outage([ + 'warntime' => $start - ($options['warn'] * 60), + 'starttime' => $start, + 'stoptime' => $start + ($options['duration'] * 60), + 'title' => $options['title'], + 'description' => $options['description'], + ]); + $id = outagedb::save($outage); + + // All done! + if ($options['onlyid']) { + printf("%d\n", $id); + } else { + printf("%s\n", get_string('clioutagecreated', 'auth_outage', ['id' => $id])); + } + + return $id; + } + + private function clonedefaults() { + $id = $this->options['clone']; + if (!is_number($id) || ($id <= 0)) { + throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'clone'])); + } + + $outage = outagedb::get_by_id((int)$id); + $this->set_defaults([ + 'warn' => (int)($outage->get_warning_duration() / 60), + 'duration' => (int)($outage->get_duration() / 60), + 'title' => $outage->title, + 'description' => $outage->description, + ]); + } + + /** + * Check parameters converting their type as needed. + * @param array $options Input options. + * @return array Output options. + * @throws cliexception + */ + private function mergeoptions_checkparameters(array $options) { + // Check parameters that must be a non-negative int while converting their type to int. + foreach (['start', 'warn', 'duration'] as $param) { + if (!is_number($options[$param])) { + throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param])); + } + $options[$param] = (int)$options[$param]; + if ($options[$param] < 0) { + throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param])); + } + } + + // Check parameters that must be a non empty string. + foreach (['title', 'description'] as $param) { + if (!is_string($options[$param])) { + throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param])); + } + $options[$param] = trim($options[$param]); + if (strlen($options[$param]) == 0) { + throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => $param])); + } + } + + return $options; + } +} diff --git a/classes/cli/finish.php b/classes/cli/finish.php new file mode 100644 index 0000000..a1d56e3 --- /dev/null +++ b/classes/cli/finish.php @@ -0,0 +1,106 @@ +. + +namespace auth_outage\cli; + +use auth_outage\models\outage; +use auth_outage\outagedb; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage CLI to finish an outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class finish extends clibase { + /** + * Generates all options (parameters) available for the CLI command. + * @return array Options. + */ + public function generateoptions() { + // Do not provide some defaults, if cloning an outage we need to know which parameters were provided. + $options = [ + 'help' => false, + 'outageid' => null, + 'active' => false, + ]; + return $options; + } + + /** + * Generate all short forms for the available options. + * @return array Short form options. + */ + public function generateshortcuts() { + return [ + 'h' => 'help', + 'id' => 'outageid', + 'a' => 'active', + ]; + } + + /** + * Executes the CLI. + */ + public function execute() { + // Help always overrides any other parameter. + if ($this->options['help']) { + $this->showhelp('finish'); + return; + } + + // Requires outageid or active but not both at the same time. + $byid = !is_null($this->options['outageid']); + $byactive = $this->options['active']; + if ($byid == $byactive) { + throw new cliexception(get_string('cliwaitforiterroridxoractive', 'auth_outage')); + } + + $outage = $this->get_outage(); + if (!$outage->is_ongoing()) { + throw new cliexception(get_string('clifinishnotongoing', 'auth_outage')); + } + + outagedb::finish($outage->id, $this->time); + } + + /** + * Gets the outage to finish. + * @return outage|null The outage to wait for. + * @throws cliexception + */ + private function get_outage() { + if ($this->options['active']) { + $outage = outagedb::get_active(); + } else { + $id = $this->options['outageid']; + if (!is_number($id) || ($id <= 0)) { + throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'outageid'])); + } + $outage = outagedb::get_by_id((int)$id); + } + + if (is_null($outage)) { + throw new cliexception(get_string('clierroroutagenotfound', 'auth_outage')); + } + + return $outage; + } +} diff --git a/classes/cli/waitforit.php b/classes/cli/waitforit.php new file mode 100644 index 0000000..8ec4ed7 --- /dev/null +++ b/classes/cli/waitforit.php @@ -0,0 +1,178 @@ +. + +namespace auth_outage\cli; + +use auth_outage\models\outage; +use auth_outage\outagedb; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Outage CLI to wait for an outage to start. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class waitforit extends clibase { + /** + * Default value if --sleep no provided. + */ + const DEFAULT_SLEEP_SECONDS = 300; + + /** + * @var callable Alternative callback for sleeping thread, must return the new reference timestamp. + */ + private $sleepcallback = null; + + /** + * Generates all options (parameters) available for the CLI command. + * @return array Options. + */ + public function generateoptions() { + // Do not provide some defaults, if cloning an outage we need to know which parameters were provided. + $options = [ + 'help' => false, + 'outageid' => null, + 'active' => false, + 'verbose' => false, + 'sleep' => self::DEFAULT_SLEEP_SECONDS, + ]; + return $options; + } + + /** + * Generate all short forms for the available options. + * @return array Short form options. + */ + public function generateshortcuts() { + return [ + 'h' => 'help', + 'id' => 'outageid', + 'a' => 'active', + 'v' => 'verbose', + 's' => 'sleep', + ]; + } + + /** + * Sets a callback to be used instead of the sleep method. + * @param callable $callback Callback function. + */ + public function set_sleepcallback(callable $callback) { + $this->sleepcallback = $callback; + } + + /** + * Executes the CLI. + */ + public function execute() { + // Help always overrides any other parameter. + if ($this->options['help']) { + $this->showhelp('waitforit'); + return; + } + + // Requires outageid or active but not both at the same time. + $byid = !is_null($this->options['outageid']); + $byactive = $this->options['active']; + if ($byid == $byactive) { + throw new cliexception(get_string('cliwaitforiterroridxoractive', 'auth_outage')); + } + + $this->verbose('Verbose mode activated.'); + + $outage = $this->get_outage(); + + while ($sleep = $this->waitforoutagestart($outage)) { + if (is_null($this->sleepcallback)) { + $this->verbose('Sleeping for ' . $sleep . ' second(s).'); + sleep($sleep); + $this->time = time(); + } else { + $this->verbose('Calling callback to sleep ' . $sleep . ' second(s).'); + $callback = $this->sleepcallback; + $this->time = $callback($sleep); + } + } + } + + /** + * Shows a message if in verbose mode. + * @param string $message Message. + */ + private function verbose($message) { + if (!$this->options['verbose']) { + return; + } + + $time = strftime('%F %T %Z'); + printf("[%s] %s\n", $time, $message); + } + + /** + * Gets the outage to wait for. + * @return outage|null The outage to wait for. + * @throws cliexception + */ + private function get_outage() { + if ($this->options['active']) { + $this->verbose('Querying database for active outage...'); + $outage = outagedb::get_active(); + } else { + $id = $this->options['outageid']; + if (!is_number($id) || ($id <= 0)) { + throw new cliexception(get_string('clierrorinvalidvalue', 'auth_outage', ['param' => 'outageid'])); + } + $this->verbose('Querying database for outage #' . $id . '...'); + $outage = outagedb::get_by_id((int)$id); + } + + if (is_null($outage)) { + throw new cliexception(get_string('clierroroutagenotfound', 'auth_outage')); + } + + $this->verbose('Found outage #' . $outage->id . ': ' . $outage->get_title()); + return $outage; + } + + private function waitforoutagestart(outage $outage) { + $this->verbose('Checking outage status...'); + // Outage should not change while waiting to start. + if (outagedb::get_by_id($outage->id) != $outage) { + throw new cliexception(get_string('clierroroutagechanged', 'auth_outage')); + } + // Outage cannot have already ended. + if ($outage->has_ended($this->time)) { + throw new cliexception(get_string('clierroroutageended', 'auth_outage')); + } + // If outage has started, do not wait. + if ($outage->is_ongoing($this->time)) { + printf("%s\n", get_string('cliwaitforitoutagestarted', 'auth_outage')); + return 0; + } + // Outage nas not started yet. + $countdown = $outage->starttime - $this->time; + printf("%s\n", get_string( + 'cliwaitforitoutagestartingin', + 'auth_outage', + ['countdown' => format_time($countdown)] + )); + return min($countdown, $this->options['sleep']); + } +} diff --git a/cli/create.php b/cli/create.php new file mode 100644 index 0000000..4e94124 --- /dev/null +++ b/cli/create.php @@ -0,0 +1,48 @@ +. + +/** + * CLI for creating outages. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\cli\cliexception; +use auth_outage\cli\create; + +define('CLI_SCRIPT', true); +require('../../config.php'); + +$cli = new create(); + +$config = get_config('auth_outage'); +$cli->set_defaults([ + 'help' => false, + 'warn' => (int)($config->warning_duration), + 'start' => null, + 'duration' => (int)($config->default_duration), + 'title' => $config->warning_title, + 'description' => $config->warning_description, +]); + +try { + $cli->execute(); +} catch (cliexception $e) { + cli_error($e->getMessage()); +} diff --git a/cli/finish.php b/cli/finish.php new file mode 100644 index 0000000..00daa0d --- /dev/null +++ b/cli/finish.php @@ -0,0 +1,39 @@ +. + +/** + * CLI for finishing an outage. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\cli\cliexception; +use auth_outage\cli\finish; + +define('CLI_SCRIPT', true); +require('../../config.php'); + +$cli = new finish(); + +try { + $cli->execute(); +} catch (cliexception $e) { + cli_error($e->getMessage()); +} + diff --git a/cli/waitforit.php b/cli/waitforit.php new file mode 100644 index 0000000..fd87d8f --- /dev/null +++ b/cli/waitforit.php @@ -0,0 +1,39 @@ +. + +/** + * CLI for waiting (blocking) until an outage starts. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use auth_outage\cli\cliexception; +use auth_outage\cli\waitforit; + +define('CLI_SCRIPT', true); +require('../../config.php'); + +$cli = new waitforit(); + +try { + $cli->execute(); +} catch (cliexception $e) { + cli_error($e->getMessage()); +} + diff --git a/lang/en/auth_outage.php b/lang/en/auth_outage.php index 76277d3..4e08833 100644 --- a/lang/en/auth_outage.php +++ b/lang/en/auth_outage.php @@ -24,6 +24,36 @@ */ $string['auth_outagedescription'] = 'Auxiliary plugin that warns users about a future outage and prevents them from logging in once the outage starts.'; +$string['clicreatehelp'] = 'Creates a new outage.'; +$string['clicreateparamblock'] = 'blocks until outage starts.'; +$string['clicreateparamclone'] = 'clone another outage except for the start time.'; +$string['clicreateparamdescription'] = 'the description of the outage.'; +$string['clicreateparamduration'] = 'how many minutes should the outage last.'; +$string['clicreateparamhelp'] = 'shows parameters help.'; +$string['clicreateparamonlyid'] = 'only outputs the new outage id, useful for scripts.'; +$string['clicreateparamstart'] = 'in how many minutes should this outage start. Required.'; +$string['clicreateparamtitle'] = 'the title of the outage.'; +$string['clicreateparamwarn'] = 'how many minutes before it starts to display a warning.'; +$string['clifinishhelp'] = 'Finishes an ongoing outage.'; +$string['clifinishnotongoing'] = 'Outage is not ongoing.'; +$string['clifinishparamhelp'] = 'shows parameters help.'; +$string['clifinishparamactive'] = 'finishes the currently active outage.'; +$string['clifinishparamoutageid'] = 'the id of the outage to finish.'; +$string['cliwaitforiterroridxoractive'] = 'You must use --outageid=# or --active parameter but not both.'; +$string['cliwaitforithelp'] = 'Waits until an outage starts.'; +$string['cliwaitforitoutagestarted'] = 'Outage started!'; +$string['cliwaitforitoutagestartingin'] = 'Outage starting in {$a->countdown}.'; +$string['cliwaitforitparamactive'] = 'wait for the currently active outage.'; +$string['cliwaitforitparamhelp'] = 'shows parameters help.'; +$string['cliwaitforitparamoutageid'] = 'the id of the outage to wait until it starts.'; +$string['cliwaitforitparamsleep'] = 'maximum amount of seconds before status output.'; +$string['cliwaitforitparamverbose'] = 'enable verbose mode.'; +$string['clierrorinvalidvalue'] = 'Invalid value for parameter: {$a->param}'; +$string['clierrormissingparamaters'] = 'You must specify the start time, use --help for more information.'; +$string['clierroroutagechanged'] = 'Outage was changed while waiting.'; +$string['clierroroutageended'] = 'Outage has already ended.'; +$string['clierroroutagenotfound'] = 'Outage not found.'; +$string['clioutagecreated'] = 'Outage created, id: {$a->id}'; $string['clone'] = 'Clone'; $string['datetimeformat'] = '%a %d %h %Y at %I:%M%P %Z'; $string['defaultlayoutcss'] = 'Layout CSS'; @@ -51,6 +81,7 @@ $string['menudefaults'] = 'Default Settings'; $string['menumanage'] = 'Manage'; $string['messageoutageongoing'] = 'Back online at {$a->stop}.'; $string['messageoutagewarning'] = 'Shutting down in {{countdown}}'; +$string['na'] = 'n/a'; $string['notfound'] = 'No outages found.'; $string['outageedit'] = 'Edit Outage'; $string['outageclone'] = 'Clone Outage'; diff --git a/renderer.php b/renderer.php index 0adcf4c..f09eb2b 100644 --- a/renderer.php +++ b/renderer.php @@ -111,17 +111,25 @@ class auth_outage_renderer extends plugin_renderer_base { private function renderoutage(outage $outage, $buttons) { global $OUTPUT; - $created = core_user::get_user($outage->createdby, 'firstname,lastname', MUST_EXIST); - $created = html_writer::link( - new moodle_url('/user/profile.php', ['id' => $outage->createdby]), - trim($created->firstname . ' ' . $created->lastname) - ); + if ($outage->createdby == 0) { + $created = get_string('na', 'auth_outage'); + } else { + $created = core_user::get_user($outage->createdby, 'firstname,lastname', MUST_EXIST); + $created = html_writer::link( + new moodle_url('/user/profile.php', ['id' => $outage->createdby]), + trim($created->firstname . ' ' . $created->lastname) + ); + } - $modified = core_user::get_user($outage->modifiedby, 'firstname,lastname', MUST_EXIST); - $modified = html_writer::link( - new moodle_url('/user/profile.php', ['id' => $outage->modifiedby]), - trim($modified->firstname . ' ' . $modified->lastname) - ); + if ($outage->modifiedby == 0) { + $modified = get_string('na', 'auth_outage'); + } else { + $modified = core_user::get_user($outage->modifiedby, 'firstname,lastname', MUST_EXIST); + $modified = html_writer::link( + new moodle_url('/user/profile.php', ['id' => $outage->modifiedby]), + trim($modified->firstname . ' ' . $modified->lastname) + ); + } $url = new moodle_url('/auth/outage/edit.php', ['id' => $outage->id]); $img = html_writer::empty_tag( diff --git a/tests/cli/cli_testcase.php b/tests/cli/cli_testcase.php new file mode 100644 index 0000000..74268bf --- /dev/null +++ b/tests/cli/cli_testcase.php @@ -0,0 +1,61 @@ +. + +use auth_outage\cli\clibase; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Tests performed on CLIs. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cli_testcase extends advanced_testcase { + public function setUp() { + $this->resetAfterTest(true); + $this->set_parameters([]); + parent::setUp(); + } + + /** + * Mocks the command line parameters. + * @param array $options Options to use as parameters. + */ + protected function set_parameters(array $options) { + array_unshift($options, 'cli.php'); + $_SERVER['argv'] = $options; + $_SERVER['argc'] = count($options); + } + + /** + * Executes the CLI. + * @param clibase $cli CLI to execute. + * @return string The output text. + */ + protected function execute(clibase $cli) { + ob_start(); + try { + $cli->execute(); + $text = ob_get_contents(); + return $text; + } finally { + ob_end_clean(); + } + } +} diff --git a/tests/cli/create_test.php b/tests/cli/create_test.php new file mode 100644 index 0000000..9ba720a --- /dev/null +++ b/tests/cli/create_test.php @@ -0,0 +1,270 @@ +. + +use auth_outage\cli\cliexception; +use auth_outage\cli\create; +use auth_outage\models\outage; +use auth_outage\outagedb; + +defined('MOODLE_INTERNAL') || die(); +require_once('cli_testcase.php'); + +/** + * Tests performed on CLI create class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @SuppressWarnings("public") + */ +class create_test extends cli_testcase { + public function test_noarguments() { + $cli = new create(); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } + + public function test_invalidargumentparam() { + $this->set_parameters(['--aninvalidparameter']); + $this->setExpectedException(cliexception::class); + new create(); + } + + public function test_invalidargumentgiven() { + $this->setExpectedException(cliexception::class); + new create(['anotherinvalidparameter']); + } + + public function test_invalidparam_notanumber() { + $cli = new create(['start' => 'some day']); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } + + public function test_invalidparam_negative() { + $cli = new create(['start' => -1]); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } + + public function test_invalidparam_emptystring() { + $cli = new create(['start' => 0, 'title' => '']); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } + + public function test_invalidparam_notastring() { + $cli = new create(['start' => 0, 'title' => true]); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } + + public function test_setreferencetime_invalid() { + $cli = new create(['start' => 0]); + $this->setExpectedException(InvalidArgumentException::class); + $cli->set_referencetime(-1); + } + + public function test_help() { + $this->set_parameters(['--help']); + $cli = new create(); + $output = $this->execute($cli); + self::assertContains('Creates', $output); + self::assertContains('--help', $output); + } + + public function test_options() { + $cli = new create(); + + $options = $cli->generateoptions(); + foreach (array_keys($options) as $k) { + self::assertTrue(is_string($k)); + } + + $shorts = $cli->generateshortcuts(); + foreach ($shorts as $s) { + self::assertArrayHasKey($s, $options); + } + } + + public function test_create_withoptions() { + $this->set_parameters([ + '--warn=10', + '--start=0', + '--duration=30', + '--title=A Title', + '--description=A Description', + ]); + $now = time(); + $cli = new create(); + $cli->set_referencetime($now); + $text = $this->execute($cli); + self::assertContains('created', $text); + // Check creted outage. + list(, $id) = explode(':', $text); + $id = (int)$id; + $outage = outagedb::get_by_id($id); + self::assertSame($now, $outage->starttime); + self::assertSame(10 * 60, $outage->get_warning_duration()); + self::assertSame(30 * 60, $outage->get_duration()); + self::assertNull($outage->finished); + self::assertSame('A Title', $outage->title); + self::assertSame('A Description', $outage->description); + } + + public function test_create_onlyid() { + $this->set_parameters([ + '--onlyid', + '--warn=10', + '--start=0', + '--duration=30', + '--title=Title', + '--description=Description', + ]); + $now = time(); + $cli = new create(); + $cli->set_referencetime($now); + $id = $this->execute($cli); + // Check if the id contains is only a number (parameter onlyid). + $id = trim($id); + self::assertTrue(is_number($id)); + $id = (int)$id; + // Check creted outage. + $outage = outagedb::get_by_id($id); + self::assertSame($now, $outage->starttime); + self::assertSame($outage->starttime - (10 * 60), $outage->warntime); + self::assertSame($outage->starttime + (30 * 60), $outage->stoptime); + self::assertNull($outage->finished); + self::assertSame('Title', $outage->title); + self::assertSame('Description', $outage->description); + } + + public function test_create_withdefaults() { + $this->set_parameters([ + '--warn=100', + '--start=50', + ]); + $now = time(); + $cli = new create(); + $cli->set_referencetime($now); + $cli->set_defaults([ + 'warn' => 50, + 'start' => 200, + 'duration' => 300, + 'title' => 'Default Title', + 'description' => 'Default Description', + ]); + $text = $this->execute($cli); + self::assertContains('created', $text); + // Check creted outage. + list(, $id) = explode(':', $text); + $id = (int)$id; + $outage = outagedb::get_by_id($id); + self::assertSame($now + (50 * 60), $outage->starttime, 'Wrong starttime.'); + self::assertSame($outage->starttime - (100 * 60), $outage->warntime, 'Wrong warntime.'); + self::assertSame($outage->starttime + (300 * 60), $outage->stoptime, 'Wrong stoptime.'); + self::assertNull($outage->finished); + self::assertSame('Default Title', $outage->title); + self::assertSame('Default Description', $outage->description); + } + + public function test_create_withclone() { + $this->setAdminUser(); + $now = time(); + // Create the outage to clone. + $original = new outage([ + 'warntime' => $now - 120, + 'starttime' => $now, + 'stoptime' => $now + 120, + 'title' => 'Title', + 'description' => 'Description', + ]); + $id = outagedb::save($original); + // Clone it using CLI. + $this->set_parameters([ + '--onlyid', + '--start=60', + '--clone=' . $id, + ]); + $cli = new create(); + $cli->set_referencetime($now); + $id = trim($this->execute($cli)); + // Check cloned data. + $cloned = outagedb::get_by_id((int)$id); + self::assertSame($now + (60 * 60), $cloned->starttime); + self::assertSame($original->get_warning_duration(), $cloned->get_warning_duration()); + self::assertSame($original->get_duration(), $cloned->get_duration()); + self::assertSame($original->title, $cloned->title); + self::assertSame($original->description, $cloned->description); + } + + public function test_create_withclone_invalid() { + $this->setExpectedException(cliexception::class); + $this->set_parameters([ + '--start=60', + '--clone=-1', + ]); + $cli = new create(); + $this->execute($cli); + } + + public function test_create_withblock() { + // Not an extensive test in the blocking API, cliwaitforit tests should cover them deeper. + $this->set_parameters([ + '--block', + '--warn=10', + '--start=0', + '--duration=30', + '--title=Title', + '--description=Description', + ]); + $now = time(); + $cli = new create(); + $cli->set_referencetime($now); + $text = $this->execute($cli); + self::assertContains('created', $text); + self::assertContains('started', $text); + } +} diff --git a/tests/cli/finish_test.php b/tests/cli/finish_test.php new file mode 100644 index 0000000..d2f9095 --- /dev/null +++ b/tests/cli/finish_test.php @@ -0,0 +1,64 @@ +. + +use auth_outage\cli\cliexception; +use auth_outage\cli\finish; + +defined('MOODLE_INTERNAL') || die(); +require_once('cli_testcase.php'); + +/** + * Tests performed on CLI finish class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class finish_test extends cli_testcase { + public function test_constructor() { + $cli = new finish(); + self::assertNotNull($cli); + } + + public function test_options() { + $cli = new finish(); + + $options = $cli->generateoptions(); + foreach (array_keys($options) as $k) { + self::assertTrue(is_string($k)); + } + + $shorts = $cli->generateshortcuts(); + foreach ($shorts as $s) { + self::assertArrayHasKey($s, $options); + } + } + + public function test_help() { + $this->set_parameters(['--help']); + $cli = new finish(); + $text = $this->execute($cli); + self::assertContains('Finishes', $text); + self::assertContains('--help', $text); + } + + public function test_noarguments() { + $cli = new finish(); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } +} diff --git a/tests/cli/waitforit_test.php b/tests/cli/waitforit_test.php new file mode 100644 index 0000000..5be7881 --- /dev/null +++ b/tests/cli/waitforit_test.php @@ -0,0 +1,171 @@ +. + +use auth_outage\cli\cliexception; +use auth_outage\cli\waitforit; +use auth_outage\models\outage; +use auth_outage\outagedb; + +defined('MOODLE_INTERNAL') || die(); +require_once('cli_testcase.php'); + +/** + * Tests performed on CLI waitforit class. + * + * @package auth_outage + * @author Daniel Thee Roperto + * @copyright Catalyst IT + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @SuppressWarnings("public") + */ +class waitforit_test extends cli_testcase { + public function test_constructor() { + $cli = new waitforit(); + self::assertNotNull($cli); + } + + public function test_generateoptions() { + $cli = new waitforit(); + $options = $cli->generateoptions(); + foreach (array_keys($options) as $k) { + self::assertTrue(is_string($k)); + } + } + + public function test_generateshortcuts() { + $cli = new waitforit(); + $options = $cli->generateoptions(); + $shorts = $cli->generateshortcuts(); + foreach ($shorts as $s) { + self::assertArrayHasKey($s, $options); + } + } + + public function test_help() { + $this->set_parameters(['--help']); + $cli = new waitforit(); + $text = $this->execute($cli); + self::assertContains('Waits', $text); + self::assertContains('--help', $text); + } + + public function test_bothparams() { + $this->set_parameters(['--outageid=1', '--active']); + $cli = new waitforit(); + $this->setExpectedException(cliexception::class); + $cli->execute(); + } + + public function test_invalidoutageid() { + $this->set_parameters(['-id=-1']); + $cli = new waitforit(); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } + + public function test_outagenotfound() { + $this->set_parameters(['-a']); + $cli = new waitforit(); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } + + public function test_endedoutage() { + $this->setAdminUser(); + $now = time(); + $id = outagedb::save(new outage([ + 'warntime' => $now - 200, + 'starttime' => $now - 100, + 'stoptime' => $now - 50, + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-id=' . $id]); + $cli = new waitforit(); + $cli->set_referencetime($now); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } + + public function test_activeverbose() { + $this->setAdminUser(); + $now = time(); + outagedb::save(new outage([ + 'warntime' => $now - 10, + 'starttime' => $now + 1, + 'stoptime' => $now + 10, + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-v', '--active']); + $cli = new waitforit(); + $cli->set_referencetime($now); + $output = $this->execute($cli); + self::assertContains('Verbose mode', $output); + self::assertContains('starting in 1 sec', $output); + self::assertContains('started', $output); + } + + public function test_countdown() { + $this->setAdminUser(); + $now = time(); + outagedb::save(new outage([ + 'warntime' => $now, + 'starttime' => $now + 45, + 'stoptime' => $now + (60 * 60), + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-v', '--active', '--sleep=30']); + $cli = new waitforit(); + $cli->set_referencetime($now); + $cli->set_sleepcallback(function ($sleep) use (&$now) { + $now += $sleep; + return $now; + }); + $output = $this->execute($cli); + self::assertContains("starting in 45", $output); + self::assertContains("sleep 30 second", $output); + self::assertContains("starting in 15", $output); + self::assertContains("sleep 15 second", $output); + self::assertContains("started!", $output); + } + + public function test_outagechanged() { + $this->setAdminUser(); + $now = time(); + $id = outagedb::save(new outage([ + 'warntime' => $now, + 'starttime' => $now + (2 * 60 * 60), + 'stoptime' => $now + (60 * 60), + 'title' => 'Title', + 'description' => 'Description', + ])); + $this->set_parameters(['-v', '--active', '--sleep=30']); + $cli = new waitforit(); + $cli->set_referencetime($now); + $cli->set_sleepcallback(function () use ($id) { + // Change outage when not expected to. + $outage = outagedb::get_by_id($id); + $outage->title = 'New title!'; + outagedb::save($outage); + // Pretend it is time to start, but it should get an error instead. + return $outage->starttime; + }); + $this->setExpectedException(cliexception::class); + $this->execute($cli); + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml new file mode 100644 index 0000000..7466503 --- /dev/null +++ b/tests/phpunit.xml @@ -0,0 +1,31 @@ + + + + + + ./ + + + + + + ../classes + + + From 267048534102cbcfdce2faee148c385c237551d3 Mon Sep 17 00:00:00 2001 From: Daniel Thee Roperto Date: Mon, 19 Sep 2016 15:55:57 +1000 Subject: [PATCH 2/2] Issue #27 - Changed deltas for CLI from minutes to seconds. --- classes/cli/create.php | 10 +++++----- lang/en/auth_outage.php | 6 +++--- tests/cli/create_test.php | 20 ++++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/classes/cli/create.php b/classes/cli/create.php index 0275c07..58d88b9 100644 --- a/classes/cli/create.php +++ b/classes/cli/create.php @@ -139,11 +139,11 @@ class create extends clibase { $this->becomeadmin(); // Create the outage. - $start = $this->time + ($options['start'] * 60); + $start = $this->time + $options['start']; $outage = new outage([ - 'warntime' => $start - ($options['warn'] * 60), + 'warntime' => $start - $options['warn'], 'starttime' => $start, - 'stoptime' => $start + ($options['duration'] * 60), + 'stoptime' => $start + $options['duration'], 'title' => $options['title'], 'description' => $options['description'], ]); @@ -167,8 +167,8 @@ class create extends clibase { $outage = outagedb::get_by_id((int)$id); $this->set_defaults([ - 'warn' => (int)($outage->get_warning_duration() / 60), - 'duration' => (int)($outage->get_duration() / 60), + 'warn' => $outage->get_warning_duration(), + 'duration' => $outage->get_duration(), 'title' => $outage->title, 'description' => $outage->description, ]); diff --git a/lang/en/auth_outage.php b/lang/en/auth_outage.php index 4e08833..c339171 100644 --- a/lang/en/auth_outage.php +++ b/lang/en/auth_outage.php @@ -28,12 +28,12 @@ $string['clicreatehelp'] = 'Creates a new outage.'; $string['clicreateparamblock'] = 'blocks until outage starts.'; $string['clicreateparamclone'] = 'clone another outage except for the start time.'; $string['clicreateparamdescription'] = 'the description of the outage.'; -$string['clicreateparamduration'] = 'how many minutes should the outage last.'; +$string['clicreateparamduration'] = 'how many seconds should the outage last.'; $string['clicreateparamhelp'] = 'shows parameters help.'; $string['clicreateparamonlyid'] = 'only outputs the new outage id, useful for scripts.'; -$string['clicreateparamstart'] = 'in how many minutes should this outage start. Required.'; +$string['clicreateparamstart'] = 'in how many seconds should this outage start. Required.'; $string['clicreateparamtitle'] = 'the title of the outage.'; -$string['clicreateparamwarn'] = 'how many minutes before it starts to display a warning.'; +$string['clicreateparamwarn'] = 'how many seconds before it starts to display a warning.'; $string['clifinishhelp'] = 'Finishes an ongoing outage.'; $string['clifinishnotongoing'] = 'Outage is not ongoing.'; $string['clifinishparamhelp'] = 'shows parameters help.'; diff --git a/tests/cli/create_test.php b/tests/cli/create_test.php index 9ba720a..0701e2e 100644 --- a/tests/cli/create_test.php +++ b/tests/cli/create_test.php @@ -147,8 +147,8 @@ class create_test extends cli_testcase { $id = (int)$id; $outage = outagedb::get_by_id($id); self::assertSame($now, $outage->starttime); - self::assertSame(10 * 60, $outage->get_warning_duration()); - self::assertSame(30 * 60, $outage->get_duration()); + self::assertSame(10, $outage->get_warning_duration()); + self::assertSame(30, $outage->get_duration()); self::assertNull($outage->finished); self::assertSame('A Title', $outage->title); self::assertSame('A Description', $outage->description); @@ -174,8 +174,8 @@ class create_test extends cli_testcase { // Check creted outage. $outage = outagedb::get_by_id($id); self::assertSame($now, $outage->starttime); - self::assertSame($outage->starttime - (10 * 60), $outage->warntime); - self::assertSame($outage->starttime + (30 * 60), $outage->stoptime); + self::assertSame($outage->starttime - 10, $outage->warntime); + self::assertSame($outage->starttime + 30, $outage->stoptime); self::assertNull($outage->finished); self::assertSame('Title', $outage->title); self::assertSame('Description', $outage->description); @@ -202,9 +202,9 @@ class create_test extends cli_testcase { list(, $id) = explode(':', $text); $id = (int)$id; $outage = outagedb::get_by_id($id); - self::assertSame($now + (50 * 60), $outage->starttime, 'Wrong starttime.'); - self::assertSame($outage->starttime - (100 * 60), $outage->warntime, 'Wrong warntime.'); - self::assertSame($outage->starttime + (300 * 60), $outage->stoptime, 'Wrong stoptime.'); + self::assertSame($now + 50, $outage->starttime, 'Wrong starttime.'); + self::assertSame($outage->starttime - 100, $outage->warntime, 'Wrong warntime.'); + self::assertSame($outage->starttime + 300, $outage->stoptime, 'Wrong stoptime.'); self::assertNull($outage->finished); self::assertSame('Default Title', $outage->title); self::assertSame('Default Description', $outage->description); @@ -233,7 +233,7 @@ class create_test extends cli_testcase { $id = trim($this->execute($cli)); // Check cloned data. $cloned = outagedb::get_by_id((int)$id); - self::assertSame($now + (60 * 60), $cloned->starttime); + self::assertSame($now + 60, $cloned->starttime); self::assertSame($original->get_warning_duration(), $cloned->get_warning_duration()); self::assertSame($original->get_duration(), $cloned->get_duration()); self::assertSame($original->title, $cloned->title); @@ -254,9 +254,9 @@ class create_test extends cli_testcase { // Not an extensive test in the blocking API, cliwaitforit tests should cover them deeper. $this->set_parameters([ '--block', - '--warn=10', + '--warn=60', '--start=0', - '--duration=30', + '--duration=600', '--title=Title', '--description=Description', ]);