1 <?php 2 // This file is part of Moodle - https://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <https://www.gnu.org/licenses/>. 16 17 namespace core\hook; 18 19 /** 20 * Hooks tests. 21 * 22 * @coversDefaultClass \core\hook\manager 23 * 24 * @package core 25 * @author Petr Skoda 26 * @copyright 2022 Open LMS 27 * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 */ 29 class manager_test extends \advanced_testcase { 30 /** 31 * Test public factory method to get hook manager. 32 * @covers ::get_instance 33 */ 34 public function test_get_instance() { 35 $manager = manager::get_instance(); 36 $this->assertInstanceOf(manager::class, $manager); 37 38 $this->assertSame($manager, manager::get_instance()); 39 } 40 41 /** 42 * Test getting of manager test instance. 43 * @covers ::phpunit_get_instance 44 */ 45 public function test_phpunit_get_instance() { 46 $testmanager = manager::phpunit_get_instance([]); 47 $this->assertSame([], $testmanager->get_hooks_with_callbacks()); 48 49 // We get a new instance every time. 50 $this->assertNotSame($testmanager, manager::phpunit_get_instance([])); 51 52 $componentfiles = [ 53 'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php', 54 ]; 55 $testmanager = manager::phpunit_get_instance($componentfiles); 56 $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks()); 57 } 58 59 /** 60 * Test loading and parsing of callbacks from files. 61 * 62 * @covers ::get_callbacks_for_hook 63 * @covers ::get_hooks_with_callbacks 64 * @covers ::load_callbacks 65 * @covers ::add_component_callbacks 66 */ 67 public function test_callbacks() { 68 $componentfiles = [ 69 'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php', 70 'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php', 71 ]; 72 $testmanager = manager::phpunit_get_instance($componentfiles); 73 $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks()); 74 $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook'); 75 $this->assertCount(2, $callbacks); 76 $this->assertSame([ 77 'callback' => 'test_plugin\\callbacks::test2', 78 'component' => 'test_plugin2', 79 'disabled' => false, 80 'priority' => 200, 81 ], $callbacks[0]); 82 $this->assertSame([ 83 'callback' => 'test_plugin\\callbacks::test1', 84 'component' => 'test_plugin1', 85 'disabled' => false, 86 'priority' => 100, 87 ], $callbacks[1]); 88 89 $this->assertDebuggingNotCalled(); 90 $componentfiles = [ 91 'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_broken.php', 92 ]; 93 $testmanager = manager::phpunit_get_instance($componentfiles); 94 $this->assertSame([], $testmanager->get_hooks_with_callbacks()); 95 $debuggings = $this->getDebuggingMessages(); 96 $this->resetDebugging(); 97 $this->assertSame('Hook callback definition requires \'hook\' name in \'test_plugin1\'', 98 $debuggings[0]->message); 99 $this->assertSame('Hook callback definition requires \'callback\' callable in \'test_plugin1\'', 100 $debuggings[1]->message); 101 $this->assertSame('Hook callback definition contains invalid \'callback\' static class method string in \'test_plugin1\'', 102 $debuggings[2]->message); 103 $this->assertCount(3, $debuggings); 104 } 105 106 /** 107 * Test hook dispatching, that is callback execution. 108 * @covers ::dispatch 109 */ 110 public function test_dispatch(): void { 111 require_once (__DIR__ . '/../fixtures/hook/hook.php'); 112 require_once (__DIR__ . '/../fixtures/hook/callbacks.php'); 113 114 $componentfiles = [ 115 'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php', 116 'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php', 117 ]; 118 $testmanager = manager::phpunit_get_instance($componentfiles); 119 \test_plugin\callbacks::$calls = []; 120 $hook = new \test_plugin\hook\hook(); 121 $result = $testmanager->dispatch($hook); 122 $this->assertSame($hook, $result); 123 $this->assertSame(['test2', 'test1'], \test_plugin\callbacks::$calls); 124 \test_plugin\callbacks::$calls = []; 125 $this->assertDebuggingNotCalled(); 126 } 127 128 /** 129 * Test hook dispatching, that is callback execution. 130 * @covers ::dispatch 131 */ 132 public function test_dispatch_with_exception(): void { 133 require_once (__DIR__ . '/../fixtures/hook/hook.php'); 134 require_once (__DIR__ . '/../fixtures/hook/callbacks.php'); 135 136 $componentfiles = [ 137 'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_exception.php', 138 'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php', 139 ]; 140 $testmanager = manager::phpunit_get_instance($componentfiles); 141 142 $hook = new \test_plugin\hook\hook(); 143 144 $this->expectException(\Exception::class); 145 $this->expectExceptionMessage('grrr'); 146 147 $testmanager->dispatch($hook); 148 } 149 150 /** 151 * Test hook dispatching, that is callback execution. 152 * @covers ::dispatch 153 */ 154 public function test_dispatch_with_invalid(): void { 155 // Missing callbacks is ignored. 156 $componentfiles = [ 157 'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_missing.php', 158 'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php', 159 ]; 160 $testmanager = manager::phpunit_get_instance($componentfiles); 161 \test_plugin\callbacks::$calls = []; 162 163 $hook = new \test_plugin\hook\hook(); 164 165 $testmanager->dispatch($hook); 166 $this->assertDebuggingCalled( 167 "Hook callback definition contains invalid 'callback' method name in 'test_plugin1'. Callback method not found.", 168 ); 169 $this->assertSame(['test2'], \test_plugin\callbacks::$calls); 170 } 171 172 /** 173 * Test stoppping of hook dispatching. 174 * @covers ::dispatch 175 */ 176 public function test_dispatch_stoppable() { 177 require_once (__DIR__ . '/../fixtures/hook/stoppablehook.php'); 178 require_once (__DIR__ . '/../fixtures/hook/callbacks.php'); 179 180 $componentfiles = [ 181 'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_stoppable.php', 182 'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_stoppable.php', 183 ]; 184 $testmanager = manager::phpunit_get_instance($componentfiles); 185 \test_plugin\callbacks::$calls = []; 186 $hook = new \test_plugin\hook\stoppablehook(); 187 $result = $testmanager->dispatch($hook); 188 $this->assertSame($hook, $result); 189 $this->assertSame(['stop1'], \test_plugin\callbacks::$calls); 190 \test_plugin\callbacks::$calls = []; 191 $this->assertDebuggingNotCalled(); 192 } 193 194 /** 195 * Tests callbacks can be overridden via CFG settings. 196 * @covers ::load_callbacks 197 * @covers ::dispatch 198 */ 199 public function test_callback_overriding() { 200 global $CFG; 201 $this->resetAfterTest(); 202 203 $componentfiles = [ 204 'test_plugin1' => __DIR__ . '/../fixtures/hook/hooks1_valid.php', 205 'test_plugin2' => __DIR__ . '/../fixtures/hook/hooks2_valid.php', 206 ]; 207 208 $testmanager = manager::phpunit_get_instance($componentfiles); 209 $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks()); 210 $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook'); 211 $this->assertCount(2, $callbacks); 212 $this->assertSame([ 213 'callback' => 'test_plugin\\callbacks::test2', 214 'component' => 'test_plugin2', 215 'disabled' => false, 216 'priority' => 200, 217 ], $callbacks[0]); 218 $this->assertSame([ 219 'callback' => 'test_plugin\\callbacks::test1', 220 'component' => 'test_plugin1', 221 'disabled' => false, 222 'priority' => 100, 223 ], $callbacks[1]); 224 225 $CFG->hooks_callback_overrides = [ 226 'test_plugin\\hook\\hook' => [ 227 'test_plugin\\callbacks::test2' => ['priority' => 33] 228 ] 229 ]; 230 231 $testmanager = manager::phpunit_get_instance($componentfiles); 232 $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks()); 233 $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook'); 234 $this->assertCount(2, $callbacks); 235 $this->normalise_callbacks($callbacks); 236 $this->assertSame([ 237 'callback' => 'test_plugin\\callbacks::test1', 238 'component' => 'test_plugin1', 239 'disabled' => false, 240 'priority' => 100, 241 ], $callbacks[0]); 242 $this->assertSame([ 243 'callback' => 'test_plugin\\callbacks::test2', 244 'component' => 'test_plugin2', 245 'defaultpriority' => 200, 246 'disabled' => false, 247 'priority' => 33, 248 ], $callbacks[1]); 249 250 $CFG->hooks_callback_overrides = [ 251 'test_plugin\\hook\\hook' => [ 252 'test_plugin\\callbacks::test2' => ['priority' => 33, 'disabled' => true] 253 ] 254 ]; 255 $testmanager = manager::phpunit_get_instance($componentfiles); 256 $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks()); 257 $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook'); 258 $this->assertCount(2, $callbacks); 259 $this->normalise_callbacks($callbacks); 260 $this->assertSame([ 261 'callback' => 'test_plugin\\callbacks::test1', 262 'component' => 'test_plugin1', 263 'disabled' => false, 264 'priority' => 100, 265 ], 266 $callbacks[0]); 267 $this->assertSame([ 268 'callback' => 'test_plugin\\callbacks::test2', 269 'component' => 'test_plugin2', 270 'defaultpriority' => 200, 271 'disabled' => true, 272 'priority' => 33, 273 ], $callbacks[1]); 274 275 $CFG->hooks_callback_overrides = [ 276 'test_plugin\\hook\\hook' => [ 277 'test_plugin\\callbacks::test2' => ['disabled' => true], 278 ] 279 ]; 280 $testmanager = manager::phpunit_get_instance($componentfiles); 281 $this->assertSame(['test_plugin\\hook\\hook'], $testmanager->get_hooks_with_callbacks()); 282 $callbacks = $testmanager->get_callbacks_for_hook('test_plugin\\hook\\hook'); 283 $this->assertCount(2, $callbacks); 284 $this->assertSame([ 285 'callback' => 'test_plugin\\callbacks::test2', 286 'component' => 'test_plugin2', 287 'disabled' => true, 288 'priority' => 200, 289 ], $callbacks[0]); 290 $this->assertSame([ 291 'callback' => 'test_plugin\\callbacks::test1', 292 'component' => 'test_plugin1', 293 'disabled' => false, 294 'priority' => 100, 295 ], $callbacks[1]); 296 297 require_once (__DIR__ . '/../fixtures/hook/hook.php'); 298 require_once (__DIR__ . '/../fixtures/hook/callbacks.php'); 299 300 \test_plugin\callbacks::$calls = []; 301 $hook = new \test_plugin\hook\hook(); 302 $result = $testmanager->dispatch($hook); 303 $this->assertSame($hook, $result); 304 $this->assertSame(['test1'], \test_plugin\callbacks::$calls); 305 \test_plugin\callbacks::$calls = []; 306 $this->assertDebuggingNotCalled(); 307 } 308 309 /** 310 * Normalise the sort order of callbacks to help with asserts. 311 * 312 * @param array $callbacks 313 * @return void 314 */ 315 private function normalise_callbacks(array &$callbacks): void { 316 foreach ($callbacks as &$callback) { 317 ksort($callback); 318 } 319 } 320 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body