Merge "SECURITY: Do not allow users to undelete a page they can't edit or create"
[lhc/web/wiklou.git] / tests / phpunit / includes / Services / ServiceContainerTest.php
1 <?php
2 use MediaWiki\Services\ServiceContainer;
3
4 /**
5 * @covers MediaWiki\Services\ServiceContainer
6 *
7 * @group MediaWiki
8 */
9 class ServiceContainerTest extends PHPUnit_Framework_TestCase {
10
11 private function newServiceContainer( $extraArgs = [] ) {
12 return new ServiceContainer( $extraArgs );
13 }
14
15 public function testGetServiceNames() {
16 $services = $this->newServiceContainer();
17 $names = $services->getServiceNames();
18
19 $this->assertInternalType( 'array', $names );
20 $this->assertEmpty( $names );
21
22 $name = 'TestService92834576';
23 $services->defineService( $name, function() {
24 return null;
25 } );
26
27 $names = $services->getServiceNames();
28 $this->assertContains( $name, $names );
29 }
30
31 public function testHasService() {
32 $services = $this->newServiceContainer();
33
34 $name = 'TestService92834576';
35 $this->assertFalse( $services->hasService( $name ) );
36
37 $services->defineService( $name, function() {
38 return null;
39 } );
40
41 $this->assertTrue( $services->hasService( $name ) );
42 }
43
44 public function testGetService() {
45 $services = $this->newServiceContainer( [ 'Foo' ] );
46
47 $theService = new stdClass();
48 $name = 'TestService92834576';
49 $count = 0;
50
51 $services->defineService(
52 $name,
53 function( $actualLocator, $extra ) use ( $services, $theService, &$count ) {
54 $count++;
55 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
56 PHPUnit_Framework_Assert::assertSame( $extra, 'Foo' );
57 return $theService;
58 }
59 );
60
61 $this->assertSame( $theService, $services->getService( $name ) );
62
63 $services->getService( $name );
64 $this->assertSame( 1, $count, 'instantiator should be called exactly once!' );
65 }
66
67 public function testGetService_fail_unknown() {
68 $services = $this->newServiceContainer();
69
70 $name = 'TestService92834576';
71
72 $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
73
74 $services->getService( $name );
75 }
76
77 public function testPeekService() {
78 $services = $this->newServiceContainer();
79
80 $services->defineService(
81 'Foo',
82 function() {
83 return new stdClass();
84 }
85 );
86
87 $services->defineService(
88 'Bar',
89 function() {
90 return new stdClass();
91 }
92 );
93
94 // trigger instantiation of Foo
95 $services->getService( 'Foo' );
96
97 $this->assertInternalType(
98 'object',
99 $services->peekService( 'Foo' ),
100 'Peek should return the service object if it had been accessed before.'
101 );
102
103 $this->assertNull(
104 $services->peekService( 'Bar' ),
105 'Peek should return null if the service was never accessed.'
106 );
107 }
108
109 public function testPeekService_fail_unknown() {
110 $services = $this->newServiceContainer();
111
112 $name = 'TestService92834576';
113
114 $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
115
116 $services->peekService( $name );
117 }
118
119 public function testDefineService() {
120 $services = $this->newServiceContainer();
121
122 $theService = new stdClass();
123 $name = 'TestService92834576';
124
125 $services->defineService( $name, function( $actualLocator ) use ( $services, $theService ) {
126 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
127 return $theService;
128 } );
129
130 $this->assertTrue( $services->hasService( $name ) );
131 $this->assertSame( $theService, $services->getService( $name ) );
132 }
133
134 public function testDefineService_fail_duplicate() {
135 $services = $this->newServiceContainer();
136
137 $theService = new stdClass();
138 $name = 'TestService92834576';
139
140 $services->defineService( $name, function() use ( $theService ) {
141 return $theService;
142 } );
143
144 $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
145
146 $services->defineService( $name, function() use ( $theService ) {
147 return $theService;
148 } );
149 }
150
151 public function testApplyWiring() {
152 $services = $this->newServiceContainer();
153
154 $wiring = [
155 'Foo' => function() {
156 return 'Foo!';
157 },
158 'Bar' => function() {
159 return 'Bar!';
160 },
161 ];
162
163 $services->applyWiring( $wiring );
164
165 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
166 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
167 }
168
169 public function testImportWiring() {
170 $services = $this->newServiceContainer();
171
172 $wiring = [
173 'Foo' => function() {
174 return 'Foo!';
175 },
176 'Bar' => function() {
177 return 'Bar!';
178 },
179 'Car' => function() {
180 return 'FUBAR!';
181 },
182 ];
183
184 $services->applyWiring( $wiring );
185
186 $newServices = $this->newServiceContainer();
187
188 // define a service before importing, so we can later check that
189 // existing service instances survive importWiring()
190 $newServices->defineService( 'Car', function() {
191 return 'Car!';
192 } );
193
194 // force instantiation
195 $newServices->getService( 'Car' );
196
197 // Define another service, so we can later check that extra wiring
198 // is not lost.
199 $newServices->defineService( 'Xar', function() {
200 return 'Xar!';
201 } );
202
203 // import wiring, but skip `Bar`
204 $newServices->importWiring( $services, [ 'Bar' ] );
205
206 $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
207 $this->assertSame( 'Foo!', $newServices->getService( 'Foo' ) );
208
209 // import all wiring, but preserve existing service instance
210 $newServices->importWiring( $services );
211
212 $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
213 $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
214 $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
215 $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
216 }
217
218 public function testLoadWiringFiles() {
219 $services = $this->newServiceContainer();
220
221 $wiringFiles = [
222 __DIR__ . '/TestWiring1.php',
223 __DIR__ . '/TestWiring2.php',
224 ];
225
226 $services->loadWiringFiles( $wiringFiles );
227
228 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
229 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
230 }
231
232 public function testLoadWiringFiles_fail_duplicate() {
233 $services = $this->newServiceContainer();
234
235 $wiringFiles = [
236 __DIR__ . '/TestWiring1.php',
237 __DIR__ . '/./TestWiring1.php',
238 ];
239
240 // loading the same file twice should fail, because
241 $this->setExpectedException( 'MediaWiki\Services\ServiceAlreadyDefinedException' );
242
243 $services->loadWiringFiles( $wiringFiles );
244 }
245
246 public function testRedefineService() {
247 $services = $this->newServiceContainer( [ 'Foo' ] );
248
249 $theService1 = new stdClass();
250 $name = 'TestService92834576';
251
252 $services->defineService( $name, function() {
253 PHPUnit_Framework_Assert::fail(
254 'The original instantiator function should not get called'
255 );
256 } );
257
258 // redefine before instantiation
259 $services->redefineService(
260 $name,
261 function( $actualLocator, $extra ) use ( $services, $theService1 ) {
262 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
263 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
264 return $theService1;
265 }
266 );
267
268 // force instantiation, check result
269 $this->assertSame( $theService1, $services->getService( $name ) );
270 }
271
272 public function testRedefineService_disabled() {
273 $services = $this->newServiceContainer( [ 'Foo' ] );
274
275 $theService1 = new stdClass();
276 $name = 'TestService92834576';
277
278 $services->defineService( $name, function() {
279 return 'Foo';
280 } );
281
282 // disable the service. we should be able to redefine it anyway.
283 $services->disableService( $name );
284
285 $services->redefineService( $name, function() use ( $theService1 ) {
286 return $theService1;
287 } );
288
289 // force instantiation, check result
290 $this->assertSame( $theService1, $services->getService( $name ) );
291 }
292
293 public function testRedefineService_fail_undefined() {
294 $services = $this->newServiceContainer();
295
296 $theService = new stdClass();
297 $name = 'TestService92834576';
298
299 $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
300
301 $services->redefineService( $name, function() use ( $theService ) {
302 return $theService;
303 } );
304 }
305
306 public function testRedefineService_fail_in_use() {
307 $services = $this->newServiceContainer( [ 'Foo' ] );
308
309 $theService = new stdClass();
310 $name = 'TestService92834576';
311
312 $services->defineService( $name, function() {
313 return 'Foo';
314 } );
315
316 // create the service, so it can no longer be redefined
317 $services->getService( $name );
318
319 $this->setExpectedException( 'MediaWiki\Services\CannotReplaceActiveServiceException' );
320
321 $services->redefineService( $name, function() use ( $theService ) {
322 return $theService;
323 } );
324 }
325
326 public function testDisableService() {
327 $services = $this->newServiceContainer( [ 'Foo' ] );
328
329 $destructible = $this->getMock( 'MediaWiki\Services\DestructibleService' );
330 $destructible->expects( $this->once() )
331 ->method( 'destroy' );
332
333 $services->defineService( 'Foo', function() use ( $destructible ) {
334 return $destructible;
335 } );
336 $services->defineService( 'Bar', function() {
337 return new stdClass();
338 } );
339 $services->defineService( 'Qux', function() {
340 return new stdClass();
341 } );
342
343 // instantiate Foo and Bar services
344 $services->getService( 'Foo' );
345 $services->getService( 'Bar' );
346
347 // disable service, should call destroy() once.
348 $services->disableService( 'Foo' );
349
350 // disabled service should still be listed
351 $this->assertContains( 'Foo', $services->getServiceNames() );
352
353 // getting other services should still work
354 $services->getService( 'Bar' );
355
356 // disable non-destructible service, and not-yet-instantiated service
357 $services->disableService( 'Bar' );
358 $services->disableService( 'Qux' );
359
360 $this->assertNull( $services->peekService( 'Bar' ) );
361 $this->assertNull( $services->peekService( 'Qux' ) );
362
363 // disabled service should still be listed
364 $this->assertContains( 'Bar', $services->getServiceNames() );
365 $this->assertContains( 'Qux', $services->getServiceNames() );
366
367 $this->setExpectedException( 'MediaWiki\Services\ServiceDisabledException' );
368 $services->getService( 'Qux' );
369 }
370
371 public function testDisableService_fail_undefined() {
372 $services = $this->newServiceContainer();
373
374 $theService = new stdClass();
375 $name = 'TestService92834576';
376
377 $this->setExpectedException( 'MediaWiki\Services\NoSuchServiceException' );
378
379 $services->redefineService( $name, function() use ( $theService ) {
380 return $theService;
381 } );
382 }
383
384 public function testDestroy() {
385 $services = $this->newServiceContainer();
386
387 $destructible = $this->getMock( 'MediaWiki\Services\DestructibleService' );
388 $destructible->expects( $this->once() )
389 ->method( 'destroy' );
390
391 $services->defineService( 'Foo', function() use ( $destructible ) {
392 return $destructible;
393 } );
394
395 $services->defineService( 'Bar', function() {
396 return new stdClass();
397 } );
398
399 // create the service
400 $services->getService( 'Foo' );
401
402 // destroy the container
403 $services->destroy();
404
405 $this->setExpectedException( 'MediaWiki\Services\ContainerDisabledException' );
406 $services->getService( 'Bar' );
407 }
408
409 }