ApiTestCase.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <?php
  2. use MediaWiki\Session\SessionManager;
  3. abstract class ApiTestCase extends MediaWikiLangTestCase {
  4. protected static $apiUrl;
  5. protected static $errorFormatter = null;
  6. /**
  7. * @var ApiTestContext
  8. */
  9. protected $apiContext;
  10. protected function setUp() {
  11. global $wgServer;
  12. parent::setUp();
  13. self::$apiUrl = $wgServer . wfScript( 'api' );
  14. ApiQueryInfo::resetTokenCache(); // tokens are invalid because we cleared the session
  15. self::$users = [
  16. 'sysop' => static::getTestSysop(),
  17. 'uploader' => static::getTestUser(),
  18. ];
  19. $this->setMwGlobals( [
  20. 'wgAuth' => new MediaWiki\Auth\AuthManagerAuthPlugin,
  21. 'wgRequest' => new FauxRequest( [] ),
  22. 'wgUser' => self::$users['sysop']->getUser(),
  23. ] );
  24. $this->apiContext = new ApiTestContext();
  25. }
  26. protected function tearDown() {
  27. // Avoid leaking session over tests
  28. MediaWiki\Session\SessionManager::getGlobalSession()->clear();
  29. parent::tearDown();
  30. }
  31. /**
  32. * Does the API request and returns the result.
  33. *
  34. * The returned value is an array containing
  35. * - the result data (array)
  36. * - the request (WebRequest)
  37. * - the session data of the request (array)
  38. * - if $appendModule is true, the Api module $module
  39. *
  40. * @param array $params
  41. * @param array|null $session
  42. * @param bool $appendModule
  43. * @param User|null $user
  44. * @param string|null $tokenType Set to a string like 'csrf' to send an
  45. * appropriate token
  46. *
  47. * @throws ApiUsageException
  48. * @return array
  49. */
  50. protected function doApiRequest( array $params, array $session = null,
  51. $appendModule = false, User $user = null, $tokenType = null
  52. ) {
  53. global $wgRequest, $wgUser;
  54. if ( is_null( $session ) ) {
  55. // re-use existing global session by default
  56. $session = $wgRequest->getSessionArray();
  57. }
  58. $sessionObj = SessionManager::singleton()->getEmptySession();
  59. if ( $session !== null ) {
  60. foreach ( $session as $key => $value ) {
  61. $sessionObj->set( $key, $value );
  62. }
  63. }
  64. // set up global environment
  65. if ( $user ) {
  66. $wgUser = $user;
  67. }
  68. if ( $tokenType !== null ) {
  69. if ( $tokenType === 'auto' ) {
  70. $tokenType = ( new ApiMain() )->getModuleManager()
  71. ->getModule( $params['action'], 'action' )->needsToken();
  72. }
  73. $params['token'] = ApiQueryTokens::getToken(
  74. $wgUser, $sessionObj, ApiQueryTokens::getTokenTypeSalts()[$tokenType]
  75. )->toString();
  76. }
  77. $wgRequest = new FauxRequest( $params, true, $sessionObj );
  78. RequestContext::getMain()->setRequest( $wgRequest );
  79. RequestContext::getMain()->setUser( $wgUser );
  80. MediaWiki\Auth\AuthManager::resetCache();
  81. // set up local environment
  82. $context = $this->apiContext->newTestContext( $wgRequest, $wgUser );
  83. $module = new ApiMain( $context, true );
  84. // run it!
  85. $module->execute();
  86. // construct result
  87. $results = [
  88. $module->getResult()->getResultData( null, [ 'Strip' => 'all' ] ),
  89. $context->getRequest(),
  90. $context->getRequest()->getSessionArray()
  91. ];
  92. if ( $appendModule ) {
  93. $results[] = $module;
  94. }
  95. return $results;
  96. }
  97. /**
  98. * Convenience function to access the token parameter of doApiRequest()
  99. * more succinctly.
  100. *
  101. * @param array $params Key-value API params
  102. * @param array|null $session Session array
  103. * @param User|null $user A User object for the context
  104. * @param string $tokenType Which token type to pass
  105. * @return array Result of the API call
  106. */
  107. protected function doApiRequestWithToken( array $params, array $session = null,
  108. User $user = null, $tokenType = 'auto'
  109. ) {
  110. return $this->doApiRequest( $params, $session, false, $user, $tokenType );
  111. }
  112. /**
  113. * Previously this would do API requests to log in, as well as setting $wgUser and the request
  114. * context's user. The API requests are unnecessary, and the global-setting is unwanted, so
  115. * this method should not be called. Instead, pass appropriate User values directly to
  116. * functions that need them. For functions that still rely on $wgUser, set that directly. If
  117. * you just want to log in the test sysop user, don't do anything -- that's the default.
  118. *
  119. * @param TestUser|string $testUser Object, or key to self::$users such as 'sysop' or 'uploader'
  120. * @deprecated since 1.31
  121. */
  122. protected function doLogin( $testUser = null ) {
  123. global $wgUser;
  124. if ( $testUser === null ) {
  125. $testUser = static::getTestSysop();
  126. } elseif ( is_string( $testUser ) && array_key_exists( $testUser, self::$users ) ) {
  127. $testUser = self::$users[$testUser];
  128. } elseif ( !$testUser instanceof TestUser ) {
  129. throw new MWException( "Can't log in to undefined user $testUser" );
  130. }
  131. $wgUser = $testUser->getUser();
  132. RequestContext::getMain()->setUser( $wgUser );
  133. }
  134. protected function getTokenList( TestUser $user, $session = null ) {
  135. $data = $this->doApiRequest( [
  136. 'action' => 'tokens',
  137. 'type' => 'edit|delete|protect|move|block|unblock|watch'
  138. ], $session, false, $user->getUser() );
  139. if ( !array_key_exists( 'tokens', $data[0] ) ) {
  140. throw new MWException( 'Api failed to return a token list' );
  141. }
  142. return $data[0]['tokens'];
  143. }
  144. protected static function getErrorFormatter() {
  145. if ( self::$errorFormatter === null ) {
  146. self::$errorFormatter = new ApiErrorFormatter(
  147. new ApiResult( false ),
  148. Language::factory( 'en' ),
  149. 'none'
  150. );
  151. }
  152. return self::$errorFormatter;
  153. }
  154. public static function apiExceptionHasCode( ApiUsageException $ex, $code ) {
  155. return (bool)array_filter(
  156. self::getErrorFormatter()->arrayFromStatus( $ex->getStatusValue() ),
  157. function ( $e ) use ( $code ) {
  158. return is_array( $e ) && $e['code'] === $code;
  159. }
  160. );
  161. }
  162. /**
  163. * @coversNothing
  164. */
  165. public function testApiTestGroup() {
  166. $groups = PHPUnit_Util_Test::getGroups( static::class );
  167. $constraint = PHPUnit_Framework_Assert::logicalOr(
  168. $this->contains( 'medium' ),
  169. $this->contains( 'large' )
  170. );
  171. $this->assertThat( $groups, $constraint,
  172. 'ApiTestCase::setUp can be slow, tests must be "medium" or "large"'
  173. );
  174. }
  175. /**
  176. * Expect an ApiUsageException to be thrown with the given parameters, which are the same as
  177. * ApiUsageException::newWithMessage()'s parameters. This allows checking for an exception
  178. * whose text is given by a message key instead of text, so as not to hard-code the message's
  179. * text into test code.
  180. */
  181. protected function setExpectedApiException(
  182. $msg, $code = null, array $data = null, $httpCode = 0
  183. ) {
  184. $expected = ApiUsageException::newWithMessage( null, $msg, $code, $data, $httpCode );
  185. $this->setExpectedException( ApiUsageException::class, $expected->getMessage() );
  186. }
  187. }