API tests to verify basic query functionality (list & props)
[lhc/web/wiklou.git] / tests / phpunit / includes / api / query / ApiQueryBasicTest.php
1 <?php
2 /**
3 *
4 *
5 * Created on Feb 6, 2013
6 *
7 * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * http://www.gnu.org/copyleft/gpl.html
23 *
24 * @file
25 *
26 * These tests validate basic functionality of the api query module
27 *
28 * @group API
29 * @group Database
30 * @group medium
31 */
32 class ApiQueryBasicTest extends ApiTestCase {
33
34 /**
35 * Create a set of pages. These must not change, otherwise the tests might give wrong results.
36 * @see MediaWikiTestCase::addDBData()
37 */
38 function addDBData() {
39 try {
40 if ( Title::newFromText( 'AQBT-All' )->exists() ) {
41 return;
42 }
43
44 // Ordering is important, as it will be returned in the same order as stored in the index
45 $this->editPage( 'AQBT-All', '[[Category:AQBT-Cat]] [[AQBT-Links]] {{AQBT-T}}' );
46 $this->editPage( 'AQBT-Categories', '[[Category:AQBT-Cat]]' );
47 $this->editPage( 'AQBT-Links', '[[AQBT-All]] [[AQBT-Categories]] [[AQBT-Templates]]' );
48 $this->editPage( 'AQBT-Templates', '{{AQBT-T}}' );
49 $this->editPage( 'AQBT-T', 'Content', '', NS_TEMPLATE );
50
51 // Refresh due to the bug with listing transclusions as links if they don't exist
52 $this->editPage( 'AQBT-All', '[[Category:AQBT-Cat]] [[AQBT-Links]] {{AQBT-T}}' );
53 $this->editPage( 'AQBT-Templates', '{{AQBT-T}}' );
54 } catch ( Exception $e ) {
55 $this->exceptionFromAddDBData = $e;
56 }
57 }
58
59 private static $links = array(
60 array( 'prop' => 'links', 'titles' => 'AQBT-All' ),
61 array( 'pages' => array(
62 '1' => array(
63 'pageid' => 1,
64 'ns' => 0,
65 'title' => 'AQBT-All',
66 'links' => array(
67 array( 'ns' => 0, 'title' => 'AQBT-Links' ),
68 ) ) ) ) );
69
70 private static $templates = array(
71 array( 'prop' => 'templates', 'titles' => 'AQBT-All' ),
72 array( 'pages' => array(
73 '1' => array(
74 'pageid' => 1,
75 'ns' => 0,
76 'title' => 'AQBT-All',
77 'templates' => array(
78 array( 'ns' => 10, 'title' => 'Template:AQBT-T' ),
79 ) ) ) ) );
80
81 private static $categories = array(
82 array( 'prop' => 'categories', 'titles' => 'AQBT-All' ),
83 array( 'pages' => array(
84 '1' => array(
85 'pageid' => 1,
86 'ns' => 0,
87 'title' => 'AQBT-All',
88 'categories' => array(
89 array( 'ns' => 14, 'title' => 'Category:AQBT-Cat' ),
90 ) ) ) ) );
91
92 private static $allpages = array(
93 array( 'list' => 'allpages', 'apprefix' => 'AQBT-' ),
94 array( 'allpages' => array(
95 array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ),
96 array( 'pageid' => 2, 'ns' => 0, 'title' => 'AQBT-Categories' ),
97 array( 'pageid' => 3, 'ns' => 0, 'title' => 'AQBT-Links' ),
98 array( 'pageid' => 4, 'ns' => 0, 'title' => 'AQBT-Templates' ),
99 ) ) );
100
101 private static $alllinks = array(
102 array( 'list' => 'alllinks', 'alprefix' => 'AQBT-' ),
103 array( 'alllinks' => array(
104 array( 'ns' => 0, 'title' => 'AQBT-All' ),
105 array( 'ns' => 0, 'title' => 'AQBT-Categories' ),
106 array( 'ns' => 0, 'title' => 'AQBT-Links' ),
107 array( 'ns' => 0, 'title' => 'AQBT-Templates' ),
108 ) ) );
109
110 private static $alltransclusions = array(
111 array( 'list' => 'alltransclusions', 'atprefix' => 'AQBT-' ),
112 array( 'alltransclusions' => array(
113 array( 'ns' => 10, 'title' => 'Template:AQBT-T' ),
114 array( 'ns' => 10, 'title' => 'Template:AQBT-T' ),
115 ) ) );
116
117 private static $allcategories = array(
118 array( 'list' => 'allcategories', 'acprefix' => 'AQBT-' ),
119 array( 'allcategories' => array(
120 array( '*' => 'AQBT-Cat' ),
121 ) ) );
122
123 private static $backlinks = array(
124 array( 'list' => 'backlinks', 'bltitle' => 'AQBT-Links' ),
125 array( 'backlinks' => array(
126 array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ),
127 ) ) );
128
129 private static $embeddedin = array(
130 array( 'list' => 'embeddedin', 'eititle' => 'Template:AQBT-T' ),
131 array( 'embeddedin' => array(
132 array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ),
133 array( 'pageid' => 4, 'ns' => 0, 'title' => 'AQBT-Templates' ),
134 ) ) );
135
136 private static $categorymembers = array(
137 array( 'list' => 'categorymembers', 'cmtitle' => 'Category:AQBT-Cat' ),
138 array( 'categorymembers' => array(
139 array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ),
140 array( 'pageid' => 2, 'ns' => 0, 'title' => 'AQBT-Categories' ),
141 ) ) );
142
143 private static $generatorAllpages = array(
144 array( 'generator' => 'allpages', 'gapprefix' => 'AQBT-' ),
145 array( 'pages' => array(
146 '1' => array(
147 'pageid' => 1,
148 'ns' => 0,
149 'title' => 'AQBT-All' ),
150 '2' => array(
151 'pageid' => 2,
152 'ns' => 0,
153 'title' => 'AQBT-Categories' ),
154 '3' => array(
155 'pageid' => 3,
156 'ns' => 0,
157 'title' => 'AQBT-Links' ),
158 '4' => array(
159 'pageid' => 4,
160 'ns' => 0,
161 'title' => 'AQBT-Templates' ),
162 ) ) );
163
164 private static $generatorLinks = array(
165 array( 'generator' => 'links', 'titles' => 'AQBT-Links' ),
166 array( 'pages' => array(
167 '1' => array(
168 'pageid' => 1,
169 'ns' => 0,
170 'title' => 'AQBT-All' ),
171 '2' => array(
172 'pageid' => 2,
173 'ns' => 0,
174 'title' => 'AQBT-Categories' ),
175 '4' => array(
176 'pageid' => 4,
177 'ns' => 0,
178 'title' => 'AQBT-Templates' ),
179 ) ) );
180
181 private static $generatorLinksPropLinks = array(
182 array( 'prop' => 'links' ),
183 array( 'pages' => array(
184 '1' => array( 'links' => array(
185 array( 'ns' => 0, 'title' => 'AQBT-Links' ),
186 ) ) ) ) );
187
188 private static $generatorLinksPropTemplates = array(
189 array( 'prop' => 'templates' ),
190 array( 'pages' => array(
191 '1' => array( 'templates' => array(
192 array( 'ns' => 10, 'title' => 'Template:AQBT-T' ) ) ),
193 '4' => array( 'templates' => array(
194 array( 'ns' => 10, 'title' => 'Template:AQBT-T' ) ) ),
195 ) ) );
196
197 /**
198 * Test basic props
199 */
200 public function testProps() {
201 $this->check( self::$links );
202 $this->check( self::$templates );
203 $this->check( self::$categories );
204 }
205
206 /**
207 * Test basic lists
208 */
209 public function testLists() {
210 $this->check( self::$allpages );
211 $this->check( self::$alllinks );
212 $this->check( self::$alltransclusions );
213 // This test is temporarily disabled until a sqlite bug is fixed
214 // $this->check( self::$allcategories );
215 $this->check( self::$backlinks );
216 $this->check( self::$embeddedin );
217 $this->check( self::$categorymembers );
218 }
219
220 /**
221 * Test basic lists
222 */
223 public function testAllTogether() {
224
225 // All props together
226 $this->check( $this->merge(
227 self::$links,
228 self::$templates,
229 self::$categories
230 ) );
231
232 // All lists together
233 $this->check( $this->merge(
234 self::$allpages,
235 self::$alllinks,
236 self::$alltransclusions,
237 // This test is temporarily disabled until a sqlite bug is fixed
238 // self::$allcategories,
239 self::$backlinks,
240 self::$embeddedin,
241 self::$categorymembers
242 ) );
243
244 // All props+lists together
245 $this->check( $this->merge(
246 self::$links,
247 self::$templates,
248 self::$categories,
249 self::$allpages,
250 self::$alllinks,
251 self::$alltransclusions,
252 // This test is temporarily disabled until a sqlite bug is fixed
253 // self::$allcategories,
254 self::$backlinks,
255 self::$embeddedin,
256 self::$categorymembers
257 ) );
258 }
259
260 /**
261 * Test basic lists
262 */
263 public function testGenerator() {
264 // generator=allpages
265 $this->check( self::$generatorAllpages );
266 // generator=allpages & list=allpages
267 $this->check( $this->merge(
268 self::$generatorAllpages,
269 self::$allpages ) );
270 // generator=links
271 $this->check( self::$generatorLinks );
272 // generator=links & prop=links
273 $this->check( $this->merge(
274 self::$generatorLinks,
275 self::$generatorLinksPropLinks ) );
276 // generator=links & prop=templates
277 $this->check( $this->merge(
278 self::$generatorLinks,
279 self::$generatorLinksPropTemplates ) );
280 // generator=links & prop=links|templates
281 $this->check( $this->merge(
282 self::$generatorLinks,
283 self::$generatorLinksPropLinks,
284 self::$generatorLinksPropTemplates ) );
285 // generator=links & prop=links|templates & list=allpages|...
286 $this->check( $this->merge(
287 self::$generatorLinks,
288 self::$generatorLinksPropLinks,
289 self::$generatorLinksPropTemplates,
290 self::$allpages,
291 self::$alllinks,
292 self::$alltransclusions,
293 // This test is temporarily disabled until a sqlite bug is fixed
294 // self::$allcategories,
295 self::$backlinks,
296 self::$embeddedin,
297 self::$categorymembers ) );
298 }
299
300 /**
301 * Merges all requests (parameter arrays) into one
302 * @return array
303 */
304 private function merge( /*...*/ ) {
305 $request = array();
306 $expected = array();
307 foreach ( func_get_args() as $v ) {
308 $request = array_merge_recursive( $request, $v[0] );
309 $this->mergeExpected( $expected, $v[1] );
310 }
311 return array( $request, $expected );
312 }
313
314 /**
315 * Recursively merges the expected values in the $item into the $all
316 */
317 private function mergeExpected( &$all, $item ) {
318 foreach ( $item as $k => $v ) {
319 if ( array_key_exists( $k, $all ) ) {
320 if ( is_array ( $all[$k] ) ) {
321 $this->mergeExpected( $all[$k], $v );
322 } else {
323 $this->assertEquals( $all[$k], $v );
324 }
325 } else {
326 $all[$k] = $v;
327 }
328 }
329 }
330
331 /**
332 * Checks that the request's result matches the expected results.
333 * @param $values array is a two element array( request, expected_results )
334 * @throws Exception
335 */
336 private function check( $values ) {
337 $request = $values[0];
338 $expected = $values[1];
339 if ( !array_key_exists( 'action', $request ) ) {
340 $request['action'] = 'query';
341 }
342 foreach ( $request as &$val ) {
343 if ( is_array( $val ) ) {
344 $val = implode( '|', array_unique( $val ) );
345 }
346 }
347 $result = $this->doApiRequest( $request );
348 $result = $result[0];
349 $expected = array( 'query' => $expected );
350 try {
351 $this->assertQueryResults( $expected, $result );
352 } catch (Exception $e) {
353 print("\nRequest:\n");
354 print_r( $request );
355 print("\nExpected:\n");
356 print_r( $expected );
357 print("\nResult:\n");
358 print_r( $result );
359 throw $e; // rethrow it
360 }
361 }
362
363 /**
364 * Recursively compare arrays, ignoring mismatches in numeric key and pageids.
365 * @param $expected array expected values
366 * @param $result array returned values
367 */
368 private function assertQueryResults( $expected, $result ) {
369 reset( $expected );
370 reset( $result );
371 while ( true ) {
372 $e = each( $expected );
373 $r = each( $result );
374 // If either of the arrays is shorter, abort. If both are done, success.
375 $this->assertEquals( (bool)$e, (bool)$r );
376 if ( !$e ) {
377 break; // done
378 }
379 // continue only if keys are identical or both keys are numeric
380 $this->assertTrue( $e['key'] === $r['key'] || ( is_numeric( $e['key'] ) && is_numeric( $r['key'] ) ) );
381 // don't compare pageids
382 if ( $e['key'] !== 'pageid' ) {
383 // If values are arrays, compare recursively, otherwise compare with ===
384 if ( is_array( $e['value'] ) && is_array( $r['value'] ) ) {
385 $this->assertQueryResults( $e['value'], $r['value'] );
386 } else {
387 $this->assertEquals( $e['value'], $r['value'] );
388 }
389 }
390 }
391 }
392 }