2016-08-29 23:35:55 +00:00
|
|
|
<?php
|
|
|
|
|
|
2022-05-06 09:09:56 +00:00
|
|
|
namespace MediaWiki\Tests\ResourceLoader;
|
|
|
|
|
|
|
|
|
|
use EmptyResourceLoader;
|
|
|
|
|
use HashConfig;
|
2022-10-28 10:04:25 +00:00
|
|
|
use MediaWiki\Request\FauxRequest;
|
2022-05-06 09:09:56 +00:00
|
|
|
use MediaWiki\ResourceLoader\Context;
|
|
|
|
|
use MediaWiki\ResourceLoader\ResourceLoader;
|
|
|
|
|
use MediaWikiCoversValidator;
|
|
|
|
|
use Message;
|
|
|
|
|
use User;
|
2022-10-27 13:14:16 +00:00
|
|
|
use WebRequest;
|
2022-05-06 09:09:56 +00:00
|
|
|
|
2016-08-29 23:35:55 +00:00
|
|
|
/**
|
|
|
|
|
* See also:
|
2022-05-06 09:09:56 +00:00
|
|
|
* - ImageModuleTest::testContext
|
2016-08-29 23:35:55 +00:00
|
|
|
*
|
2019-04-11 20:28:53 +00:00
|
|
|
* @group ResourceLoader
|
2022-05-06 09:09:56 +00:00
|
|
|
* @covers \MediaWiki\ResourceLoader\Context
|
2016-08-29 23:35:55 +00:00
|
|
|
*/
|
2022-05-06 09:09:56 +00:00
|
|
|
class ContextTest extends \PHPUnit\Framework\TestCase {
|
2017-12-29 23:22:37 +00:00
|
|
|
|
|
|
|
|
use MediaWikiCoversValidator;
|
|
|
|
|
|
2016-08-29 23:35:55 +00:00
|
|
|
protected static function getResourceLoader() {
|
|
|
|
|
return new EmptyResourceLoader( new HashConfig( [
|
|
|
|
|
'ResourceLoaderDebug' => false,
|
resourceloader: Don't let module exception break startup
When getScript (or some other method used in a module response)
throws an error, only that module fails (by outputting mw.loader.state
instead of mw.loader.implement). Other modules will work.
This has always been the case and is working fine. For example,
"load.php?modules=foo|bar", where 'foo' throws, will return:
```js
/* exception message: .. */
mw.loader.implement('bar', ..)
mw.loader.state('foo', 'error')
```
The problem, however, is that during the generation of the startup
module, we iterate over all other modules. In 2011, the
getVersionHash method (then: getModifiedTime) was fairly simple
and unlikely to throw errors.
Nowadays, some modules use enableModuleContentVersion which will
involve the same code path as for regular module responses.
The try/catch in ResourceLoader::makeModuleResponse() suffices
for the case of loading modules other than startup. But when
loading the startup module, and an exception happens in getVersionHash,
then the entire startup response is replaced with an exception comment.
Example case:
* A file not existing for a FileModule subclass that uses
enableModuleContentVersion.
* A database error from a data module, like CiteDataModule or
CNChoiceData.
Changes:
* Ensure E-Tag is still useful while an error happens in production
because we respond with 200 OK and one error isn't the same as
another.
Fixed by try/catch in getCombinedVersion.
* Ensure start manifest isn't disrupted by one broken module.
Fixed by try/catch in StartupModule::getModuleRegistrations().
Tests:
* testMakeModuleResponseError: The case that already worked fined.
* testMakeModuleResponseStartupError: The case fixed in this commit.
* testGetCombinedVersion: The case fixed in this commit for E-Tag.
Bug: T152266
Change-Id: Ice4ede5ea594bf3fa591134bc9382bd9c24e2f39
2016-12-03 00:48:14 +00:00
|
|
|
'LoadScript' => '/w/load.php',
|
2016-08-29 23:35:55 +00:00
|
|
|
] ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testEmpty() {
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [] ) );
|
2016-08-29 23:35:55 +00:00
|
|
|
|
|
|
|
|
// Request parameters
|
|
|
|
|
$this->assertEquals( [], $ctx->getModules() );
|
2019-04-11 22:17:00 +00:00
|
|
|
$this->assertEquals( 'qqx', $ctx->getLanguage() );
|
2020-05-22 02:01:42 +00:00
|
|
|
$this->assertSame( 0, $ctx->getDebug() );
|
2019-09-17 14:03:28 +00:00
|
|
|
$this->assertNull( $ctx->getOnly() );
|
2016-08-29 23:35:55 +00:00
|
|
|
$this->assertEquals( 'fallback', $ctx->getSkin() );
|
2019-09-17 14:03:28 +00:00
|
|
|
$this->assertNull( $ctx->getUser() );
|
2017-02-28 20:52:17 +00:00
|
|
|
$this->assertNull( $ctx->getContentOverrideCallback() );
|
2016-08-29 23:35:55 +00:00
|
|
|
|
|
|
|
|
// Misc
|
|
|
|
|
$this->assertEquals( 'ltr', $ctx->getDirection() );
|
2020-05-22 02:01:42 +00:00
|
|
|
$this->assertEquals( 'qqx|fallback|0|||||||', $ctx->getHash() );
|
2019-08-25 18:01:48 +00:00
|
|
|
$this->assertSame( [], $ctx->getReqBase() );
|
2016-08-29 23:35:55 +00:00
|
|
|
$this->assertInstanceOf( User::class, $ctx->getUserObj() );
|
2021-10-26 20:34:00 +00:00
|
|
|
$this->assertNull( $ctx->getUserIdentity() );
|
2016-08-29 23:35:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testDummy() {
|
|
|
|
|
$this->assertInstanceOf(
|
2022-05-06 09:09:56 +00:00
|
|
|
Context::class,
|
|
|
|
|
Context::newDummyContext()
|
2016-08-29 23:35:55 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testAccessors() {
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [] ) );
|
2019-04-11 22:22:08 +00:00
|
|
|
$this->assertInstanceOf( ResourceLoader::class, $ctx->getResourceLoader() );
|
2016-08-29 23:35:55 +00:00
|
|
|
$this->assertInstanceOf( WebRequest::class, $ctx->getRequest() );
|
2022-05-06 09:09:56 +00:00
|
|
|
$this->assertInstanceOf( \Psr\Log\LoggerInterface::class, $ctx->getLogger() );
|
2016-08-29 23:35:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testTypicalRequest() {
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [
|
2016-08-29 23:35:55 +00:00
|
|
|
'debug' => 'false',
|
|
|
|
|
'lang' => 'zh',
|
|
|
|
|
'modules' => 'foo|foo.quux,baz,bar|baz.quux',
|
|
|
|
|
'only' => 'styles',
|
|
|
|
|
'skin' => 'fallback',
|
|
|
|
|
] ) );
|
|
|
|
|
|
|
|
|
|
// Request parameters
|
|
|
|
|
$this->assertEquals(
|
2023-03-11 19:04:01 +00:00
|
|
|
[ 'foo', 'foo.quux', 'foo.baz', 'foo.bar', 'baz.quux' ],
|
|
|
|
|
$ctx->getModules()
|
2016-08-29 23:35:55 +00:00
|
|
|
);
|
2020-05-22 02:01:42 +00:00
|
|
|
$this->assertSame( 0, $ctx->getDebug() );
|
2016-08-29 23:35:55 +00:00
|
|
|
$this->assertEquals( 'zh', $ctx->getLanguage() );
|
|
|
|
|
$this->assertEquals( 'styles', $ctx->getOnly() );
|
|
|
|
|
$this->assertEquals( 'fallback', $ctx->getSkin() );
|
2019-09-17 14:03:28 +00:00
|
|
|
$this->assertNull( $ctx->getUser() );
|
2016-08-29 23:35:55 +00:00
|
|
|
|
|
|
|
|
// Misc
|
|
|
|
|
$this->assertEquals( 'ltr', $ctx->getDirection() );
|
2020-05-22 02:01:42 +00:00
|
|
|
$this->assertEquals( 'zh|fallback|0||styles|||||', $ctx->getHash() );
|
2019-08-25 18:01:48 +00:00
|
|
|
$this->assertSame( [ 'lang' => 'zh' ], $ctx->getReqBase() );
|
2016-08-29 23:35:55 +00:00
|
|
|
}
|
|
|
|
|
|
2019-06-11 19:54:57 +00:00
|
|
|
public static function provideDirection() {
|
|
|
|
|
yield 'LTR language' => [
|
|
|
|
|
[ 'lang' => 'en' ],
|
|
|
|
|
'ltr',
|
|
|
|
|
];
|
|
|
|
|
yield 'RTL language' => [
|
|
|
|
|
[ 'lang' => 'he' ],
|
|
|
|
|
'rtl',
|
|
|
|
|
];
|
|
|
|
|
yield 'explicit LTR' => [
|
|
|
|
|
[ 'lang' => 'he', 'dir' => 'ltr' ],
|
|
|
|
|
'ltr',
|
|
|
|
|
];
|
|
|
|
|
yield 'explicit RTL' => [
|
|
|
|
|
[ 'lang' => 'en', 'dir' => 'rtl' ],
|
|
|
|
|
'rtl',
|
|
|
|
|
];
|
|
|
|
|
// Not supported, but tested to cover the case and detect change
|
|
|
|
|
yield 'invalid dir' => [
|
|
|
|
|
[ 'lang' => 'he', 'dir' => 'xyz' ],
|
|
|
|
|
'rtl',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @dataProvider provideDirection
|
|
|
|
|
*/
|
|
|
|
|
public function testDirection( array $params, $expected ) {
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( $params ) );
|
2019-06-11 19:54:57 +00:00
|
|
|
$this->assertEquals( $expected, $ctx->getDirection() );
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-29 23:35:55 +00:00
|
|
|
public function testShouldInclude() {
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [] ) );
|
2016-08-29 23:35:55 +00:00
|
|
|
$this->assertTrue( $ctx->shouldIncludeScripts(), 'Scripts in combined' );
|
|
|
|
|
$this->assertTrue( $ctx->shouldIncludeStyles(), 'Styles in combined' );
|
|
|
|
|
$this->assertTrue( $ctx->shouldIncludeMessages(), 'Messages in combined' );
|
|
|
|
|
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [
|
2016-08-29 23:35:55 +00:00
|
|
|
'only' => 'styles'
|
|
|
|
|
] ) );
|
|
|
|
|
$this->assertFalse( $ctx->shouldIncludeScripts(), 'Scripts not in styles-only' );
|
|
|
|
|
$this->assertTrue( $ctx->shouldIncludeStyles(), 'Styles in styles-only' );
|
|
|
|
|
$this->assertFalse( $ctx->shouldIncludeMessages(), 'Messages not in styles-only' );
|
|
|
|
|
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [
|
2016-08-29 23:35:55 +00:00
|
|
|
'only' => 'scripts'
|
|
|
|
|
] ) );
|
|
|
|
|
$this->assertTrue( $ctx->shouldIncludeScripts(), 'Scripts in scripts-only' );
|
|
|
|
|
$this->assertFalse( $ctx->shouldIncludeStyles(), 'Styles not in scripts-only' );
|
|
|
|
|
$this->assertFalse( $ctx->shouldIncludeMessages(), 'Messages not in scripts-only' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGetUser() {
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [] ) );
|
2016-08-29 23:35:55 +00:00
|
|
|
$this->assertSame( null, $ctx->getUser() );
|
2021-10-23 19:27:44 +00:00
|
|
|
$this->assertFalse( $ctx->getUserObj()->isRegistered() );
|
2021-10-26 20:34:00 +00:00
|
|
|
$this->assertNull( $ctx->getUserIdentity() );
|
2016-08-29 23:35:55 +00:00
|
|
|
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [
|
2016-08-29 23:35:55 +00:00
|
|
|
'user' => 'Example'
|
|
|
|
|
] ) );
|
|
|
|
|
$this->assertSame( 'Example', $ctx->getUser() );
|
|
|
|
|
$this->assertEquals( 'Example', $ctx->getUserObj()->getName() );
|
2021-10-26 20:34:00 +00:00
|
|
|
$this->assertEquals( 'Example', $ctx->getUserIdentity()->getName() );
|
2016-08-29 23:35:55 +00:00
|
|
|
}
|
2016-12-02 04:47:05 +00:00
|
|
|
|
|
|
|
|
public function testMsg() {
|
2022-05-06 09:09:56 +00:00
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [
|
2016-12-02 04:47:05 +00:00
|
|
|
'lang' => 'en'
|
|
|
|
|
] ) );
|
|
|
|
|
$msg = $ctx->msg( 'mainpage' );
|
|
|
|
|
$this->assertInstanceOf( Message::class, $msg );
|
|
|
|
|
$this->assertSame( 'Main Page', $msg->useDatabase( false )->plain() );
|
|
|
|
|
}
|
2023-02-14 23:40:20 +00:00
|
|
|
|
|
|
|
|
public function testEncodeJson() {
|
|
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [] ) );
|
|
|
|
|
|
|
|
|
|
$json = $ctx->encodeJson( [ 'x' => 'A' ] );
|
|
|
|
|
$this->assertSame( '{"x":"A"}', $json );
|
|
|
|
|
|
|
|
|
|
// Regression: https://phabricator.wikimedia.org/T329330
|
|
|
|
|
$json = @$ctx->encodeJson( [
|
|
|
|
|
'x' => 'A',
|
|
|
|
|
'y' => "Foo\x80\xf0Bar",
|
|
|
|
|
'z' => 'C',
|
|
|
|
|
] );
|
|
|
|
|
$this->assertSame( '{"x":"A","y":null,"z":"C"}', $json, 'Ignore invalid UTF-8' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testEncodeJsonWarning() {
|
|
|
|
|
$ctx = new Context( $this->getResourceLoader(), new FauxRequest( [] ) );
|
|
|
|
|
|
|
|
|
|
$this->expectWarning();
|
|
|
|
|
$this->expectWarningMessage( 'encodeJson partially failed: Malformed UTF-8' );
|
|
|
|
|
$ctx->encodeJson( [
|
|
|
|
|
'x' => 'A',
|
|
|
|
|
'y' => "Foo\x80\xf0Bar",
|
|
|
|
|
'z' => 'C',
|
|
|
|
|
] );
|
|
|
|
|
}
|
2016-08-29 23:35:55 +00:00
|
|
|
}
|