API tests to verify basic query functionality (list & props)
authorYuri Astrakhan <yuriastrakhan@gmail.com>
Fri, 8 Feb 2013 06:45:19 +0000 (01:45 -0500)
committerYuri Astrakhan <yuriastrakhan@gmail.com>
Fri, 8 Feb 2013 06:45:19 +0000 (01:45 -0500)
* Several tests to verify query with prop=, list=, and generator
* Moved query related tests to query\ dir
* Added a generic ApiTestCase::editPage() to simplify page creation
* Fixed minor warnings complained by jenkins/phpcs

Change-Id: I5e3984d797178ae03f048792c7bac8e6a881aa41

tests/phpunit/includes/api/ApiQueryRevisionsTest.php [deleted file]
tests/phpunit/includes/api/ApiQueryTest.php [deleted file]
tests/phpunit/includes/api/ApiTestCase.php
tests/phpunit/includes/api/query/ApiQueryBasicTest.php [new file with mode: 0644]
tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php [new file with mode: 0644]
tests/phpunit/includes/api/query/ApiQueryTest.php [new file with mode: 0644]

diff --git a/tests/phpunit/includes/api/ApiQueryRevisionsTest.php b/tests/phpunit/includes/api/ApiQueryRevisionsTest.php
deleted file mode 100644 (file)
index 19da81c..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/**
- * @group API
- * @group Database
- * @group medium
- */
-class ApiQueryRevisionsTest extends ApiTestCase {
-
-       /**
-        * @group medium
-        */
-       function testContentComesWithContentModelAndFormat() {
-
-               $pageName = 'Help:' . __METHOD__ ;
-               $title = Title::newFromText( $pageName );
-               $page = WikiPage::factory( $title );
-               $page->doEdit( 'Some text', 'inserting content' );
-
-               $apiResult = $this->doApiRequest( array(
-                       'action' => 'query',
-                       'prop' => 'revisions',
-                       'titles' => $pageName,
-                       'rvprop' => 'content',
-               ) );
-               $this->assertArrayHasKey( 'query', $apiResult[0] );
-               $this->assertArrayHasKey( 'pages', $apiResult[0]['query'] );
-               foreach( $apiResult[0]['query']['pages'] as $page ) {
-                       $this->assertArrayHasKey( 'revisions', $page );
-                       foreach( $page['revisions'] as $revision ) {
-                               $this->assertArrayHasKey( 'contentformat', $revision,
-                                       'contentformat should be included when asking content so'
-                                       . ' client knows how to interpretate it'
-                               );
-                               $this->assertArrayHasKey( 'contentmodel', $revision,
-                                       'contentmodel should be included when asking content so'
-                                       . ' client knows how to interpretate it'
-                               );
-                       }
-               }
-       }
-}
diff --git a/tests/phpunit/includes/api/ApiQueryTest.php b/tests/phpunit/includes/api/ApiQueryTest.php
deleted file mode 100644 (file)
index 1b1886e..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-
-/**
- * @group API
- * @group Database
- * @group medium
- */
-class ApiQueryTest extends ApiTestCase {
-
-       protected function setUp() {
-               parent::setUp();
-               $this->doLogin();
-       }
-
-       function testTitlesGetNormalized() {
-
-               global $wgMetaNamespace;
-
-               $data = $this->doApiRequest( array(
-                       'action' => 'query',
-                       'titles' => 'Project:articleA|article_B' ) );
-
-
-               $this->assertArrayHasKey( 'query', $data[0] );
-               $this->assertArrayHasKey( 'normalized', $data[0]['query'] );
-
-               // Forge a normalized title
-               $to = Title::newFromText( $wgMetaNamespace.':ArticleA' );
-
-               $this->assertEquals(
-                       array(
-                               'from' => 'Project:articleA',
-                               'to' => $to->getPrefixedText(),
-                       ),
-                       $data[0]['query']['normalized'][0]
-               );
-
-               $this->assertEquals(
-                       array(
-                               'from' => 'article_B',
-                               'to' => 'Article B'
-                       ),
-                       $data[0]['query']['normalized'][1]
-               );
-
-       }
-
-       function testTitlesAreRejectedIfInvalid() {
-               $title = false;
-               while( !$title || Title::newFromText( $title )->exists() ) {
-                       $title = md5( mt_rand( 0, 10000 ) + rand( 0, 999000 ) );
-               }
-
-               $data = $this->doApiRequest( array(
-                       'action' => 'query',
-                       'titles' => $title . '|Talk:' ) );
-
-               $this->assertArrayHasKey( 'query', $data[0] );
-               $this->assertArrayHasKey( 'pages', $data[0]['query'] );
-               $this->assertEquals( 2, count( $data[0]['query']['pages'] ) );
-
-               $this->assertArrayHasKey( -2, $data[0]['query']['pages'] );
-               $this->assertArrayHasKey( -1, $data[0]['query']['pages'] );
-
-               $this->assertArrayHasKey( 'missing', $data[0]['query']['pages'][-2] );
-               $this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] );
-       }
-
-}
index fcd581a..d152b1b 100644 (file)
@@ -41,6 +41,20 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
 
        }
 
+       /**
+        * Edits or creates a page/revision
+        * @param $pageName string page title
+        * @param $text string content of the page
+        * @param $summary string optional summary string for the revision
+        * @param $defaultNs int optional namespace id
+        * @return array as returned by WikiPage::doEditContent()
+        */
+       protected function editPage( $pageName, $text, $summary = '', $defaultNs = NS_MAIN ) {
+               $title = Title::newFromText( $pageName, $defaultNs );
+               $page = WikiPage::factory( $title );
+               return $page->doEditContent( ContentHandler::makeContent( $text, $title ), $summary );
+       }
+
        /**
         * Does the API request and returns the result.
         *
@@ -102,6 +116,8 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
         * @param $params Array: key-value API params
         * @param $session Array|null: session array
         * @param $user User|null A User object for the context
+        * @return result of the API call
+        * @throws Exception in case wsToken is not set in the session
         */
        protected function doApiRequestWithToken( array $params, array $session = null, User $user = null ) {
                global $wgRequest;
@@ -161,7 +177,9 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
 }
 
 class UserWrapper {
-       public $userName, $password, $user;
+       public $userName;
+       public $password;
+       public $user;
 
        public function __construct( $userName, $password, $group = '' ) {
                $this->userName = $userName;
diff --git a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php
new file mode 100644 (file)
index 0000000..b7dac06
--- /dev/null
@@ -0,0 +1,392 @@
+<?php
+/**
+ *
+ *
+ * Created on Feb 6, 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
+ *
+ * These tests validate basic functionality of the api query module
+ *
+ * @group API
+ * @group Database
+ * @group medium
+ */
+class ApiQueryBasicTest extends ApiTestCase {
+
+       /**
+        * Create a set of pages. These must not change, otherwise the tests might give wrong results.
+        * @see MediaWikiTestCase::addDBData()
+        */
+       function addDBData() {
+               try {
+                       if ( Title::newFromText( 'AQBT-All' )->exists() ) {
+                               return;
+                       }
+
+                       // Ordering is important, as it will be returned in the same order as stored in the index
+                       $this->editPage( 'AQBT-All', '[[Category:AQBT-Cat]] [[AQBT-Links]] {{AQBT-T}}' );
+                       $this->editPage( 'AQBT-Categories', '[[Category:AQBT-Cat]]' );
+                       $this->editPage( 'AQBT-Links', '[[AQBT-All]] [[AQBT-Categories]] [[AQBT-Templates]]' );
+                       $this->editPage( 'AQBT-Templates', '{{AQBT-T}}' );
+                       $this->editPage( 'AQBT-T', 'Content', '', NS_TEMPLATE );
+
+                       // Refresh due to the bug with listing transclusions as links if they don't exist
+                       $this->editPage( 'AQBT-All', '[[Category:AQBT-Cat]] [[AQBT-Links]] {{AQBT-T}}' );
+                       $this->editPage( 'AQBT-Templates', '{{AQBT-T}}' );
+               } catch ( Exception $e ) {
+                       $this->exceptionFromAddDBData = $e;
+               }
+       }
+
+       private static $links = array(
+               array( 'prop' => 'links', 'titles' => 'AQBT-All' ),
+               array( 'pages' => array(
+                       '1' => array(
+                               'pageid' => 1,
+                               'ns' => 0,
+                               'title' => 'AQBT-All',
+                               'links' => array(
+                                       array( 'ns' => 0, 'title' => 'AQBT-Links' ),
+       ) ) ) ) );
+
+       private static $templates = array(
+               array( 'prop' => 'templates', 'titles' => 'AQBT-All' ),
+               array( 'pages' => array(
+                       '1' => array(
+                               'pageid' => 1,
+                               'ns' => 0,
+                               'title' => 'AQBT-All',
+                               'templates' => array(
+                                       array( 'ns' => 10, 'title' => 'Template:AQBT-T' ),
+       ) ) ) ) );
+
+       private static $categories = array(
+               array( 'prop' => 'categories', 'titles' => 'AQBT-All' ),
+               array( 'pages' => array(
+                       '1' => array(
+                               'pageid' => 1,
+                               'ns' => 0,
+                               'title' => 'AQBT-All',
+                               'categories' => array(
+                                       array( 'ns' => 14, 'title' => 'Category:AQBT-Cat' ),
+       ) ) ) ) );
+
+       private static $allpages = array(
+               array( 'list' => 'allpages', 'apprefix' => 'AQBT-' ),
+               array( 'allpages' => array(
+                       array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ),
+                       array( 'pageid' => 2, 'ns' => 0, 'title' => 'AQBT-Categories' ),
+                       array( 'pageid' => 3, 'ns' => 0, 'title' => 'AQBT-Links' ),
+                       array( 'pageid' => 4, 'ns' => 0, 'title' => 'AQBT-Templates' ),
+       ) ) );
+
+       private static $alllinks = array(
+               array( 'list' => 'alllinks', 'alprefix' => 'AQBT-' ),
+               array( 'alllinks' => array(
+                       array( 'ns' => 0, 'title' => 'AQBT-All' ),
+                       array( 'ns' => 0, 'title' => 'AQBT-Categories' ),
+                       array( 'ns' => 0, 'title' => 'AQBT-Links' ),
+                       array( 'ns' => 0, 'title' => 'AQBT-Templates' ),
+       ) ) );
+
+       private static $alltransclusions = array(
+               array( 'list' => 'alltransclusions', 'atprefix' => 'AQBT-' ),
+               array( 'alltransclusions' => array(
+                       array( 'ns' => 10, 'title' => 'Template:AQBT-T' ),
+                       array( 'ns' => 10, 'title' => 'Template:AQBT-T' ),
+       ) ) );
+
+       private static $allcategories = array(
+               array( 'list' => 'allcategories', 'acprefix' => 'AQBT-' ),
+               array( 'allcategories' => array(
+                       array( '*' => 'AQBT-Cat' ),
+       ) ) );
+
+       private static $backlinks = array(
+               array( 'list' => 'backlinks', 'bltitle' => 'AQBT-Links' ),
+               array( 'backlinks' => array(
+                       array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ),
+       ) ) );
+
+       private static $embeddedin = array(
+               array( 'list' => 'embeddedin', 'eititle' => 'Template:AQBT-T' ),
+               array( 'embeddedin' => array(
+                       array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ),
+                       array( 'pageid' => 4, 'ns' => 0, 'title' => 'AQBT-Templates' ),
+       ) ) );
+
+       private static $categorymembers = array(
+               array( 'list' => 'categorymembers', 'cmtitle' => 'Category:AQBT-Cat' ),
+               array( 'categorymembers' => array(
+                       array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ),
+                       array( 'pageid' => 2, 'ns' => 0, 'title' => 'AQBT-Categories' ),
+       ) ) );
+
+       private static $generatorAllpages = array(
+               array( 'generator' => 'allpages', 'gapprefix' => 'AQBT-' ),
+               array( 'pages' => array(
+                       '1' => array(
+                               'pageid' => 1,
+                               'ns' => 0,
+                               'title' => 'AQBT-All' ),
+                       '2' => array(
+                               'pageid' => 2,
+                               'ns' => 0,
+                               'title' => 'AQBT-Categories' ),
+                       '3' => array(
+                               'pageid' => 3,
+                               'ns' => 0,
+                               'title' => 'AQBT-Links' ),
+                       '4' => array(
+                               'pageid' => 4,
+                               'ns' => 0,
+                               'title' => 'AQBT-Templates' ),
+       ) ) );
+
+       private static $generatorLinks = array(
+               array( 'generator' => 'links', 'titles' => 'AQBT-Links' ),
+               array( 'pages' => array(
+                       '1' => array(
+                               'pageid' => 1,
+                               'ns' => 0,
+                               'title' => 'AQBT-All' ),
+                       '2' => array(
+                               'pageid' => 2,
+                               'ns' => 0,
+                               'title' => 'AQBT-Categories' ),
+                       '4' => array(
+                               'pageid' => 4,
+                               'ns' => 0,
+                               'title' => 'AQBT-Templates' ),
+       ) ) );
+
+       private static $generatorLinksPropLinks = array(
+               array( 'prop' => 'links' ),
+               array( 'pages' => array(
+                       '1' => array( 'links' => array(
+                               array( 'ns' => 0, 'title' => 'AQBT-Links' ),
+       ) ) ) ) );
+
+       private static $generatorLinksPropTemplates = array(
+               array( 'prop' => 'templates' ),
+               array( 'pages' => array(
+                       '1' => array( 'templates' => array(
+                               array( 'ns' => 10, 'title' => 'Template:AQBT-T' ) ) ),
+                       '4' => array( 'templates' => array(
+                               array( 'ns' => 10, 'title' => 'Template:AQBT-T' ) ) ),
+               ) ) );
+
+       /**
+        * Test basic props
+        */
+       public function testProps() {
+               $this->check( self::$links );
+               $this->check( self::$templates );
+               $this->check( self::$categories );
+       }
+
+       /**
+        * Test basic lists
+        */
+       public function testLists() {
+               $this->check( self::$allpages );
+               $this->check( self::$alllinks );
+               $this->check( self::$alltransclusions );
+               // This test is temporarily disabled until a sqlite bug is fixed
+               // $this->check( self::$allcategories );
+               $this->check( self::$backlinks );
+               $this->check( self::$embeddedin );
+               $this->check( self::$categorymembers );
+       }
+
+       /**
+        * Test basic lists
+        */
+       public function testAllTogether() {
+
+               // All props together
+               $this->check( $this->merge(
+                       self::$links,
+                       self::$templates,
+                       self::$categories
+               ) );
+
+               // All lists together
+               $this->check( $this->merge(
+                       self::$allpages,
+                       self::$alllinks,
+                       self::$alltransclusions,
+                       // This test is temporarily disabled until a sqlite bug is fixed
+                       // self::$allcategories,
+                       self::$backlinks,
+                       self::$embeddedin,
+                       self::$categorymembers
+               ) );
+
+               // All props+lists together
+               $this->check( $this->merge(
+                       self::$links,
+                       self::$templates,
+                       self::$categories,
+                       self::$allpages,
+                       self::$alllinks,
+                       self::$alltransclusions,
+                       // This test is temporarily disabled until a sqlite bug is fixed
+                       // self::$allcategories,
+                       self::$backlinks,
+                       self::$embeddedin,
+                       self::$categorymembers
+               ) );
+       }
+
+       /**
+        * Test basic lists
+        */
+       public function testGenerator() {
+               // generator=allpages
+               $this->check( self::$generatorAllpages );
+               // generator=allpages & list=allpages
+               $this->check( $this->merge(
+                       self::$generatorAllpages,
+                       self::$allpages ) );
+               // generator=links
+               $this->check( self::$generatorLinks );
+               // generator=links & prop=links
+               $this->check( $this->merge(
+                       self::$generatorLinks,
+                       self::$generatorLinksPropLinks ) );
+               // generator=links & prop=templates
+               $this->check( $this->merge(
+                       self::$generatorLinks,
+                       self::$generatorLinksPropTemplates ) );
+               // generator=links & prop=links|templates
+               $this->check( $this->merge(
+                       self::$generatorLinks,
+                       self::$generatorLinksPropLinks,
+                       self::$generatorLinksPropTemplates ) );
+               // generator=links & prop=links|templates & list=allpages|...
+               $this->check( $this->merge(
+                       self::$generatorLinks,
+                       self::$generatorLinksPropLinks,
+                       self::$generatorLinksPropTemplates,
+                       self::$allpages,
+                       self::$alllinks,
+                       self::$alltransclusions,
+                       // This test is temporarily disabled until a sqlite bug is fixed
+                       // self::$allcategories,
+                       self::$backlinks,
+                       self::$embeddedin,
+                       self::$categorymembers ) );
+       }
+
+       /**
+        * 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 ) {
+               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
+        */
+       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
+        */
+       private function assertQueryResults( $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->assertQueryResults( $e['value'], $r['value'] );
+                               } else {
+                                       $this->assertEquals( $e['value'], $r['value'] );
+                               }
+                       }
+               }
+       }
+}
diff --git a/tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php b/tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php
new file mode 100644 (file)
index 0000000..594dc66
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ */
+class ApiQueryRevisionsTest extends ApiTestCase {
+
+       /**
+        * @group medium
+        */
+       function testContentComesWithContentModelAndFormat() {
+
+               $pageName = 'Help:' . __METHOD__ ;
+               $title = Title::newFromText( $pageName );
+               $page = WikiPage::factory( $title );
+               $page->doEdit( 'Some text', 'inserting content' );
+
+               $apiResult = $this->doApiRequest( array(
+                       'action' => 'query',
+                       'prop' => 'revisions',
+                       'titles' => $pageName,
+                       'rvprop' => 'content',
+               ) );
+               $this->assertArrayHasKey( 'query', $apiResult[0] );
+               $this->assertArrayHasKey( 'pages', $apiResult[0]['query'] );
+               foreach( $apiResult[0]['query']['pages'] as $page ) {
+                       $this->assertArrayHasKey( 'revisions', $page );
+                       foreach( $page['revisions'] as $revision ) {
+                               $this->assertArrayHasKey( 'contentformat', $revision,
+                                       'contentformat should be included when asking content so client knows how to interpret it'
+                               );
+                               $this->assertArrayHasKey( 'contentmodel', $revision,
+                                       'contentmodel should be included when asking content so client knows how to interpret it'
+                               );
+                       }
+               }
+       }
+}
diff --git a/tests/phpunit/includes/api/query/ApiQueryTest.php b/tests/phpunit/includes/api/query/ApiQueryTest.php
new file mode 100644 (file)
index 0000000..1b1886e
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ */
+class ApiQueryTest extends ApiTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+               $this->doLogin();
+       }
+
+       function testTitlesGetNormalized() {
+
+               global $wgMetaNamespace;
+
+               $data = $this->doApiRequest( array(
+                       'action' => 'query',
+                       'titles' => 'Project:articleA|article_B' ) );
+
+
+               $this->assertArrayHasKey( 'query', $data[0] );
+               $this->assertArrayHasKey( 'normalized', $data[0]['query'] );
+
+               // Forge a normalized title
+               $to = Title::newFromText( $wgMetaNamespace.':ArticleA' );
+
+               $this->assertEquals(
+                       array(
+                               'from' => 'Project:articleA',
+                               'to' => $to->getPrefixedText(),
+                       ),
+                       $data[0]['query']['normalized'][0]
+               );
+
+               $this->assertEquals(
+                       array(
+                               'from' => 'article_B',
+                               'to' => 'Article B'
+                       ),
+                       $data[0]['query']['normalized'][1]
+               );
+
+       }
+
+       function testTitlesAreRejectedIfInvalid() {
+               $title = false;
+               while( !$title || Title::newFromText( $title )->exists() ) {
+                       $title = md5( mt_rand( 0, 10000 ) + rand( 0, 999000 ) );
+               }
+
+               $data = $this->doApiRequest( array(
+                       'action' => 'query',
+                       'titles' => $title . '|Talk:' ) );
+
+               $this->assertArrayHasKey( 'query', $data[0] );
+               $this->assertArrayHasKey( 'pages', $data[0]['query'] );
+               $this->assertEquals( 2, count( $data[0]['query']['pages'] ) );
+
+               $this->assertArrayHasKey( -2, $data[0]['query']['pages'] );
+               $this->assertArrayHasKey( -1, $data[0]['query']['pages'] );
+
+               $this->assertArrayHasKey( 'missing', $data[0]['query']['pages'][-2] );
+               $this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] );
+       }
+
+}