1<?php
2
3namespace MediaWiki\HookContainer {
4
5	use ExtensionRegistry;
6	use MediaWiki\MediaWikiServices;
7	use Wikimedia\ScopedCallback;
8
9	class HookContainerIntegrationTest extends \MediaWikiIntegrationTestCase {
10
11		/**
12		 * @covers \MediaWiki\HookContainer\HookContainer::run
13		 */
14		public function testHookRunsWhenExtensionRegistered() {
15			$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
16			$extensionRegistry = ExtensionRegistry::getInstance();
17			$numHandlersExecuted = 0;
18			$handlers = [ 'FooHook' => [ [
19				'handler' => [
20					'class' => 'FooExtension\\FooExtensionHooks',
21					'name' => 'FooExtension-FooHandler',
22				] ] ]
23			];
24			$reset = $extensionRegistry->setAttributeForTest( 'Hooks', $handlers );
25			$this->assertSame( 0, $numHandlersExecuted );
26			$hookContainer->run( 'FooHook', [ &$numHandlersExecuted ] );
27			$this->assertSame( 1, $numHandlersExecuted );
28			ScopedCallback::consume( $reset );
29		}
30
31		/**
32		 * @covers \MediaWiki\HookContainer\HookContainer::run
33		 * @covers \MediaWiki\HookContainer\HookContainer::scopedRegister
34		 */
35		public function testPreviouslyRegisteredHooksAreReAppliedAfterScopedRegisterRemovesThem() {
36			$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
37
38			// Some handlers for FooHook have been previously set
39			$reset = $hookContainer->register( 'FooHook', static function () {
40				return true;
41			} );
42			$reset1 = $hookContainer->register( 'FooHook', static function () {
43				return true;
44			} );
45			$handlersBeforeScopedRegister = $hookContainer->getLegacyHandlers( 'FooHook' );
46			$this->assertCount( 2, $handlersBeforeScopedRegister );
47
48			// Wipe out the 2 existing handlers and add a new scoped handler
49			$reset2 = $hookContainer->scopedRegister( 'FooHook', static function () {
50				return true;
51			}, true );
52			$handlersAfterScopedRegister = $hookContainer->getLegacyHandlers( 'FooHook' );
53			$this->assertCount( 1, $handlersAfterScopedRegister );
54
55			ScopedCallback::consume( $reset2 );
56
57			// Teardown causes the original handlers to be re-applied
58			$this->mediaWikiTearDown();
59
60			$handlersAfterTearDown = $hookContainer->getLegacyHandlers( 'FooHook' );
61			$this->assertCount( 2, $handlersAfterTearDown );
62		}
63
64		/**
65		 * @covers \MediaWiki\HookContainer\HookContainer::run
66		 * @covers \MediaWiki\HookContainer\HookContainer::scopedRegister
67		 */
68		public function testHookRunsWithMultipleMixedHandlerTypes() {
69			$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
70			$numHandlersExecuted = 0;
71			$reset = $hookContainer->scopedRegister( 'FooHook', static function ( &$numHandlersRun ) {
72				$numHandlersRun++;
73			}, false );
74			$reset2 = $hookContainer->scopedRegister( 'FooHook', static function ( &$numHandlersRun ) {
75				$numHandlersRun++;
76			}, false );
77			$handlerThree = [
78				'FooHook' => [
79					[ 'handler' => [
80						'class' => 'FooExtension\\FooExtensionHooks',
81						'name' => 'FooExtension-FooHandler',
82					]
83					]
84				]
85			];
86			$reset3 = ExtensionRegistry::getInstance()->setAttributeForTest( 'Hooks', $handlerThree );
87			$hookContainer->run( 'FooHook', [ &$numHandlersExecuted ] );
88			$this->assertEquals( 3, $numHandlersExecuted );
89			ScopedCallback::consume( $reset );
90			ScopedCallback::consume( $reset2 );
91			ScopedCallback::consume( $reset3 );
92		}
93
94		/**
95		 * @covers \MediaWiki\HookContainer\HookContainer
96		 */
97		public function testValidServiceInjection() {
98			$handler = [
99				'handler' => [
100					'name' => 'FooExtension-Mash',
101					'class' => 'FooExtension\\ServiceHooks',
102					'services' => [ 'ReadOnlyMode' ]
103				],
104				'extensionPath' => '/path/to/extension.json'
105			];
106			$hooks = [ 'Mash' => [ $handler ] ];
107			$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
108			$reset = ExtensionRegistry::getInstance()->setAttributeForTest( 'Hooks', $hooks );
109			$arg = 0;
110			$ret = $hookContainer->run( 'Mash', [ &$arg ] );
111			$this->assertTrue( $ret );
112			$this->assertSame( 1, $arg );
113		}
114
115		/**
116		 * @covers \MediaWiki\HookContainer\HookContainer
117		 */
118		public function testInvalidServiceInjection() {
119			$handler = [
120				'handler' => [
121					'name' => 'FooExtension-Mash',
122					'class' => 'FooExtension\\ServiceHooks',
123					'services' => [ 'ReadOnlyMode' ]
124				],
125				'extensionPath' => '/path/to/extension.json'
126			];
127			$hooks = [ 'Mash' => [ $handler ] ];
128			$hookContainer = MediaWikiServices::getInstance()->getHookContainer();
129			$reset = ExtensionRegistry::getInstance()->setAttributeForTest( 'Hooks', $hooks );
130			$this->expectException( \UnexpectedValueException::class );
131			$arg = 0;
132			$hookContainer->run( 'Mash', [ &$arg ], [ 'noServices' => true ] );
133		}
134	}
135}
136
137namespace FooExtension {
138
139	class FooExtensionHooks {
140
141		public function onFooHook( &$numHandlersRun ) {
142			$numHandlersRun++;
143		}
144	}
145
146	class ServiceHooks {
147		public function __construct( \ReadOnlyMode $readOnlyMode ) {
148		}
149
150		public function onMash( &$arg ) {
151			$arg++;
152			return true;
153		}
154	}
155
156}
157