Yurik has uploaded a new change for review.
https://gerrit.wikimedia.org/r/198667
Change subject: API Unittests and bugfixes
......................................................................
API Unittests and bugfixes
Implemented tons of unit tests for all 3 current gather APIs,
and discovered a number of nasty bugs in the process.
Also, unified and simplified editlist security to lock it down.
Change-Id: I3e08d17bf0d83f0d813eccee2d27feeccd357a9b
---
M includes/api/ApiEditList.php
M includes/api/ApiQueryListPages.php
M includes/api/ApiQueryLists.php
D tests/phpunit/api/ApiQueryLists.php
A tests/phpunit/api/GatherTests.php
5 files changed, 946 insertions(+), 584 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Gather
refs/changes/67/198667/1
diff --git a/includes/api/ApiEditList.php b/includes/api/ApiEditList.php
index 6b62e90..86b3975 100644
--- a/includes/api/ApiEditList.php
+++ b/includes/api/ApiEditList.php
@@ -57,67 +57,15 @@
if ( $params['label'] !== null ) {
$params['label'] = trim( $params['label'] );
- if ( $params['label'] === '' ) {
- $this->dieUsage( 'If given, label must not be
empty', 'badlabel' );
- }
}
+ $this->checkPermissions( $params );
- $user = $this->getUser(); // TBD: We might want to allow other
users with getWatchlistUser()
-
- if ( !$user->isLoggedIn() ) {
- $this->dieUsage( 'You must be logged-in to have a
list', 'notloggedin' );
- }
- if ( !$user->isAllowed( 'editmywatchlist' ) ) {
- $this->dieUsage( 'You don\'t have permission to edit
your list',
- 'permissiondenied' );
- }
-
- $pageSet = $this->getPageSet();
$p = $this->getModulePrefix();
-
+ $user = $this->getUser(); // TBD: We might want to allow other
users with getWatchlistUser()
$isDeletingList = $params['deletelist'];
$listId = $params['id'];
$isNew = $listId === null;
$isWatchlist = $listId === 0;
-
- // Validate 'deletelist' parameters
- if ( $isDeletingList ) {
-
- // ID == 0 is a watchlist
- if ( $isWatchlist ) {
- $this->dieUsage( "List #0 (watchlist) may not
be deleted", 'badid' );
- }
- if ( $isNew ) {
- $this->dieUsage(
- "List must be identified with {$p}id
when {$p}deletelist is used", 'invalidparammix'
- );
- }
-
- // For deletelist, disallow all parameters except those
unset
- $tmp = $params + $pageSet->extractRequestParams();
- unset( $tmp['deletelist'] );
- unset( $tmp['id'] );
- unset( $tmp['token'] );
- $extraParams =
- array_keys( array_filter( $tmp, function ( $x )
{
- return $x !== null && $x !== false;
- } ) );
- if ( $extraParams ) {
- $this->dieUsage( "The parameter {$p}deletelist
must not be used with " .
- implode( ", ", $extraParams ),
'invalidparammix' );
- }
- } elseif ( $isNew ) {
- if ( $params['remove'] ) {
- $this->dieUsage( "List must be identified with
{$p}id when {$p}remove is used",
- 'invalidparammix' );
- }
- if ( !$params['label'] ) {
- $this->dieUsage( "List {$p}label must be given
for new lists", 'invalidparammix' );
- }
- }
- if ( $isWatchlist && $params['label'] ) {
- $this->dieUsage( "List {$p}label may not be set for the
id==0", 'invalidparammix' );
- }
$dbw = wfGetDB( DB_MASTER, 'api' );
@@ -126,25 +74,22 @@
$listId = $this->createRow( $dbw, $user, $params,
$isWatchlist );
} else {
// Find existing list
- $row = $this->getListRow( $dbw, array( 'gl_id' =>
$listId ) );
+ $row = $this->getListRow( $params, $dbw, array( 'gl_id'
=> $listId ) );
if ( $row === false ) {
// No database record with the given ID
$this->dieUsage( "List {$p}id was not found",
'badid' );
}
+ $isWatchlist = $row->gl_label === '';
if ( !$isDeletingList ) {
+ // ACTION: update list
$this->updateListDb( $dbw, $params, $row );
} else {
- // Check again - we didn't know it was a
watchlist until DB query
- if ( $row->gl_label === '' ) {
- $this->dieUsage( "Watchlist may not be
deleted", 'badid' );
- }
- if ( strval( $row->gl_user ) !== strval(
$user->getId() ) ) {
- $this->dieUsage( "List {$p}id does not
belong to current user", 'permissiondenied' );
- }
- // ACTION: deleting list
- $dbw->delete( 'gather_list', array( 'gl_id' =>
$row->gl_id ), __METHOD__ );
+ // ACTION: delete list (items + list itself)
+ $dbw->delete( 'gather_list_item', array(
'gli_gl_id' => $listId ), __METHOD__ );
+ $dbw->delete( 'gather_list', array( 'gl_id' =>
$listId ), __METHOD__ );
$this->getResult()->addValue( null,
$this->getModuleName(), array(
'status' => 'deleted',
+ 'id' => $listId,
) );
}
}
@@ -152,6 +97,82 @@
if ( !$isDeletingList ) {
// Add the titles to the list (or subscribe with the
legacy watchlist)
$this->processTitles( $params, $user, $listId, $dbw,
$isWatchlist );
+ }
+ }
+
+ /**
+ * This method should be called twice - once before accessing DB, and
once when db row is found
+ * @param array $params
+ * @param stdClass $row
+ * @throws \UsageException
+ */
+ private function checkPermissions( array $params, $row = null ) {
+
+ if ( $row ) {
+ $isNew = false;
+ $isWatchlist = $row->gl_label === '';
+ } else {
+ $isNew = $params['id'] === null;
+ $isWatchlist = $params['id'] === 0;
+ }
+
+ $user = $this->getUser(); // TBD: We might want to allow other
users with getWatchlistUser()
+ $p = $this->getModulePrefix();
+ $deleteList = $params['deletelist'];
+
+ if ( !$user->isLoggedIn() ) {
+ $this->dieUsage( 'You must be logged-in to edit a
list', 'notloggedin' );
+ } elseif ( !$user->isAllowed( 'editmywatchlist' ) ) {
+ $this->dieUsage( 'You don\'t have permission to edit
your list', 'permissiondenied' );
+ } elseif ( $params['label'] === '' ) {
+ $this->dieUsage( 'If given, label must not be empty',
'badlabel' );
+ }
+
+ if ( $isWatchlist ) {
+ if ( $params['label'] !== null ) {
+ $this->dieUsage( "{$p}label cannot be set for a
watchlist", 'invalidparammix' );
+ } elseif ( $deleteList ) {
+ $this->dieUsage( "List #0 (watchlist) may not
be deleted", 'badid' );
+ } elseif ( $params['perm'] === 'public' ) {
+ // Per team discussion, introducing artificial
limitation for now
+ // until we establish that making watchlist
public would cause no harm.
+ // This check can be deleted at any time since
all other API code supports it.
+ $this->dieUsage( 'Making watchlist public is
not supported for security reasons',
+ 'publicwatchlist' );
+ }
+ }
+ if ( $isNew ) {
+ if ( $deleteList ) {
+ // This is more for safety - it shouldn't be
possible to delete a list by label
+ $this->dieUsage( "List must be identified with
{$p}id when {$p}deletelist is used",
+ 'invalidparammix' );
+ } elseif ( $params['remove'] ) {
+ $this->dieUsage( "List must be identified with
{$p}id when {$p}remove is used",
+ 'invalidparammix' );
+ } elseif ( $params['label'] === null ) {
+ $this->dieUsage( "List {$p}label must be given
for new lists", 'invalidparammix' );
+ }
+ }
+ if ( $deleteList && !$row ) {
+ // For deletelist, disallow all parameters except those
unset
+ // Minor optimization - don't validate on the second
pass
+ $tmp = $params +
$this->getPageSet()->extractRequestParams();
+ unset( $tmp['deletelist'] );
+ unset( $tmp['id'] );
+ unset( $tmp['token'] );
+ $extraParams = array_keys( array_filter( $tmp, function
( $x ) {
+ return $x !== null && $x !== false;
+ } ) );
+ if ( $extraParams ) {
+ $this->dieUsage( "The parameter {$p}deletelist
must not be used with " .
+ implode( ", ",
$extraParams ), 'invalidparammix' );
+ }
+ }
+ if ( $row ) {
+ if ( strval( $row->gl_user ) !== strval( $user->getId()
) ) {
+ $this->dieUsage( "List {$p}id does not belong
to the current user",
+ 'permissiondenied' );
+ }
}
}
@@ -220,22 +241,12 @@
* Given an info object, update it with arguments from params, and
return JSON str if changed
* @param stdClass $v
* @param Array $params
- * @param bool $isWatchlist
* @return string JSON encoded info object in case it changed, or NULL
if update is not needed
* @throws \UsageException
*/
- private function updateInfo( stdClass $v, array $params, $isWatchlist )
{
+ private function updateInfo( stdClass $v, array $params ) {
$updated = false;
- if ( $isWatchlist && $params['perm'] === 'public' ) {
- // Per team discussion, introducing artificial
limitation for now
- // until we establish that making watchlist public
would cause no harm.
- // This check can be deleted at any time since all
other API code supports it.
- $this->dieUsage( 'Making watchlist public is not
supported for security reasons',
- 'publicwatchlist' );
- }
-
- //
// Set default
if ( !property_exists( $v, 'description' ) ) {
$v->description = '';
@@ -244,7 +255,6 @@
$v->image = '';
}
- //
// Update from api parameters
if ( $params['description'] !== null && $v->description !==
$params['description'] ) {
$v->description = $params['description'];
@@ -310,22 +320,22 @@
if ( $id === 0 ) {
// List already exists, update instead, or might not
need it
- $row = $this->getListRow( $dbw, array(
+ $row = $this->getListRow( $params, $dbw, array(
'gl_user' => $user->getId(), 'gl_label' =>
$label
) );
- if ( $row === false ) {
- if ( $createRow ) {
- // If creation failed, the second query
should have succeeded
- $this->dieDebug( "List was not found",
'badid' );
- }
+ if ( $row !== false ) {
+ $id = $row->gl_id;
+ $isWatchlist = $row->gl_label === '';
+ $this->updateListDb( $dbw, $params, $row );
+ } elseif ( $createRow ) {
+ // If creation failed, the second query should
have succeeded
+ $this->dieDebug( "List was not found", 'badid'
);
+ } else {
+ // Watchlist, no changes
$this->getResult()->addValue( null,
$this->getModuleName(), array(
'status' => 'nochanges',
'id' => 0,
) );
- } else {
- $id = $row->gl_id;
- $isWatchlist = $row->gl_label === '';
- $this->updateListDb( $dbw, $params, $row );
}
} else {
$this->getResult()->addValue( null,
$this->getModuleName(), array(
@@ -345,19 +355,19 @@
*/
private function updateListDb( DatabaseBase $dbw, array $params, $row )
{
$update = array();
+ $info = self::parseListInfo( $row->gl_info, $row->gl_id, true );
+ $info = $this->updateInfo( $info, $params, $row->gl_label ===
'' );
+ if ( $info ) {
+ $update['gl_info'] = $info;
+ }
if ( $params['label'] !== null && $row->gl_label !==
$params['label'] ) {
$update['gl_label'] = $params['label'];
}
if ( $params['perm'] !== null ) {
- $perm = $params['perm'] === 'public' ? 1 : 0;
- if ( $row->gl_perm !== $perm ) {
+ $perm = $params['perm'] === 'public' ? '1' : '0';
+ if ( strval( $row->gl_perm ) !== $perm ) {
$update['gl_perm'] = $perm;
}
- }
- $info = self::parseListInfo( $row->gl_info, $row->gl_id, true );
- $json = $this->updateInfo( $info, $params, $row->gl_label ===
'' );
- if ( $json ) {
- $update['gl_info'] = $json;
}
if ( $update ) {
// ACTION: update list record
@@ -460,7 +470,7 @@
$dbw->delete( 'gather_list_item', array(
'gli_gl_id' => $listId,
$set
- ) );
+ ), __METHOD__ );
}
}
@@ -523,14 +533,20 @@
}
/**
+ * Get DB row and if found, validate it against user parameters
+ * @param array $params
* @param DatabaseBase $dbw
* @param array $conds
* @return bool|stdClass
*/
- private function getListRow( DatabaseBase $dbw, $conds ) {
- return $dbw->selectRow( 'gather_list',
+ private function getListRow( array $params, DatabaseBase $dbw, array
$conds ) {
+ $row = $dbw->selectRow( 'gather_list',
array( 'gl_id', 'gl_user', 'gl_label', 'gl_perm',
'gl_info' ),
$conds,
__METHOD__ );
+ if ( $row ) {
+ $this->checkPermissions( $params, $row );
+ }
+ return $row;
}
}
diff --git a/includes/api/ApiQueryListPages.php
b/includes/api/ApiQueryListPages.php
index dca0811..5f919a2 100644
--- a/includes/api/ApiQueryListPages.php
+++ b/includes/api/ApiQueryListPages.php
@@ -79,7 +79,7 @@
// Id was given, this could be public or private list,
legacy watchlist or regular
// Allow access to any public list/watchlist, and to
private with proper owner/self
$db = $this->getDB();
- $listRow = $db->selectRow( 'gather_list', array(
'gl_label', 'gl_user' ),
+ $listRow = $db->selectRow( 'gather_list', array(
'gl_label', 'gl_user', 'gl_perm' ),
array( 'gl_id' => $params['id'] ), __METHOD__ );
if ( $listRow === false ) {
$this->dieUsage( "List does not exist", 'badid'
);
@@ -91,17 +91,18 @@
// but that might be unexpected behavior
$user = $this->getWatchlistUser( $params );
if ( strval( $user->getId() ) !==
$listRow->gl_user ) {
- $this->dieUsage( 'The owner supplied
does not match the list\'s owner', 'permissiondenied' );
+ $this->dieUsage( 'The owner supplied
does not match the list\'s owner',
+ 'permissiondenied' );
}
$showPrivate = true;
} else {
$user = $this->getUser();
- $showPrivate = $user->isLoggedIn() && strval(
$user->getId() ) === $listRow->gl_user &&
- $user->isAllowed( 'viewmywatchlist' );
+ $showPrivate = $user->isLoggedIn() && strval(
$user->getId() ) === $listRow->gl_user
+ && $user->isAllowed( 'viewmywatchlist'
);
}
// Check if this is a public list (if required)
- if ( !$showPrivate && $listRow->perm !== 1 ) {
+ if ( !$showPrivate && strval( $listRow->gl_perm ) !==
'1' ) {
$this->dieUsage( "You have no rights to see
this list", 'badid' );
}
diff --git a/includes/api/ApiQueryLists.php b/includes/api/ApiQueryLists.php
index 0528d8f..0072754 100644
--- a/includes/api/ApiQueryLists.php
+++ b/includes/api/ApiQueryLists.php
@@ -235,7 +235,7 @@
}
}
if ( $fld_public ) {
- $data['public'] = $row->gl_perm === 1;
+ $data['public'] = strval( $row->gl_perm ) ===
'1';
}
if ( $useInfo ) {
$info = ApiEditList::parseListInfo(
$row->gl_info, $row->gl_id, false );
diff --git a/tests/phpunit/api/ApiQueryLists.php
b/tests/phpunit/api/ApiQueryLists.php
deleted file mode 100644
index bc7512c..0000000
--- a/tests/phpunit/api/ApiQueryLists.php
+++ /dev/null
@@ -1,482 +0,0 @@
-<?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
- * @covers ApiQuery
- */
-class ApiQueryLists extends ApiQueryTestBase {
- protected $exceptionFromAddDBData;
-
- /** @var TestUser[] */
- private static $wlUsers = null;
-
- /**
- * Create a set of pages. These must not change, otherwise the tests
might give wrong results.
- * @see MediaWikiTestCase::addDBData()
- */
- public function addDBData() {
- try {
- if ( !Title::newFromText( 'Gather-All' )->exists() ) {
- $this->editPage( 'Gather-ListA', 'a' );
- $this->editPage( 'Gather-ListB', 'b' );
- $this->editPage( 'Gather-ListAB', 'ab' );
- $this->editPage( 'Gather-ListW', 'w' );
- $this->editPage( 'Gather-ListWA', 'wa' );
- $this->editPage( 'Gather-ListWAB', 'wab' );
- }
- } catch ( Exception $e ) {
- $this->exceptionFromAddDBData = $e;
- }
- }
-
- protected function setUp() {
- parent::setUp();
- if ( !self::$wlUsers ) {
- foreach ( array(
- 'GatherML',
- 'GatherML2',
- 'GatherWML',
- 'GatherWML2',
- 'GatherWlOnly',
- 'GatherWlOnly2',
- ) as $name ) {
- self::$wlUsers[$name] = new TestUser( $name );
- }
- }
- self::$users = array_merge( self::$users, self::$wlUsers );
- }
-
- public function testAnonymous() {
- $a = User::newFromId( 0 );
-
- $this->assertUsage( 'an-0', '{ "list": "lists" }', $a );
- $this->assertUsage( 'an-1', '{ "list": "lists", "lstids": 0 }',
$a );
- }
-
- public function testMultipleLists() {
- $this->allTests( false );
- }
-
- public function testMultipleListsWithWatchlist() {
- $this->allTests( true );
- }
-
- private function allTests( $createWatchlist ) {
- $p = $createWatchlist ? 'Test With watchlist: #' : 'Test
without watchlist: #';
- $n = $createWatchlist ? 'GatherWML' : 'GatherML';
- $n2 = $createWatchlist ? 'GatherWML2' : 'GatherML2';
-
- $a = User::newFromId( 0 ); // Anonymous user
- $u = self::$users[$n]->getUser(); // User for this test
- $u2 = self::$users[$n2]->getUser(); // Second user for this test
-
- $token = $this->getToken( $u );
-
- if ( $createWatchlist ) {
- // Create watchlist row
- $res = $this->updateList( "$p a1",
'{"id":0,"description":"x"}', $u, $token );
- $wlId = $this->getVal( "$p a1", '"id"', $res );
- } else {
- $wlId = 0;
- }
-
- // Add pages to various lists
- $res = $this->updateList( "$p a1",
-
'{"id":0,"titles":"Gather-ListW|Gather-ListWA|Gather-ListWAB"}', $u, $token );
- $this->getVal( "$p a1", '"status"', $res, 'nochanges' );
- $this->getVal( "$p a1", '"pages",0,"added"', $res, '' );
- $this->getVal( "$p a1", '"pages",1,"added"', $res, '' );
- $this->getVal( "$p a1", '"pages",2,"added"', $res, '' );
- $this->getVal( "$p a1", '"id"', $res, $wlId );
-
- $res = $this->updateList( "$p a2",
-
'{"label":"A","titles":"Gather-ListWA|Gather-ListWAB"}', $u, $token );
- $this->getVal( "$p a2", '"status"', $res, 'created' );
- $this->getVal( "$p a2", '"pages",0,"added"', $res, '' );
- $this->getVal( "$p a2", '"pages",1,"added"', $res, '' );
- $idA = $this->getVal( "$p a2", '"id"', $res );
-
- $res = $this->updateList( "$p a3",
- '{"label":"B", "perm":"public",
"titles":"Gather-ListWAB"}', $u, $token );
- $this->getVal( "$p a3", '"status"', $res, 'created' );
- $this->getVal( "$p a3", '"pages",0,"added"', $res, '' );
- $idB = $this->getVal( "$p a3", '"id"', $res );
-
- $res = $this->callApi( "$p b1", '{ "list": "lists" }', $u );
- $this->assertListNoId( "$p b1", $res, $wlId
- ? '[{"watchlist":true,"label":"Watchlist"},
{"label":"A"}, {"label":"B"}]'
- : '[{"id":0, "watchlist":true,"label":"Watchlist"},
{"label":"A"}, {"label":"B"}]' );
- $this->getVal( "$p b1", '"query","lists",1,"id"', $res, $idA );
- $this->getVal( "$p b1", '"query","lists",2,"id"', $res, $idB );
-
- //
- // Continuation
- $request = $this->toApiParams( "$p c1", '{ "list": "lists",
"lstlimit": 1 }' );
-
- $res = $this->callApi( "$p c1", $request, $u );
- $this->assertListsEquals( "$p c1a", $res,
- '[{"id":' . $wlId . ',
"watchlist":true,"label":"Watchlist"}]' );
- $continue = $this->getVal( "$p c1b", '"continue"', $res );
-
- $res = $this->callApi( "$p c2", array_merge( $continue,
$request ), $u );
- $this->assertListNoId( "$p c2a", $res, '[{"label":"A"}]' );
- $continue = $this->getVal( "$p c2b", '"continue"', $res );
-
- $res = $this->callApi( "$p c3", array_merge( $continue,
$request ), $u );
- $this->assertListNoId( "$p c3a", $res, '[{"label":"B"}]' );
- $this->assertArrayNotHasKey( 'continue', $res, "$p c3c" );
-
- //
- // ids=A
- $res = $this->callApi( "$p d1", '{ "list": "lists", "lstids":'
. $idA . ' }', $u );
- $this->assertListNoId( "$p d1", $res, '[{"label":"A"}]' );
-
- // ids=A as anon user
- $res = $this->callApi( "$p d2", '{ "list": "lists", "lstids":'
. $idA . ' }', $a );
- $this->assertListNoId( "$p d2", $res, '[]' );
-
- // ids=A as another user
- $res = $this->callApi( "$p d3", '{ "list": "lists", "lstids":'
. $idA . ' }', $u2 );
- $this->assertListNoId( "$p d3", $res, '[]' );
-
- //
- // ids=B
- $res = $this->callApi( "$p e1", '{ "list": "lists", "lstids":'
. $idB . ' }', $u );
- $this->assertListNoId( "$p e1", $res, '[{"label":"B"}]' );
-
- // ids=B as anon user
- $res = $this->callApi( "$p e2", '{ "list": "lists", "lstids":'
. $idB . ' }', $a );
- $this->assertListNoId( "$p e2", $res, '[{"label":"B"}]' );
-
- // ids=B as another user
- $res = $this->callApi( "$p e3", '{ "list": "lists", "lstids":'
. $idB . ' }', $u2 );
- $this->assertListNoId( "$p e3", $res, '[{"label":"B"}]' );
-
- //
- // Use owner param
- // user: get all with owner=user
- $res = $this->callApi( "$p i0", '{ "list": "lists", "lstowner":
"' . $n . '" }', $u );
- $this->assertListsEquals( "$p i0", $res,
- '[{"id":' . $wlId . ',
"watchlist":true,"label":"Watchlist"}, ' .
- '{"id":' . $idA . ', "label":"A"},' . '{"id":' . $idB .
', "label":"B"}]' );
-
- // user: get by idA with owner=user
- $res = $this->callApi( "$p i0a", '{ "list": "lists",
"lstowner": "' . $n .
- '", "lstids": ' . $idA . ' }', $u );
- $this->assertListsEquals( "$p i0a", $res, '[{"id":' . $idA . ',
"label":"A"}]' );
-
- // anon: get all with owner=user
- $res = $this->callApi( "$p i1", '{ "list": "lists", "lstowner":
"' . $n .
- '"
}', $a );
- $this->assertListNoId( "$p i1", $res, '[{"label":"B"}]' );
-
- // anon: get by idA with owner=user
- $res = $this->callApi( "$p i2", '{ "list": "lists", "lstowner":
"' . $n .
- '",
"lstids": ' . $idA . ' }', $a );
- $this->assertListNoId( "$p i2", $res, '[]' );
-
- // anon: get by idB with owner=user
- $res = $this->callApi( "$p i3", '{ "list": "lists", "lstowner":
"' . $n .
- '",
"lstids": ' . $idB . ' }', $a );
- $this->assertListNoId( "$p i3", $res, '[{"label":"B"}]' );
-
- // user2: get all with owner=user
- $res = $this->callApi( "$p i4", '{ "list": "lists", "lstowner":
"' . $n . '" }', $u2 );
- $this->assertListNoId( "$p i4", $res, '[{"label":"B"}]' );
-
- // user2: get by idA with owner=user
- $res = $this->callApi( "$p i5", '{ "list": "lists", "lstowner":
"' . $n .
- '",
"lstids": ' . $idA . ' }', $u2 );
- $this->assertListNoId( "$p i5", $res, '[]' );
-
- // user2: get by idB with owner=user
- $res = $this->callApi( "$p i5", '{ "list": "lists", "lstowner":
"' . $n .
- '",
"lstids": ' . $idB . ' }', $u2 );
- $this->assertListNoId( "$p i5", $res, '[{"label":"B"}]' );
- }
-
- public function testWatchlistOnly() {
- $u = self::$users['GatherWlOnly']->getUser(); // User for this
test
- $n = $u->getName(); // Name of the user for this test
- $a = User::newFromId( 0 ); // Anonymous user
- $u2 = self::$users['GatherWlOnly2']->getUser(); // Second user
for this test
-
- $token = $this->getToken( $u );
- $wlOnly = '[{"id":0, "watchlist":true, "label":"Watchlist"}]';
-
- //
- // Validate empty watchlist / lists
- $res = $this->callApi( 'nc-a0', '{ "list": "lists" }', $u );
- $this->assertListNoId( 'nc-a0', $res, $wlOnly );
-
- $res = $this->callApi( 'nc-a1', '{ "list": "lists", "lstids": 0
}', $u );
- $this->assertListNoId( 'nc-a1', $res, $wlOnly );
-
- $res = $this->callApi( 'nc-a2', '{ "list": "lists", "lstlimit":
1 }', $u );
- $this->assertListNoId( 'nc-a2', $res, $wlOnly );
-
- $res = $this->callApi( 'nc-a3',
- '{ "list": "lists", "lstprop":
"label|description|public|count" }', $u );
- $this->assertListNoId( 'nc-a3', $res,
-
'[{"id":0,"watchlist":true,"count":0,"label":"Watchlist","description":"","public":false}]'
- );
- $res = $this->callApi( 'nc-a4', '{ "list": "lists", "lsttitle":
"Missing" }', $u );
- $this->assertListNoId( 'nc-a4', $res,
-
'[{"id":0,"watchlist":true,"label":"Watchlist","title":false}]' );
-
- //
- // Add page to watchlist
- $this->legacyAddToWatchlist( 'nc-b0', 'Gather-ListW', $u,
$token );
- $res = $this->callApi( 'nc-b0', '{ "list": "lists", "lstprop":
"count" }', $u );
- $this->assertListNoId( 'nc-b0', $res, '[{"id": 0,
"watchlist":true, "count": 1}]' );
-
- $res = $this->callApi( 'nc-b1', '{ "list": "lists", "lsttitle":
"Gather-ListW" }', $u );
- $this->assertListNoId( 'nc-b1', $res,
-
'[{"id":0,"watchlist":true,"label":"Watchlist","title":true}]' );
-
- //
- // Re-add the same page, using action=editlist & id=0
- $res = $this->updateList( 'nc-c0',
'{"id":0,"titles":"Gather-ListW"}', $u, $token );
- $this->getVal( "nc-c0", '"status"', $res, 'nochanges' );
- $this->getVal( "nc-c0", '"id"', $res, 0 );
- $this->getVal( "nc-c0", '"pages",0,"added"', $res, '' );
-
- $res = $this->callApi( 'nc-c0a', '{ "list": "lists" }', $u );
- $this->assertListNoId( 'nc-c0a', $res, $wlOnly );
-
- $res = $this->callApi( 'nc-c1', '{ "list": "lists", "lstids": 0
}', $u );
- $this->assertListNoId( 'nc-c1', $res, $wlOnly );
-
- $res = $this->callApi( 'nc-c3', '{ "list": "lists", "lstprop":
"count" }', $u );
- $this->assertListNoId( 'nc-c3', $res, '[{"id":0,
"watchlist":true, "count": 1}]' );
-
- $res = $this->callApi( 'nc-c4', '{ "list": "lists", "lsttitle":
"Gather-ListW" }', $u );
- $this->assertListNoId( 'nc-c4', $res,
-
'[{"id":0,"watchlist":true,"label":"Watchlist","title":true}]' );
-
- $res = $this->callApi( 'nc-c5',
- '{ "list": "lists", "lstids": 0, "lsttitle":
"Gather-ListW" }', $u );
- $this->assertListNoId( 'nc-c5', $res,
-
'[{"id":0,"watchlist":true,"label":"Watchlist","title":true}]' );
-
- //
- // What can others see from this user
- $res = $this->callApi( 'nc-e0', '{ "list": "lists", "lstowner":
"' . $n . '" }', $a );
- $this->assertListNoId( 'nc-e0', $res, '[]' );
-
- $res = $this->callApi( 'nc-e1', '{ "list": "lists", "lstowner":
"' . $n .
- '", "lstids": 0 }', $a );
- $this->assertListNoId( 'nc-e1', $res, '[]' );
-
- $res = $this->callApi( 'nc-e2', '{ "list": "lists", "lstowner":
"' . $n . '" }', $u2 );
- $this->assertListNoId( 'nc-e2', $res, '[]' );
-
- $res = $this->callApi( 'nc-e3',
- '{ "list": "lists", "lstowner": "' . $n . '", "lstids":
0 }', $u2 );
- $this->assertListNoId( 'nc-e3', $res, '[]' );
-
- //
- // Create watchlist list DB record
- $res = $this->updateList( 'nc-f0', '{ "id":0,
"description":"aa" }', $u, $token );
- $this->getVal( 'nc-f0', '"status"', $res, 'created' );
- $id = $this->getVal( 'nc-f0', '"id"', $res );
- $this->assertNotEquals( 0, $id );
-
- $wlOnly = array( array( 'id' => $id, 'watchlist' => true,
'label' => 'Watchlist' ) );
-
- $res = $this->callApi( 'nc-f2', '{ "list": "lists" }', $u );
- $this->assertListsEquals( 'nc-f2', $res, $wlOnly );
-
- $res = $this->callApi( 'nc-f3', '{ "list": "lists", "lstids": 0
}', $u );
- $this->assertListsEquals( 'nc-f3', $res, $wlOnly );
-
- $res = $this->callApi( 'nc-f4',
- '{ "list": "lists", "lstprop":
"label|description|public|count" }', $u );
- $this->assertListsEquals( 'nc-f4', $res,
- '[{"id":' . $id .
-
',"watchlist":true,"count":1,"label":"Watchlist","description":"aa","public":false}]'
);
-
- $res = $this->callApi( 'nc-f5', '{ "list": "lists", "lsttitle":
"Gather-ListW" }', $u );
- $this->assertListsEquals( 'nc-f5', $res, '[{"id":' . $id .
- ',"watchlist":true,"label":"Watchlist","title":true}]'
);
-
- //
- // Others still can't see the watchlist
- $res = $this->callApi( 'nc-g0', '{ "list": "lists", "lstowner":
"' . $n . '" }', $a );
- $this->assertListNoId( 'nc-g0', $res, '[]' );
-
- $res = $this->callApi( 'nc-g1', '{ "list": "lists", "lstowner":
"' . $n .
- '", "lstids": 0 }', $a );
- $this->assertListNoId( 'nc-g1', $res, '[]' );
-
- $res = $this->callApi( 'nc-g2', '{ "list": "lists", "lstids": '
. $id . ' }', $a );
- $this->assertListNoId( 'nc-g2', $res, '[]' );
-
- $res = $this->callApi( 'nc-g3', '{ "list": "lists", "lstowner":
"' . $n . '", "lstids": ' .
- $id . ' }', $a );
- $this->assertListNoId( 'nc-g3', $res, '[]' );
-
- $res = $this->callApi( 'nc-h0', '{ "list": "lists", "lstowner":
"' . $n . '" }', $u2 );
- $this->assertListNoId( 'nc-h0', $res, '[]' );
-
- $res = $this->callApi( 'nc-h1', '{ "list": "lists", "lstowner":
"' . $n .
- '", "lstids": 0 }', $u2 );
- $this->assertListNoId( 'nc-h1', $res, '[]' );
-
- $res = $this->callApi( 'nc-h2', '{ "list": "lists", "lstids": '
. $id . ' }', $u2 );
- $this->assertListNoId( 'nc-h2', $res, '[]' );
-
- $res = $this->callApi( 'nc-h3', '{ "list": "lists", "lstowner":
"' . $n . '", "lstids": ' .
- $id . ' }', $u2 );
- $this->assertListNoId( 'nc-h3', $res, '[]' );
-
- //
- // Watchlist editing assertions
- $this->assertUsage( 'nc-i0', '{ "action": "editlist", "id":0,
"label":"bb" }', $u );
- $this->assertUsage( 'nc-i1', '{ "action": "editlist", "id":' .
$id . ', "label":"bb" }', $u );
- }
-
- private function assertListNoId( $message, $actual, $expected ) {
- $this->assertListsEquals( $message, $actual, $expected, true );
- }
-
- private function assertListsEquals( $message, $actual, $expected,
$removeIds = false ) {
- $actual = $this->getVal( $message, '"query", "lists"', $actual
);
- $expected = $this->toArray( $message, $expected );
- if ( $removeIds ) {
- $actual = self::removeIds( $actual );
- }
- $this->assertArrayEquals( $expected, $actual, true, true,
$message );
- }
-
- private function updateList( $message, $params, $user, $token ) {
- $params = $this->toArray( $message, $params );
- if ( !isset( $params['action'] ) ) {
- $params['action'] = 'editlist';
- }
- if ( !isset( $params['token'] ) ) {
- $params['token'] = $token;
- }
- $res = $this->callApi( $message, $params, $user );
- return $this->getVal( $message, array( $params['action'] ),
$res );
-
- }
-
- private function legacyAddToWatchlist( $message, $titles, $user, $token
) {
- $params = array(
- 'action' => 'watch',
- 'titles' => $titles,
- 'token' => $token,
- );
- $res = $this->callApi( $message, $params, $user );
- $this->getVal( $message, '"watch", 0, "watched"', $res );
- }
-
- private function getToken( User $user ) {
- $message = 'token-' . $user->getName();
- $res = $this->callApi( $message, array(
- 'meta' => 'tokens',
- 'type' => 'watch',
- ), $user );
- return $this->getVal( $message, '"query", "tokens",
"watchtoken"', $res );
- }
-
- private function assertUsage( $message, $params, User $user = null ) {
- try {
- $params = $this->toApiParams( $message, $params );
- $result = $this->callApi( $message, $params, $user );
- $params = $this->toStr( $params );
- $this->fail( "No UsageException for $params,
received:\n" .
- $this->toStr( $result[0], true ) );
- } catch ( UsageException $e ) {
- $this->assertTrue( true );
- }
- }
-
- private function callApi( $message, $params, User $user = null ) {
- $params = $this->toApiParams( $message, $params );
- $res = $this->doApiRequest( $params, null, false, $user );
- return $res[0];
- }
-
- private function toApiParams( $message, $params ) {
- $params = $this->toArray( $message, $params );
- if ( !isset( $params['action'] ) ) {
- $params['action'] = 'query';
- }
- if ( $params['action'] === 'query' && !isset(
$params['continue'] ) ) {
- $params['continue'] = '';
- }
- return $params;
- }
-
- private function toArray( $message, $params ) {
- if ( is_string( $params ) && $params ) {
- $p = $params[0] !== '[' && $params[0] !== '{' ?
"[$params]" : $params;
- $st = FormatJson::parse( $p, FormatJson::FORCE_ASSOC );
- $this->assertTrue( $st->isOK(), 'invalid JSON value ' .
$params, $message );
- $params = $st->getValue();
- }
- return $params;
- }
-
- private function toStr( $params, $pretty = false ) {
- if ( is_string( $params ) ) {
- return $params;
- }
- return FormatJson::encode( $params, $pretty, FormatJson::ALL_OK
);
- }
-
- private static function removeIds( $arr ) {
- foreach ( $arr as &$v ) {
- if ( array_key_exists( 'id', $v ) && $v['id'] !== 0 ) {
- unset( $v['id'] );
- }
- }
- return $arr;
- }
-
- private function getVal( $message, $path, $result, $expectedValue =
null ) {
- $path = $this->toArray( $message, $path );
- $res = $result;
- foreach ( $path as $p ) {
- if ( !array_key_exists( $p, $res ) ) {
- $path = $this->toStr( $path );
- $this->fail( "$message: Request has no key $p
of $path in result\n" .
- $this->toStr( $result,
true ) );
- }
- $res = $res[$p];
- }
- if ( $expectedValue !== null ) {
- $this->assertEquals( $expectedValue, $res, $message );
- }
- return $res;
- }
-}
diff --git a/tests/phpunit/api/GatherTests.php
b/tests/phpunit/api/GatherTests.php
new file mode 100644
index 0000000..995e82d
--- /dev/null
+++ b/tests/phpunit/api/GatherTests.php
@@ -0,0 +1,827 @@
+<?php
+/**
+ * Copyright © 2015 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 Gather API functionality
+ *
+ * @group API
+ * @group Database
+ * @group medium
+ * @covers ApiQuery
+ */
+class GatherTests extends ApiTestCase {
+ protected $exceptionFromAddDBData;
+
+ /** @var TestUser[] */
+ private static $wlUsers = null;
+
+ /**
+ * Create a set of pages. These must not change, otherwise the tests
might give wrong results.
+ * @see MediaWikiTestCase::addDBData()
+ */
+ public function addDBData() {
+ try {
+ if ( !Title::newFromText( 'Gather-All' )->exists() ) {
+ $this->editPage( 'Gather-ListA', 'a' );
+ $this->editPage( 'Gather-ListB', 'b' );
+ $this->editPage( 'Gather-ListAB', 'ab' );
+ $this->editPage( 'Gather-ListW', 'w' );
+ $this->editPage( 'Gather-ListWA', 'wa' );
+ $this->editPage( 'Gather-ListWAB', 'wab' );
+ }
+ } catch ( Exception $e ) {
+ $this->exceptionFromAddDBData = $e;
+ }
+ }
+
+ protected function setUp() {
+ parent::setUp();
+ if ( !self::$wlUsers ) {
+ foreach ( array(
+ 'GatherML',
+ 'GatherML2',
+ 'GatherWML',
+ 'GatherWML2',
+ 'GatherWlOnly',
+ 'GatherWlOnly2',
+ 'GatherE1',
+ 'GatherE2',
+ ) as $name ) {
+ self::$wlUsers[$name] = new TestUser( $name );
+ }
+ }
+ self::$users = array_merge( self::$users, self::$wlUsers );
+ }
+
+ public function testListEditAndPages() {
+ $n = 'GatherE1';
+ $n2 = 'GatherE2';
+ $nS = 'sysop';
+ $pageW = array( 'ns' => 0, 'title' => 'Gather-ListW' );
+ $pageTW = array( 'ns' => 1, 'title' => 'Talk:Gather-ListW' );
+ $pageWA = array( 'ns' => 0, 'title' => 'Gather-ListWA' );
+ $pageTWA = array( 'ns' => 1, 'title' => 'Talk:Gather-ListWA' );
+ $pageWAB = array( 'ns' => 0, 'title' => 'Gather-ListWAB' );
+ $pageTWAB = array( 'ns' => 1, 'title' => 'Talk:Gather-ListWAB'
);
+ $pageAB = array( 'ns' => 0, 'title' => 'Gather-ListAB' );
+ $pageTAB = array( 'ns' => 1, 'title' => 'Talk:Gather-ListAB' );
+ $pageB = array( 'ns' => 0, 'title' => 'Gather-ListB' );
+ $pageTB = array( 'ns' => 1, 'title' => 'Talk:Gather-ListB' );
+
+ $usr = self::$users[$n]->getUser();
+ $usr2 = self::$users[$n2]->getUser();
+ $usrS = self::$users[$nS]->getUser();
+ $usrA = User::newFromId( 0 ); // Anonymous user
+
+ $token = $this->getToken( $usr );
+ $token2 = $this->getToken( $usr2 );
+ $tokenS = $this->getToken( $usrS );
+ $tokenA = $this->getToken( $usrA );
+
+ // Make sure there are no watchlists yet for these users
(starting from clean slate)
+ foreach ( array( $usr, $usr2 ) as $user ) {
+ $res = $this->getLists( 'ed-a0', $user, '{}' );
+ $this->assertListsEquals( 'ed-a0', $res,
+ '[{"id":0, "watchlist":true,
"label":"Watchlist"}]' );
+ $this->assertPages( 'ed-a1', $user, null, array(),
array() );
+ }
+
+ $this->badUsePage( 'ed-a2', $usr, '"lspid": 9999999' );
+ $this->badUsePage( 'ed-a3', $usrA, '"lspid": 9999999' );
+ $this->badUsePage( 'ed-a4', $usrA, '{}' );
+
+ // General use
+ $this->badUseEdit( 'ed-b1', $usr, $token, '{}' );
+ $this->badUseEdit( 'ed-b2', $usr, $token, '"label": ""' );
+ // TODO/BUG/SECURITY - Token of one user should not be accepted
for another user
+ // $this->badUseEdit( 'ed-b3', $u, $token2, '"label": "x"' );
+ $this->badUseEdit( 'ed-b4', $usr, $tokenA, '"label": "x"' );
+ $this->badUseEdit( 'ed-b5', $usr, false, '"label": "x"' );
+ $this->badUseEdit( 'ed-b6', $usrA, $tokenA, '"label": "x"' );
+ $this->badUseEdit( 'ed-b7', $usrA, false, '"label": "x"' );
+
+ // watchlist should not be modifiable this way
+ $idWL = '"id":0';
+ $this->badUseEdit( 'ed-ba1', $usr, false, $idWL . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ba2', $usrA, false, $idWL . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ba3', $usr, $token, $idWL . ', "label":
"x"' );
+ $this->badUseEdit( 'ed-ba3a', $usr, $token, $idWL . ', "label":
""' );
+ $this->badUseEdit( 'ed-ba4', $usrA, $tokenA, $idWL . ',
"label": "x"' );
+ $this->badUseEdit( 'ed-ba5', $usr, $tokenA, $idWL . ',
"description": "x"' );
+ // Test #ba6 is ok for ID=0, but not OK for non-zero (#b6)
+ $this->badUseEdit( 'ed-ba7', $usr, $token, $idWL . ',
"deletelist": 1' );
+ $this->badUseEdit( 'ed-ba8', $usr, $token, $idWL . ', "perm":
"public"' );
+
+ $expListsW = array( 'id' => 0, 'watchlist' => true, 'label' =>
'Watchlist' );
+ $expListsW2 = array_merge( $expListsW, array(
+ 'public' => false,
+ 'description' => '',
+ 'image' => false,
+ 'count' => 0,
+ ) );
+ $this->assertOneList( 'ed-bb1', $usr, 0, $expListsW,
$expListsW2 );
+
+ //
+ // Add one page to the non-created watchlist
+ $res = $this->editList( 'ed-c1', $usr, $token, $idWL . ',
"titles": "Gather-ListW"' );
+ $this->getVal( 'ed-c1', '"id"', $res, 0 );
+ $this->getVal( 'ed-c1', '"status"', $res, 'nochanges' );
+ $this->getVal( 'ed-c1', '"pages", 0, "title"', $res,
'Gather-ListW' );
+ $this->getVal( 'ed-c1', '"pages", 0, "added"', $res, '' );
+
+ $expListsW2['count'] = 1;
+ $expPagesW = array( $pageW, $pageTW );
+
+ $this->assertPages( 'ed-c2', $usr, null, $expPagesW );
+ $this->assertPages( 'ed-c3', $usr, 0, $expPagesW );
+ $this->assertOneList( 'ed-c4', $usr, 0, $expListsW, $expListsW2
);
+
+ //
+ // Create Watchlist row
+ $res = $this->editList( 'ed-d1', $usr, $token, '"id":0,
"description": "x"' );
+ $id0 = $this->getVal( 'ed-d1', '"id"', $res );
+ $idWL = '"id":' . $id0;
+ $expListsW['id'] = $id0;
+ $expListsW2['id'] = $id0;
+ $expListsW2['description'] = 'x';
+
+ $this->assertPages( 'ed-d2', $usr, 0, $expPagesW );
+ $this->assertPages( 'ed-d3', $usr, $id0, $expPagesW );
+ $this->assertOneList( 'ed-d4', $usr, 0, $expListsW, $expListsW2
);
+ $this->assertOneList( 'ed-d5', $usr, $id0, $expListsW,
$expListsW2 );
+ $this->assertOneList( 'ed-d6', $usrA, $id0, null );
+ $this->assertOneList( 'ed-d7', $usr2, $id0, null );
+ $this->assertOneList( 'ed-d7a', $usrS, $id0, null );
+ $this->badUsePage( 'ed-d8', $usrA, '"lspid": ' . $id0 );
+ $this->badUsePage( 'ed-d9', $usr2, '"lspid": ' . $id0 );
+ $this->badUsePage( 'ed-d10', $usrS, '"lspid": ' . $id0 );
+
+ // watchlist should not be modifiable this way
+ $this->badUseEdit( 'ed-da1', $usr, false, $idWL . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-da2', $usrA, false, $idWL . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-da3', $usr, $token, $idWL . ', "label":
"x"' );
+ $this->badUseEdit( 'ed-da4', $usr, $token, $idWL . ', "label":
""' );
+ $this->badUseEdit( 'ed-da5', $usrA, $tokenA, $idWL . ',
"label": "x"' );
+ $this->badUseEdit( 'ed-da6', $usr, $tokenA, $idWL . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-da7', $usr2, $token2, $idWL . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-da7a', $usrS, $tokenS, $idWL . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-da8', $usr, $token, $idWL . ',
"deletelist": 1' );
+ $this->badUseEdit( 'ed-da9', $usr, $token, $idWL . ', "perm":
"public"' );
+
+ //
+ // Add Gather-ListWA to the created watchlist
+ $res = $this->editList( 'ed-e1', $usr, $token, $idWL . ',
"titles": "Gather-ListWA"' );
+ $this->getVal( 'ed-e1', '"id"', $res, $id0 );
+ $this->getVal( 'ed-e1', '"status"', $res, 'nochanges' );
+ $this->getVal( 'ed-e1', '"pages", 0, "title"', $res,
'Gather-ListWA' );
+ $this->getVal( 'ed-e1', '"pages", 0, "added"', $res, '' );
+
+ $expListsW2['count'] = 2;
+ $expPagesW = array( $pageW, $pageWA, $pageTW, $pageTWA );
+
+ $this->assertPages( 'ed-e2', $usr, null, $expPagesW );
+ $this->assertPages( 'ed-e3', $usr, 0, $expPagesW );
+ $this->assertPages( 'ed-e4', $usr, $id0, $expPagesW );
+ $this->assertOneList( 'ed-e5', $usr, 0, $expListsW, $expListsW2
);
+ $this->assertOneList( 'ed-e6', $usr, $id0, $expListsW,
$expListsW2 );
+
+ //
+ // Add Gather-ListWAB to the created watchlist with ID=0 and
description change
+ $res = $this->editList( 'ed-f1', $usr, $token,
+ '"id":0, "description":"y", "titles": "Gather-ListWAB"'
);
+ $this->getVal( 'ed-f1', '"id"', $res, $id0 );
+ $this->getVal( 'ed-f1', '"status"', $res, 'updated' );
+ $this->getVal( 'ed-f1', '"pages", 0, "title"', $res,
'Gather-ListWAB' );
+ $this->getVal( 'ed-f1', '"pages", 0, "added"', $res, '' );
+
+ $expListsW2['count'] = 3;
+ $expListsW2['description'] = 'y';
+ $expPagesW = array( $pageW, $pageWA, $pageWAB, $pageTW,
$pageTWA, $pageTWAB );
+
+ $this->assertPages( 'ed-f2', $usr, null, $expPagesW );
+ $this->assertPages( 'ed-f3', $usr, 0, $expPagesW );
+ $this->assertPages( 'ed-f4', $usr, $id0, $expPagesW );
+ $this->assertOneList( 'ed-f5', $usr, 0, $expListsW, $expListsW2
);
+ $this->assertOneList( 'ed-f6', $usr, $id0, $expListsW,
$expListsW2 );
+
+ //
+ // Create new list A
+ $res = $this->editList( 'ed-i1', $usr, $token, '"label": "A"' );
+ $this->getVal( 'ed-i1', '"status"', $res, 'created' );
+ $idA = $this->getVal( 'ed-i1', '"id"', $res );
+ $idAs = '"id":' . $idA;
+ $expListsA = array( 'id' => $idA, 'label' => 'A' );
+ $expListsA2 = array_merge( $expListsA, array(
+ 'public' => false,
+ 'description' => '',
+ 'image' => false,
+ 'count' => 0,
+ ) );
+ $expPagesA = array();
+
+ $this->assertPages( 'ed-i2', $usr, $idA, $expPagesA );
+ $this->assertOneList( 'ed-i3', $usr, $idA, $expListsA,
$expListsA2 );
+ $this->assertOneList( 'ed-i4', $usrA, $idA, null );
+ $this->assertOneList( 'ed-i5', $usr2, $idA, null );
+ $this->assertOneList( 'ed-i6', $usrS, $idA, null );
+
+ $this->badUsePage( 'ed-ia1', $usrA, '"lspid": ' . $idA );
+ $this->badUsePage( 'ed-ia2', $usr2, '"lspid": ' . $idA );
+ $this->badUsePage( 'ed-ia2a', $usrS, '"lspid": ' . $idA );
+ $this->badUseEdit( 'ed-ia3', $usr, false, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ia4', $usrA, false, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ia5', $usr, $token, $idAs . ', "label":
""' );
+ $this->badUseEdit( 'ed-ia6', $usrA, $tokenA, $idAs . ',
"label": "x"' );
+ $this->badUseEdit( 'ed-ia7', $usr, $tokenA, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ia8', $usr2, $token2, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ia9', $usrS, $tokenS, $idAs . ',
"description": "x"' );
+
+ //
+ // Rename list A to 'a'
+ $res = $this->editList( 'ed-j1', $usr, $token, $idAs . ',
"label": "a"' );
+ $this->getVal( 'ed-j1', '"status"', $res, 'updated' );
+ $this->getVal( 'ed-j1', '"id"', $res, $idA );
+ $expListsA['label'] = 'a';
+ $expListsA2['label'] = 'a';
+
+ $this->assertPages( 'ed-j2', $usr, $idA, $expPagesA );
+ $this->assertOneList( 'ed-j3', $usr, $idA, $expListsA,
$expListsA2 );
+ $this->assertOneList( 'ed-j4', $usrA, $id0, null );
+ $this->assertOneList( 'ed-j5', $usr2, $id0, null );
+ $this->assertOneList( 'ed-j6', $usrS, $id0, null );
+
+ $this->badUsePage( 'ed-ja1', $usrA, '"lspid": ' . $idA );
+ $this->badUsePage( 'ed-ja2', $usr2, '"lspid": ' . $idA );
+ $this->badUsePage( 'ed-ja2a', $usrS, '"lspid": ' . $idA );
+ $this->badUseEdit( 'ed-ja3', $usr, false, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ja4', $usrA, false, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ja5', $usr, $token, $idAs . ', "label":
""' );
+ $this->badUseEdit( 'ed-ja6', $usrA, $tokenA, $idAs . ',
"label": "x"' );
+ $this->badUseEdit( 'ed-ja7', $usr, $tokenA, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ja8', $usr2, $token2, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ja8a', $usrS, $tokenS, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ja9', $usrA, $token, $idAs . ',
"deletelist": 1' );
+ $this->badUseEdit( 'ed-ja10', $usr2, $token, $idAs . ',
"deletelist": 1' );
+ $this->badUseEdit( 'ed-ja11', $usrS, $token, $idAs . ',
"deletelist": 1' );
+
+ //
+ // Make list a public
+ $res = $this->editList( 'ed-k1', $usr, $token, '"label": "a",
"perm": "public"' );
+ $this->getVal( 'ed-k1', '"status"', $res, 'updated' );
+ $this->getVal( 'ed-k1', '"id"', $res, $idA );
+ $expListsA2['public'] = true;
+
+ $this->assertPages( 'ed-k2', $usr, $idA, $expPagesA );
+ $this->assertPages( 'ed-k2a', $usr2, $idA, $expPagesA );
+ $this->assertPages( 'ed-k2b', $usrA, $idA, $expPagesA );
+ $this->assertPages( 'ed-k2c', $usrS, $idA, $expPagesA );
+ $this->assertOneList( 'ed-k3', $usr, $idA, $expListsA,
$expListsA2 );
+ $this->assertOneList( 'ed-k4', $usrA, $idA, $expListsA,
$expListsA2 );
+ $this->assertOneList( 'ed-k5', $usr2, $idA, $expListsA,
$expListsA2 );
+ $this->assertOneList( 'ed-k6', $usrS, $idA, $expListsA,
$expListsA2 );
+
+ $this->badUseEdit( 'ed-ka1', $usr, false, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ka2', $usrA, false, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ka3', $usr, $token, $idAs . ', "label":
""' );
+ $this->badUseEdit( 'ed-ka4', $usrA, $tokenA, $idAs . ',
"label": "x"' );
+ $this->badUseEdit( 'ed-ka5', $usr, $tokenA, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ka6', $usr2, $token2, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ka6', $usrS, $tokenS, $idAs . ',
"description": "x"' );
+ $this->badUseEdit( 'ed-ka7', $usrA, $tokenA, $idAs . ',
"deletelist": 1' );
+ $this->badUseEdit( 'ed-ka8', $usr2, $token2, $idAs . ',
"deletelist": 1' );
+ $this->badUseEdit( 'ed-ka9', $usr2, $token2, $idAs . ', "perm":
"public"' );
+ $this->badUseEdit( 'ed-ka10', $usr2, $token2, $idAs . ',
"label": "xx"' );
+ $this->badUseEdit( 'ed-ka11', $usrS, $tokenS, $idAs . ',
"deletelist": 1' );
+ $this->badUseEdit( 'ed-ka12', $usrS, $tokenS, $idAs . ',
"perm": "public"' );
+ $this->badUseEdit( 'ed-ka13', $usrS, $tokenS, $idAs . ',
"label": "xx"' );
+
+ //
+ // Delete list A
+ $res = $this->editList( 'ed-l1', $usr, $token, $idAs . ',
"deletelist": 1' );
+ $this->getVal( 'ed-l1', '"status"', $res, 'deleted' );
+ $this->getVal( 'ed-l1', '"id"', $res, $idA );
+
+ $this->badUsePage( 'ed-l2', $usr, '"lspid": ' . $idA );
+ $this->badUsePage( 'ed-l3', $usr2, '"lspid": ' . $idA );
+ $this->badUsePage( 'ed-l3a', $usrS, '"lspid": ' . $idA );
+ $this->badUsePage( 'ed-l4', $usrA, '"lspid": ' . $idA );
+ $this->assertOneList( 'ed-l5', $usr, $idA, null );
+ $this->assertOneList( 'ed-l6', $usrA, $idA, null );
+ $this->assertOneList( 'ed-l7', $usr2, $idA, null );
+ $this->assertOneList( 'ed-l7a', $usrS, $idA, null );
+
+ $this->badUseEdit( 'ed-l8', $usr, $token, $idAs . ', "titles":
"ABC"' );
+ $this->badUseEdit( 'ed-l9', $usr, $token, $idAs . ', "label":
"x"' );
+ $this->badUseEdit( 'ed-l10', $usr2, $token2, $idAs . ',
"label": "xx"' );
+ $this->badUseEdit( 'ed-l11', $usrS, $tokenS, $idAs . ',
"label": "xx"' );
+
+ //
+ // Create public list B
+ $res = $this->editList( 'ed-n1', $usr, $token,
+ '"label": "B", "perm":"public", ' .
+ '"titles":"Gather-ListB|Gather-ListAB|Gather-ListWAB"'
);
+ $this->getVal( 'ed-n1', '"status"', $res, 'created' );
+ $idB = $this->getVal( 'ed-n1', '"id"', $res );
+ $idBs = '"id":' . $idB;
+ $expListsB = array( 'id' => $idB, 'label' => 'B' );
+ $expListsB2 = array_merge( $expListsB, array(
+ 'public' => true,
+ 'description' => '',
+ 'image' => false,
+ 'count' => 3,
+ ) );
+ // Non-alphabetic order should be preserved
+ $expPagesB = array( $pageB, $pageAB, $pageWAB );
+
+ $this->assertPages( 'ed-n2', $usr, $idB, $expPagesB );
+ $this->assertPages( 'ed-n3', $usr2, $idB, $expPagesB );
+ $this->assertPages( 'ed-n3a', $usrS, $idB, $expPagesB );
+ $this->assertPages( 'ed-n4', $usrA, $idB, $expPagesB );
+ $this->assertOneList( 'ed-n5', $usr, $idB, $expListsB,
$expListsB2 );
+ $this->assertOneList( 'ed-n6', $usrA, $idB, $expListsB,
$expListsB2 );
+ $this->assertOneList( 'ed-n7', $usr2, $idB, $expListsB,
$expListsB2 );
+ $this->assertOneList( 'ed-n8', $usrS, $idB, $expListsB,
$expListsB2 );
+ }
+
+ public function testMultipleLists() {
+ $this->intTestMultipleLists( false );
+ }
+
+ public function testMultipleListsWithWatchlist() {
+ $this->intTestMultipleLists( true );
+ }
+
+ private function intTestMultipleLists( $createWatchlist ) {
+ $p = $createWatchlist ? 'Test With watchlist: #' : 'Test
without watchlist: #';
+ $n = $createWatchlist ? 'GatherWML' : 'GatherML';
+ $n2 = $createWatchlist ? 'GatherWML2' : 'GatherML2';
+
+ $a = User::newFromId( 0 ); // Anonymous user
+ $u = self::$users[$n]->getUser(); // User for this test
+ $u2 = self::$users[$n2]->getUser(); // Second user for this test
+
+ $token = $this->getToken( $u );
+ $n = '"' . $n . '"';
+
+ // Anonymous tests
+ $this->badUseLists( "$p 0", $a, '{}' );
+ $this->badUseLists( "$p 0", $a, '"lstids": 0' );
+
+ if ( $createWatchlist ) {
+ // Create watchlist row
+ $res = $this->editList( "$p a0", $u, $token,
'"id":0,"description":"x"' );
+ $wlId = $this->getVal( "$p a0", '"id"', $res );
+ } else {
+ $wlId = 0;
+ }
+
+ // Add pages to various lists
+ $res = $this->editList( "$p a1", $u, $token,
+
'"id":0,"titles":"Gather-ListW|Gather-ListWA|Gather-ListWAB"' );
+ $this->getVal( "$p a1", '"status"', $res, 'nochanges' );
+ $this->getVal( "$p a1", '"pages",0,"added"', $res, '' );
+ $this->getVal( "$p a1", '"pages",1,"added"', $res, '' );
+ $this->getVal( "$p a1", '"pages",2,"added"', $res, '' );
+ $this->getVal( "$p a1", '"id"', $res, $wlId );
+
+ $res = $this->editList( "$p a2", $u, $token,
+ '"label":"A","titles":"Gather-ListWA|Gather-ListWAB"' );
+ $this->getVal( "$p a2", '"status"', $res, 'created' );
+ $this->getVal( "$p a2", '"pages",0,"added"', $res, '' );
+ $this->getVal( "$p a2", '"pages",1,"added"', $res, '' );
+ $idA = $this->getVal( "$p a2", '"id"', $res );
+
+ $res = $this->editList( "$p a3", $u, $token,
+ '"label":"B", "perm":"public",
"titles":"Gather-ListWAB"' );
+ $this->getVal( "$p a3", '"status"', $res, 'created' );
+ $this->getVal( "$p a3", '"pages",0,"added"', $res, '' );
+ $idB = $this->getVal( "$p a3", '"id"', $res );
+
+ $res = $this->getLists( "$p b1", $u, '{}' );
+ $this->assertListNoId( "$p b1", $res, $wlId
+ ? '[{"watchlist":true,"label":"Watchlist"},
{"label":"A"}, {"label":"B"}]'
+ : '[{"id":0, "watchlist":true,"label":"Watchlist"},
{"label":"A"}, {"label":"B"}]' );
+ $this->getVal( "$p b1", '"query","lists",1,"id"', $res, $idA );
+ $this->getVal( "$p b1", '"query","lists",2,"id"', $res, $idB );
+
+ //
+ // Continuation
+ $request = $this->toApiParams( "$p c1", 'lists', false,
'"lstlimit": 1' );
+
+ $res = $this->getLists( "$p c1", $u, $request );
+ $this->assertListsEquals( "$p c1a", $res,
+ '[{"id":' . $wlId . ',
"watchlist":true,"label":"Watchlist"}]' );
+ $continue = $this->getVal( "$p c1b", '"continue"', $res );
+
+ $res = $this->getLists( "$p c2", $u, array_merge( $continue,
$request ) );
+ $this->assertListNoId( "$p c2a", $res, '[{"label":"A"}]' );
+ $continue = $this->getVal( "$p c2b", '"continue"', $res );
+
+ $res = $this->getLists( "$p c3", $u, array_merge( $continue,
$request ) );
+ $this->assertListNoId( "$p c3a", $res, '[{"label":"B"}]' );
+ $this->assertArrayNotHasKey( 'continue', $res, "$p c3c" );
+
+ //
+ // ids=A
+ $res = $this->getLists( "$p d1", $u, '"lstids":' . $idA );
+ $this->assertListNoId( "$p d1", $res, '[{"label":"A"}]' );
+
+ // ids=A as anon user
+ $res = $this->getLists( "$p d2", $a, '"lstids":' . $idA );
+ $this->assertListNoId( "$p d2", $res, '[]' );
+
+ // ids=A as another user
+ $res = $this->getLists( "$p d3", $u2, '"lstids":' . $idA );
+ $this->assertListNoId( "$p d3", $res, '[]' );
+
+ //
+ // ids=B
+ $res = $this->getLists( "$p e1", $u, '"lstids":' . $idB );
+ $this->assertListNoId( "$p e1", $res, '[{"label":"B"}]' );
+
+ // ids=B as anon user
+ $res = $this->getLists( "$p e2", $a, '"lstids":' . $idB );
+ $this->assertListNoId( "$p e2", $res, '[{"label":"B"}]' );
+
+ // ids=B as another user
+ $res = $this->getLists( "$p e3", $u2, '"lstids":' . $idB );
+ $this->assertListNoId( "$p e3", $res, '[{"label":"B"}]' );
+
+ //
+ // Use owner param
+ // user: get all with owner=user
+ $res = $this->getLists( "$p i0", $u, '"lstowner":' . $n );
+ $this->assertListsEquals( "$p i0", $res,
+ '[{"id":' . $wlId . ',
"watchlist":true,"label":"Watchlist"}, ' .
+ '{"id":' . $idA . ', "label":"A"},' . '{"id":' . $idB .
', "label":"B"}]' );
+
+ // user: get by idA with owner=user
+ $res = $this->getLists( "$p i0a", $u,
+ '"lstowner": ' . $n . ', "lstids": ' . $idA );
+ $this->assertListsEquals( "$p i0a", $res, '[{"id":' . $idA . ',
"label":"A"}]' );
+
+ // anon: get all with owner=user
+ $res = $this->getLists( "$p i1", $a, '"lstowner":' . $n );
+ $this->assertListNoId( "$p i1", $res, '[{"label":"B"}]' );
+
+ // anon: get by idA with owner=user
+ $res = $this->getLists( "$p i2", $a, '"lstowner": ' . $n . ',
"lstids": ' . $idA );
+ $this->assertListNoId( "$p i2", $res, '[]' );
+
+ // anon: get by idB with owner=user
+ $res = $this->getLists( "$p i3", $a, '"lstowner": ' . $n . ',
"lstids": ' . $idB );
+ $this->assertListNoId( "$p i3", $res, '[{"label":"B"}]' );
+
+ // user2: get all with owner=user
+ $res = $this->getLists( "$p i4", $u2, '"lstowner":' . $n );
+ $this->assertListNoId( "$p i4", $res, '[{"label":"B"}]' );
+
+ // user2: get by idA with owner=user
+ $res = $this->getLists( "$p i5", $u2, '"lstowner": ' . $n . ',
"lstids": ' . $idA );
+ $this->assertListNoId( "$p i5", $res, '[]' );
+
+ // user2: get by idB with owner=user
+ $res = $this->getLists( "$p i5", $u2, '"lstowner": ' . $n . ',
"lstids": ' . $idB );
+ $this->assertListNoId( "$p i5", $res, '[{"label":"B"}]' );
+ }
+
+ public function testWatchlistOnly() {
+ $u = self::$users['GatherWlOnly']->getUser(); // User for this
test
+ $a = User::newFromId( 0 ); // Anonymous user
+ $u2 = self::$users['GatherWlOnly2']->getUser(); // Second user
for this test
+
+ $token = $this->getToken( $u );
+ $wlOnly = '[{"id":0, "watchlist":true, "label":"Watchlist"}]';
+ $n = '"' . $u->getName() . '"'; // Name of the user for this
test
+
+ //
+ // Validate empty watchlist / lists
+ $res = $this->getLists( 'nc-a0', $u, '{}' );
+ $this->assertListNoId( 'nc-a0', $res, $wlOnly );
+
+ $res = $this->getLists( 'nc-a1', $u, '"lstids": 0' );
+ $this->assertListNoId( 'nc-a1', $res, $wlOnly );
+
+ $res = $this->getLists( 'nc-a2', $u, '"lstlimit": 1' );
+ $this->assertListNoId( 'nc-a2', $res, $wlOnly );
+
+ $res = $this->getLists( 'nc-a3', $u, '"lstprop":
"label|description|public|count"' );
+ $this->assertListNoId( 'nc-a3', $res,
+
'[{"id":0,"watchlist":true,"count":0,"label":"Watchlist","description":"","public":false}]'
+ );
+ $res = $this->getLists( 'nc-a4', $u, '"lsttitle": "Missing"' );
+ $this->assertListNoId( 'nc-a4', $res,
+
'[{"id":0,"watchlist":true,"label":"Watchlist","title":false}]' );
+
+ //
+ // Add page to watchlist
+ $this->legacyAddToWatchlist( 'nc-b0', $u, $token,
'Gather-ListW' );
+ $res = $this->getLists( 'nc-b0', $u, '"lstprop": "count"' );
+ $this->assertListNoId( 'nc-b0', $res, '[{"id": 0,
"watchlist":true, "count": 1}]' );
+
+ $res = $this->getLists( 'nc-b1', $u, '"lsttitle":
"Gather-ListW"' );
+ $this->assertListNoId( 'nc-b1', $res,
+
'[{"id":0,"watchlist":true,"label":"Watchlist","title":true}]' );
+
+ //
+ // Re-add the same page, using action=editlist & id=0
+ $res = $this->editList( 'nc-c0', $u, $token,
'"id":0,"titles":"Gather-ListW"' );
+ $this->getVal( "nc-c0", '"status"', $res, 'nochanges' );
+ $this->getVal( "nc-c0", '"id"', $res, 0 );
+ $this->getVal( "nc-c0", '"pages",0,"added"', $res, '' );
+
+ $res = $this->getLists( 'nc-c0a', $u, '{}' );
+ $this->assertListNoId( 'nc-c0a', $res, $wlOnly );
+
+ $res = $this->getLists( 'nc-c1', $u, '"lstids": 0' );
+ $this->assertListNoId( 'nc-c1', $res, $wlOnly );
+
+ $res = $this->getLists( 'nc-c3', $u, '"lstprop": "count"' );
+ $this->assertListNoId( 'nc-c3', $res, '[{"id":0,
"watchlist":true, "count": 1}]' );
+
+ $res = $this->getLists( 'nc-c4', $u, '"lsttitle":
"Gather-ListW"' );
+ $this->assertListNoId( 'nc-c4', $res,
+
'[{"id":0,"watchlist":true,"label":"Watchlist","title":true}]' );
+
+ $res = $this->getLists( 'nc-c5', $u, '"lstids": 0, "lsttitle":
"Gather-ListW"' );
+ $this->assertListNoId( 'nc-c5', $res,
+
'[{"id":0,"watchlist":true,"label":"Watchlist","title":true}]' );
+
+ //
+ // What can others see from this user
+ $res = $this->getLists( 'nc-e0', $a, '"lstowner":' . $n );
+ $this->assertListNoId( 'nc-e0', $res, '[]' );
+
+ $res = $this->getLists( 'nc-e1', $a, '"lstowner": ' . $n . ',
"lstids": 0' );
+ $this->assertListNoId( 'nc-e1', $res, '[]' );
+
+ $res = $this->getLists( 'nc-e2', $u2, '"lstowner":' . $n );
+ $this->assertListNoId( 'nc-e2', $res, '[]' );
+
+ $res = $this->getLists( 'nc-e3', $u2, '"lstowner": ' . $n . ',
"lstids": 0' );
+ $this->assertListNoId( 'nc-e3', $res, '[]' );
+
+ //
+ // Create watchlist list DB record
+ $res = $this->editList( 'nc-f0', $u, $token, '"id":0,
"description":"aa"' );
+ $this->getVal( 'nc-f0', '"status"', $res, 'created' );
+ $id = $this->getVal( 'nc-f0', '"id"', $res );
+ $this->assertNotEquals( 0, $id );
+
+ $wlOnly = array( array( 'id' => $id, 'watchlist' => true,
'label' => 'Watchlist' ) );
+
+ $res = $this->getLists( 'nc-f2', $u, '{}' );
+ $this->assertListsEquals( 'nc-f2', $res, $wlOnly );
+
+ $res = $this->getLists( 'nc-f3', $u, '"lstids": 0' );
+ $this->assertListsEquals( 'nc-f3', $res, $wlOnly );
+
+ $res = $this->getLists( 'nc-f4', $u, '"lstprop":
"label|description|public|count"' );
+ $this->assertListsEquals( 'nc-f4', $res,
+ '[{"id":' . $id .
+
',"watchlist":true,"count":1,"label":"Watchlist","description":"aa","public":false}]'
);
+
+ $res = $this->getLists( 'nc-f5', $u, '"lsttitle":
"Gather-ListW"' );
+ $this->assertListsEquals( 'nc-f5', $res, '[{"id":' . $id .
+ ',"watchlist":true,"label":"Watchlist","title":true}]'
);
+
+ //
+ // Others still can't see the watchlist
+ $res = $this->getLists( 'nc-g0', $a, '"lstowner":' . $n );
+ $this->assertListNoId( 'nc-g0', $res, '[]' );
+
+ $res = $this->getLists( 'nc-g1', $a, '"lstowner": ' . $n . ',
"lstids": 0' );
+ $this->assertListNoId( 'nc-g1', $res, '[]' );
+
+ $res = $this->getLists( 'nc-g2', $a, '"lstids": ' . $id );
+ $this->assertListNoId( 'nc-g2', $res, '[]' );
+
+ $res = $this->getLists( 'nc-g3', $a, '"lstowner": ' . $n . ',
"lstids": ' . $id );
+ $this->assertListNoId( 'nc-g3', $res, '[]' );
+
+ $res = $this->getLists( 'nc-h0', $u2, '"lstowner":' . $n );
+ $this->assertListNoId( 'nc-h0', $res, '[]' );
+
+ $res = $this->getLists( 'nc-h1', $u2, '"lstowner": ' . $n . ',
"lstids": 0' );
+ $this->assertListNoId( 'nc-h1', $res, '[]' );
+
+ $res = $this->getLists( 'nc-h2', $u2, '"lstids": ' . $id );
+ $this->assertListNoId( 'nc-h2', $res, '[]' );
+
+ $res = $this->getLists( 'nc-h3', $u2, '"lstowner": ' . $n . ',
"lstids": ' . $id );
+ $this->assertListNoId( 'nc-h3', $res, '[]' );
+
+ //
+ // Watchlist editing assertions
+ $this->badUseEdit( 'nc-i0', $u, false, '"id":0, "label":"bb"' );
+ $this->badUseEdit( 'nc-i1', $u, false, '"id":' . $id . ',
"label":"bb"' );
+ }
+
+ private function assertListNoId( $message, $actual, $exp ) {
+ $this->assertListsEquals( $message, $actual, $exp, true );
+ }
+
+ private function assertListsEquals( $message, $actual, $exp, $removeIds
= false ) {
+ $actual = $this->getVal( $message, '"query", "lists"', $actual
);
+ $exp = $this->toArr( $message, $exp );
+ if ( $removeIds ) {
+ $actual = self::removeIds( $actual );
+ }
+ $this->assertArrayEquals( $exp, $actual, true, true, $message );
+ }
+
+ private function legacyAddToWatchlist( $message, $user, $token, $titles
) {
+ $params = array(
+ 'action' => 'watch',
+ 'titles' => $titles,
+ 'token' => $token,
+ );
+ $res = $this->getLists( $message, $user, $params );
+ $this->getVal( $message, '"watch", 0, "watched"', $res );
+ }
+
+ private function getToken( User $user ) {
+ $message = 'token-' . $user->getName();
+ $res = $this->doApiRequest2( $message, $user, array(
+ 'action' => 'query',
+ 'meta' => 'tokens',
+ 'type' => 'watch',
+ ) );
+ return $this->getVal( $message, '0, "query", "tokens",
"watchtoken"', $res );
+ }
+
+ private function badUseLists( $message, User $user, $params ) {
+ $this->badUse( $message, $user, 'lists', false, $params );
+ }
+
+ private function badUsePage( $message, User $user, $params ) {
+ $this->badUse( $message, $user, 'listpages', false, $params );
+ }
+
+ private function badUseEdit( $message, User $user, $token, $params ) {
+ $this->badUse( $message, $user, 'editlist', $token, $params );
+ }
+
+ private function badUse( $message, User $user, $action, $token, $params
) {
+ try {
+ $params = $this->toApiParams( $message, $action,
$token, $params );
+ $result = $this->doApiRequest( $params, null, false,
$user );
+ $params = $this->toStr( $params );
+ $this->fail( "$message: No UsageException for $params,
received:\n" .
+ $this->toStr( $result[0], true ) );
+ } catch ( UsageException $e ) {
+ $this->assertTrue( true );
+ }
+ }
+
+ private function editList( $message, $user, $token, $params ) {
+ $params = $this->toApiParams( $message, 'editlist', $token,
$params );
+ $res = $this->doApiRequest2( $message, $user, $params );
+ return $this->getVal( $message, array( $params['action'] ),
$res[0] );
+ }
+
+ private function getLists( $message, User $user, $params ) {
+ $params = $this->toApiParams( $message, 'lists', false, $params
);
+ $res = $this->doApiRequest2( $message, $user, $params );
+ return $res[0];
+ }
+
+ private function getPages( $message, User $user, $params ) {
+ $params = $this->toApiParams( $message, 'listpages', false,
$params );
+ $res = $this->doApiRequest2( $message, $user, $params );
+ return $res[0];
+ }
+
+ private function doApiRequest2( $message, User $user, array $params ) {
+ try {
+ return parent::doApiRequest( $params, null, false,
$user );
+ } catch ( Exception $ex ) {
+ echo "Failed API call $message\n";
+ throw $ex;
+ }
+ }
+
+
+ private function toApiParams( $message, $default, $token, $params ) {
+ $params = $this->toArr( $message, $params, true );
+ if ( !isset( $params['action'] ) ) {
+ $params['action'] = $default === 'editlist' ? $default
: 'query';
+ }
+ if ( $params['action'] === 'query' ) {
+ if ( !isset( $params['list'] ) ) {
+ $params['list'] = $default;
+ }
+ if ( !isset( $params['continue'] ) ) {
+ $params['continue'] = '';
+ }
+ }
+ if ( $token && !isset( $params['token'] ) ) {
+ $params['token'] = $token;
+ }
+ return $params;
+ }
+
+ private function toArr( $message, $params, $dictByDefault = false ) {
+ if ( is_string( $params ) && $params ) {
+ $p = $params;
+ if ( $p[0] !== '[' && $p[0] !== '{' ) {
+ $p = $dictByDefault ? '{' . $params . '}' :
"[$params]";
+ }
+ $st = FormatJson::parse( $p, FormatJson::FORCE_ASSOC );
+ $this->assertTrue( $st->isOK(), "$message: invalid JSON
value $params" );
+ $params = $st->getValue();
+ }
+ return $params;
+ }
+
+ private function toStr( $params, $pretty = false ) {
+ if ( is_string( $params ) ) {
+ return $params;
+ }
+ return FormatJson::encode( $params, $pretty, FormatJson::ALL_OK
);
+ }
+
+ private static function removeIds( $arr ) {
+ foreach ( $arr as &$v ) {
+ if ( array_key_exists( 'id', $v ) && $v['id'] !== 0 ) {
+ unset( $v['id'] );
+ }
+ }
+ return $arr;
+ }
+
+ private function getVal( $message, $path, $result, $expValue = null ) {
+ $path = $this->toArr( $message, $path );
+ $res = $result;
+ foreach ( $path as $p ) {
+ if ( !array_key_exists( $p, $res ) ) {
+ $path = $this->toStr( $path );
+ $this->fail( "$message: Request has no key $p
of $path in result\n" .
+ $this->toStr( $result, true ) );
+ }
+ $res = $res[$p];
+ }
+ if ( $expValue !== null ) {
+ $this->assertEquals( $expValue, $res, $message );
+ }
+ return $res;
+ }
+
+ /**
+ * Debugging function to track the sate of the table during test
development
+ * @param string $table
+ */
+ private function dumpTable( $table ) {
+ echo "\nTable dump $table:\n";
+ foreach ( $this->db->select( $table, '*' ) as $row ) {
+ echo $this->toStr( $row ) . "\n";
+ }
+ echo "\nEnd of the table dump $table\n";
+ }
+
+ private function assertOneList( $message, $u, $id, $expected,
$expectedProp = null ) {
+ $params = '"lstids":' . $id;
+ $res = $this->getLists( $message, $u, $params );
+ $lst = $this->getVal( $message, '"query", "lists"', $res );
+ if ( $expected === null ) {
+ $this->assertCount( 0, $lst, $message );
+ } else {
+ $this->assertCount( 1, $lst, $message );
+ $this->assertEquals( $expected, $lst[0], $message );
+ }
+
+ if ( $expectedProp ) {
+ $params .= ',
"lstprop":"label|description|public|image|count"';
+ $message .= '-p';
+ $res = $this->getLists( $message, $u, $params );
+ $lst = $this->getVal( $message, '"query", "lists"',
$res );
+ $this->assertCount( 1, $lst, $message );
+ $this->assertEquals( $expectedProp, $lst[0], $message );
+ }
+ }
+
+ private function assertPages( $message, $u, $id, $expected ) {
+ $params = $id === null ? '{}' : '"lspid":' . $id;
+ $res = $this->getPages( $message, $u, $params );
+ $this->getVal( $message, '"listpages"', $res, $expected );
+ }
+}
--
To view, visit https://gerrit.wikimedia.org/r/198667
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I3e08d17bf0d83f0d813eccee2d27feeccd357a9b
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Gather
Gerrit-Branch: master
Gerrit-Owner: Yurik <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits