summaryrefslogtreecommitdiff
path: root/bin/reevotech/vendor/addwiki/mediawiki-api/tests/integration/CategoryTraverserTest.php
blob: 7161447db0c63b208f5ddac55405b8057e5d94eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
<?php

namespace Mediawiki\Api\Test;

use Mediawiki\Api\CategoryLoopException;
use Mediawiki\Api\Service\CategoryTraverser;
use Mediawiki\DataModel\Page;
use Mediawiki\DataModel\PageIdentifier;
use Mediawiki\DataModel\Title;
use Mediawiki\DataModel\Revision;
use Mediawiki\DataModel\Content;

class CategoryTraverserTest extends \PHPUnit_Framework_TestCase {

	/** @var TestEnvironment */
	protected $testEnvironment;

	/** @var \Mediawiki\Api\MediawikiFactory */
	protected $factory;

	/** @var \Mediawiki\Api\Service\CategoryTraverser */
	protected $traverser;

	public function setUp() {
		parent::setUp();
		$this->testEnvironment = TestEnvironment::newDefault();
		$this->factory = $this->testEnvironment->getFactory();
		$this->traverser = $this->factory->newCategoryTraverser();
	}

	/**
	 * A convenience wrapper around a PageDeleter.
	 * @param string[] $titles The titles to delete.
	 */
	public function deletePages( $titles ) {
		$deleter = $this->factory->newPageDeleter();
		foreach ( $titles as $t ) {
			// @todo Properly delete?
			// $deleter->deleteFromPageTitle( new Title( $t ) );
			$this->savePage( $t, '' );
		}
	}

	/**
	 * A convenience wrapper to a RevisionSaver.
	 * @param string $title The title of the new page.
	 * @param string $content The wikitext to save to the page.
	 * @return Page The saved Page.
	 */
	protected function savePage( $title, $content ) {
		$pageIdentifier = new PageIdentifier( new Title( $title ) );
		$revision = new Revision( new Content( $content ), $pageIdentifier );
		$this->factory->newRevisionSaver()->save( $revision );
		return $this->factory->newPageGetter()->getFromPageIdentifier( $pageIdentifier );
	}

	/**
	 * Get a list of all pages in a category or any of its descendants.
	 */
	public function testDescendants() {
		$rootCat = $this->savePage( 'Category:Root category', '' );
		$this->savePage( 'Category:Sub category B', '[[Category:Root category]]' );
		$this->savePage( 'Category:Sub category C', '[[Category:Root category]]' );
		$this->savePage( 'Test page A1', 'Testing. [[Category:Root category]]' );
		$this->savePage( 'Test page B1', 'Testing. [[Category:Sub category B]]' );
		$this->savePage( 'Test page B2', 'Testing. [[Category:Sub category B]]' );
		$this->savePage( 'Test page C1', 'Testing. [[Category:Sub category C]]' );
		$this->testEnvironment->runJobs();

		$callback = function ( Page $pageInfo, Page $parentCat ) {
			$parentCatName = $parentCat->getPageIdentifier()->getTitle()->getText();
			$thisPageName = $pageInfo->getPageIdentifier()->getTitle()->getText();
			if ( $parentCatName === 'Category:Root category' ) {
				$this->assertEquals( 'Test page A1', $thisPageName );
			}
			if ( $parentCatName === 'Category:Sub category C' ) {
				$this->assertEquals( 'Test page C1', $thisPageName );
			}
		};
		$this->traverser->addCallback( CategoryTraverser::CALLBACK_PAGE, $callback );
		$decendants = $this->traverser->descend( $rootCat );
		$this->assertCount( 4, $decendants->toArray() );
		$this->deletePages( [
			'Category:Root category',
			'Category:Sub category B',
			'Category:Sub category C',
			'Test page A1',
			'Test page B1',
			'Test page B2',
			'Test page C1',
		] );
	}

	/**
	 * Make sure there aren't duplicate results when there are multiple paths to
	 * the same page.
	 */
	public function testDescendantsWithMultiplePaths() {
		$grandparent = $this->savePage( 'Category:Grandparent', '' );
		$this->savePage( 'Category:Parent 1', '[[Category:Grandparent]]' );
		$this->savePage( 'Category:Parent 2', '[[Category:Grandparent]]' );
		$this->savePage( 'Parent 1', '[[Category:Grandparent]]' );
		$this->savePage( 'Child 1', '[[Category:Parent 1]]' );
		$this->savePage( 'Child 2', '[[Category:Parent 1]]' );
		$this->savePage( 'Child 3', '[[Category:Parent 2]]' );
		$this->testEnvironment->runJobs();
		$decendants = $this->traverser->descend( $grandparent );
		$this->assertCount( 4, $decendants->toArray() );
		$this->deletePages( [
			'Category:Grandparent',
			'Category:Parent 1',
			'Category:Parent 2',
			'Child 1',
			'Child 2',
			'Child 3',
		] );
	}

	/**
	 * Categories should only be traversed once. For example, in the following graph, 'C' can be
	 * reached as a child of 'A' or of 'B', but only the first arrival will proceed to 'D':
	 *
	 *     A
	 *    |  \
	 *    |   B
	 *    |  /
	 *    C
	 *    |
	 *    D
	 *
	 */
	public function testDescendantsOnlyVisitCatsOnce() {
		global $wgVisitedCats;
		$wgVisitedCats = [];
		$catA = $this->savePage( 'Category:A cat', '' );
		$this->savePage( 'Category:B cat', 'Testing. [[Category:A cat]]' );
		$this->savePage( 'Category:C cat', 'Testing. [[Category:A cat]][[Category:B cat]]' );
		$this->savePage( 'Category:D cat', 'Testing. [[Category:C cat]]' );
		$this->testEnvironment->runJobs();
		$callback = function ( Page $pageInfo, Page $parentCat ) {
			global $wgVisitedCats;
			$wgVisitedCats[] = $parentCat->getPageIdentifier()->getTitle()->getText();
		};
		$this->traverser->addCallback( CategoryTraverser::CALLBACK_CATEGORY, $callback );
		$descendants = $this->traverser->descend( $catA );
		$this->assertCount( 0, $descendants->toArray() );
		$this->assertCount( 3, $wgVisitedCats );
		$this->deletePages( [
			'Category:A cat',
			'Category:B cat',
			'Category:C cat',
			'Category:D cat',
		] );
	}

	/**
	 * Category loops are caught on descent.
	 *
	 *          E
	 *        /  \
	 *       F    G
	 *     /  \
	 *    H    I
	 *    |
	 *    E    <-- throw an Exception when we get to this repetition
	 *
	 */
	public function testDescendIntoLoop() {
		$catA = $this->savePage( 'Category:E cat', '[[Category:H cat]]' );
		$catB = $this->savePage( 'Category:F cat', '[[Category:E cat]]' );
		$catC = $this->savePage( 'Category:G cat', '[[Category:E cat]]' );
		$catD = $this->savePage( 'Category:H cat', '[[Category:F cat]]' );
		$catE = $this->savePage( 'Category:I cat', '[[Category:F cat]]' );
		$this->testEnvironment->runJobs();
		$haveCaught = false;
		try {
			$this->traverser->descend( $catA );
		} catch ( CategoryLoopException $ex ) {
			$haveCaught = true;
			$expectedCatLoop = [
				'Category:E cat',
				'Category:F cat',
				'Category:H cat',
			];
			// Build a simplified representation of the thrown loop pages, to get around different
			// revision IDs.
			$actualCatLoop = [];
			foreach ( $ex->getCategoryPath()->toArray() as $p ) {
				$actualCatLoop[] = $p->getPageIdentifier()->getTitle()->getText();
			}
			$this->assertEquals( $expectedCatLoop, $actualCatLoop );
		}
		$this->assertTrue( $haveCaught );
		$this->deletePages( [
			'Category:E cat',
			'Category:F cat',
			'Category:G cat',
			'Category:H cat',
			'Category:I cat',
		] );
	}

}