aboutsummaryrefslogtreecommitdiffstats
path: root/tests/phpunit/includes/api
diff options
context:
space:
mode:
authorYuri Astrakhan <yuriastrakhan@gmail.com>2013-03-01 19:06:46 -0500
committerYuri Astrakhan <yuriastrakhan@gmail.com>2013-03-01 19:06:46 -0500
commit68820277aff9409f54494eb35c4cb005d3cd98aa (patch)
treee16502be60339906c8e7c0188fa87f50778fbadf /tests/phpunit/includes/api
parentea77ca659d8a4d15ed34ef8383a6830055d9e15c (diff)
downloadmediawikicore-68820277aff9409f54494eb35c4cb005d3cd98aa.tar.gz
mediawikicore-68820277aff9409f54494eb35c4cb005d3cd98aa.zip
API continue param to streamline iteration of complex queries
Greatly simplifies query result iteration by the clients by providing a mechanism to track sub-iterations (props in generated set) Assuming the client has the param=>value dictionary with the original request parameters, client will only need to perform this operation in their language to get all results from the server regardless of what query they make. $request = array_merge( $request, $result['continue'] ); Related changes: * Moved dieContinueUsageIf() from ApiQueryBase to ApiBase * Internal calls will also return unused param warnings * Reworked query unit tests for easier testing Change-Id: Ieb45241fc6db2109f1d92fa3381165ec30701b63
Diffstat (limited to 'tests/phpunit/includes/api')
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryBasicTest.php55
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryContinue2Test.php68
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryContinueTest.php313
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php203
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryTestBase.php149
5 files changed, 739 insertions, 49 deletions
diff --git a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php
index a5ca256eadc9..6d4e3711df5b 100644
--- a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php
+++ b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php
@@ -22,14 +22,17 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- *
- * These tests validate basic functionality of the api query module
+ */
+
+require_once( 'ApiQueryTestBase.php' );
+
+/** These tests validate basic functionality of the api query module
*
* @group API
* @group Database
* @group medium
*/
-class ApiQueryBasicTest extends ApiTestCase {
+class ApiQueryBasicTest extends ApiQueryTestBase {
/**
* Create a set of pages. These must not change, otherwise the tests might give wrong results.
* @see MediaWikiTestCase::addDBData()
@@ -297,20 +300,6 @@ class ApiQueryBasicTest extends ApiTestCase {
}
/**
- * Merges all requests (parameter arrays) into one
- * @return array
- */
- private function merge( /*...*/ ) {
- $request = array();
- $expected = array();
- foreach ( func_get_args() as $v ) {
- $request = array_merge_recursive( $request, $v[0] );
- $this->mergeExpected( $expected, $v[1] );
- }
- return array( $request, $expected );
- }
-
- /**
* Recursively merges the expected values in the $item into the $all
*/
private function mergeExpected( &$all, $item ) {
@@ -328,38 +317,6 @@ class ApiQueryBasicTest extends ApiTestCase {
}
/**
- * Checks that the request's result matches the expected results.
- * @param $values array is a two element array( request, expected_results )
- * @throws Exception
- */
- private function check( $values ) {
- $request = $values[0];
- $expected = $values[1];
- if ( !array_key_exists( 'action', $request ) ) {
- $request['action'] = 'query';
- }
- foreach ( $request as &$val ) {
- if ( is_array( $val ) ) {
- $val = implode( '|', array_unique( $val ) );
- }
- }
- $result = $this->doApiRequest( $request );
- $result = $result[0];
- $expected = array( 'query' => $expected );
- try {
- $this->assertQueryResults( $expected, $result );
- } catch ( Exception $e ) {
- print( "\nRequest:\n" );
- print_r( $request );
- print( "\nExpected:\n" );
- print_r( $expected );
- print( "\nResult:\n" );
- print_r( $result );
- throw $e; // rethrow it
- }
- }
-
- /**
* Recursively compare arrays, ignoring mismatches in numeric key and pageids.
* @param $expected array expected values
* @param $result array returned values
diff --git a/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php
new file mode 100644
index 000000000000..0a3ac1da7c87
--- /dev/null
+++ b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+require_once( 'ApiQueryContinueTestBase.php' );
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ */
+class ApiQueryContinue2Test extends ApiQueryContinueTestBase {
+ /**
+ * Create a set of pages. These must not change, otherwise the tests might give wrong results.
+ * @see MediaWikiTestCase::addDBData()
+ */
+ function addDBData() {
+ try {
+ $this->editPage( 'AQCT73462-A', '**AQCT73462-A** [[AQCT73462-B]] [[AQCT73462-C]]' );
+ $this->editPage( 'AQCT73462-B', '[[AQCT73462-A]] **AQCT73462-B** [[AQCT73462-C]]' );
+ $this->editPage( 'AQCT73462-C', '[[AQCT73462-A]] [[AQCT73462-B]] **AQCT73462-C**' );
+ $this->editPage( 'AQCT73462-A', '**AQCT73462-A** [[AQCT73462-B]] [[AQCT73462-C]]' );
+ $this->editPage( 'AQCT73462-B', '[[AQCT73462-A]] **AQCT73462-B** [[AQCT73462-C]]' );
+ $this->editPage( 'AQCT73462-C', '[[AQCT73462-A]] [[AQCT73462-B]] **AQCT73462-C**' );
+ } catch ( Exception $e ) {
+ $this->exceptionFromAddDBData = $e;
+ }
+ }
+
+ /**
+ * @medium
+ */
+ public function testA() {
+ $this->mVerbose = false;
+ $mk = function( $g, $p, $gDir ) {
+ return array(
+ 'generator' => 'allpages',
+ 'gapprefix' => 'AQCT73462-',
+ 'prop' => 'links',
+ 'gaplimit' => "$g",
+ 'pllimit' => "$p",
+ 'gapdir' => $gDir ? "ascending" : "descending",
+ );
+ };
+ // generator + 1 prop + 1 list
+ $data = $this->query( $mk(99,99,true), 1, 'g1p', false );
+ $this->checkC( $data, $mk(1,1,true), 6, 'g1p-11t' );
+ $this->checkC( $data, $mk(2,2,true), 3, 'g1p-22t' );
+ $this->checkC( $data, $mk(1,1,false), 6, 'g1p-11f' );
+ $this->checkC( $data, $mk(2,2,false), 3, 'g1p-22f' );
+ }
+}
diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTest.php b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php
new file mode 100644
index 000000000000..cb8f181285da
--- /dev/null
+++ b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php
@@ -0,0 +1,313 @@
+<?php
+/**
+ * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+require_once( 'ApiQueryContinueTestBase.php' );
+
+/**
+ * These tests validate the new continue functionality of the api query module by
+ * doing multiple requests with varying parameters, merging the results, and checking
+ * that the result matches the full data received in one no-limits call.
+ *
+ * @group API
+ * @group Database
+ * @group medium
+ */
+class ApiQueryContinueTest extends ApiQueryContinueTestBase {
+ /**
+ * Create a set of pages. These must not change, otherwise the tests might give wrong results.
+ * @see MediaWikiTestCase::addDBData()
+ */
+ function addDBData() {
+ try {
+ $this->editPage( 'Template:AQCT-T1', '**Template:AQCT-T1**' );
+ $this->editPage( 'Template:AQCT-T2', '**Template:AQCT-T2**' );
+ $this->editPage( 'Template:AQCT-T3', '**Template:AQCT-T3**' );
+ $this->editPage( 'Template:AQCT-T4', '**Template:AQCT-T4**' );
+ $this->editPage( 'Template:AQCT-T5', '**Template:AQCT-T5**' );
+
+ $this->editPage( 'AQCT-1', '**AQCT-1** {{AQCT-T2}} {{AQCT-T3}} {{AQCT-T4}} {{AQCT-T5}}' );
+ $this->editPage( 'AQCT-2', '[[AQCT-1]] **AQCT-2** {{AQCT-T3}} {{AQCT-T4}} {{AQCT-T5}}' );
+ $this->editPage( 'AQCT-3', '[[AQCT-1]] [[AQCT-2]] **AQCT-3** {{AQCT-T4}} {{AQCT-T5}}' );
+ $this->editPage( 'AQCT-4', '[[AQCT-1]] [[AQCT-2]] [[AQCT-3]] **AQCT-4** {{AQCT-T5}}' );
+ $this->editPage( 'AQCT-5', '[[AQCT-1]] [[AQCT-2]] [[AQCT-3]] [[AQCT-4]] **AQCT-5**' );
+ } catch ( Exception $e ) {
+ $this->exceptionFromAddDBData = $e;
+ }
+ }
+
+ /**
+ * Test smart continue - list=allpages
+ * @medium
+ */
+ public function test1List() {
+ $this->mVerbose = false;
+ $mk = function( $l ) {
+ return array(
+ 'list' => 'allpages',
+ 'apprefix' => 'AQCT-',
+ 'aplimit' => "$l",
+ );
+ };
+ $data = $this->query( $mk(99), 1, '1L', false );
+
+ // 1 list
+ $this->checkC( $data, $mk(1), 5, '1L-1' );
+ $this->checkC( $data, $mk(2), 3, '1L-2' );
+ $this->checkC( $data, $mk(3), 2, '1L-3' );
+ $this->checkC( $data, $mk(4), 2, '1L-4' );
+ $this->checkC( $data, $mk(5), 1, '1L-5' );
+ }
+
+ /**
+ * Test smart continue - list=allpages|alltransclusions
+ * @medium
+ */
+ public function test2Lists() {
+ $this->mVerbose = false;
+ $mk = function( $l1, $l2 ) {
+ return array(
+ 'list' => 'allpages|alltransclusions',
+ 'apprefix' => 'AQCT-',
+ 'atprefix' => 'AQCT-',
+ 'atunique' => '',
+ 'aplimit' => "$l1",
+ 'atlimit' => "$l2",
+ );
+ };
+ // 2 lists
+ $data = $this->query( $mk(99,99), 1, '2L', false );
+ $this->checkC( $data, $mk(1,1), 5, '2L-11' );
+ $this->checkC( $data, $mk(2,2), 3, '2L-22' );
+ $this->checkC( $data, $mk(3,3), 2, '2L-33' );
+ $this->checkC( $data, $mk(4,4), 2, '2L-44' );
+ $this->checkC( $data, $mk(5,5), 1, '2L-55' );
+ }
+
+ /**
+ * Test smart continue - generator=allpages, prop=links
+ * @medium
+ */
+ public function testGen1Prop() {
+ $this->mVerbose = false;
+ $mk = function( $g, $p ) {
+ return array(
+ 'generator' => 'allpages',
+ 'gapprefix' => 'AQCT-',
+ 'gaplimit' => "$g",
+ 'prop' => 'links',
+ 'pllimit' => "$p",
+ );
+ };
+ // generator + 1 prop
+ $data = $this->query( $mk(99,99), 1, 'G1P', false );
+ $this->checkC( $data, $mk(1,1), 11, 'G1P-11' );
+ $this->checkC( $data, $mk(2,2), 6, 'G1P-22' );
+ $this->checkC( $data, $mk(3,3), 4, 'G1P-33' );
+ $this->checkC( $data, $mk(4,4), 3, 'G1P-44' );
+ $this->checkC( $data, $mk(5,5), 2, 'G1P-55' );
+ }
+
+ /**
+ * Test smart continue - generator=allpages, prop=links|templates
+ * @medium
+ */
+ public function testGen2Prop() {
+ $this->mVerbose = false;
+ $mk = function( $g, $p1, $p2 ) {
+ return array(
+ 'generator' => 'allpages',
+ 'gapprefix' => 'AQCT-',
+ 'gaplimit' => "$g",
+ 'prop' => 'links|templates',
+ 'pllimit' => "$p1",
+ 'tllimit' => "$p2",
+ );
+ };
+ // generator + 2 props
+ $data = $this->query( $mk(99,99,99), 1, 'G2P', false );
+ $this->checkC( $data, $mk(1,1,1), 16, 'G2P-111' );
+ $this->checkC( $data, $mk(2,2,2), 9, 'G2P-222' );
+ $this->checkC( $data, $mk(3,3,3), 6, 'G2P-333' );
+ $this->checkC( $data, $mk(4,4,4), 4, 'G2P-444' );
+ $this->checkC( $data, $mk(5,5,5), 2, 'G2P-555' );
+ $this->checkC( $data, $mk(5,1,1), 10, 'G2P-511' );
+ $this->checkC( $data, $mk(4,2,2), 7, 'G2P-422' );
+ $this->checkC( $data, $mk(2,3,3), 7, 'G2P-233' );
+ $this->checkC( $data, $mk(2,4,4), 5, 'G2P-244' );
+ $this->checkC( $data, $mk(1,5,5), 5, 'G2P-155' );
+ }
+
+ /**
+ * Test smart continue - generator=allpages, prop=links, list=alltransclusions
+ * @medium
+ */
+ public function testGen1Prop1List() {
+ $this->mVerbose = false;
+ $mk = function( $g, $p, $l ) {
+ return array(
+ 'generator' => 'allpages',
+ 'gapprefix' => 'AQCT-',
+ 'gaplimit' => "$g",
+ 'prop' => 'links',
+ 'pllimit' => "$p",
+ 'list' => 'alltransclusions',
+ 'atprefix' => 'AQCT-',
+ 'atunique' => '',
+ 'atlimit' => "$l",
+ );
+ };
+ // generator + 1 prop + 1 list
+ $data = $this->query( $mk(99,99,99), 1, 'G1P1L', false );
+ $this->checkC( $data, $mk(1,1,1), 11, 'G1P1L-111' );
+ $this->checkC( $data, $mk(2,2,2), 6, 'G1P1L-222' );
+ $this->checkC( $data, $mk(3,3,3), 4, 'G1P1L-333' );
+ $this->checkC( $data, $mk(4,4,4), 3, 'G1P1L-444' );
+ $this->checkC( $data, $mk(5,5,5), 2, 'G1P1L-555' );
+ $this->checkC( $data, $mk(5,5,1), 4, 'G1P1L-551' );
+ $this->checkC( $data, $mk(5,5,2), 2, 'G1P1L-552' );
+ }
+
+ /**
+ * Test smart continue - generator=allpages, prop=links|templates,
+ * list=alllinks|alltransclusions, meta=siteinfo
+ * @medium
+ */
+ public function testGen2Prop2List1Meta() {
+ $this->mVerbose = false;
+ $mk = function( $g, $p1, $p2, $l1, $l2 ) {
+ return array(
+ 'generator' => 'allpages',
+ 'gapprefix' => 'AQCT-',
+ 'gaplimit' => "$g",
+ 'prop' => 'links|templates',
+ 'pllimit' => "$p1",
+ 'tllimit' => "$p2",
+ 'list' => 'alllinks|alltransclusions',
+ 'alprefix' => 'AQCT-',
+ 'alunique' => '',
+ 'allimit' => "$l1",
+ 'atprefix' => 'AQCT-',
+ 'atunique' => '',
+ 'atlimit' => "$l2",
+ 'meta' => 'siteinfo',
+ 'siprop' => 'namespaces',
+ );
+ };
+ // generator + 1 prop + 1 list
+ $data = $this->query( $mk(99,99,99,99,99), 1, 'G2P2L1M', false );
+ $this->checkC( $data, $mk(1,1,1,1,1), 16, 'G2P2L1M-11111' );
+ $this->checkC( $data, $mk(2,2,2,2,2), 9, 'G2P2L1M-22222' );
+ $this->checkC( $data, $mk(3,3,3,3,3), 6, 'G2P2L1M-33333' );
+ $this->checkC( $data, $mk(4,4,4,4,4), 4, 'G2P2L1M-44444' );
+ $this->checkC( $data, $mk(5,5,5,5,5), 2, 'G2P2L1M-55555' );
+ $this->checkC( $data, $mk(5,5,5,1,1), 4, 'G2P2L1M-55511' );
+ $this->checkC( $data, $mk(5,5,5,2,2), 2, 'G2P2L1M-55522' );
+ $this->checkC( $data, $mk(5,1,1,5,5), 10, 'G2P2L1M-51155' );
+ $this->checkC( $data, $mk(5,2,2,5,5), 5, 'G2P2L1M-52255' );
+ }
+
+ /**
+ * Test smart continue - generator=templates, prop=templates
+ * @medium
+ */
+ public function testSameGenAndProp() {
+ $this->mVerbose = false;
+ $mk = function( $g, $gDir, $p, $pDir ) {
+ return array(
+ 'titles' => 'AQCT-1',
+ 'generator' => 'templates',
+ 'gtllimit' => "$g",
+ 'gtldir' => $gDir ? 'ascending' : 'descending',
+ 'prop' => 'templates',
+ 'tllimit' => "$p",
+ 'tldir' => $pDir ? 'ascending' : 'descending',
+ );
+ };
+ // generator + 1 prop
+ $data = $this->query( $mk(99,true,99,true), 1, 'G=P', false );
+
+ $this->checkC( $data, $mk(1,true,1,true), 4, 'G=P-1t1t' );
+ $this->checkC( $data, $mk(2,true,2,true), 2, 'G=P-2t2t' );
+ $this->checkC( $data, $mk(3,true,3,true), 2, 'G=P-3t3t' );
+ $this->checkC( $data, $mk(1,true,3,true), 4, 'G=P-1t3t' );
+ $this->checkC( $data, $mk(3,true,1,true), 2, 'G=P-3t1t' );
+
+ $this->checkC( $data, $mk(1,true,1,false), 4, 'G=P-1t1f' );
+ $this->checkC( $data, $mk(2,true,2,false), 2, 'G=P-2t2f' );
+ $this->checkC( $data, $mk(3,true,3,false), 2, 'G=P-3t3f' );
+ $this->checkC( $data, $mk(1,true,3,false), 4, 'G=P-1t3f' );
+ $this->checkC( $data, $mk(3,true,1,false), 2, 'G=P-3t1f' );
+
+ $this->checkC( $data, $mk(1,false,1,true), 4, 'G=P-1f1t' );
+ $this->checkC( $data, $mk(2,false,2,true), 2, 'G=P-2f2t' );
+ $this->checkC( $data, $mk(3,false,3,true), 2, 'G=P-3f3t' );
+ $this->checkC( $data, $mk(1,false,3,true), 4, 'G=P-1f3t' );
+ $this->checkC( $data, $mk(3,false,1,true), 2, 'G=P-3f1t' );
+
+ $this->checkC( $data, $mk(1,false,1,false), 4, 'G=P-1f1f' );
+ $this->checkC( $data, $mk(2,false,2,false), 2, 'G=P-2f2f' );
+ $this->checkC( $data, $mk(3,false,3,false), 2, 'G=P-3f3f' );
+ $this->checkC( $data, $mk(1,false,3,false), 4, 'G=P-1f3f' );
+ $this->checkC( $data, $mk(3,false,1,false), 2, 'G=P-3f1f' );
+ }
+
+ /**
+ * Test smart continue - generator=allpages, list=allpages
+ * @medium
+ */
+ public function testSameGenList() {
+ $this->mVerbose = false;
+ $mk = function( $g, $gDir, $l, $pDir ) {
+ return array(
+ 'generator' => 'allpages',
+ 'gapprefix' => 'AQCT-',
+ 'gaplimit' => "$g",
+ 'gapdir' => $gDir ? 'ascending' : 'descending',
+ 'list' => 'allpages',
+ 'apprefix' => 'AQCT-',
+ 'aplimit' => "$l",
+ 'apdir' => $pDir ? 'ascending' : 'descending',
+ );
+ };
+ // generator + 1 list
+ $data = $this->query( $mk(99,true,99,true), 1, 'G=L', false );
+
+ $this->checkC( $data, $mk(1,true,1,true), 5, 'G=L-1t1t' );
+ $this->checkC( $data, $mk(2,true,2,true), 3, 'G=L-2t2t' );
+ $this->checkC( $data, $mk(3,true,3,true), 2, 'G=L-3t3t' );
+ $this->checkC( $data, $mk(1,true,3,true), 5, 'G=L-1t3t' );
+ $this->checkC( $data, $mk(3,true,1,true), 5, 'G=L-3t1t' );
+ $this->checkC( $data, $mk(1,true,1,false), 5, 'G=L-1t1f' );
+ $this->checkC( $data, $mk(2,true,2,false), 3, 'G=L-2t2f' );
+ $this->checkC( $data, $mk(3,true,3,false), 2, 'G=L-3t3f' );
+ $this->checkC( $data, $mk(1,true,3,false), 5, 'G=L-1t3f' );
+ $this->checkC( $data, $mk(3,true,1,false), 5, 'G=L-3t1f' );
+ $this->checkC( $data, $mk(1,false,1,true), 5, 'G=L-1f1t' );
+ $this->checkC( $data, $mk(2,false,2,true), 3, 'G=L-2f2t' );
+ $this->checkC( $data, $mk(3,false,3,true), 2, 'G=L-3f3t' );
+ $this->checkC( $data, $mk(1,false,3,true), 5, 'G=L-1f3t' );
+ $this->checkC( $data, $mk(3,false,1,true), 5, 'G=L-3f1t' );
+ $this->checkC( $data, $mk(1,false,1,false), 5, 'G=L-1f1f' );
+ $this->checkC( $data, $mk(2,false,2,false), 3, 'G=L-2f2f' );
+ $this->checkC( $data, $mk(3,false,3,false), 2, 'G=L-3f3f' );
+ $this->checkC( $data, $mk(1,false,3,false), 5, 'G=L-1f3f' );
+ $this->checkC( $data, $mk(3,false,1,false), 5, 'G=L-3f1f' );
+ }
+}
diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php
new file mode 100644
index 000000000000..471747964692
--- /dev/null
+++ b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php
@@ -0,0 +1,203 @@
+<?php
+/**
+ *
+ *
+ * Created on Jan 1, 2013
+ *
+ * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+require_once( 'ApiQueryTestBase.php' );
+
+abstract class ApiQueryContinueTestBase extends ApiQueryTestBase {
+
+ /**
+ * Enable to print in-depth debugging info during the test run
+ */
+ protected $mVerbose = false;
+
+ /**
+ * Run query() and compare against expected values
+ */
+ protected function checkC( $expected, $params, $expectedCount, $id, $continue = true ) {
+ $result = $this->query( $params, $expectedCount, $id, $continue );
+ $this->assertResult( $expected, $result, $id );
+ }
+
+ /**
+ * Run query in a loop until no more values are available
+ * @param array $params api parameters
+ * @param int $expectedCount max number of iterations
+ * @param string $id unit test id
+ * @param boolean $useContinue true to use smart continue
+ * @return mixed: merged results data array
+ * @throws Exception
+ */
+ protected function query( $params, $expectedCount, $id, $useContinue = true ) {
+ if ( isset( $params['action'] ) ) {
+ $this->assertEquals( 'query', $params['action'], 'Invalid query action');
+ } else {
+ $params['action'] = 'query';
+ }
+ if ( $useContinue && !isset( $params['continue'] ) ) {
+ $params['continue'] = '';
+ }
+ $count = 0;
+ $result = array();
+ $continue = array();
+ do {
+ $request = array_merge( $params, $continue );
+ uksort( $request, function( $a, $b ) {
+ // put 'continue' params at the end - lazy method
+ $a = strpos( $a, 'continue' ) !== false ? 'zzz ' . $a : $a;
+ $b = strpos( $b, 'continue' ) !== false ? 'zzz ' . $b : $b;
+ return strcmp( $a, $b );
+ } );
+ $reqStr = http_build_query( $request );
+ //$reqStr = str_replace( '&', ' & ', $reqStr );
+ $this->assertLessThan( $expectedCount, $count, "$id more data: $reqStr" );
+ if ( $this->mVerbose ) {
+ print ("$id (#$count): $reqStr\n");
+ }
+ try {
+ $data = $this->doApiRequest( $request );
+ } catch ( Exception $e ) {
+ throw new Exception( "$id on $count", 0, $e );
+ }
+ $data = $data[0];
+ if ( isset( $data['warnings'] ) ) {
+ $warnings = json_encode( $data['warnings'] );
+ $this->fail( "$id Warnings on #$count in $reqStr\n$warnings" );
+ }
+ $this->assertArrayHasKey( 'query', $data, "$id no 'query' on #$count in $reqStr" );
+ if ( isset( $data['continue'] ) ) {
+ $continue = $data['continue'];
+ unset( $data['continue'] );
+ } else {
+ $continue = array();
+ }
+ if ( $this->mVerbose ) {
+ $this->printResult( $data );
+ }
+ $this->mergeResult( $result, $data );
+ $count++;
+ if ( empty( $continue ) ) {
+ // $this->assertEquals( $expectedCount, $count, "$id finished early" );
+ if ( $expectedCount > $count ) {
+ print "***** $id Finished early in $count turns. $expectedCount was expected\n";
+ }
+ return $result;
+ } elseif ( !$useContinue ) {
+ $this->assertFalse( 'Non-smart query must be requested all at once' );
+ }
+ } while( true );
+ }
+
+ private function printResult( $data ) {
+ $q = $data['query'];
+ $print = array();
+ if (isset($q['pages'])) {
+ foreach ($q['pages'] as $p) {
+ $m = $p['title'];
+ if (isset($p['links'])) {
+ $m .= '/[' . implode(',', array_map(
+ function ($v) {
+ return $v['title'];
+ },
+ $p['links'])) . ']';
+ }
+ if (isset($p['categories'])) {
+ $m .= '/(' . implode(',', array_map(
+ function ($v) {
+ return str_replace('Category:', '', $v['title']);
+ },
+ $p['categories'])) . ')';
+ }
+ $print[] = $m;
+ }
+ }
+ if (isset($q['allcategories'])) {
+ $print[] = '*Cats/(' . implode(',', array_map(
+ function ($v) { return $v['*']; },
+ $q['allcategories'])) . ')';
+ }
+ self::GetItems( $q, 'allpages', 'Pages', $print );
+ self::GetItems( $q, 'alllinks', 'Links', $print );
+ self::GetItems( $q, 'alltransclusions', 'Trnscl', $print );
+ print(' ' . implode(' ', $print) . "\n");
+ }
+
+ private static function GetItems( $q, $moduleName, $name, &$print ) {
+ if (isset($q[$moduleName])) {
+ $print[] = "*$name/[" . implode(',',
+ array_map( function ($v) { return $v['title']; },
+ $q[$moduleName])) . ']';
+ }
+ }
+
+ /**
+ * Recursively merge the new result returned from the query to the previous results.
+ * @param mixed $results
+ * @param mixed $newResult
+ * @param bool $numericIds If true, treat keys as ids to be merged instead of appending
+ */
+ protected function mergeResult( &$results, $newResult, $numericIds = false ) {
+ $this->assertEquals( is_array( $results ), is_array( $newResult ), 'Type of result and data do not match' );
+ if ( !is_array( $results ) ) {
+ $this->assertEquals( $results, $newResult, 'Repeated result must be the same as before' );
+ } else {
+ $sort = null;
+ foreach( $newResult as $key => $value ) {
+ if ( !$numericIds && $sort === null ) {
+ if ( !is_array( $value ) ) {
+ $sort = false;
+ } elseif ( array_key_exists( 'title', $value ) ) {
+ $sort = function( $a, $b ) {
+ return strcmp( $a['title'], $b['title'] );
+ };
+ } else {
+ $sort = false;
+ }
+ }
+ $keyExists = array_key_exists( $key, $results );
+ if ( is_numeric( $key ) ) {
+ if ( $numericIds ) {
+ if ( !$keyExists ) {
+ $results[$key] = $value;
+ } else {
+ $this->mergeResult( $results[$key], $value );
+ }
+ } else {
+ $results[] = $value;
+ }
+ } elseif ( !$keyExists ) {
+ $results[$key] = $value;
+ } else {
+ $this->mergeResult( $results[$key], $value, $key === 'pages' );
+ }
+ }
+ if ( $numericIds ) {
+ ksort( $results, SORT_NUMERIC );
+ } elseif ( $sort !== null && $sort !== false ) {
+ uasort( $results, $sort );
+ }
+ }
+ }
+}
diff --git a/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/tests/phpunit/includes/api/query/ApiQueryTestBase.php
new file mode 100644
index 000000000000..7b9f8ede5a08
--- /dev/null
+++ b/tests/phpunit/includes/api/query/ApiQueryTestBase.php
@@ -0,0 +1,149 @@
+<?php
+/**
+ *
+ *
+ * Created on Feb 10, 2013
+ *
+ * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+
+/** This class has some common functionality for testing query module
+ */
+abstract class ApiQueryTestBase extends ApiTestCase {
+
+ const PARAM_ASSERT = <<<STR
+Each parameter must be an array of two elements,
+first - an array of params to the API call,
+and the second array - expected results as returned by the API
+STR;
+
+ /**
+ * Merges all requests parameter + expected values into one
+ * @param ... list of arrays, each of which contains exactly two
+ * @return array
+ */
+ protected function merge( /*...*/ ) {
+ $request = array();
+ $expected = array();
+ foreach ( func_get_args() as $v ) {
+ list( $req, $exp ) = $this->validateRequestExpectedPair( $v );
+ $request = array_merge_recursive( $request, $req );
+ $this->mergeExpected( $expected, $exp );
+ }
+ return array( $request, $expected );
+ }
+
+ /**
+ * Check that the parameter is a valid two element array,
+ * with the first element being API request and the second - expected result
+ */
+ private function validateRequestExpectedPair( $v ) {
+ $this->assertType( 'array', $v, self::PARAM_ASSERT );
+ $this->assertEquals( 2, count($v), self::PARAM_ASSERT );
+ $this->assertArrayHasKey( 0, $v, self::PARAM_ASSERT );
+ $this->assertArrayHasKey( 1, $v, self::PARAM_ASSERT );
+ $this->assertType( 'array', $v[0], self::PARAM_ASSERT );
+ $this->assertType( 'array', $v[1], self::PARAM_ASSERT );
+ return $v;
+ }
+
+ /**
+ * Recursively merges the expected values in the $item into the $all
+ */
+ private function mergeExpected( &$all, $item ) {
+ foreach ( $item as $k => $v ) {
+ if ( array_key_exists( $k, $all ) ) {
+ if ( is_array ( $all[$k] ) ) {
+ $this->mergeExpected( $all[$k], $v );
+ } else {
+ $this->assertEquals( $all[$k], $v );
+ }
+ } else {
+ $all[$k] = $v;
+ }
+ }
+ }
+
+ /**
+ * Checks that the request's result matches the expected results.
+ * @param $values array is a two element array( request, expected_results )
+ * @throws Exception
+ */
+ protected function check( $values ) {
+ list( $req, $exp ) = $this->validateRequestExpectedPair( $values );
+ if ( !array_key_exists( 'action', $req ) ) {
+ $req['action'] = 'query';
+ }
+ foreach ( $req as &$val ) {
+ if ( is_array( $val ) ) {
+ $val = implode( '|', array_unique( $val ) );
+ }
+ }
+ $result = $this->doApiRequest( $req );
+ $this->assertResult( array( 'query' => $exp ), $result[0], $req );
+ }
+
+ protected function assertResult( $exp, $result, $message = '' ) {
+ try {
+ $this->assertResultRecursive( $exp, $result );
+ } catch ( Exception $e ) {
+ if ( is_array( $message ) ) {
+ $message = http_build_query( $message );
+ }
+ print( "\nRequest: $message\n" );
+ print( "\nExpected:\n" );
+ print_r( $exp );
+ print( "\nResult:\n" );
+ print_r( $result );
+ throw $e; // rethrow it
+ }
+ }
+
+ /**
+ * Recursively compare arrays, ignoring mismatches in numeric key and pageids.
+ * @param $expected array expected values
+ * @param $result array returned values
+ */
+ private function assertResultRecursive( $expected, $result ) {
+ reset( $expected );
+ reset( $result );
+ while ( true ) {
+ $e = each( $expected );
+ $r = each( $result );
+ // If either of the arrays is shorter, abort. If both are done, success.
+ $this->assertEquals( (bool)$e, (bool)$r );
+ if ( !$e ) {
+ break; // done
+ }
+ // continue only if keys are identical or both keys are numeric
+ $this->assertTrue( $e['key'] === $r['key'] || ( is_numeric( $e['key'] ) && is_numeric( $r['key'] ) ) );
+ // don't compare pageids
+ if ( $e['key'] !== 'pageid' ) {
+ // If values are arrays, compare recursively, otherwise compare with ===
+ if ( is_array( $e['value'] ) && is_array( $r['value'] ) ) {
+ $this->assertResultRecursive( $e['value'], $r['value'] );
+ } else {
+ $this->assertEquals( $e['value'], $r['value'] );
+ }
+ }
+ }
+ }
+}