100% test coverage for NamespaceInfo
[lhc/web/wiklou.git] / tests / phpunit / includes / title / NamespaceInfoTest.php
1 <?php
2 /**
3 * @author Antoine Musso
4 * @copyright Copyright © 2011, Antoine Musso
5 * @file
6 */
7
8 use MediaWiki\Config\ServiceOptions;
9
10 class NamespaceInfoTest extends MediaWikiTestCase {
11 /**********************************************************************************************
12 * Shared code
13 * %{
14 */
15 private $scopedCallback;
16
17 public function setUp() {
18 parent::setUp();
19
20 // Boo, there's still some global state in the class :(
21 global $wgHooks;
22 $hooks = $wgHooks;
23 unset( $hooks['CanonicalNamespaces'] );
24 $this->setMwGlobals( 'wgHooks', $hooks );
25
26 $this->scopedCallback =
27 ExtensionRegistry::getInstance()->setAttributeForTest( 'ExtensionNamespaces', [] );
28 }
29
30 public function tearDown() {
31 $this->scopedCallback = null;
32
33 parent::tearDown();
34 }
35
36 /**
37 * TODO Make this a const once HHVM support is dropped (T192166)
38 */
39 private static $defaultOptions = [
40 'AllowImageMoving' => true,
41 'CanonicalNamespaceNames' => [
42 NS_TALK => 'Talk',
43 NS_USER => 'User',
44 NS_USER_TALK => 'User_talk',
45 NS_SPECIAL => 'Special',
46 NS_MEDIA => 'Media',
47 ],
48 'CapitalLinkOverrides' => [],
49 'CapitalLinks' => true,
50 'ContentNamespaces' => [ NS_MAIN ],
51 'ExtraNamespaces' => [],
52 'ExtraSignatureNamespaces' => [],
53 'NamespaceContentModels' => [],
54 'NamespaceProtection' => [],
55 'NamespacesWithSubpages' => [
56 NS_TALK => true,
57 NS_USER => true,
58 NS_USER_TALK => true,
59 ],
60 'NonincludableNamespaces' => [],
61 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
62 ];
63
64 private function newObj( array $options = [] ) : NamespaceInfo {
65 return new NamespaceInfo( new ServiceOptions( NamespaceInfo::$constructorOptions,
66 $options, self::$defaultOptions ) );
67 }
68
69 // %} End shared code
70
71 /**********************************************************************************************
72 * Basic methods
73 * %{
74 */
75
76 /**
77 * @covers NamespaceInfo::__construct
78 * @dataProvider provideConstructor
79 * @param ServiceOptions $options
80 * @param string|null $expectedExceptionText
81 */
82 public function testConstructor( ServiceOptions $options, $expectedExceptionText = null ) {
83 if ( $expectedExceptionText !== null ) {
84 $this->setExpectedException( \Wikimedia\Assert\PreconditionException::class,
85 $expectedExceptionText );
86 }
87 new NamespaceInfo( $options );
88 $this->assertTrue( true );
89 }
90
91 public function provideConstructor() {
92 return [
93 [ new ServiceOptions( NamespaceInfo::$constructorOptions, self::$defaultOptions ) ],
94 [ new ServiceOptions( [], [] ), 'Required options missing: ' ],
95 [ new ServiceOptions(
96 array_merge( NamespaceInfo::$constructorOptions, [ 'invalid' ] ),
97 self::$defaultOptions,
98 [ 'invalid' => '' ]
99 ), 'Unsupported options passed: invalid' ],
100 ];
101 }
102
103 /**
104 * @dataProvider provideIsMovable
105 * @covers NamespaceInfo::isMovable
106 *
107 * @param bool $expected
108 * @param int $ns
109 * @param bool $allowImageMoving
110 */
111 public function testIsMovable( $expected, $ns, $allowImageMoving = true ) {
112 $obj = $this->newObj( [ 'AllowImageMoving' => $allowImageMoving ] );
113 $this->assertSame( $expected, $obj->isMovable( $ns ) );
114 }
115
116 public function provideIsMovable() {
117 return [
118 'Main' => [ true, NS_MAIN ],
119 'Talk' => [ true, NS_TALK ],
120 'Special' => [ false, NS_SPECIAL ],
121 'Nonexistent even namespace' => [ true, 1234 ],
122 'Nonexistent odd namespace' => [ true, 12345 ],
123
124 'Media with image moving' => [ false, NS_MEDIA, true ],
125 'Media with no image moving' => [ false, NS_MEDIA, false ],
126 'File with image moving' => [ true, NS_FILE, true ],
127 'File with no image moving' => [ false, NS_FILE, false ],
128 ];
129 }
130
131 /**
132 * @param int $ns
133 * @param bool $expected
134 * @dataProvider provideIsSubject
135 * @covers NamespaceInfo::isSubject
136 */
137 public function testIsSubject( $ns, $expected ) {
138 $this->assertSame( $expected, $this->newObj()->isSubject( $ns ) );
139 }
140
141 /**
142 * @param int $ns
143 * @param bool $expected
144 * @dataProvider provideIsSubject
145 * @covers NamespaceInfo::isTalk
146 */
147 public function testIsTalk( $ns, $expected ) {
148 $this->assertSame( !$expected, $this->newObj()->isTalk( $ns ) );
149 }
150
151 public function provideIsSubject() {
152 return [
153 // Special namespaces
154 [ NS_MEDIA, true ],
155 [ NS_SPECIAL, true ],
156
157 // Subject pages
158 [ NS_MAIN, true ],
159 [ NS_USER, true ],
160 [ 100, true ],
161
162 // Talk pages
163 [ NS_TALK, false ],
164 [ NS_USER_TALK, false ],
165 [ 101, false ],
166 ];
167 }
168
169 /**
170 * @covers NamespaceInfo::getSubject
171 */
172 public function testGetSubject() {
173 // Special namespaces are their own subjects
174 $obj = $this->newObj();
175 $this->assertEquals( NS_MEDIA, $obj->getSubject( NS_MEDIA ) );
176 $this->assertEquals( NS_SPECIAL, $obj->getSubject( NS_SPECIAL ) );
177
178 $this->assertEquals( NS_MAIN, $obj->getSubject( NS_TALK ) );
179 $this->assertEquals( NS_USER, $obj->getSubject( NS_USER_TALK ) );
180 }
181
182 /**
183 * Regular getTalk() calls
184 * Namespaces without a talk page (NS_MEDIA, NS_SPECIAL) are tested in
185 * the function testGetTalkExceptions()
186 * @covers NamespaceInfo::getTalk
187 * @covers NamespaceInfo::isMethodValidFor
188 */
189 public function testGetTalk() {
190 $obj = $this->newObj();
191 $this->assertEquals( NS_TALK, $obj->getTalk( NS_MAIN ) );
192 $this->assertEquals( NS_TALK, $obj->getTalk( NS_TALK ) );
193 $this->assertEquals( NS_USER_TALK, $obj->getTalk( NS_USER ) );
194 $this->assertEquals( NS_USER_TALK, $obj->getTalk( NS_USER_TALK ) );
195 }
196
197 /**
198 * Exceptions with getTalk()
199 * NS_MEDIA does not have talk pages. MediaWiki raise an exception for them.
200 * @expectedException MWException
201 * @covers NamespaceInfo::getTalk
202 * @covers NamespaceInfo::isMethodValidFor
203 */
204 public function testGetTalkExceptionsForNsMedia() {
205 $this->assertNull( $this->newObj()->getTalk( NS_MEDIA ) );
206 }
207
208 /**
209 * Exceptions with getTalk()
210 * NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them.
211 * @expectedException MWException
212 * @covers NamespaceInfo::getTalk
213 */
214 public function testGetTalkExceptionsForNsSpecial() {
215 $this->assertNull( $this->newObj()->getTalk( NS_SPECIAL ) );
216 }
217
218 /**
219 * Regular getAssociated() calls
220 * Namespaces without an associated page (NS_MEDIA, NS_SPECIAL) are tested in
221 * the function testGetAssociatedExceptions()
222 * @covers NamespaceInfo::getAssociated
223 */
224 public function testGetAssociated() {
225 $this->assertEquals( NS_TALK, $this->newObj()->getAssociated( NS_MAIN ) );
226 $this->assertEquals( NS_MAIN, $this->newObj()->getAssociated( NS_TALK ) );
227 }
228
229 # ## Exceptions with getAssociated()
230 # ## NS_MEDIA and NS_SPECIAL do not have talk pages. MediaWiki raises
231 # ## an exception for them.
232 /**
233 * @expectedException MWException
234 * @covers NamespaceInfo::getAssociated
235 */
236 public function testGetAssociatedExceptionsForNsMedia() {
237 $this->assertNull( $this->newObj()->getAssociated( NS_MEDIA ) );
238 }
239
240 /**
241 * @expectedException MWException
242 * @covers NamespaceInfo::getAssociated
243 */
244 public function testGetAssociatedExceptionsForNsSpecial() {
245 $this->assertNull( $this->newObj()->getAssociated( NS_SPECIAL ) );
246 }
247
248 /**
249 * @covers NamespaceInfo::exists
250 * @dataProvider provideExists
251 * @param int $ns
252 * @param bool $expected
253 */
254 public function testExists( $ns, $expected ) {
255 $this->assertSame( $expected, $this->newObj()->exists( $ns ) );
256 }
257
258 public function provideExists() {
259 return [
260 'Main' => [ NS_MAIN, true ],
261 'Talk' => [ NS_TALK, true ],
262 'Media' => [ NS_MEDIA, true ],
263 'Special' => [ NS_SPECIAL, true ],
264 'Nonexistent' => [ 12345, false ],
265 'Negative nonexistent' => [ -12345, false ],
266 ];
267 }
268
269 /**
270 * Note if we add a namespace registration system with keys like 'MAIN'
271 * we should add tests here for equivalence on things like 'MAIN' == 0
272 * and 'MAIN' == NS_MAIN.
273 * @covers NamespaceInfo::equals
274 */
275 public function testEquals() {
276 $obj = $this->newObj();
277 $this->assertTrue( $obj->equals( NS_MAIN, NS_MAIN ) );
278 $this->assertTrue( $obj->equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN'
279 $this->assertTrue( $obj->equals( NS_USER, NS_USER ) );
280 $this->assertTrue( $obj->equals( NS_USER, 2 ) );
281 $this->assertTrue( $obj->equals( NS_USER_TALK, NS_USER_TALK ) );
282 $this->assertTrue( $obj->equals( NS_SPECIAL, NS_SPECIAL ) );
283 $this->assertFalse( $obj->equals( NS_MAIN, NS_TALK ) );
284 $this->assertFalse( $obj->equals( NS_USER, NS_USER_TALK ) );
285 $this->assertFalse( $obj->equals( NS_PROJECT, NS_TEMPLATE ) );
286 }
287
288 /**
289 * @param int $ns1
290 * @param int $ns2
291 * @param bool $expected
292 * @dataProvider provideSubjectEquals
293 * @covers NamespaceInfo::subjectEquals
294 */
295 public function testSubjectEquals( $ns1, $ns2, $expected ) {
296 $this->assertSame( $expected, $this->newObj()->subjectEquals( $ns1, $ns2 ) );
297 }
298
299 public function provideSubjectEquals() {
300 return [
301 [ NS_MAIN, NS_MAIN, true ],
302 // In case we make NS_MAIN 'MAIN'
303 [ NS_MAIN, 0, true ],
304 [ NS_USER, NS_USER, true ],
305 [ NS_USER, 2, true ],
306 [ NS_USER_TALK, NS_USER_TALK, true ],
307 [ NS_SPECIAL, NS_SPECIAL, true ],
308 [ NS_MAIN, NS_TALK, true ],
309 [ NS_USER, NS_USER_TALK, true ],
310
311 [ NS_PROJECT, NS_TEMPLATE, false ],
312 [ NS_SPECIAL, NS_MAIN, false ],
313 [ NS_MEDIA, NS_SPECIAL, false ],
314 [ NS_SPECIAL, NS_MEDIA, false ],
315 ];
316 }
317
318 /**
319 * @dataProvider provideHasTalkNamespace
320 * @covers NamespaceInfo::hasTalkNamespace
321 *
322 * @param int $ns
323 * @param bool $expected
324 */
325 public function testHasTalkNamespace( $ns, $expected ) {
326 $this->assertSame( $expected, $this->newObj()->hasTalkNamespace( $ns ) );
327 }
328
329 public function provideHasTalkNamespace() {
330 return [
331 [ NS_MEDIA, false ],
332 [ NS_SPECIAL, false ],
333
334 [ NS_MAIN, true ],
335 [ NS_TALK, true ],
336 [ NS_USER, true ],
337 [ NS_USER_TALK, true ],
338
339 [ 100, true ],
340 [ 101, true ],
341 ];
342 }
343
344 /**
345 * @param int $ns
346 * @param bool $expected
347 * @param array $contentNamespaces
348 * @covers NamespaceInfo::isContent
349 * @dataProvider provideIsContent
350 */
351 public function testIsContent( $ns, $expected, $contentNamespaces = [ NS_MAIN ] ) {
352 $obj = $this->newObj( [ 'ContentNamespaces' => $contentNamespaces ] );
353 $this->assertSame( $expected, $obj->isContent( $ns ) );
354 }
355
356 public function provideIsContent() {
357 return [
358 [ NS_MAIN, true ],
359 [ NS_MEDIA, false ],
360 [ NS_SPECIAL, false ],
361 [ NS_TALK, false ],
362 [ NS_USER, false ],
363 [ NS_CATEGORY, false ],
364 [ 100, false ],
365 [ 100, true, [ NS_MAIN, 100, 252 ] ],
366 [ 252, true, [ NS_MAIN, 100, 252 ] ],
367 [ NS_MAIN, true, [ NS_MAIN, 100, 252 ] ],
368 // NS_MAIN is always content
369 [ NS_MAIN, true, [] ],
370 ];
371 }
372
373 /**
374 * @dataProvider provideWantSignatures
375 * @covers NamespaceInfo::wantSignatures
376 *
377 * @param int $index
378 * @param bool $expected
379 */
380 public function testWantSignatures( $index, $expected ) {
381 $this->assertSame( $expected, $this->newObj()->wantSignatures( $index ) );
382 }
383
384 public function provideWantSignatures() {
385 return [
386 'Main' => [ NS_MAIN, false ],
387 'Talk' => [ NS_TALK, true ],
388 'User' => [ NS_USER, false ],
389 'User talk' => [ NS_USER_TALK, true ],
390 'Special' => [ NS_SPECIAL, false ],
391 'Media' => [ NS_MEDIA, false ],
392 'Nonexistent talk' => [ 12345, true ],
393 'Nonexistent subject' => [ 123456, false ],
394 'Nonexistent negative odd' => [ -12345, false ],
395 ];
396 }
397
398 /**
399 * @dataProvider provideWantSignatures_ExtraSignatureNamespaces
400 * @covers NamespaceInfo::wantSignatures
401 *
402 * @param int $index
403 * @param int $expected
404 */
405 public function testWantSignatures_ExtraSignatureNamespaces( $index, $expected ) {
406 $obj = $this->newObj( [ 'ExtraSignatureNamespaces' =>
407 [ NS_MAIN, NS_USER, NS_SPECIAL, NS_MEDIA, 123456, -12345 ] ] );
408 $this->assertSame( $expected, $obj->wantSignatures( $index ) );
409 }
410
411 public function provideWantSignatures_ExtraSignatureNamespaces() {
412 $ret = array_map(
413 function ( $arr ) {
414 // We've added all these as extra signature namespaces, so expect true
415 return [ $arr[0], true ];
416 },
417 self::provideWantSignatures()
418 );
419
420 // Add one more that's false
421 $ret['Another nonexistent subject'] = [ 12345678, false ];
422 return $ret;
423 }
424
425 /**
426 * @param int $ns
427 * @param bool $expected
428 * @covers NamespaceInfo::isWatchable
429 * @dataProvider provideIsWatchable
430 */
431 public function testIsWatchable( $ns, $expected ) {
432 $this->assertSame( $expected, $this->newObj()->isWatchable( $ns ) );
433 }
434
435 public function provideIsWatchable() {
436 return [
437 // Specials namespaces are not watchable
438 [ NS_MEDIA, false ],
439 [ NS_SPECIAL, false ],
440
441 // Core defined namespaces are watchables
442 [ NS_MAIN, true ],
443 [ NS_TALK, true ],
444
445 // Additional, user defined namespaces are watchables
446 [ 100, true ],
447 [ 101, true ],
448 ];
449 }
450
451 /**
452 * @param int $ns
453 * @param int $expected
454 * @param array|null $namespacesWithSubpages To pass to constructor
455 * @covers NamespaceInfo::hasSubpages
456 * @dataProvider provideHasSubpages
457 */
458 public function testHasSubpages( $ns, $expected, array $namespacesWithSubpages = null ) {
459 $obj = $this->newObj( $namespacesWithSubpages
460 ? [ 'NamespacesWithSubpages' => $namespacesWithSubpages ]
461 : [] );
462 $this->assertSame( $expected, $obj->hasSubpages( $ns ) );
463 }
464
465 public function provideHasSubpages() {
466 return [
467 // Special namespaces:
468 [ NS_MEDIA, false ],
469 [ NS_SPECIAL, false ],
470
471 // Namespaces without subpages
472 [ NS_MAIN, false ],
473 [ NS_MAIN, true, [ NS_MAIN => true ] ],
474 [ NS_MAIN, false, [ NS_MAIN => false ] ],
475
476 // Some namespaces with subpages
477 [ NS_TALK, true ],
478 [ NS_USER, true ],
479 [ NS_USER_TALK, true ],
480 ];
481 }
482
483 /**
484 * @param $contentNamespaces To pass to constructor
485 * @param array $expected
486 * @dataProvider provideGetContentNamespaces
487 * @covers NamespaceInfo::getContentNamespaces
488 */
489 public function testGetContentNamespaces( $contentNamespaces, array $expected ) {
490 $obj = $this->newObj( [ 'ContentNamespaces' => $contentNamespaces ] );
491 $this->assertSame( $expected, $obj->getContentNamespaces() );
492 }
493
494 public function provideGetContentNamespaces() {
495 return [
496 // Non-array
497 [ '', [ NS_MAIN ] ],
498 [ false, [ NS_MAIN ] ],
499 [ null, [ NS_MAIN ] ],
500 [ 5, [ NS_MAIN ] ],
501
502 // Empty array
503 [ [], [ NS_MAIN ] ],
504
505 // NS_MAIN is forced to be content even if unwanted
506 [ [ NS_USER, NS_CATEGORY ], [ NS_MAIN, NS_USER, NS_CATEGORY ] ],
507
508 // In other cases, return as-is
509 [ [ NS_MAIN ], [ NS_MAIN ] ],
510 [ [ NS_MAIN, NS_USER, NS_CATEGORY ], [ NS_MAIN, NS_USER, NS_CATEGORY ] ],
511 ];
512 }
513
514 /**
515 * @covers NamespaceInfo::getSubjectNamespaces
516 */
517 public function testGetSubjectNamespaces() {
518 $subjectsNS = $this->newObj()->getSubjectNamespaces();
519 $this->assertContains( NS_MAIN, $subjectsNS,
520 "Talk namespaces should have NS_MAIN" );
521 $this->assertNotContains( NS_TALK, $subjectsNS,
522 "Talk namespaces should have NS_TALK" );
523
524 $this->assertNotContains( NS_MEDIA, $subjectsNS,
525 "Talk namespaces should not have NS_MEDIA" );
526 $this->assertNotContains( NS_SPECIAL, $subjectsNS,
527 "Talk namespaces should not have NS_SPECIAL" );
528 }
529
530 /**
531 * @covers NamespaceInfo::getTalkNamespaces
532 */
533 public function testGetTalkNamespaces() {
534 $talkNS = $this->newObj()->getTalkNamespaces();
535 $this->assertContains( NS_TALK, $talkNS,
536 "Subject namespaces should have NS_TALK" );
537 $this->assertNotContains( NS_MAIN, $talkNS,
538 "Subject namespaces should not have NS_MAIN" );
539
540 $this->assertNotContains( NS_MEDIA, $talkNS,
541 "Subject namespaces should not have NS_MEDIA" );
542 $this->assertNotContains( NS_SPECIAL, $talkNS,
543 "Subject namespaces should not have NS_SPECIAL" );
544 }
545
546 /**
547 * @param int $ns
548 * @param bool $expected
549 * @param bool $capitalLinks To pass to constructor
550 * @param array $capitalLinkOverrides To pass to constructor
551 * @dataProvider provideIsCapitalized
552 * @covers NamespaceInfo::isCapitalized
553 */
554 public function testIsCapitalized(
555 $ns, $expected, $capitalLinks = true, array $capitalLinkOverrides = []
556 ) {
557 $obj = $this->newObj( [
558 'CapitalLinks' => $capitalLinks,
559 'CapitalLinkOverrides' => $capitalLinkOverrides,
560 ] );
561 $this->assertSame( $expected, $obj->isCapitalized( $ns ) );
562 }
563
564 public function provideIsCapitalized() {
565 return [
566 // Test default settings
567 [ NS_PROJECT, true ],
568 [ NS_PROJECT_TALK, true ],
569 [ NS_MEDIA, true ],
570 [ NS_FILE, true ],
571
572 // Always capitalized no matter what
573 [ NS_SPECIAL, true, false ],
574 [ NS_USER, true, false ],
575 [ NS_MEDIAWIKI, true, false ],
576
577 // Even with an override too
578 [ NS_SPECIAL, true, false, [ NS_SPECIAL => false ] ],
579 [ NS_USER, true, false, [ NS_USER => false ] ],
580 [ NS_MEDIAWIKI, true, false, [ NS_MEDIAWIKI => false ] ],
581
582 // Overrides work for other namespaces
583 [ NS_PROJECT, false, true, [ NS_PROJECT => false ] ],
584 [ NS_PROJECT, true, false, [ NS_PROJECT => true ] ],
585
586 // NS_MEDIA is treated like NS_FILE, and ignores NS_MEDIA overrides
587 [ NS_MEDIA, false, true, [ NS_FILE => false, NS_MEDIA => true ] ],
588 [ NS_MEDIA, true, false, [ NS_FILE => true, NS_MEDIA => false ] ],
589 [ NS_FILE, false, true, [ NS_FILE => false, NS_MEDIA => true ] ],
590 [ NS_FILE, true, false, [ NS_FILE => true, NS_MEDIA => false ] ],
591 ];
592 }
593
594 /**
595 * @covers NamespaceInfo::hasGenderDistinction
596 */
597 public function testHasGenderDistinction() {
598 $obj = $this->newObj();
599
600 // Namespaces with gender distinctions
601 $this->assertTrue( $obj->hasGenderDistinction( NS_USER ) );
602 $this->assertTrue( $obj->hasGenderDistinction( NS_USER_TALK ) );
603
604 // Other ones, "genderless"
605 $this->assertFalse( $obj->hasGenderDistinction( NS_MEDIA ) );
606 $this->assertFalse( $obj->hasGenderDistinction( NS_SPECIAL ) );
607 $this->assertFalse( $obj->hasGenderDistinction( NS_MAIN ) );
608 $this->assertFalse( $obj->hasGenderDistinction( NS_TALK ) );
609 }
610
611 /**
612 * @covers NamespaceInfo::isNonincludable
613 */
614 public function testIsNonincludable() {
615 $obj = $this->newObj( [ 'NonincludableNamespaces' => [ NS_USER ] ] );
616 $this->assertTrue( $obj->isNonincludable( NS_USER ) );
617 $this->assertFalse( $obj->isNonincludable( NS_TEMPLATE ) );
618 }
619
620 /**
621 * @dataProvider provideGetNamespaceContentModel
622 * @covers NamespaceInfo::getNamespaceContentModel
623 *
624 * @param int $ns
625 * @param string $expected
626 */
627 public function testGetNamespaceContentModel( $ns, $expected ) {
628 $obj = $this->newObj( [ 'NamespaceContentModels' =>
629 [ NS_USER => CONTENT_MODEL_WIKITEXT, 123 => CONTENT_MODEL_JSON, 1234 => 'abcdef' ],
630 ] );
631 $this->assertSame( $expected, $obj->getNamespaceContentModel( $ns ) );
632 }
633
634 public function provideGetNamespaceContentModel() {
635 return [
636 [ NS_MAIN, null ],
637 [ NS_TALK, null ],
638 [ NS_USER, CONTENT_MODEL_WIKITEXT ],
639 [ NS_USER_TALK, null ],
640 [ NS_SPECIAL, null ],
641 [ 122, null ],
642 [ 123, CONTENT_MODEL_JSON ],
643 [ 1234, 'abcdef' ],
644 [ 1235, null ],
645 ];
646 }
647
648 /**
649 * @dataProvider provideGetCategoryLinkType
650 * @covers NamespaceInfo::getCategoryLinkType
651 *
652 * @param int $ns
653 * @param string $expected
654 */
655 public function testGetCategoryLinkType( $ns, $expected ) {
656 $this->assertSame( $expected, $this->newObj()->getCategoryLinkType( $ns ) );
657 }
658
659 public function provideGetCategoryLinkType() {
660 return [
661 [ NS_MAIN, 'page' ],
662 [ NS_TALK, 'page' ],
663 [ NS_USER, 'page' ],
664 [ NS_USER_TALK, 'page' ],
665
666 [ NS_FILE, 'file' ],
667 [ NS_FILE_TALK, 'page' ],
668
669 [ NS_CATEGORY, 'subcat' ],
670 [ NS_CATEGORY_TALK, 'page' ],
671
672 [ 100, 'page' ],
673 [ 101, 'page' ],
674 ];
675 }
676
677 // %} End basic methods
678
679 /**********************************************************************************************
680 * Canonical namespaces
681 * %{
682 */
683
684 // Default canonical namespaces
685 // %{
686 private function getDefaultNamespaces() {
687 return [ NS_MAIN => '' ] + self::$defaultOptions['CanonicalNamespaceNames'];
688 }
689
690 /**
691 * @covers NamespaceInfo::getCanonicalNamespaces
692 */
693 public function testGetCanonicalNamespaces() {
694 $this->assertSame(
695 $this->getDefaultNamespaces(),
696 $this->newObj()->getCanonicalNamespaces()
697 );
698 }
699
700 /**
701 * @dataProvider provideGetCanonicalName
702 * @covers NamespaceInfo::getCanonicalName
703 *
704 * @param int $index
705 * @param string|bool $expected
706 */
707 public function testGetCanonicalName( $index, $expected ) {
708 $this->assertSame( $expected, $this->newObj()->getCanonicalName( $index ) );
709 }
710
711 public function provideGetCanonicalName() {
712 return [
713 'Main' => [ NS_MAIN, '' ],
714 'Talk' => [ NS_TALK, 'Talk' ],
715 'With underscore not space' => [ NS_USER_TALK, 'User_talk' ],
716 'Special' => [ NS_SPECIAL, 'Special' ],
717 'Nonexistent' => [ 12345, false ],
718 'Nonexistent negative' => [ -12345, false ],
719 ];
720 }
721
722 /**
723 * @dataProvider provideGetCanonicalIndex
724 * @covers NamespaceInfo::getCanonicalIndex
725 *
726 * @param string $name
727 * @param int|null $expected
728 */
729 public function testGetCanonicalIndex( $name, $expected ) {
730 $this->assertSame( $expected, $this->newObj()->getCanonicalIndex( $name ) );
731 }
732
733 public function provideGetCanonicalIndex() {
734 return [
735 'Main' => [ '', NS_MAIN ],
736 'Talk' => [ 'talk', NS_TALK ],
737 'Not lowercase' => [ 'Talk', null ],
738 'With underscore' => [ 'user_talk', NS_USER_TALK ],
739 'Space is not recognized for underscore' => [ 'user talk', null ],
740 '0' => [ '0', null ],
741 ];
742 }
743
744 /**
745 * @covers NamespaceInfo::getValidNamespaces
746 */
747 public function testGetValidNamespaces() {
748 $this->assertSame(
749 [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK ],
750 $this->newObj()->getValidNamespaces()
751 );
752 }
753
754 // %} End default canonical namespaces
755
756 // No canonical namespace names
757 // %{
758 /**
759 * @covers NamespaceInfo::getCanonicalNamespaces
760 */
761 public function testGetCanonicalNamespaces_NoCanonicalNamespaceNames() {
762 $obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] );
763
764 $this->assertSame( [ NS_MAIN => '' ], $obj->getCanonicalNamespaces() );
765 }
766
767 /**
768 * @covers NamespaceInfo::getCanonicalName
769 */
770 public function testGetCanonicalName_NoCanonicalNamespaceNames() {
771 $obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] );
772
773 $this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) );
774 $this->assertFalse( $obj->getCanonicalName( NS_TALK ) );
775 }
776
777 /**
778 * @covers NamespaceInfo::getCanonicalIndex
779 */
780 public function testGetCanonicalIndex_NoCanonicalNamespaceNames() {
781 $obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] );
782
783 $this->assertSame( NS_MAIN, $obj->getCanonicalIndex( '' ) );
784 $this->assertNull( $obj->getCanonicalIndex( 'talk' ) );
785 }
786
787 /**
788 * @covers NamespaceInfo::getValidNamespaces
789 */
790 public function testGetValidNamespaces_NoCanonicalNamespaceNames() {
791 $obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] );
792
793 $this->assertSame( [ NS_MAIN ], $obj->getValidNamespaces() );
794 }
795
796 // %} End no canonical namespace names
797
798 // Test extension namespaces
799 // %{
800 private function setupExtensionNamespaces() {
801 $this->scopedCallback = null;
802 $this->scopedCallback = ExtensionRegistry::getInstance()->setAttributeForTest(
803 'ExtensionNamespaces',
804 [ NS_MAIN => 'No effect', NS_TALK => 'No effect', 12345 => 'Extended' ]
805 );
806 }
807
808 /**
809 * @covers NamespaceInfo::getCanonicalNamespaces
810 */
811 public function testGetCanonicalNamespaces_ExtensionNamespaces() {
812 $this->setupExtensionNamespaces();
813
814 $this->assertSame(
815 $this->getDefaultNamespaces() + [ 12345 => 'Extended' ],
816 $this->newObj()->getCanonicalNamespaces()
817 );
818 }
819
820 /**
821 * @covers NamespaceInfo::getCanonicalName
822 */
823 public function testGetCanonicalName_ExtensionNamespaces() {
824 $this->setupExtensionNamespaces();
825 $obj = $this->newObj();
826
827 $this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) );
828 $this->assertSame( 'Talk', $obj->getCanonicalName( NS_TALK ) );
829 $this->assertSame( 'Extended', $obj->getCanonicalName( 12345 ) );
830 }
831
832 /**
833 * @covers NamespaceInfo::getCanonicalIndex
834 */
835 public function testGetCanonicalIndex_ExtensionNamespaces() {
836 $this->setupExtensionNamespaces();
837 $obj = $this->newObj();
838
839 $this->assertSame( NS_MAIN, $obj->getCanonicalIndex( '' ) );
840 $this->assertSame( NS_TALK, $obj->getCanonicalIndex( 'talk' ) );
841 $this->assertSame( 12345, $obj->getCanonicalIndex( 'extended' ) );
842 }
843
844 /**
845 * @covers NamespaceInfo::getValidNamespaces
846 */
847 public function testGetValidNamespaces_ExtensionNamespaces() {
848 $this->setupExtensionNamespaces();
849
850 $this->assertSame(
851 [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 12345 ],
852 $this->newObj()->getValidNamespaces()
853 );
854 }
855
856 // %} End extension namespaces
857
858 // Hook namespaces
859 // %{
860 /**
861 * @return array Expected canonical namespaces
862 */
863 private function setupHookNamespaces() {
864 $callback =
865 function ( &$canonicalNamespaces ) {
866 $canonicalNamespaces[NS_MAIN] = 'Main';
867 unset( $canonicalNamespaces[NS_MEDIA] );
868 $canonicalNamespaces[123456] = 'Hooked';
869 };
870 $this->setTemporaryHook( 'CanonicalNamespaces', $callback );
871 $expected = $this->getDefaultNamespaces();
872 ( $callback )( $expected );
873 return $expected;
874 }
875
876 /**
877 * @covers NamespaceInfo::getCanonicalNamespaces
878 */
879 public function testGetCanonicalNamespaces_HookNamespaces() {
880 $expected = $this->setupHookNamespaces();
881
882 $this->assertSame( $expected, $this->newObj()->getCanonicalNamespaces() );
883 }
884
885 /**
886 * @covers NamespaceInfo::getCanonicalName
887 */
888 public function testGetCanonicalName_HookNamespaces() {
889 $this->setupHookNamespaces();
890 $obj = $this->newObj();
891
892 $this->assertSame( 'Main', $obj->getCanonicalName( NS_MAIN ) );
893 $this->assertFalse( $obj->getCanonicalName( NS_MEDIA ) );
894 $this->assertSame( 'Hooked', $obj->getCanonicalName( 123456 ) );
895 }
896
897 /**
898 * @covers NamespaceInfo::getCanonicalIndex
899 */
900 public function testGetCanonicalIndex_HookNamespaces() {
901 $this->setupHookNamespaces();
902 $obj = $this->newObj();
903
904 $this->assertSame( NS_MAIN, $obj->getCanonicalIndex( 'main' ) );
905 $this->assertNull( $obj->getCanonicalIndex( 'media' ) );
906 $this->assertSame( 123456, $obj->getCanonicalIndex( 'hooked' ) );
907 }
908
909 /**
910 * @covers NamespaceInfo::getValidNamespaces
911 */
912 public function testGetValidNamespaces_HookNamespaces() {
913 $this->setupHookNamespaces();
914
915 $this->assertSame(
916 [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 123456 ],
917 $this->newObj()->getValidNamespaces()
918 );
919 }
920
921 // %} End hook namespaces
922
923 // Extra namespaces
924 // %{
925 /**
926 * @return NamespaceInfo
927 */
928 private function setupExtraNamespaces() {
929 return $this->newObj( [ 'ExtraNamespaces' =>
930 [ NS_MAIN => 'No effect', NS_TALK => 'No effect', 1234567 => 'Extra' ]
931 ] );
932 }
933
934 /**
935 * @covers NamespaceInfo::getCanonicalNamespaces
936 */
937 public function testGetCanonicalNamespaces_ExtraNamespaces() {
938 $this->assertSame(
939 $this->getDefaultNamespaces() + [ 1234567 => 'Extra' ],
940 $this->setupExtraNamespaces()->getCanonicalNamespaces()
941 );
942 }
943
944 /**
945 * @covers NamespaceInfo::getCanonicalName
946 */
947 public function testGetCanonicalName_ExtraNamespaces() {
948 $obj = $this->setupExtraNamespaces();
949
950 $this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) );
951 $this->assertSame( 'Talk', $obj->getCanonicalName( NS_TALK ) );
952 $this->assertSame( 'Extra', $obj->getCanonicalName( 1234567 ) );
953 }
954
955 /**
956 * @covers NamespaceInfo::getCanonicalIndex
957 */
958 public function testGetCanonicalIndex_ExtraNamespaces() {
959 $obj = $this->setupExtraNamespaces();
960
961 $this->assertNull( $obj->getCanonicalIndex( 'no effect' ) );
962 $this->assertNull( $obj->getCanonicalIndex( 'no_effect' ) );
963 $this->assertSame( 1234567, $obj->getCanonicalIndex( 'extra' ) );
964 }
965
966 /**
967 * @covers NamespaceInfo::getValidNamespaces
968 */
969 public function testGetValidNamespaces_ExtraNamespaces() {
970 $this->assertSame(
971 [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 1234567 ],
972 $this->setupExtraNamespaces()->getValidNamespaces()
973 );
974 }
975
976 // %} End extra namespaces
977
978 // Canonical namespace caching
979 // %{
980 /**
981 * @covers NamespaceInfo::getCanonicalNamespaces
982 */
983 public function testGetCanonicalNamespaces_caching() {
984 $obj = $this->newObj();
985
986 // This should cache the values
987 $obj->getCanonicalNamespaces();
988
989 // Now try to alter them through nefarious means
990 $this->setupExtensionNamespaces();
991 $this->setupHookNamespaces();
992
993 // Should have no effect
994 $this->assertSame( $this->getDefaultNamespaces(), $obj->getCanonicalNamespaces() );
995 }
996
997 /**
998 * @covers NamespaceInfo::getCanonicalName
999 */
1000 public function testGetCanonicalName_caching() {
1001 $obj = $this->newObj();
1002
1003 // This should cache the values
1004 $obj->getCanonicalName( NS_MAIN );
1005
1006 // Now try to alter them through nefarious means
1007 $this->setupExtensionNamespaces();
1008 $this->setupHookNamespaces();
1009
1010 // Should have no effect
1011 $this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) );
1012 $this->assertSame( 'Media', $obj->getCanonicalName( NS_MEDIA ) );
1013 $this->assertFalse( $obj->getCanonicalName( 12345 ) );
1014 $this->assertFalse( $obj->getCanonicalName( 123456 ) );
1015 }
1016
1017 /**
1018 * @covers NamespaceInfo::getCanonicalIndex
1019 */
1020 public function testGetCanonicalIndex_caching() {
1021 $obj = $this->newObj();
1022
1023 // This should cache the values
1024 $obj->getCanonicalIndex( '' );
1025
1026 // Now try to alter them through nefarious means
1027 $this->setupExtensionNamespaces();
1028 $this->setupHookNamespaces();
1029
1030 // Should have no effect
1031 $this->assertSame( NS_MAIN, $obj->getCanonicalIndex( '' ) );
1032 $this->assertSame( NS_MEDIA, $obj->getCanonicalIndex( 'media' ) );
1033 $this->assertNull( $obj->getCanonicalIndex( 'extended' ) );
1034 $this->assertNull( $obj->getCanonicalIndex( 'hooked' ) );
1035 }
1036
1037 /**
1038 * @covers NamespaceInfo::getValidNamespaces
1039 */
1040 public function testGetValidNamespaces_caching() {
1041 $obj = $this->newObj();
1042
1043 // This should cache the values
1044 $obj->getValidNamespaces();
1045
1046 // Now try to alter through nefarious means
1047 $this->setupExtensionNamespaces();
1048 $this->setupHookNamespaces();
1049
1050 // Should have no effect
1051 $this->assertSame(
1052 [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK ],
1053 $obj->getValidNamespaces()
1054 );
1055 }
1056
1057 // %} End canonical namespace caching
1058
1059 // Miscellaneous
1060 // %{
1061
1062 /**
1063 * @dataProvider provideGetValidNamespaces_misc
1064 * @covers NamespaceInfo::getValidNamespaces
1065 *
1066 * @param array $namespaces List of namespace indices to return from getCanonicalNamespaces()
1067 * (list is overwritten by a hook, so NS_MAIN doesn't have to be present)
1068 * @param array $expected
1069 */
1070 public function testGetValidNamespaces_misc( array $namespaces, array $expected ) {
1071 // Each namespace's name is just its index
1072 $this->setTemporaryHook( 'CanonicalNamespaces',
1073 function ( &$canonicalNamespaces ) use ( $namespaces ) {
1074 $canonicalNamespaces = array_combine( $namespaces, $namespaces );
1075 }
1076 );
1077 $this->assertSame( $expected, $this->newObj()->getValidNamespaces() );
1078 }
1079
1080 public function provideGetValidNamespaces_misc() {
1081 return [
1082 'Out of order (T109137)' => [ [ 1, 0 ], [ 0, 1 ] ],
1083 'Alphabetical order' => [ [ 10, 2 ], [ 2, 10 ] ],
1084 'Negative' => [ [ -1000, -500, -2, 0 ], [ 0 ] ],
1085 ];
1086 }
1087
1088 // %} End miscellaneous
1089 // %} End canonical namespaces
1090
1091 /**********************************************************************************************
1092 * Restriction levels
1093 * %{
1094 */
1095
1096 /**
1097 * This mock user can only have isAllowed() called on it.
1098 *
1099 * @param array $groups Groups for the mock user to have
1100 * @return User
1101 */
1102 private function getMockUser( array $groups = [] ) : User {
1103 $groups[] = '*';
1104
1105 $mock = $this->createMock( User::class );
1106 $mock->method( 'isAllowed' )->will( $this->returnCallback(
1107 function ( $action ) use ( $groups ) {
1108 global $wgGroupPermissions, $wgRevokePermissions;
1109 if ( $action == '' ) {
1110 return true;
1111 }
1112 foreach ( $wgRevokePermissions as $group => $rights ) {
1113 if ( !in_array( $group, $groups ) ) {
1114 continue;
1115 }
1116 if ( isset( $rights[$action] ) && $rights[$action] ) {
1117 return false;
1118 }
1119 }
1120 foreach ( $wgGroupPermissions as $group => $rights ) {
1121 if ( !in_array( $group, $groups ) ) {
1122 continue;
1123 }
1124 if ( isset( $rights[$action] ) && $rights[$action] ) {
1125 return true;
1126 }
1127 }
1128 return false;
1129 }
1130 ) );
1131 $mock->expects( $this->never() )->method( $this->anythingBut( 'isAllowed' ) );
1132 return $mock;
1133 }
1134
1135 /**
1136 * @dataProvider provideGetRestrictionLevels
1137 * @covers NamespaceInfo::getRestrictionLevels
1138 *
1139 * @param array $expected
1140 * @param int $ns
1141 * @param User|null $user
1142 */
1143 public function testGetRestrictionLevels( array $expected, $ns, User $user = null ) {
1144 $this->setMwGlobals( [
1145 'wgGroupPermissions' => [
1146 '*' => [ 'edit' => true ],
1147 'autoconfirmed' => [ 'editsemiprotected' => true ],
1148 'sysop' => [
1149 'editsemiprotected' => true,
1150 'editprotected' => true,
1151 ],
1152 'privileged' => [ 'privileged' => true ],
1153 ],
1154 'wgRevokePermissions' => [
1155 'noeditsemiprotected' => [ 'editsemiprotected' => true ],
1156 ],
1157 ] );
1158 $obj = $this->newObj( [
1159 'NamespaceProtection' => [
1160 NS_MAIN => 'autoconfirmed',
1161 NS_USER => 'sysop',
1162 101 => [ 'editsemiprotected', 'privileged' ],
1163 ],
1164 ] );
1165 $this->assertSame( $expected, $obj->getRestrictionLevels( $ns, $user ) );
1166 }
1167
1168 public function provideGetRestrictionLevels() {
1169 return [
1170 'No namespace restriction' => [ [ '', 'autoconfirmed', 'sysop' ], NS_TALK ],
1171 'Restricted to autoconfirmed' => [ [ '', 'sysop' ], NS_MAIN ],
1172 'Restricted to sysop' => [ [ '' ], NS_USER ],
1173 // @todo Bug -- 'sysop' protection should be allowed in this case. Someone who's
1174 // autoconfirmed and also privileged can edit this namespace, and would be blocked by
1175 // the sysop protection.
1176 'Restricted to someone in two groups' => [ [ '' ], 101 ],
1177
1178 'No special permissions' => [ [ '' ], NS_TALK, $this->getMockUser() ],
1179 'autoconfirmed' => [
1180 [ '', 'autoconfirmed' ],
1181 NS_TALK,
1182 $this->getMockUser( [ 'autoconfirmed' ] )
1183 ],
1184 'autoconfirmed revoked' => [
1185 [ '' ],
1186 NS_TALK,
1187 $this->getMockUser( [ 'autoconfirmed', 'noeditsemiprotected' ] )
1188 ],
1189 'sysop' => [
1190 [ '', 'autoconfirmed', 'sysop' ],
1191 NS_TALK,
1192 $this->getMockUser( [ 'sysop' ] )
1193 ],
1194 'sysop with autoconfirmed revoked (a bit silly)' => [
1195 [ '', 'sysop' ],
1196 NS_TALK,
1197 $this->getMockUser( [ 'sysop', 'noeditsemiprotected' ] )
1198 ],
1199 ];
1200 }
1201
1202 // %} End restriction levels
1203 }
1204
1205 /**
1206 * For really cool vim folding this needs to be at the end:
1207 * vim: foldmarker=%{,%} foldmethod=marker
1208 */